From 74ed5e1beecba4b1bdc729de6c45ca5cc397d112 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Wed, 19 Feb 2025 07:29:26 +0000 Subject: [PATCH 001/117] Fixed bug where swamp tar interactions could steal an entire stack --- .../kandarin/witchhaven/quest/seaslug/SeaSlugListeners.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Server/src/main/content/region/kandarin/witchhaven/quest/seaslug/SeaSlugListeners.kt b/Server/src/main/content/region/kandarin/witchhaven/quest/seaslug/SeaSlugListeners.kt index 62d8ceb11..79a1b10ab 100644 --- a/Server/src/main/content/region/kandarin/witchhaven/quest/seaslug/SeaSlugListeners.kt +++ b/Server/src/main/content/region/kandarin/witchhaven/quest/seaslug/SeaSlugListeners.kt @@ -9,6 +9,7 @@ import core.game.interaction.QueueStrength import core.game.node.entity.combat.ImpactHandler import core.game.node.entity.combat.ImpactHandler.HitsplatType import core.game.node.entity.player.Player +import core.game.node.item.Item import core.game.world.map.Location import org.rs09.consts.Components import org.rs09.consts.Items @@ -76,7 +77,8 @@ class SeaSlugListeners : InteractionListener { // Your torch goes out on the crossing. onUseWith(IntType.ITEM, Items.SWAMP_TAR_1939, Items.POT_OF_FLOUR_1933){ player, used, with -> - if(removeItem(player, used) && removeItem(player, with)) { + val toRemove = Item(used.id, 1, used.asItem().slot) + if(removeItem(player, toRemove) && removeItem(player, with)) { sendMessage(player, "You mix the flour with the swamp tar.") sendMessage(player, "It mixes into a paste.") addItemOrDrop(player, Items.EMPTY_POT_1931) @@ -88,7 +90,8 @@ class SeaSlugListeners : InteractionListener { // You can only cook it using firewood. // sendMessage(player, "You can't cook that in a range.") onUseWith(SCENERY, Items.RAW_SWAMP_PASTE_1940, Scenery.FIRE_2732) { player, used, with -> - if(removeItem(player, used)) { + val toRemove = Item(used.id, 1, used.asItem().slot) + if(removeItem(player, toRemove)) { sendMessage(player, "You warm the paste over the fire. It thickens into a sticky goo.") addItemOrDrop(player, Items.SWAMP_PASTE_1941) } From af58fae1fcff8488efca3a62bf6b8315b76b6218 Mon Sep 17 00:00:00 2001 From: Player Name Date: Wed, 19 Feb 2025 07:30:51 +0000 Subject: [PATCH 002/117] Corrected Biohazard requirements check when using West Ardougne's eastern gates --- .../ardougne/westardougne/handlers/MainGatesListener.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/src/main/content/region/kandarin/ardougne/westardougne/handlers/MainGatesListener.kt b/Server/src/main/content/region/kandarin/ardougne/westardougne/handlers/MainGatesListener.kt index b76f5ac83..82a8cd5e3 100644 --- a/Server/src/main/content/region/kandarin/ardougne/westardougne/handlers/MainGatesListener.kt +++ b/Server/src/main/content/region/kandarin/ardougne/westardougne/handlers/MainGatesListener.kt @@ -13,7 +13,7 @@ class MainGatesListener : InteractionListener { override fun defineListeners() { on(intArrayOf(Scenery.ARDOUGNE_WALL_DOOR_9738, Scenery.ARDOUGNE_WALL_DOOR_9330), IntType.SCENERY, "open") { player, node -> - if (isQuestComplete(player, Quests.BIOHAZARD)) { + if (hasRequirement(player, Quests.BIOHAZARD)) { DoorActionHandler.handleAutowalkDoor(player, node.asScenery()) } else if(inBorders(player, 2556, 3298, 2557, 3301)){ lock(player,2) From 0e65f202235e2f805fe0d96ca6c66e530919f565 Mon Sep 17 00:00:00 2001 From: Player Name Date: Wed, 19 Feb 2025 07:31:29 +0000 Subject: [PATCH 003/117] Fixed another softlock in The Fremennik Trials --- .../quest/thefremenniktrials/TFTInteractionListeners.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/TFTInteractionListeners.kt b/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/TFTInteractionListeners.kt index 4005844f9..51331c124 100644 --- a/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/TFTInteractionListeners.kt +++ b/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/TFTInteractionListeners.kt @@ -213,7 +213,9 @@ class TFTInteractionListeners : InteractionListener { on(SWENSEN_LADDER, IntType.SCENERY, "climb-down") { player, _ -> if (!getAttribute(player,"fremtrials:swensen-accepted",false)) { sendNPCDialogue(player,1283,"Where do you think you're going?", core.game.dialogue.FacialExpression.ANGRY) + return@on true } + core.game.global.action.ClimbActionHandler.climb(player, Animation(828), Location.create(2631, 10006, 0)) return@on true } From f0ee476e42ad558c018dfb58a4765a2955364065 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Wed, 19 Feb 2025 07:33:42 +0000 Subject: [PATCH 004/117] Fixed pyramid spawns not despawning Relaxed getting thrown out of the pyramid to rates that appear more authentic --- .../desert/quest/deserttreasure/MummyNPC.kt | 18 +++++ .../quest/deserttreasure/PyramidArea.kt | 73 ++++++++++--------- .../desert/quest/deserttreasure/ScarabNPC.kt | 18 +++++ 3 files changed, 75 insertions(+), 34 deletions(-) create mode 100644 Server/src/main/content/region/desert/quest/deserttreasure/MummyNPC.kt create mode 100644 Server/src/main/content/region/desert/quest/deserttreasure/ScarabNPC.kt diff --git a/Server/src/main/content/region/desert/quest/deserttreasure/MummyNPC.kt b/Server/src/main/content/region/desert/quest/deserttreasure/MummyNPC.kt new file mode 100644 index 000000000..d5fed9d6e --- /dev/null +++ b/Server/src/main/content/region/desert/quest/deserttreasure/MummyNPC.kt @@ -0,0 +1,18 @@ +package content.region.desert.quest.deserttreasure + +import core.api.* +import core.game.node.entity.npc.NPC +import core.game.node.entity.npc.NPCBehavior +import org.rs09.consts.NPCs + +class MummyNPC : NPCBehavior(NPCs.MUMMY_1958) { + var clearTime = 0 + + override fun tick(self: NPC): Boolean { + if (clearTime++ > 100) { + clearTime = 0 + poofClear(self) + } + return true + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/desert/quest/deserttreasure/PyramidArea.kt b/Server/src/main/content/region/desert/quest/deserttreasure/PyramidArea.kt index ab45baf12..2a28610b2 100644 --- a/Server/src/main/content/region/desert/quest/deserttreasure/PyramidArea.kt +++ b/Server/src/main/content/region/desert/quest/deserttreasure/PyramidArea.kt @@ -109,7 +109,7 @@ class PyramidArea { Location(3259, 9313, 0), ) - val safeZone = ZoneBorders(3227, 3239, 9310, 9320) + val safeZone = ZoneBorders(3227, 9310, 3239, 9320) // Direction.NORTH - LEFT // rot 0 - LEFT @@ -150,12 +150,15 @@ class PyramidArea { /** Trapdoor randomly throws you out of the Pyramid. */ fun trapdoorTrap(player: Player) { stopWalk(player) + player.walkingQueue.reset() + forceWalk(player, Location(3233, 2887, 0), "") lock(player, 8) sendMessage(player, "You accidentally trigger a trap...") // addScenery(6521, location) -> animateScenery(scenery, 1939), but 6522 does it for you. val pitfallScenery = addScenery(6522, player.location) // Scenery - Trapdoor Scenery animate(player, 1950) // Anim - Player Falling Animation queueScript(player, 4, QueueStrength.SOFT) { stage -> + player.walkingQueue.reset() when (stage) { 0 -> { sendGraphics(354, player.location) // Gfx - Puff of Smoke @@ -169,7 +172,14 @@ class PyramidArea { sendMessage(player, "...and tumble unharmed outside the pyramid.") closeOverlay(player) openOverlay(player, Components.FADE_FROM_BLACK_170) + stopWalk(player) + player.walkingQueue.reset() // animate(player, ??) // Anim - Player Getting Up https://www.youtube.com/watch?v=95OvIPFYCwg + return@queueScript delayScript(player, 3) + } + 2 -> { + player.walkingQueue.reset() + forceWalk(player, Location(3233, 2887, 0), "") return@queueScript stopExecuting(player) } else -> return@queueScript stopExecuting(player) @@ -179,8 +189,6 @@ class PyramidArea { /** Mummies randomly spawns out of a sarcophagus. */ fun spawnMummy(player: Player, sarcophagusLocation: Location) { - stopWalk(player) - lock(player, 3) val sarcophagusScenery = getScenery(sarcophagusLocation) ?: return val locationInFront = sarcophagusScenery.location.transform(getNewLocation(sarcophagusScenery.direction)) // There are 6 sarcophagus, 6512 - 6517 with different door designs. @@ -201,6 +209,7 @@ class PyramidArea { mummyNpc.init() mummyNpc.walkingQueue.addPath(locationInFront.x, locationInFront.y) sendChat(mummyNpc, "Rawr!") + lock(player, 1) stopWalk(player) queueScript(player, 2, QueueStrength.SOFT) { stage -> stopWalk(player) @@ -252,34 +261,30 @@ class PyramidAreaFirstThree: MapArea { getRegionBorders(11597), getRegionBorders(11341), getRegionBorders(11085), - getRegionBorders(12945), ) } override fun entityStep(entity: Entity, location: Location, lastLocation: Location) { if (entity is Player) { + val averageLevel = ( + getDynLevel(entity, Skills.AGILITY) + + getDynLevel(entity, Skills.THIEVING) + ) / 2.0 + val randomValue = RandomFunction.randomDouble(99.5) - if (!PyramidArea.safeZone.insideBorder(entity.location)) { // Safezone is talking to Azzanadra. - val averageLevel = ( - getDynLevel(entity, Skills.AGILITY) + - getDynLevel(entity, Skills.THIEVING) - ) / 2 - val randomValue = RandomFunction.randomDouble(99.5) + if ((1..20).random() == 1) { + // A mummy would jump out if you walk near a sarcophagus of 2 radius. + val sarcoph = PyramidArea.nearSarcophagus(entity.location) + if (sarcoph != null) { + PyramidArea.spawnMummy(entity, sarcoph) + } + } - if ((1..10).random() == 1) { - // A mummy would jump out if you walk near a sarcophagus of 2 radius. - val sarcoph = PyramidArea.nearSarcophagus(entity.location) - if (sarcoph != null) { - PyramidArea.spawnMummy(entity, sarcoph) - } - } - - if ((1..60).random() == 1) { - PyramidArea.spawnScarabs(entity) - } - if (randomValue > averageLevel) { - PyramidArea.trapdoorTrap(entity) - } + if ((1..80).random() == 2) { + PyramidArea.spawnScarabs(entity) + } + if (randomValue > averageLevel && (1..128).random() == 1) { + PyramidArea.trapdoorTrap(entity) } } } @@ -295,16 +300,16 @@ class PyramidAreaFinal: MapArea { override fun entityStep(entity: Entity, location: Location, lastLocation: Location) { if (entity is Player) { - - if ((1..30).random() == 1) { - PyramidArea.spawnScarabs(entity) - } - - if ((1..15).random() == 1) { - // A mummy would jump out if you walk near a sarcophagus of 2 radius. - val sarcoph = PyramidArea.nearSarcophagus(entity.location) - if (sarcoph != null) { - PyramidArea.spawnMummy(entity, sarcoph) + if (!PyramidArea.safeZone.insideBorder(entity.location)) { // Safezone is talking to Azzanadra. + if ((1..20).random() == 1) { + // A mummy would jump out if you walk near a sarcophagus of 2 radius. + val sarcoph = PyramidArea.nearSarcophagus(entity.location) + if (sarcoph != null) { + PyramidArea.spawnMummy(entity, sarcoph) + } + } + if ((1..80).random() == 2) { + PyramidArea.spawnScarabs(entity) } } diff --git a/Server/src/main/content/region/desert/quest/deserttreasure/ScarabNPC.kt b/Server/src/main/content/region/desert/quest/deserttreasure/ScarabNPC.kt new file mode 100644 index 000000000..93dd60d7b --- /dev/null +++ b/Server/src/main/content/region/desert/quest/deserttreasure/ScarabNPC.kt @@ -0,0 +1,18 @@ +package content.region.desert.quest.deserttreasure + +import core.api.* +import core.game.node.entity.npc.NPC +import core.game.node.entity.npc.NPCBehavior +import org.rs09.consts.NPCs + +class ScarabNPC : NPCBehavior(NPCs.SCARABS_1969) { + var clearTime = 0 + + override fun tick(self: NPC): Boolean { + if (clearTime++ > 100) { + clearTime = 0 + poofClear(self) + } + return true + } +} \ No newline at end of file From 6362eee75304788f8de3d94e156470d4907cd95e Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Thu, 20 Feb 2025 10:53:59 +0000 Subject: [PATCH 005/117] Can now recover your digsite trowel from the examiner --- .../misthalin/digsite/dialogue/ExaminerDialogue.kt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Server/src/main/content/region/misthalin/digsite/dialogue/ExaminerDialogue.kt b/Server/src/main/content/region/misthalin/digsite/dialogue/ExaminerDialogue.kt index f97b20425..d9f94dfba 100644 --- a/Server/src/main/content/region/misthalin/digsite/dialogue/ExaminerDialogue.kt +++ b/Server/src/main/content/region/misthalin/digsite/dialogue/ExaminerDialogue.kt @@ -45,7 +45,18 @@ class ExaminerDialogueFile : DialogueBuilderFile() { } b.onQuestStages(Quests.THE_DIG_SITE, 6, 7, 8, 9, 10, 11, 12) - .npcl(FacialExpression.FRIENDLY, "Well, what are you doing here? Get digging!") + .branch { player -> if(inInventory(player, Items.TROWEL_676)) { 0 } else { 1 } } + .let{ branch -> + branch.onValue(0) + .npcl(FacialExpression.FRIENDLY, "Well, what are you doing here? Get digging!") + .end() + branch.onValue(1) + .playerl("I have lost my trowel.") + .npcl("Deary me. That was a good one as well. It's a good job I have another. Here you go...") + .endWith { _, player -> + addItemOrDrop(player, Items.TROWEL_676) + } + } b.onQuestStages(Quests.THE_DIG_SITE, 5) .playerl(FacialExpression.FRIENDLY, "Hello.") From 83b8689b8618524cae9d8468fd3001b534d58410 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Sun, 23 Feb 2025 04:02:11 +0000 Subject: [PATCH 006/117] Corrected quizmaster lamp reward --- .../global/ame/events/quizmaster/QuizMasterDialogueFile.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/src/main/content/global/ame/events/quizmaster/QuizMasterDialogueFile.kt b/Server/src/main/content/global/ame/events/quizmaster/QuizMasterDialogueFile.kt index 3796fac17..4790a8550 100644 --- a/Server/src/main/content/global/ame/events/quizmaster/QuizMasterDialogueFile.kt +++ b/Server/src/main/content/global/ame/events/quizmaster/QuizMasterDialogueFile.kt @@ -58,7 +58,7 @@ class QuizMasterDialogueFile : DialogueFile() { // Random Item should be "Mystery Box", but the current MYSTERY_BOX_6199 is already inauthentically used by Giftmas. val tableRoll = WeightBasedTable.create( - WeightedItem(Items.LAMP_6796, 1, 1, 1.0, false), + WeightedItem(Items.LAMP_2528, 1, 1, 1.0, false), WeightedItem(Items.CABBAGE_1965, 1, 1, 1.0, false), WeightedItem(Items.DIAMOND_1601, 1, 1, 1.0, false), WeightedItem(Items.BUCKET_1925, 1, 1, 1.0, false), From 448f970c10bd1ea869beb801df72ab7cf92de5de Mon Sep 17 00:00:00 2001 From: Kennynes Date: Sun, 23 Feb 2025 13:13:47 +0000 Subject: [PATCH 007/117] Fixed wrong door used in heroes' quest --- Server/data/configs/door_configs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/data/configs/door_configs.json b/Server/data/configs/door_configs.json index 9608881e7..ec17288a2 100644 --- a/Server/data/configs/door_configs.json +++ b/Server/data/configs/door_configs.json @@ -312,7 +312,7 @@ "metal": "false" }, { - "id": "2036", + "id": "1530", "replaceId": "1531", "fence": "false", "metal": "false" From dda80d535bc1e2c6bd824eaf65c1467cae3e2b20 Mon Sep 17 00:00:00 2001 From: Player Name Date: Mon, 10 Mar 2025 03:18:27 +0000 Subject: [PATCH 008/117] Fixed a pet growth bug that could cause an error logging in --- .../skill/summoning/familiar/FamiliarManager.java | 10 +++++++++- .../main/content/global/skill/summoning/pet/Pet.java | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Server/src/main/content/global/skill/summoning/familiar/FamiliarManager.java b/Server/src/main/content/global/skill/summoning/familiar/FamiliarManager.java index 888fa8f3e..3f210dd95 100644 --- a/Server/src/main/content/global/skill/summoning/familiar/FamiliarManager.java +++ b/Server/src/main/content/global/skill/summoning/familiar/FamiliarManager.java @@ -128,7 +128,12 @@ public final class FamiliarManager { } if (currentPet != -1) { int last = this.petDetails.get(currentPet).size() - 1; - PetDetails details = this.petDetails.get(currentPet).get(last); + PetDetails details; + if (last < 0) { //missing data in save due to historical bug (see GL !2077) + details = new PetDetails(0); + } else { + details = this.petDetails.get(currentPet).get(last); + } Pets pets = Pets.forId(currentPet); familiar = new Pet(player, details, currentPet, pets.getNpcId(currentPet)); } else if (familiarData.containsKey("familiar")) { @@ -414,6 +419,9 @@ public final class FamiliarManager { * @param details The new pet details. */ public void addDetails(int itemId, PetDetails details) { + if (petDetails.get(itemId) == null) { + petDetails.put(itemId, new ArrayList<>()); + } petDetails.get(itemId).add(details); } diff --git a/Server/src/main/content/global/skill/summoning/pet/Pet.java b/Server/src/main/content/global/skill/summoning/pet/Pet.java index bb83f16d4..99048c140 100644 --- a/Server/src/main/content/global/skill/summoning/pet/Pet.java +++ b/Server/src/main/content/global/skill/summoning/pet/Pet.java @@ -129,8 +129,8 @@ public final class Pet extends Familiar { if (pet.isKitten(itemId)) { owner.incrementAttribute("/save:stats_manager:cats_raised"); } - owner.getFamiliarManager().removeDetails(getItemId()); owner.getFamiliarManager().addDetails(newItemId, details); + owner.getFamiliarManager().removeDetails(getItemId()); owner.getFamiliarManager().morphPet(new Item(newItemId), false, location, details.getHunger(), 0); owner.getPacketDispatch().sendMessage("Your pet has grown larger."); } From 0622aed00cb61ab912166683ff01a38444044f44 Mon Sep 17 00:00:00 2001 From: Player Name Date: Tue, 25 Mar 2025 09:28:14 +0000 Subject: [PATCH 009/117] Fixed incorrect items in circulation Corrected incorrect rune kite (h) Corrected incorrect adamant kite (h) Corrected incorrect lamps Corrected incorrect blurberry specials --- .../quest/deathplateau/HaroldDialogueFile.kt | 8 +------- Server/src/main/core/ServerConstants.kt | 2 +- .../entity/player/info/login/SaveVersionHooks.kt | 16 ++++++++++++++++ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Server/src/main/content/region/asgarnia/burthorpe/quest/deathplateau/HaroldDialogueFile.kt b/Server/src/main/content/region/asgarnia/burthorpe/quest/deathplateau/HaroldDialogueFile.kt index 005206e10..e4242212b 100644 --- a/Server/src/main/content/region/asgarnia/burthorpe/quest/deathplateau/HaroldDialogueFile.kt +++ b/Server/src/main/content/region/asgarnia/burthorpe/quest/deathplateau/HaroldDialogueFile.kt @@ -109,15 +109,9 @@ class HaroldDialogueFile : DialogueFile() { 21 -> playerl(FacialExpression.ASKING, "What?").also { stage++ } 22 -> npcl(FacialExpression.FRIENDLY, "I really fancy one of those Blurberry Specials. I never get over to the Gnome Stronghold so I haven't had one for ages!").also { stage++ } 23 -> { - if (removeItem(player!!, Items.BLURBERRY_SPECIAL_2064)) { + if (removeItem(player!!, Items.BLURBERRY_SPECIAL_2064) || removeItem(player!!, Items.PREMADE_BLURB_SP_2028)) { sendMessage(player!!, "You give Harold a Blurberry Special.") sendItemDialogue(player!!, Items.BLURBERRY_SPECIAL_2064, "You give Harold a Blurberry Special.").also { stage++ } - } else if (removeItem(player!!, Items.BLURBERRY_SPECIAL_9520)) { // This should not be here since 9520 is used by the gnome restaurant minigame. - sendMessage(player!!, "You give Harold a Blurberry Special.") - sendItemDialogue(player!!, Items.BLURBERRY_SPECIAL_2064, "You give Harold a Blurberry Special.").also { stage++ } - } else if (removeItem(player!!, Items.PREMADE_BLURB_SP_2028)) { - sendMessage(player!!, "You give Harold a Blurberry Special.") - sendItemDialogue(player!!, Items.PREMADE_BLURB_SP_2028, "You give Harold a Blurberry Special.").also { stage++ } } else { player(FacialExpression.FRIENDLY, "I'll go and get you one.").also { stage = END_DIALOGUE } } diff --git a/Server/src/main/core/ServerConstants.kt b/Server/src/main/core/ServerConstants.kt index 26923a269..30a6ab377 100644 --- a/Server/src/main/core/ServerConstants.kt +++ b/Server/src/main/core/ServerConstants.kt @@ -18,7 +18,7 @@ class ServerConstants { var NOAUTH_DEFAULT_ADMIN: Boolean = true @JvmField - var CURRENT_SAVEFILE_VERSION = 3 + var CURRENT_SAVEFILE_VERSION = 4 @JvmField var DAILY_ACCOUNT_LIMIT = 3 diff --git a/Server/src/main/core/game/node/entity/player/info/login/SaveVersionHooks.kt b/Server/src/main/core/game/node/entity/player/info/login/SaveVersionHooks.kt index fe26b8b8f..f1d5e4819 100644 --- a/Server/src/main/core/game/node/entity/player/info/login/SaveVersionHooks.kt +++ b/Server/src/main/core/game/node/entity/player/info/login/SaveVersionHooks.kt @@ -81,6 +81,22 @@ class SaveVersionHooks : LoginListener { } } + if (player.version < 4) { //GL !2065 + replaceAllItems(player, Items.BLURBERRY_SPECIAL_9520, Items.BLURBERRY_SPECIAL_2064) + replaceAllItems(player, Items.BLURBERRY_SPECIAL_9521, Items.BLURBERRY_SPECIAL_2065) + replaceAllItems(player, Items.LAMP_6796, Items.LAMP_2528) + replaceAllItems(player, Items.RUNE_SHIELDH1_10667, Items.RUNE_SHIELDH1_7336) + replaceAllItems(player, Items.RUNE_SHIELDH2_10670, Items.RUNE_SHIELDH2_7342) + replaceAllItems(player, Items.RUNE_SHIELDH3_10673, Items.RUNE_SHIELDH3_7348) + replaceAllItems(player, Items.RUNE_SHIELDH4_10676, Items.RUNE_SHIELDH4_7354) + replaceAllItems(player, Items.RUNE_SHIELDH5_10679, Items.RUNE_SHIELDH5_7360) + replaceAllItems(player, Items.ADAMANT_SHIELDH1_10666, Items.ADAMANT_SHIELDH1_7334) + replaceAllItems(player, Items.ADAMANT_SHIELDH2_10669, Items.ADAMANT_SHIELDH2_7340) + replaceAllItems(player, Items.ADAMANT_SHIELDH3_10672, Items.ADAMANT_SHIELDH3_7346) + replaceAllItems(player, Items.ADAMANT_SHIELDH4_10675, Items.ADAMANT_SHIELDH4_7352) + replaceAllItems(player, Items.ADAMANT_SHIELDH5_10678, Items.ADAMANT_SHIELDH5_7358) + } + // Finish up player.version = ServerConstants.CURRENT_SAVEFILE_VERSION } From 63f8ec3cbfbcc7f0b7a42edbbc8c67b6298fec07 Mon Sep 17 00:00:00 2001 From: Lucid Enigma Date: Tue, 25 Mar 2025 09:37:26 +0000 Subject: [PATCH 010/117] Fixed facial expressions in dialogue for Tree Gnome Village NPCs --- .../quest/tree/CommanderMontaiDialogue.kt | 53 ++++++------ .../kandarin/quest/tree/ElkoyDialogue.kt | 57 ++++++------- .../kandarin/quest/tree/KingBolrenDialogue.kt | 81 ++++++++++--------- .../quest/tree/LieutenantSchepburDialogue.kt | 9 ++- .../kandarin/quest/tree/LocalGnomeDialogue.kt | 3 +- .../kandarin/quest/tree/RemsaiDialogue.kt | 15 ++-- .../quest/tree/TrackerGnomeOneDialogue.kt | 11 +-- .../quest/tree/TrackerGnomeThreeDialogue.kt | 21 ++--- .../quest/tree/TrackerGnomeTwoDialogue.kt | 17 ++-- 9 files changed, 138 insertions(+), 129 deletions(-) diff --git a/Server/src/main/content/region/kandarin/quest/tree/CommanderMontaiDialogue.kt b/Server/src/main/content/region/kandarin/quest/tree/CommanderMontaiDialogue.kt index cc79075df..e758479a2 100644 --- a/Server/src/main/content/region/kandarin/quest/tree/CommanderMontaiDialogue.kt +++ b/Server/src/main/content/region/kandarin/quest/tree/CommanderMontaiDialogue.kt @@ -4,6 +4,7 @@ import content.data.Quests import core.api.* import org.rs09.consts.Items import core.game.dialogue.DialogueFile +import core.game.dialogue.FacialExpression import core.tools.END_DIALOGUE class CommanderMontaiDialogue : DialogueFile(){ @@ -12,19 +13,19 @@ class CommanderMontaiDialogue : DialogueFile(){ if (questStage == 10) { when(stage) { 0 -> playerl("Hello.").also { stage++ } - 1 -> npcl("Hello traveller, are you here to help or just to watch?").also { stage++ } + 1 -> npcl(FacialExpression.OLD_NORMAL, "Hello traveller, are you here to help or just to watch?").also { stage++ } 2 -> playerl("I've been sent by King Bolren to retrieve the orb of protection.").also { stage++ } - 3 -> npcl("Excellent we need all the help we can get.").also { stage++ } - 4 -> npcl("I'm Commander Montai. The orb is in the Khazard stronghold to the north, but until we weaken their defences we can't get close.").also { stage++ } + 3 -> npcl(FacialExpression.OLD_NORMAL, "Excellent we need all the help we can get.").also { stage++ } + 4 -> npcl(FacialExpression.OLD_NORMAL, "I'm Commander Montai. The orb is in the Khazard stronghold to the north, but until we weaken their defences we can't get close.").also { stage++ } 5 -> playerl("What can I do?").also { stage++ } - 6 -> npcl("Firstly we need to strengthen our own defences. We desperately need wood to make more battlements, once the battlements are gone it's all over. Six loads of normal logs should do it.").also { stage++ } + 6 -> npcl(FacialExpression.OLD_NORMAL, "Firstly we need to strengthen our own defences. We desperately need wood to make more battlements, once the battlements are gone it's all over. Six loads of normal logs should do it.").also { stage++ } 7 -> options("Ok, I'll gather some wood.", "Sorry, I no longer want to be involved.").also { stage++ } 8 -> when (buttonID) { 1 -> playerl("Ok, I'll gather some wood.").also { stage = 10 } 2 -> playerl("Sorry, I no longer want to be involved.").also { stage = 9 } } - 9 -> npcl("That's a shame, we could have done with your help.").also { stage = END_DIALOGUE } - 10 -> npcl("Please be as quick as you can, I don't know how much longer we can hold out.").also { + 9 -> npcl(FacialExpression.OLD_NORMAL, "That's a shame, we could have done with your help.").also { stage = END_DIALOGUE } + 10 -> npcl(FacialExpression.OLD_NORMAL, "Please be as quick as you can, I don't know how much longer we can hold out.").also { setQuestStage(player!!, Quests.TREE_GNOME_VILLAGE, 20) stage = END_DIALOGUE } @@ -33,43 +34,43 @@ class CommanderMontaiDialogue : DialogueFile(){ if(inInventory(player!!, Items.LOGS_1511,6)){ when(stage) { 0 -> playerl("Hello.").also { stage++ } - 1 -> npcl("Hello again, we're still desperate for wood soldier.").also { stage++ } + 1 -> npcl(FacialExpression.OLD_NORMAL, "Hello again, we're still desperate for wood soldier.").also { stage++ } 2 -> playerl("I have some here. (You give six loads of logs to the commander.)").also{ stage++ } 3 -> { // Remove the 6 normal logs for(i in 1..6) { removeItem(player!!,Items.LOGS_1511) } setQuestStage(player!!, Quests.TREE_GNOME_VILLAGE, 25) - npcl("That's excellent, now we can make more defensive battlements. Give me a moment to organize the troops and then come speak to me. I'll inform you of our next phase of attack.") + npcl(FacialExpression.OLD_NORMAL, "That's excellent, now we can make more defensive battlements. Give me a moment to organize the troops and then come speak to me. I'll inform you of our next phase of attack.") stage = END_DIALOGUE } } } else { when(stage) { 0 -> playerl("Hello.").also { stage++ } - 1 -> npcl("Hello again, we're still desperate for wood soldier. We need six loads of normal logs.").also { stage++ } + 1 -> npcl(FacialExpression.OLD_NORMAL, "Hello again, we're still desperate for wood soldier. We need six loads of normal logs.").also { stage++ } 2 -> playerl("I'll see what I can do.").also { stage++ } - 3 -> npcl("Thank you.").also { stage = END_DIALOGUE } + 3 -> npcl(FacialExpression.OLD_NORMAL, "Thank you.").also { stage = END_DIALOGUE } } } } else if (questStage == 25) { when(stage) { 0 -> playerl("How are you doing Montai?").also { stage++ } - 1 -> npcl("We're hanging in there soldier. For the next phase of our attack we need to breach their stronghold.").also { stage++ } - 2 -> npcl("The ballista can break through the stronghold wall, and then we can advance and seize back the orb.").also { stage++ } + 1 -> npcl(FacialExpression.OLD_NORMAL, "We're hanging in there soldier. For the next phase of our attack we need to breach their stronghold.").also { stage++ } + 2 -> npcl(FacialExpression.OLD_NORMAL, "The ballista can break through the stronghold wall, and then we can advance and seize back the orb.").also { stage++ } 3 -> playerl("So what's the problem?").also { stage++ } - 4 -> npcl("From this distance we can't get an accurate enough shot. We need the correct coordinates of the stronghold for a direct hit. I've sent out three tracker gnomes to gather them.").also { stage++ } + 4 -> npcl(FacialExpression.OLD_NORMAL, "From this distance we can't get an accurate enough shot. We need the correct coordinates of the stronghold for a direct hit. I've sent out three tracker gnomes to gather them.").also { stage++ } 5 -> playerl("Have they returned?").also { stage++ } - 6 -> npcl("I'm afraid not, and we're running out of time. I need you to go into the heart of the battlefield, find the trackers, and bring back the coordinates.").also { stage++ } - 7 -> npcl("Do you think you can do it?").also { stage++ } + 6 -> npcl(FacialExpression.OLD_NORMAL, "I'm afraid not, and we're running out of time. I need you to go into the heart of the battlefield, find the trackers, and bring back the coordinates.").also { stage++ } + 7 -> npcl(FacialExpression.OLD_NORMAL, "Do you think you can do it?").also { stage++ } 8 -> options("No, I've had enough of your battle.", "I'll try my best.").also { stage++ } 9 -> when(buttonID) { 1 -> playerl("No, I've had enough of your battle.").also { stage = 10 } 2 -> playerl("I'll try my best.").also { stage = 11 } } - 10 -> npcl("I understand, this isn't your fight.").also { stage = END_DIALOGUE } - 11 -> npcl("Thank you, you're braver than most.").also { stage++ } - 12 -> npcl("I don't know how long I will be able to hold out. Once you have the coordinates come back and fire the ballista right into those monsters.").also { stage++ } - 13 -> npcl("If you can retrieve the orb and bring safety back to my people, none of the blood spilled on this field will be in vain.").also { + 10 -> npcl(FacialExpression.OLD_NORMAL, "I understand, this isn't your fight.").also { stage = END_DIALOGUE } + 11 -> npcl(FacialExpression.OLD_NORMAL, "Thank you, you're braver than most.").also { stage++ } + 12 -> npcl(FacialExpression.OLD_NORMAL, "I don't know how long I will be able to hold out. Once you have the coordinates come back and fire the ballista right into those monsters.").also { stage++ } + 13 -> npcl(FacialExpression.OLD_NORMAL, "If you can retrieve the orb and bring safety back to my people, none of the blood spilled on this field will be in vain.").also { setQuestStage(player!!, Quests.TREE_GNOME_VILLAGE, 30) stage = END_DIALOGUE } @@ -77,29 +78,29 @@ class CommanderMontaiDialogue : DialogueFile(){ } else if (questStage == 30) { when(stage) { 0 -> playerl("Hello.").also { stage++ } - 1 -> npcl("Hello warrior. We need the coordinates for a direct hit from the ballista.").also { stage++ } - 2 -> npcl("Once you have a direct hit you will be able to enter the stronghold and retrieve the orb.").also { stage = END_DIALOGUE } + 1 -> npcl(FacialExpression.OLD_NORMAL, "Hello warrior. We need the coordinates for a direct hit from the ballista.").also { stage++ } + 2 -> npcl(FacialExpression.OLD_NORMAL, "Once you have a direct hit you will be able to enter the stronghold and retrieve the orb.").also { stage = END_DIALOGUE } } } else if (questStage == 31) { if(inInventory(player!!,Items.ORB_OF_PROTECTION_587)){ when(stage) { 0 -> playerl("I have the orb of protection.").also { stage++ } - 1 -> npcl("Incredible, for a human you really are something.").also { stage++ } + 1 -> npcl(FacialExpression.OLD_HAPPY, "Incredible, for a human you really are something.").also { stage++ } 2 -> playerl("Thanks... I think!").also { stage++ } - 3 -> npcl("I'll stay here with my troops and try and hold Khazard's men back. You return the orb to the gnome village. Go as quick as you can, the village is still unprotected.").also { stage = END_DIALOGUE } + 3 -> npcl(FacialExpression.OLD_NORMAL, "I'll stay here with my troops and try and hold Khazard's men back. You return the orb to the gnome village. Go as quick as you can, the village is still unprotected.").also { stage = END_DIALOGUE } } } else { when(stage) { 0 -> playerl("I've breached the stronghold.").also { stage++ } - 1 -> npcl("I saw, that was a beautiful sight. The Khazard troops didn't know what hit them.").also { stage++ } - 2 -> npcl("Now is the time to retrieve the orb. It's all in your hands. I'll be praying for you.").also { stage = END_DIALOGUE } + 1 -> npcl(FacialExpression.OLD_NORMAL, "I saw, that was a beautiful sight. The Khazard troops didn't know what hit them.").also { stage++ } + 2 -> npcl(FacialExpression.OLD_NORMAL, "Now is the time to retrieve the orb. It's all in your hands. I'll be praying for you.").also { stage = END_DIALOGUE } } } } else if (questStage != 0){ when(stage) { 0 -> playerl("Hello Montai, how are you?").also { stage++ } - 1 -> npcl("I'm ok, this battle is going to take longer to win than I expected. The Khazard troops won't give up even without the orb.").also { stage++ } + 1 -> npcl(FacialExpression.OLD_NORMAL, "I'm ok, this battle is going to take longer to win than I expected. The Khazard troops won't give up even without the orb.").also { stage++ } 2 -> playerl("Hang in there.").also { stage = END_DIALOGUE } } } diff --git a/Server/src/main/content/region/kandarin/quest/tree/ElkoyDialogue.kt b/Server/src/main/content/region/kandarin/quest/tree/ElkoyDialogue.kt index 135624829..708033a0d 100644 --- a/Server/src/main/content/region/kandarin/quest/tree/ElkoyDialogue.kt +++ b/Server/src/main/content/region/kandarin/quest/tree/ElkoyDialogue.kt @@ -11,6 +11,7 @@ import org.rs09.consts.Items import core.game.dialogue.DialogueFile import content.region.kandarin.quest.tree.TreeGnomeVillage.Companion.mazeEntrance import content.region.kandarin.quest.tree.TreeGnomeVillage.Companion.mazeVillage +import core.game.dialogue.FacialExpression import core.game.world.GameWorld.Pulser import core.tools.END_DIALOGUE @@ -45,30 +46,30 @@ class ElkoyDialogue : DialogueFile(){ inInventory(player!!, Items.ORBS_OF_PROTECTION_588) && followLocation == "exit" -> { when(stage) { 0 -> playerl("Hello Elkoy. I have the orb.").also { stage++ } - 1 -> npcl("Take it to King Bolren, I'm sure he'll be pleased to see you.").also { stage++ } + 1 -> npcl(FacialExpression.OLD_HAPPY, "Take it to King Bolren, I'm sure he'll be pleased to see you.").also { stage++ } 2 -> options("Alright, I'll do that.", "Can you guide me out of the maze now?").also { stage++ } 3 -> when(buttonID) { 1 -> playerl("Alright, I'll do that.").also { stage = END_DIALOGUE } 2 -> playerl("Can you guide me out of the maze now?").also { stage = 4 } } - 4 -> npcl("If you like, but please take the orb to King Bolren soon.").also { stage++ } + 4 -> npcl(FacialExpression.OLD_NORMAL, "If you like, but please take the orb to King Bolren soon.").also { stage++ } 5 -> { travelCutscene(player!!, mazeEntrance) stage++ } - 6 -> npcl("Here we are. Please don't lose the orb!").also { stage = END_DIALOGUE } + 6 -> npcl(FacialExpression.OLD_NORMAL, "Here we are. Please don't lose the orb!").also { stage = END_DIALOGUE } } } inInventory(player!!, Items.ORB_OF_PROTECTION_587) -> { when(stage) { 0 -> playerl("Hello Elkoy.").also { stage++ } - 1 -> npcl("You're back! And the orb?").also { stage++ } + 1 -> npcl(FacialExpression.OLD_NORMAL, "You're back! And the orb?").also { stage++ } 2 -> playerl("I have it here.").also { stage++ } 3 -> { if(locY > 3161){ - npcl("You're our saviour. Please return it to the village and we are all saved. Would you like me to show you the way to the village?").also { stage++ } + npcl(FacialExpression.OLD_NORMAL, "You're our saviour. Please return it to the village and we are all saved. Would you like me to show you the way to the village?").also { stage++ } } else { - npcl("Take the orb to King Bolren, I'm sure he'll be pleased to see you.").also { stage = END_DIALOGUE } + npcl(FacialExpression.OLD_NORMAL, "Take the orb to King Bolren, I'm sure he'll be pleased to see you.").also { stage = END_DIALOGUE } } } 4 -> options("Yes please.", "No thanks Elkoy.").also { stage++ } @@ -76,24 +77,24 @@ class ElkoyDialogue : DialogueFile(){ 1 -> playerl("Yes please.").also { stage = 7 } 2 -> playerl("No thanks Elkoy.").also { stage = 6 } } - 6 -> npcl("Ok then, take care.").also { stage = END_DIALOGUE } + 6 -> npcl(FacialExpression.OLD_NORMAL, "Ok then, take care.").also { stage = END_DIALOGUE } 7 -> travelCutscene(player!!, mazeVillage).also { stage++ } - 8 -> npcl("Here we are. Take the orb to King Bolren, I'm sure he'll be pleased to see you.").also { stage = END_DIALOGUE } + 8 -> npcl(FacialExpression.OLD_NORMAL, "Here we are. Take the orb to King Bolren, I'm sure he'll be pleased to see you.").also { stage = END_DIALOGUE } } } inInventory(player!!, Items.ORBS_OF_PROTECTION_588) || questStage == 100 -> { when(stage) { 0 -> playerl("Hello Elkoy.").also { stage++ } - 1 -> npcl("You truly are a hero.").also { stage++ } + 1 -> npcl(FacialExpression.OLD_HAPPY, "You truly are a hero.").also { stage++ } 2 -> playerl("Thanks.").also { stage++ } - 3 -> npcl("You saved us by defeating the warlord. I'm humbled and wish you well.").also { stage++ } - 4 -> npcl("Would you like me to show you the way to the ${followLocation}?").also { stage++ } + 3 -> npcl(FacialExpression.OLD_NORMAL, "You saved us by defeating the warlord. I'm humbled and wish you well.").also { stage++ } + 4 -> npcl(FacialExpression.OLD_NORMAL, "Would you like me to show you the way to the ${followLocation}?").also { stage++ } 5 -> options("Yes please.", "No thanks Elkoy.").also { stage++ } 6 -> when(buttonID) { 1 -> playerl("Yes please.").also { stage = 8 } 2 -> playerl("No thanks Elkoy.").also { stage = 7 } } - 7 -> npcl("Ok then, take care.").also { stage = END_DIALOGUE } + 7 -> npcl(FacialExpression.OLD_NORMAL, "Ok then, take care.").also { stage = END_DIALOGUE } 8 -> { if(followLocation == "village") { travelCutscene(player!!, mazeVillage) @@ -104,33 +105,33 @@ class ElkoyDialogue : DialogueFile(){ stage++ } } - 9 -> npcl("Here we are. Have a safe journey.").also { stage = END_DIALOGUE } - 10 -> npcl("Here we are. Feel free to have a look around.").also { stage = END_DIALOGUE } + 9 -> npcl(FacialExpression.OLD_NORMAL, "Here we are. Have a safe journey.").also { stage = END_DIALOGUE } + 10 -> npcl(FacialExpression.OLD_NORMAL, "Here we are. Feel free to have a look around.").also { stage = END_DIALOGUE } } } questStage == 0 -> { when(stage) { 0 -> playerl("Hello there.").also { stage++ } - 1 -> npcl("Hello, welcome to our maze. I'm Elkoy the tree gnome.").also { stage++ } + 1 -> npcl(FacialExpression.OLD_NORMAL, "Hello, welcome to our maze. I'm Elkoy the tree gnome.").also { stage++ } 2 -> playerl("I haven't heard of your sort.").also { stage++ } - 3 -> npcl("There's not many of us left. Once you could find tree gnomes anywhere in the world, now we hide in small groups to avoid capture.").also { stage++ } + 3 -> npcl(FacialExpression.OLD_SAD, "There's not many of us left. Once you could find tree gnomes anywhere in the world, now we hide in small groups to avoid capture.").also { stage++ } 4 -> playerl("Capture from whom?").also { stage++ } - 5 -> npcl("Tree gnomes have been hunted for so called 'fun' since as long as I can remember.").also { stage++ } - 6 -> npcl("Our main threat nowadays are General Khazard's troops. They know no mercy, but are also very dense. They'll never find their way through our maze.").also { stage++ } - 7 -> npcl("Have fun.").also { stage = END_DIALOGUE } + 5 -> npcl(FacialExpression.OLD_NORMAL, "Tree gnomes have been hunted for so called 'fun' since as long as I can remember.").also { stage++ } + 6 -> npcl(FacialExpression.OLD_NORMAL, "Our main threat nowadays are General Khazard's troops. They know no mercy, but are also very dense. They'll never find their way through our maze.").also { stage++ } + 7 -> npcl(FacialExpression.OLD_NORMAL, "Have fun.").also { stage = END_DIALOGUE } } } questStage in 1..39 -> { when (stage) { - 0 -> npcl("Oh my! The orb, they have the orb. We're doomed.").also { stage++ } + 0 -> npcl(FacialExpression.OLD_DISTRESSED, "Oh my! The orb, they have the orb. We're doomed.").also { stage++ } 1 -> playerl("Perhaps I'll be able to get it back for you.").also { stage++ } - 2 -> npcl("Would you like me to show you the way to the ${followLocation}?").also { stage++ } + 2 -> npcl(FacialExpression.OLD_NORMAL, "Would you like me to show you the way to the ${followLocation}?").also { stage++ } 3 -> options("Yes please.", "No thanks Elkoy.").also { stage++ } 4 -> when (buttonID) { 1 -> playerl("Yes please.").also { stage = 6 } 2 -> playerl("No thanks Elkoy.").also { stage = 5 } } - 5 -> npcl("Ok then, take care.").also { stage = END_DIALOGUE } + 5 -> npcl(FacialExpression.OLD_NORMAL, "Ok then, take care.").also { stage = END_DIALOGUE } 6 -> { if(followLocation == "village") travelCutscene(player!!, mazeVillage) @@ -138,20 +139,20 @@ class ElkoyDialogue : DialogueFile(){ travelCutscene(player!!, mazeEntrance) stage++ } - 7 -> npcl("Here we are. I hope you get the orb back soon.").also { stage = END_DIALOGUE } + 7 -> npcl(FacialExpression.OLD_NORMAL, "Here we are. I hope you get the orb back soon.").also { stage = END_DIALOGUE } } } questStage == 40 -> { when(stage) { 0 -> playerl("Hello Elkoy.").also { stage++ } - 1 -> npcl("Did you hear? Khazard's men have pillaged the village! They slaughtered many, and took the other orbs in an attempt to lead us out of the maze. When will the misery end?").also { stage++ } - 2 -> npcl("Would you like me to show you the way to the ${followLocation}?").also { stage++ } + 1 -> npcl(FacialExpression.OLD_NORMAL, "Did you hear? Khazard's men have pillaged the village! They slaughtered many, and took the other orbs in an attempt to lead us out of the maze. When will the misery end?").also { stage++ } + 2 -> npcl(FacialExpression.OLD_NORMAL, "Would you like me to show you the way to the ${followLocation}?").also { stage++ } 3 -> options("Yes please.", "No thanks Elkoy.").also { stage++ } 4 -> when(buttonID) { 1 -> playerl("Yes please.").also { stage = 6 } 2 -> playerl("No thanks Elkoy.").also { stage = 5 } } - 5 -> npcl("Ok then, take care.").also { stage = END_DIALOGUE } + 5 -> npcl(FacialExpression.OLD_NORMAL, "Ok then, take care.").also { stage = END_DIALOGUE } 6 -> { if(followLocation == "village") { travelCutscene(player!!, mazeVillage) @@ -161,8 +162,8 @@ class ElkoyDialogue : DialogueFile(){ stage++ } } - 7 -> npcl("Please try to get our orbs back for us, otherwise the village is doomed!").also { stage = END_DIALOGUE } - 8 -> npcl("Here we are. Despite what has happened here, I hope you feel welcome.").also { stage = END_DIALOGUE } + 7 -> npcl(FacialExpression.OLD_NORMAL, "Please try to get our orbs back for us, otherwise the village is doomed!").also { stage = END_DIALOGUE } + 8 -> npcl(FacialExpression.OLD_NORMAL, "Here we are. Despite what has happened here, I hope you feel welcome.").also { stage = END_DIALOGUE } } } } diff --git a/Server/src/main/content/region/kandarin/quest/tree/KingBolrenDialogue.kt b/Server/src/main/content/region/kandarin/quest/tree/KingBolrenDialogue.kt index 73c26fc14..89f6d5c11 100644 --- a/Server/src/main/content/region/kandarin/quest/tree/KingBolrenDialogue.kt +++ b/Server/src/main/content/region/kandarin/quest/tree/KingBolrenDialogue.kt @@ -10,6 +10,7 @@ import org.rs09.consts.Items import org.rs09.consts.NPCs import core.game.dialogue.DialogueFile import content.region.kandarin.quest.tree.TreeGnomeVillage.Companion.mazeEntrance +import core.game.dialogue.FacialExpression import core.game.world.GameWorld import core.tools.END_DIALOGUE @@ -20,37 +21,37 @@ class KingBolrenDialogue : DialogueFile() { questStage < 10 -> { when (stage) { 0 -> playerl("Hello.").also { stage++ } - 1 -> npcl("Well hello stranger. My name's Bolren, I'm the king of the tree gnomes.").also { stage++ } - 2 -> npcl("I'm surprised you made it in, maybe I made the maze too easy.").also { stage++ } + 1 -> npcl(FacialExpression.OLD_NORMAL, "Well hello stranger. My name's Bolren, I'm the king of the tree gnomes.").also { stage++ } + 2 -> npcl(FacialExpression.OLD_NORMAL, "I'm surprised you made it in, maybe I made the maze too easy.").also { stage++ } 3 -> playerl("Maybe.").also { stage++ } - 4 -> npcl("I'm afraid I have more serious concerns at the moment. Very serious.").also { stage++ } + 4 -> npcl(FacialExpression.OLD_NORMAL, "I'm afraid I have more serious concerns at the moment. Very serious.").also { stage++ } 5 -> options("I'll leave you to it then.", "Can I help at all?").also { stage++ } 6 -> when (buttonID) { 1 -> playerl("I'll leave you to it then.").also { stage = 7 } 2 -> playerl("Can I help at all?").also { stage = 8 } } - 7 -> npcl("Ok, take care.").also { stage = END_DIALOGUE } - 8 -> npcl("I'm glad you asked.").also { stage++ } - 9 -> npcl("The truth is my people are in grave danger. We have always been protected by the Spirit Tree. No creature of dark can harm us while its three orbs are in place.").also { stage++ } - 10 -> npcl("We are not a violent race, but we fight when we must. Many gnomes have fallen battling the dark forces of Khazard to the North.").also { stage++ } - 11 -> npcl("We became desperate, so we took one orb of protection to the battlefield. It was a foolish move.").also { stage++ } - 12 -> npcl("Khazard troops seized the orb. Now we are completely defenceless.").also { stage++ } + 7 -> npcl(FacialExpression.OLD_NORMAL, "Ok, take care.").also { stage = END_DIALOGUE } + 8 -> npcl(FacialExpression.OLD_NORMAL, "I'm glad you asked.").also { stage++ } + 9 -> npcl(FacialExpression.OLD_SAD, "The truth is my people are in grave danger. We have always been protected by the Spirit Tree. No creature of dark can harm us while its three orbs are in place.").also { stage++ } + 10 -> npcl(FacialExpression.OLD_SAD, "We are not a violent race, but we fight when we must. Many gnomes have fallen battling the dark forces of Khazard to the North.").also { stage++ } + 11 -> npcl(FacialExpression.OLD_SAD, "We became desperate, so we took one orb of protection to the battlefield. It was a foolish move.").also { stage++ } + 12 -> npcl(FacialExpression.OLD_NORMAL, "Khazard troops seized the orb. Now we are completely defenceless.").also { stage++ } 13 -> playerl("How can I help?").also { stage++ } - 14 -> npcl("You would be a huge benefit on the battlefield. If you would go there and try to retrieve the orb, my people and I will be forever grateful.").also { stage++ } + 14 -> npcl(FacialExpression.OLD_NORMAL,"You would be a huge benefit on the battlefield. If you would go there and try to retrieve the orb, my people and I will be forever grateful.").also { stage++ } 15 -> options("I would be glad to help.", "I'm sorry but I won't be involved.").also { stage++ } 16 -> when (buttonID) { 1 -> playerl("I would be glad to help.").also { stage = 18 } 2 -> playerl("I'm sorry but I won't be involved.").also { stage = 17 } } - 17 -> npcl("Ok then, travel safe.").also { stage = END_DIALOGUE } - 18 -> npcl("Thank you. The battlefield is to the north of the maze. Commander Montai will inform you of their current situation.").also { stage++ } - 19 -> npcl("That is if he's still alive.").also { stage++ } - 20 -> npcl("My assistant shall guide you out. Good luck friend, try your best to return the orb.").also { + 17 -> npcl(FacialExpression.OLD_NORMAL, "Ok then, travel safe.").also { stage = END_DIALOGUE } + 18 -> npcl(FacialExpression.OLD_NORMAL, "Thank you. The battlefield is to the north of the maze. Commander Montai will inform you of their current situation.").also { stage++ } + 19 -> npcl(FacialExpression.OLD_NORMAL, "That is if he's still alive.").also { stage++ } + 20 -> npcl(FacialExpression.OLD_NORMAL, "My assistant shall guide you out. Good luck friend, try your best to return the orb.").also { stage++ } 21 -> { teleport(player!!, mazeEntrance) - sendNPCDialogue(player!!, NPCs.ELKOY_5179, "We're out of the maze now. Please hurry, we must have the orb if we are to survive.") + sendNPCDialogue(player!!, NPCs.ELKOY_5179, "We're out of the maze now. Please hurry, we must have the orb if we are to survive.", FacialExpression.OLD_NORMAL) setQuestStage(player!!, Quests.TREE_GNOME_VILLAGE, 10) stage = END_DIALOGUE } @@ -59,7 +60,7 @@ class KingBolrenDialogue : DialogueFile() { questStage < 31 -> { when (stage) { 0 -> playerl("Hello Bolren.").also { stage++ } - 1 -> npcl("Hello traveller, we must retrieve the orb. It's being held by Khazard troops north of here.").also { stage++ } + 1 -> npcl(FacialExpression.OLD_NORMAL, "Hello traveller, we must retrieve the orb. It's being held by Khazard troops north of here.").also { stage++ } 2 -> playerl("Ok, I'll try my best.").also { stage = END_DIALOGUE } } } @@ -67,32 +68,32 @@ class KingBolrenDialogue : DialogueFile() { if(inInventory(player!!,Items.ORB_OF_PROTECTION_587)){ when(stage) { 0 -> playerl("I have the orb.").also { stage++ } - 1 -> npcl("Oh my... The misery, the horror!").also { stage++ } + 1 -> npcl(FacialExpression.OLD_DISTRESSED, "Oh my... The misery, the horror!").also { stage++ } 2 -> playerl("King Bolren, are you OK?").also { stage++ } - 3 -> npcl("Thank you traveller, but it's too late. We're all doomed.").also { stage++ } + 3 -> npcl(FacialExpression.OLD_DISTRESSED, "Thank you traveller, but it's too late. We're all doomed.").also { stage++ } 4 -> playerl("What happened?").also { stage++ } - 5 -> npcl("They came in the night. I don't know how many, but enough.").also { stage++ } + 5 -> npcl(FacialExpression.OLD_DISTRESSED, "They came in the night. I don't know how many, but enough.").also { stage++ } 6 -> playerl("Who?").also { stage++ } - 7 -> npcl("Khazard troops. They slaughtered anyone who got in their way. Women, children, my wife.").also { stage++ } + 7 -> npcl(FacialExpression.OLD_DISTRESSED, "Khazard troops. They slaughtered anyone who got in their way. Women, children, my wife.").also { stage++ } 8 -> playerl("I'm sorry.").also { stage++ } - 9 -> npcl("They took the other orbs, now we are defenceless.").also { stage++ } + 9 -> npcl(FacialExpression.OLD_BOWS_HEAD_SAD, "They took the other orbs, now we are defenceless.").also { stage++ } 10 -> playerl("Where did they take them?").also { stage++ } - 11 -> npcl("They headed north of the stronghold. A warlord carries the orbs.").also { stage++ } + 11 -> npcl(FacialExpression.OLD_NORMAL, "They headed north of the stronghold. A warlord carries the orbs.").also { stage++ } 12 -> options("I will find the warlord and bring back the orbs.", "I'm sorry but I can't help.").also { stage++ } 13 -> when(buttonID) { 1 -> playerl("I will find the warlord and bring back the orbs.").also { stage = 15 } 2 -> playerl("I'm sorry but I can't help.").also { stage = 14 } } - 14 -> npcl("I understand, this isn't your battle.").also { stage = END_DIALOGUE } - 15 -> npcl("You are brave, but this task will be tough even for you. I wish you the best of luck. Once again you are our only hope.").also { stage++ } - 16 -> npcl("I will safeguard this orb and pray for your safe return. My assistant will guide you out.").also { + 14 -> npcl(FacialExpression.OLD_NORMAL, "I understand, this isn't your battle.").also { stage = END_DIALOGUE } + 15 -> npcl(FacialExpression.OLD_NORMAL, "You are brave, but this task will be tough even for you. I wish you the best of luck. Once again you are our only hope.").also { stage++ } + 16 -> npcl(FacialExpression.OLD_NORMAL, "I will safeguard this orb and pray for your safe return. My assistant will guide you out.").also { stage++ } 17 -> { if(removeItem(player!!,Items.ORB_OF_PROTECTION_587)){ teleport(player!!,mazeEntrance) setQuestStage(player!!, Quests.TREE_GNOME_VILLAGE, 40) - sendNPCDialogue(player!!, NPCs.ELKOY_5179, "Good luck friend.") + sendNPCDialogue(player!!, NPCs.ELKOY_5179, "Good luck friend.", FacialExpression.OLD_NORMAL) } stage = END_DIALOGUE } @@ -100,9 +101,9 @@ class KingBolrenDialogue : DialogueFile() { } else { when(stage) { 0 -> playerl("Hello Bolren.").also { stage++ } - 1 -> npcl("Do you have the orb?").also { stage++ } + 1 -> npcl(FacialExpression.OLD_NORMAL, "Do you have the orb?").also { stage++ } 2 -> playerl("No, I'm afraid not.").also { stage++ } - 3 -> npcl("Please, we must have the orb if we are to survive.").also { stage = END_DIALOGUE } + 3 -> npcl(FacialExpression.OLD_NORMAL, "Please, we must have the orb if we are to survive.").also { stage = END_DIALOGUE } } } } @@ -110,12 +111,12 @@ class KingBolrenDialogue : DialogueFile() { if(inInventory(player!!,Items.ORBS_OF_PROTECTION_588)){ when(stage) { 0 -> playerl("Bolren, I have returned.").also { stage++ } - 1 -> npcl("You made it back! Do you have the orbs?").also { stage++ } + 1 -> npcl(FacialExpression.OLD_NORMAL, "You made it back! Do you have the orbs?").also { stage++ } 2 -> playerl("I have them here.").also { stage++ } - 3 -> npcl("Hooray, you're amazing. I didn't think it was possible but you've saved us.").also { stage++ } - 4 -> npcl("Once the orbs are replaced we will be safe once more. We must begin the ceremony immediately.").also { stage++ } + 3 -> npcl(FacialExpression.OLD_HAPPY, "Hooray, you're amazing. I didn't think it was possible but you've saved us.").also { stage++ } + 4 -> npcl(FacialExpression.OLD_NORMAL, "Once the orbs are replaced we will be safe once more. We must begin the ceremony immediately.").also { stage++ } 5 -> playerl("What does the ceremony involve?").also { stage++ } - 6 -> npcl("The spirit tree has looked over us for centuries. Now we must pay our respects.").also { stage++ } + 6 -> npcl(FacialExpression.OLD_NORMAL, "The spirit tree has looked over us for centuries. Now we must pay our respects.").also { stage++ } 7 -> sendDialogue(player!!,"The gnomes begin to chant. Meanwhile, King Bolren holds the orbs of protection out in front of him.").also { stage++ } 8 -> { // Orbs fly up, gnomes chant, spirit tree animates @@ -166,20 +167,20 @@ class KingBolrenDialogue : DialogueFile() { } else { when(stage) { 0 -> playerl("Bolren, I have returned.").also { stage++ } - 1 -> npcl("You made it back! Do you have the orbs?").also { stage++ } + 1 -> npcl(FacialExpression.OLD_NORMAL, "You made it back! Do you have the orbs?").also { stage++ } 2 -> playerl("No, I'm afraid not.").also { stage++ } - 3 -> npcl("Please, we must have the orb if we are to survive.").also { stage = END_DIALOGUE } + 3 -> npcl(FacialExpression.OLD_NORMAL, "Please, we must have the orb if we are to survive.").also { stage = END_DIALOGUE } } } } questStage == 99 -> { when(stage){ - 0 -> npcl("Now at last my people are safe once more. We can live in peace again.").also { stage++ } + 0 -> npcl(FacialExpression.OLD_NORMAL, "Now at last my people are safe once more. We can live in peace again.").also { stage++ } 1 -> playerl("I'm pleased I could help.").also { stage++ } - 2 -> npcl("You are modest brave traveller.").also { stage++ } - 3 -> npcl("Please, for your efforts take this amulet. It's made from the same sacred stone as the orbs of protection. It will help keep you safe on your journeys.").also { stage++ } + 2 -> npcl(FacialExpression.OLD_NORMAL, "You are modest brave traveller.").also { stage++ } + 3 -> npcl(FacialExpression.OLD_NORMAL, "Please, for your efforts take this amulet. It's made from the same sacred stone as the orbs of protection. It will help keep you safe on your journeys.").also { stage++ } 4 -> playerl("Thank you King Bolren.").also { stage++ } - 5 -> npcl("The tree has many other powers, some of which I cannot reveal. As a friend of the gnome people, I can now allow you to use the tree's magic to teleport to other trees grown from related seeds.").also { + 5 -> npcl(FacialExpression.OLD_NORMAL, "The tree has many other powers, some of which I cannot reveal. As a friend of the gnome people, I can now allow you to use the tree's magic to teleport to other trees grown from related seeds.").also { finishQuest(player!!, Quests.TREE_GNOME_VILLAGE) stage = END_DIALOGUE } @@ -188,9 +189,9 @@ class KingBolrenDialogue : DialogueFile() { isQuestComplete(player!!, Quests.TREE_GNOME_VILLAGE) -> { when(stage) { 0 -> playerl("Hello again Bolren.").also { stage++ } - 1 -> npcl("Well hello, it's good to see you again.").also { stage = if (hasAnItem(player!!, Items.GNOME_AMULET_589).container != null) END_DIALOGUE else 2 } + 1 -> npcl(FacialExpression.OLD_NORMAL, "Well hello, it's good to see you again.").also { stage = if (hasAnItem(player!!, Items.GNOME_AMULET_589).container != null) END_DIALOGUE else 2 } 2 -> playerl("I've lost my amulet.").also { stage++ } - 3 -> npcl("Oh dear. Here, take another. We are truly indebted to you.").also { + 3 -> npcl(FacialExpression.OLD_NORMAL, "Oh dear. Here, take another. We are truly indebted to you.").also { addItemOrDrop(player!!, Items.GNOME_AMULET_589) stage = END_DIALOGUE } diff --git a/Server/src/main/content/region/kandarin/quest/tree/LieutenantSchepburDialogue.kt b/Server/src/main/content/region/kandarin/quest/tree/LieutenantSchepburDialogue.kt index 7f29582d1..2571454a9 100644 --- a/Server/src/main/content/region/kandarin/quest/tree/LieutenantSchepburDialogue.kt +++ b/Server/src/main/content/region/kandarin/quest/tree/LieutenantSchepburDialogue.kt @@ -1,17 +1,18 @@ package content.region.kandarin.quest.tree import core.game.dialogue.DialogueFile +import core.game.dialogue.FacialExpression import core.tools.END_DIALOGUE class LieutenantSchepburDialogue : DialogueFile(){ override fun handle(componentID: Int, buttonID: Int) { when(stage) { - 0 -> npcl("Move into position lads! eh? Who are you and what do you want?").also { stage++ } + 0 -> npcl(FacialExpression.OLD_NORMAL, "Move into position lads! eh? Who are you and what do you want?").also { stage++ } 1 -> playerl("Who are you then?").also { stage++ } - 2 -> npcl("Lieutenant Schepbur, commanding officer of the new Armoured Tortoise Regiment.").also { stage++ } + 2 -> npcl(FacialExpression.OLD_NORMAL, "Lieutenant Schepbur, commanding officer of the new Armoured Tortoise Regiment.").also { stage++ } 3 -> playerl("There's only two tortoises here, that's hardly a regiment.").also { stage++ } - 4 -> npcl("This is just the beginning! Gnome breeders and trainers are already working to expand the number of units. Soon we'll have hundreds of these beauties, nay thousands! And they will not only carry mages and").also { stage++ } - 5 -> npcl("archers but other fiendish weapons of destruction of gnome devising. An army of giant tortoises will march upon this battlefield and rain the fire of our wrath upon all our enemies! Nothing will be able to stop us!").also { stage++ } + 4 -> npcl(FacialExpression.OLD_NORMAL, "This is just the beginning! Gnome breeders and trainers are already working to expand the number of units. Soon we'll have hundreds of these beauties, nay thousands! And they will not only carry mages and").also { stage++ } + 5 -> npcl(FacialExpression.OLD_NORMAL, "archers but other fiendish weapons of destruction of gnome devising. An army of giant tortoises will march upon this battlefield and rain the fire of our wrath upon all our enemies! Nothing will be able to stop us!").also { stage++ } 6 -> playerl("Oooookayy...... I'll leave you to it then....").also { stage = END_DIALOGUE } } } diff --git a/Server/src/main/content/region/kandarin/quest/tree/LocalGnomeDialogue.kt b/Server/src/main/content/region/kandarin/quest/tree/LocalGnomeDialogue.kt index b78813553..b6a0ff54c 100644 --- a/Server/src/main/content/region/kandarin/quest/tree/LocalGnomeDialogue.kt +++ b/Server/src/main/content/region/kandarin/quest/tree/LocalGnomeDialogue.kt @@ -1,13 +1,14 @@ package content.region.kandarin.quest.tree import core.game.dialogue.DialogueFile +import core.game.dialogue.FacialExpression import core.tools.END_DIALOGUE class LocalGnomeDialogue : DialogueFile() { override fun handle(componentID: Int, buttonID: Int) { when (stage) { 0 -> playerl("Hello little man.").also { stage++ } - 1 -> npcl("Little man stronger than big man. Hee hee, lardi dee, lardi da.").also { stage = END_DIALOGUE } + 1 -> npcl(FacialExpression.OLD_LAUGH1, "Little man stronger than big man. Hee hee, lardi dee, lardi da.").also { stage = END_DIALOGUE } } } } \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/tree/RemsaiDialogue.kt b/Server/src/main/content/region/kandarin/quest/tree/RemsaiDialogue.kt index 79b70b806..3038fed58 100644 --- a/Server/src/main/content/region/kandarin/quest/tree/RemsaiDialogue.kt +++ b/Server/src/main/content/region/kandarin/quest/tree/RemsaiDialogue.kt @@ -5,6 +5,7 @@ import core.api.inInventory import core.api.getQuestStage import org.rs09.consts.Items import core.game.dialogue.DialogueFile +import core.game.dialogue.FacialExpression import core.tools.END_DIALOGUE class RemsaiDialogue : DialogueFile(){ @@ -14,35 +15,35 @@ class RemsaiDialogue : DialogueFile(){ inInventory(player!!,Items.ORBS_OF_PROTECTION_588) -> { when(stage) { 0 -> playerl("I've returned.").also { stage++ } - 1 -> npcl("You're back, well done brave adventurer. Now the orbs are safe we can perform the ritual for the spirit tree. We can live in peace once again.").also { stage = END_DIALOGUE } + 1 -> npcl(FacialExpression.OLD_NORMAL, "You're back, well done brave adventurer. Now the orbs are safe we can perform the ritual for the spirit tree. We can live in peace once again.").also { stage = END_DIALOGUE } } } inInventory(player!!, Items.ORB_OF_PROTECTION_587) -> { when(stage) { 0 -> playerl("Hello Remsai.").also { stage++ } - 1 -> npcl("Hello, did you find the orb?").also { stage++ } + 1 -> npcl(FacialExpression.OLD_NORMAL, "Hello, did you find the orb?").also { stage++ } 2 -> playerl("I have it here.").also { stage++ } - 3 -> npcl("You're our saviour.").also { stage = END_DIALOGUE } + 3 -> npcl(FacialExpression.OLD_HAPPY, "You're our saviour.").also { stage = END_DIALOGUE } } } questStage < 40 -> { when(stage) { 0 -> playerl("Hello Remsai.").also { stage++ } - 1 -> npcl("Hello, did you find the orb?").also { stage++ } + 1 -> npcl(FacialExpression.OLD_NORMAL, "Hello, did you find the orb?").also { stage++ } 2 -> playerl("No, I'm afraid not.").also { stage++ } - 3 -> npcl("Please, we must have the orb if we are to survive.").also { stage = END_DIALOGUE } + 3 -> npcl(FacialExpression.OLD_NORMAL, "Please, we must have the orb if we are to survive.").also { stage = END_DIALOGUE } } } questStage == 40 -> { when(stage) { 0 -> playerl("Are you ok?").also { stage++ } - 1 -> npcl("Khazard's men came. Without the orb we were defenceless. They killed many and then took our last hope, the other orbs. Now surely we're all doomed. Without them the spirit tree is useless.").also { stage = END_DIALOGUE } + 1 -> npcl(FacialExpression.OLD_DISTRESSED, "Khazard's men came. Without the orb we were defenceless. They killed many and then took our last hope, the other orbs. Now surely we're all doomed. Without them the spirit tree is useless.").also { stage = END_DIALOGUE } } } questStage > 40 -> { when(stage) { 0 -> playerl("I've returned.").also { stage++ } - 1 -> npcl("You're back, well done brave adventurer. Now the orbs are safe we can perform the ritual for the spirit tree. We can live in peace once again.").also { stage = END_DIALOGUE } + 1 -> npcl(FacialExpression.OLD_NORMAL, "You're back, well done brave adventurer. Now the orbs are safe we can perform the ritual for the spirit tree. We can live in peace once again.").also { stage = END_DIALOGUE } } } } diff --git a/Server/src/main/content/region/kandarin/quest/tree/TrackerGnomeOneDialogue.kt b/Server/src/main/content/region/kandarin/quest/tree/TrackerGnomeOneDialogue.kt index 6ad5e3028..d4a4919fb 100644 --- a/Server/src/main/content/region/kandarin/quest/tree/TrackerGnomeOneDialogue.kt +++ b/Server/src/main/content/region/kandarin/quest/tree/TrackerGnomeOneDialogue.kt @@ -4,6 +4,7 @@ import content.data.Quests import core.api.* import org.rs09.consts.Items import core.game.dialogue.DialogueFile +import core.game.dialogue.FacialExpression import core.tools.END_DIALOGUE class TrackerGnomeOneDialogue : DialogueFile(){ @@ -13,32 +14,32 @@ class TrackerGnomeOneDialogue : DialogueFile(){ questStage >= 40 -> { when (stage) { 0 -> playerl("Hello").also { stage++ } - 1 -> npcl("When will this battle end? I feel like I've been fighting forever.").also { stage = END_DIALOGUE } + 1 -> npcl(FacialExpression.OLD_BOWS_HEAD_SAD, "When will this battle end? I feel like I've been fighting forever.").also { stage = END_DIALOGUE } } } questStage > 30 -> { if(inInventory(player!!, Items.ORB_OF_PROTECTION_587)){ when(stage) { 0 -> playerl("How are you tracker?").also { stage++ } - 1 -> npcl("Now we have the orb I'm much better. They won't stand a chance without it.").also { stage = END_DIALOGUE } + 1 -> npcl(FacialExpression.OLD_NORMAL, "Now we have the orb I'm much better. They won't stand a chance without it.").also { stage = END_DIALOGUE } } } else { when(stage) { 0 -> playerl("Hello again.").also { stage++ } - 1 -> npcl("Well done, you've broken down their defences. This battle must be ours.").also { stage = END_DIALOGUE } + 1 -> npcl(FacialExpression.OLD_NORMAL, "Well done, you've broken down their defences. This battle must be ours.").also { stage = END_DIALOGUE } } } } questStage == 30 -> { when (stage) { 0 -> playerl("Do you know the coordinates of the Khazard stronghold?").also { stage++ } - 1 -> npcl("I managed to get one, although it wasn't easy.").also { stage++ } + 1 -> npcl(FacialExpression.OLD_NORMAL, "I managed to get one, although it wasn't easy.").also { stage++ } 2 -> sendDialogue(player!!, "The gnome tells you the height coordinate.").also { setAttribute(player!!, "/save:treegnome:tracker1", true) stage++ } 3 -> playerl("Well done.").also { stage++ } - 4 -> npcl("The other two tracker gnomes should have the other coordinates if they're still alive.").also { stage++ } + 4 -> npcl(FacialExpression.OLD_NORMAL, "The other two tracker gnomes should have the other coordinates if they're still alive.").also { stage++ } 5 -> playerl("OK, take care.").also { stage = END_DIALOGUE } } } diff --git a/Server/src/main/content/region/kandarin/quest/tree/TrackerGnomeThreeDialogue.kt b/Server/src/main/content/region/kandarin/quest/tree/TrackerGnomeThreeDialogue.kt index 30070138b..de00880e0 100644 --- a/Server/src/main/content/region/kandarin/quest/tree/TrackerGnomeThreeDialogue.kt +++ b/Server/src/main/content/region/kandarin/quest/tree/TrackerGnomeThreeDialogue.kt @@ -3,6 +3,7 @@ package content.region.kandarin.quest.tree import content.data.Quests import core.api.* import core.game.dialogue.DialogueFile +import core.game.dialogue.FacialExpression import core.tools.END_DIALOGUE class TrackerGnomeThreeDialogue : DialogueFile(){ @@ -19,31 +20,31 @@ class TrackerGnomeThreeDialogue : DialogueFile(){ questStage == 30 -> { when(stage) { 0 -> playerl("Are you OK?").also { stage++ } - 1 -> npcl("OK? Who's OK? Not me! Hee hee!").also { stage++ } + 1 -> npcl(FacialExpression.OLD_LAUGH1, "OK? Who's OK? Not me! Hee hee!").also { stage++ } 2 -> playerl("What's wrong?").also { stage++ } - 3 -> npcl("You can't see me, no one can. Monsters, demons, they're all around me!").also { stage++ } + 3 -> npcl(FacialExpression.OLD_LAUGH1, "You can't see me, no one can. Monsters, demons, they're all around me!").also { stage++ } 4 -> playerl("What do you mean?").also { stage++ } - 5 -> npcl("They're dancing, all of them, hee hee.").also { stage++ } + 5 -> npcl(FacialExpression.OLD_LAUGH1, "They're dancing, all of them, hee hee.").also { stage++ } 6 -> sendDialogue(player!!,"He's clearly lost the plot.").also { stage++ } 7 -> playerl("Do you have the coordinate for the Khazard stronghold?").also { stage++ } - 8 -> npcl("Who holds the stronghold?").also { stage++ } + 8 -> npcl(FacialExpression.OLD_NORMAL, "Who holds the stronghold?").also { stage++ } 9 -> playerl("What?").also { stage++ } 10 -> { // Generate the x coordinate answer if(getAttribute(player!!,"treegnome:xcoord",0) == 0){ val answer = (1..4).random() - npcl(xcoordMap[answer]) + npcl(FacialExpression.OLD_NORMAL, xcoordMap[answer]) setAttribute(player!!,"/save:treegnome:xcoord",answer) } else { - npcl(xcoordMap[getAttribute(player!!,"treegnome:xcoord",1)]) + npcl(FacialExpression.OLD_NORMAL, xcoordMap[getAttribute(player!!,"treegnome:xcoord",1)]) } stage++ } 11 -> playerl("You're mad").also { stage++ } - 12 -> npcl("Dance with me, and Khazard's men are beat.").also { stage++ } + 12 -> npcl(FacialExpression.OLD_LAUGH1, "Dance with me, and Khazard's men are beat.").also { stage++ } 13 -> sendDialogue(player!!,"The toll of war has affected his mind.").also { stage++ } 14 -> playerl("I'll pray for you little man.").also { stage++ } - 15 -> npcl("All day we pray in the hay, hee hee.").also { + 15 -> npcl(FacialExpression.OLD_LAUGH1, "All day we pray in the hay, hee hee.").also { setAttribute(player!!, "/save:treegnome:tracker3", true) stage = END_DIALOGUE } @@ -52,13 +53,13 @@ class TrackerGnomeThreeDialogue : DialogueFile(){ questStage == 31 -> { when(stage) { 0 -> playerl("Hello again.").also { stage++ } - 1 -> npcl("Don't talk to me, you can't see me. No one can, just the demons.").also { stage = END_DIALOGUE } + 1 -> npcl(FacialExpression.OLD_BOWS_HEAD_SAD, "Don't talk to me, you can't see me. No one can, just the demons.").also { stage = END_DIALOGUE } } } questStage > 31 -> { when(stage) { 0 -> playerl("Hello").also { stage++ } - 1 -> npcl("I feel dizzy, where am I? Oh dear, oh dear I need some rest.").also { stage++ } + 1 -> npcl(FacialExpression.OLD_NORMAL, "I feel dizzy, where am I? Oh dear, oh dear I need some rest.").also { stage++ } 2 -> playerl("I think you do.").also { stage = END_DIALOGUE } } } diff --git a/Server/src/main/content/region/kandarin/quest/tree/TrackerGnomeTwoDialogue.kt b/Server/src/main/content/region/kandarin/quest/tree/TrackerGnomeTwoDialogue.kt index 6a845937d..d4bbd63c0 100644 --- a/Server/src/main/content/region/kandarin/quest/tree/TrackerGnomeTwoDialogue.kt +++ b/Server/src/main/content/region/kandarin/quest/tree/TrackerGnomeTwoDialogue.kt @@ -4,6 +4,7 @@ import content.data.Quests import core.api.* import org.rs09.consts.Items import core.game.dialogue.DialogueFile +import core.game.dialogue.FacialExpression import core.tools.END_DIALOGUE class TrackerGnomeTwoDialogue : DialogueFile(){ @@ -13,36 +14,36 @@ class TrackerGnomeTwoDialogue : DialogueFile(){ questStage == 30 -> { when (stage) { 0 -> playerl("Are you OK?").also { stage++ } - 1 -> npcl("They caught me spying on the stronghold. They beat and tortured me.").also { stage++ } - 2 -> npcl("But I didn't crack. I told them nothing. They can't break me!").also { stage++ } + 1 -> npcl(FacialExpression.OLD_DISTRESSED, "They caught me spying on the stronghold. They beat and tortured me.").also { stage++ } + 2 -> npcl(FacialExpression.OLD_LAUGH1, "But I didn't crack. I told them nothing. They can't break me!").also { stage++ } 3 -> playerl("I'm sorry little man.").also { stage++ } - 4 -> npcl("Don't be. I have the position of the stronghold!").also { stage++ } + 4 -> npcl(FacialExpression.OLD_LAUGH1, "Don't be. I have the position of the stronghold!").also { stage++ } 5 -> sendDialogue(player!!, "The gnome tells you the y coordinate.").also { setAttribute(player!!, "/save:treegnome:tracker2", true) stage++ } 6 -> playerl("Well done.").also { stage++ } - 7 -> npcl("Now leave before they find you and all is lost.").also { stage++ } + 7 -> npcl(FacialExpression.OLD_NORMAL, "Now leave before they find you and all is lost.").also { stage++ } 8 -> playerl("Hang in there.").also { stage++ } - 9 -> npcl("Go!").also { stage = END_DIALOGUE } + 9 -> npcl(FacialExpression.OLD_NORMAL, "Go!").also { stage = END_DIALOGUE } } } questStage >= 40 -> { when(stage) { 0 -> playerl("Hello").also { stage++ } - 1 -> npcl("When will this battle end? I feel like I've been locked up my whole life.").also { stage = END_DIALOGUE } + 1 -> npcl(FacialExpression.OLD_DISTRESSED, "When will this battle end? I feel like I've been locked up my whole life.").also { stage = END_DIALOGUE } } } questStage > 30 -> { if(inInventory(player!!,Items.ORB_OF_PROTECTION_587)){ when(stage) { 0 -> playerl("How are you tracker?").also { stage++ } - 1 -> npcl("Now we have the orb I'm much better. Soon my comrades will come and free me.").also { stage = END_DIALOGUE } + 1 -> npcl(FacialExpression.OLD_NORMAL, "Now we have the orb I'm much better. Soon my comrades will come and free me.").also { stage = END_DIALOGUE } } } else { when(stage) { 0 -> playerl("Hello again.").also { stage++ } - 1 -> npcl("Well done, you've broken down their defences. This battle must be ours.").also { stage = END_DIALOGUE } + 1 -> npcl(FacialExpression.OLD_NORMAL, "Well done, you've broken down their defences. This battle must be ours.").also { stage = END_DIALOGUE } } } } From 47ac5e93d662634a2e6a94d73c52aac61bdb2779 Mon Sep 17 00:00:00 2001 From: Alex Page Date: Tue, 25 Mar 2025 09:48:27 +0000 Subject: [PATCH 011/117] Fixed issues causing pie crafting to stop or consume additional ingredients --- .../skill/cooking/CookingRecipePlugin.java | 18 ++++++++++-------- .../global/skill/cooking/recipe/Recipe.java | 3 +++ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Server/src/main/content/global/skill/cooking/CookingRecipePlugin.java b/Server/src/main/content/global/skill/cooking/CookingRecipePlugin.java index 9ef36bcaf..067217358 100644 --- a/Server/src/main/content/global/skill/cooking/CookingRecipePlugin.java +++ b/Server/src/main/content/global/skill/cooking/CookingRecipePlugin.java @@ -44,29 +44,30 @@ public final class CookingRecipePlugin extends UseWithHandler { @Override public boolean handle(NodeUsageEvent event) { Recipe recipe = null; + Item part = null; // TODO: Transitioning to a Listener would save an O(n) pass through the recipes list on every use-with + recipeloop: for (Recipe temp : Recipe.RECIPES) { if (temp.isSingular()) { if (temp.getBase().getId() == event.getUsedItem().getId() || temp.getBase().getId() == event.getBaseItem().getId()) { for (Item ingredient : temp.getIngredients()) { if (ingredient.getId() == event.getBaseItem().getId() || ingredient.getId() == event.getUsedItem().getId()) { recipe = temp; - break; + break recipeloop; } } } } else { - Item part = null; - Item ingredient = null; for (int k = 0; k < temp.getParts().length; k++) { for (int i = 0; i < temp.getIngredients().length; i++) { - part = temp.getParts()[k]; - ingredient = temp.getIngredients()[i]; - if (part.getId() == event.getUsedItem().getId() && ingredient.getId() == event.getBaseItem().getId() || part.getId() == event.getBaseItem().getId() && ingredient.getId() == event.getUsedItem().getId()) { + Item tempPart = temp.getParts()[k]; + Item ingredient = temp.getIngredients()[i]; + if (tempPart.getId() == event.getUsedItem().getId() && ingredient.getId() == event.getBaseItem().getId() || tempPart.getId() == event.getBaseItem().getId() && ingredient.getId() == event.getUsedItem().getId()) { if (k == i) {// represents that this ingredient can // mix with the other. recipe = temp; - break; + part = tempPart; + break recipeloop; } } } @@ -76,6 +77,7 @@ public final class CookingRecipePlugin extends UseWithHandler { if (recipe != null) { final Player player = event.getPlayer(); final Recipe recipe_ = recipe; + final Item part_ = part; SkillDialogueHandler handler = new SkillDialogueHandler(player, SkillDialogue.ONE_OPTION, recipe.getProduct()) { @Override public void create(final int amount, int index) { @@ -91,7 +93,7 @@ public final class CookingRecipePlugin extends UseWithHandler { @Override public int getAll(int index) { - return player.getInventory().getAmount(recipe_.getBase()); + return player.getInventory().getAmount(part_ != null ? part_ : recipe_.getBase()); } }; if (player.getInventory().getAmount(recipe.getBase()) == 1) { diff --git a/Server/src/main/content/global/skill/cooking/recipe/Recipe.java b/Server/src/main/content/global/skill/cooking/recipe/Recipe.java index 2402a9772..3e2eff513 100644 --- a/Server/src/main/content/global/skill/cooking/recipe/Recipe.java +++ b/Server/src/main/content/global/skill/cooking/recipe/Recipe.java @@ -119,6 +119,9 @@ public abstract class Recipe { } } if (index != -1) { + if (!player.getInventory().containItems(event.getBaseItem().getId(), event.getUsedItem().getId())) { + return; + } if (player.getInventory().remove(event.getBaseItem()) && player.getInventory().remove(event.getUsedItem())) { player.getInventory().add(getParts()[index + 1]); String message = getMixMessage(event); From fc2247f4576e5a98c0724104004fda2d4fe7eb92 Mon Sep 17 00:00:00 2001 From: sirdabalot Date: Tue, 25 Mar 2025 09:49:09 +0000 Subject: [PATCH 012/117] Fixed teleport on logout for pest control landers --- Server/src/main/content/minigame/pestcontrol/PCLanderZone.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/src/main/content/minigame/pestcontrol/PCLanderZone.java b/Server/src/main/content/minigame/pestcontrol/PCLanderZone.java index 21cc7fce5..5ace493bb 100644 --- a/Server/src/main/content/minigame/pestcontrol/PCLanderZone.java +++ b/Server/src/main/content/minigame/pestcontrol/PCLanderZone.java @@ -80,7 +80,7 @@ public final class PCLanderZone extends MapZone { for (PestControlActivityPlugin a : activities) { if (a.getWaitingPlayers().remove(e)) { if (logout) { - e.getProperties().setTeleportLocation(a.getLeaveLocation()); + e.setLocation(a.getLeaveLocation()); } break; } From 1b0a7d5fcacb9ab9c70fa0b3de7a4ed2fba08a27 Mon Sep 17 00:00:00 2001 From: Alex Page Date: Tue, 25 Mar 2025 09:51:47 +0000 Subject: [PATCH 013/117] Fixed random events returning players to Lumbridge --- Server/src/main/content/global/ame/KidnapHelper.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Server/src/main/content/global/ame/KidnapHelper.kt b/Server/src/main/content/global/ame/KidnapHelper.kt index 2efc90f2d..5cef011ba 100644 --- a/Server/src/main/content/global/ame/KidnapHelper.kt +++ b/Server/src/main/content/global/ame/KidnapHelper.kt @@ -15,6 +15,11 @@ fun kidnapPlayer(player: Player, loc: Location, type: TeleportType) { } fun returnPlayer(player: Player) { + // Prevent returning more than once and sending the player back to HOME_LOCATION + if (getAttribute(player, "kidnapped-by-random", null) == null) { + return + } + player.locks.unlockTeleport() val destination = getAttribute(player, "/save:original-loc", ServerConstants.HOME_LOCATION ?: Location.create(3222, 3218, 0)) teleport(player, destination) From 7b1ebd460826476f3d8ffadd905112830ee7519c Mon Sep 17 00:00:00 2001 From: DeadlyGenga <19836947-matthewhurleychch@users.noreply.gitlab.com> Date: Tue, 25 Mar 2025 09:52:15 +0000 Subject: [PATCH 014/117] Added melee attack animation to Mystic Mud staff --- Server/data/configs/item_configs.json | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Server/data/configs/item_configs.json b/Server/data/configs/item_configs.json index c7da93ae9..34d0d7b90 100644 --- a/Server/data/configs/item_configs.json +++ b/Server/data/configs/item_configs.json @@ -59959,6 +59959,7 @@ "attack_speed": "5", "turn180_anim": "1206", "equipment_slot": "3", + "attack_anims": "419,419,419,419", "grand_exchange_price": "423100", "stand_anim": "813", "tradeable": "true", @@ -119757,18 +119758,18 @@ }, { "requirements": "{11,79}", - "destroy_message": "To get another pair of Flame Gloves, you need to keep ten beacons alight simultaneously and then talk to King Roald.", "shop_price": "200", "examine": "The hottest gloves in town.", "durability": null, - "name": "Flame gloves", "destroy": "true", - "archery_ticket_price": "0", "attack_speed": "4", - "id": "13660", "absorb": "0,0,0", - "bonuses": "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0", - "equipment_slot": "9" + "equipment_slot": "9", + "destroy_message": "To get another pair of Flame Gloves, you need to keep ten beacons alight simultaneously and then talk to King Roald.", + "name": "Flame gloves", + "archery_ticket_price": "0", + "id": "13660", + "bonuses": "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0" }, { "requirements": "{11,92}", From 48bc0ffb281f979b0834660ba815c81a0260bd31 Mon Sep 17 00:00:00 2001 From: Player Name Date: Tue, 25 Mar 2025 09:55:37 +0000 Subject: [PATCH 015/117] Fixed requirement check issue with PoH portals checking for Plague City completion --- .../decoration/portalchamber/PortalChamberPlugin.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Server/src/main/content/global/skill/construction/decoration/portalchamber/PortalChamberPlugin.java b/Server/src/main/content/global/skill/construction/decoration/portalchamber/PortalChamberPlugin.java index ad8c68db8..bf1f26c54 100644 --- a/Server/src/main/content/global/skill/construction/decoration/portalchamber/PortalChamberPlugin.java +++ b/Server/src/main/content/global/skill/construction/decoration/portalchamber/PortalChamberPlugin.java @@ -90,7 +90,7 @@ public class PortalChamberPlugin extends OptionHandler { for (Locations l : Locations.values()) { if (l.name().contains(identifier)) { if (l == Locations.ARDOUGNE){ - if (player.getAttribute(ARDOUGNE_TELE_ATTRIBUTE, false)){ + if (!player.getAttribute(ARDOUGNE_TELE_ATTRIBUTE, false)) { player.sendMessage("You do not have the requirements to direct the portal there"); return; } @@ -138,12 +138,6 @@ public class PortalChamberPlugin extends OptionHandler { case "enter": String objectName = object.getName(); for (Locations l : Locations.values()) { - if (l == Locations.ARDOUGNE){ - if (player.getAttribute(ARDOUGNE_TELE_ATTRIBUTE, false)){ - player.sendMessage("You do not have the requirements to enter this portal."); - return false; - } - } if (objectName.toLowerCase().contains(l.name().toLowerCase())) { player.teleport(l.location); if (player.getHouseManager().isInHouse(player) && node.getId() == 13635) { From bcc3bc069e7c85311ba91ca00a8cb9334a43dd7d Mon Sep 17 00:00:00 2001 From: Lucid Enigma Date: Tue, 25 Mar 2025 09:58:27 +0000 Subject: [PATCH 016/117] Potentially fixed bank visuals when opening bank --- Server/src/main/core/game/container/impl/BankContainer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/src/main/core/game/container/impl/BankContainer.java b/Server/src/main/core/game/container/impl/BankContainer.java index 5fb95791b..2b1640293 100644 --- a/Server/src/main/core/game/container/impl/BankContainer.java +++ b/Server/src/main/core/game/container/impl/BankContainer.java @@ -130,8 +130,8 @@ public final class BankContainer extends Container { }); player.getInterfaceManager().openSingleTab(new Component(763)); super.refresh(); - player.getInventory().getListeners().add(listener); player.getInventory().refresh(); + player.getInventory().getListeners().add(listener); setVarp(player, 1249, lastAmountX); int settings = new IfaceSettingsBuilder().enableOptions(new IntRange(0, 5)).enableExamine().enableSlotSwitch().build(); player.getPacketDispatch().sendIfaceSettings(settings, 0, 763, 0, 27); From 3d7f1689f34920806d17265dd2a3647a04785c39 Mon Sep 17 00:00:00 2001 From: Player Name Date: Wed, 2 Apr 2025 21:48:11 +0000 Subject: [PATCH 017/117] Reverted Lumbridge random event teleport fix due to trapping players in random event Spam clicking maze shrine now handled --- Server/src/main/content/global/ame/KidnapHelper.kt | 9 ++------- .../main/content/global/ame/events/maze/MazeInterface.kt | 5 ++--- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/Server/src/main/content/global/ame/KidnapHelper.kt b/Server/src/main/content/global/ame/KidnapHelper.kt index 5cef011ba..6fa40c6d1 100644 --- a/Server/src/main/content/global/ame/KidnapHelper.kt +++ b/Server/src/main/content/global/ame/KidnapHelper.kt @@ -7,7 +7,7 @@ import core.game.node.entity.player.link.TeleportManager.TeleportType import core.game.world.map.Location fun kidnapPlayer(player: Player, loc: Location, type: TeleportType) { - setAttribute(player, "kidnapped-by-random", true) + setAttribute(player, "kidnapped-by-random", true) //only used in POH code when you leave the hut, so does not need /save. Do not rely on this outside of its intended POH use case. if (getAttribute(player, "/save:original-loc", null) == null) { setAttribute(player, "/save:original-loc", player.location) } @@ -15,14 +15,9 @@ fun kidnapPlayer(player: Player, loc: Location, type: TeleportType) { } fun returnPlayer(player: Player) { - // Prevent returning more than once and sending the player back to HOME_LOCATION - if (getAttribute(player, "kidnapped-by-random", null) == null) { - return - } - player.locks.unlockTeleport() val destination = getAttribute(player, "/save:original-loc", ServerConstants.HOME_LOCATION ?: Location.create(3222, 3218, 0)) teleport(player, destination) unlock(player) removeAttributes(player, "/save:original-loc", "kidnapped-by-random") -} \ No newline at end of file +} diff --git a/Server/src/main/content/global/ame/events/maze/MazeInterface.kt b/Server/src/main/content/global/ame/events/maze/MazeInterface.kt index 108d456ca..f408b1507 100644 --- a/Server/src/main/content/global/ame/events/maze/MazeInterface.kt +++ b/Server/src/main/content/global/ame/events/maze/MazeInterface.kt @@ -199,17 +199,16 @@ class MazeInterface : InteractionListener, EventHook, MapArea { on(Scenery.STRANGE_SHRINE_3634, IntType.SCENERY, "touch") { player, node -> player.unhook(this) + lock(player, 12) closeOverlay(player) queueScript(player, 0, QueueStrength.SOFT) { stage: Int -> when (stage) { 0 -> { - lock(player, 6) sendGraphics(Graphics(86, 0, 3), player.location) animate(player,862) return@queueScript delayScript(player, 6) } 1 -> { - lock(player, 6) sendGraphics(Graphics(1576, 0, 0), player.location) animate(player,8939) playAudio(player, Sounds.TELEPORT_ALL_200) @@ -274,4 +273,4 @@ class MazeInterface : InteractionListener, EventHook, MapArea { } } -} \ No newline at end of file +} From 300b7140483d3d42b5a403c4235bbdb49feb9c43 Mon Sep 17 00:00:00 2001 From: Player Name Date: Sun, 6 Apr 2025 08:17:21 +0000 Subject: [PATCH 018/117] Huge refactor improving the handling of players being given items, or situations where items are exchanged --- .../handlers/iface/FurClothingInterface.kt | 7 +- .../global/handlers/item/EctophialListener.kt | 3 +- .../item/EnchantJewelleryTabListener.kt | 3 +- .../item/withitem/CrystalKeyCreateListener.kt | 6 +- .../item/withitem/OilFishingRodListener.kt | 16 +- .../item/withitem/WatermelonOnSack.kt | 9 +- .../item/withobject/SandSourceListener.kt | 5 +- .../item/withobject/SapCollectListener.kt | 5 +- .../withobject/SpiritShieldBlessListener.kt | 6 +- .../global/handlers/npc/SheepBehavior.kt | 4 +- .../handlers/scenery/MillingListener.kt | 10 +- .../crafting/silver/SilverCraftingPulse.kt | 4 +- .../global/skill/farming/PatchRaker.kt | 4 +- .../skill/gather/fishing/FishingPulse.kt | 4 +- .../skill/magic/modern/ModernListeners.kt | 5 +- .../skill/summoning/pet/IncubatorHandler.kt | 5 +- .../barbassault/CaptainCainDialogue.kt | 17 +- .../taverley/dialogue/VelrakDialogue.kt | 2 +- .../shadowofthestorm/DarklightListener.kt | 4 +- .../thefremenniktrials/SigliTheHuntsman.kt | 24 +- .../thefremenniktrials/SigmundDialogue.kt | 6 +- .../thefremenniktrials/SkulgrimenDialogue.kt | 11 +- .../thefremenniktrials/SwensenTheNavigator.kt | 13 +- .../TFTInteractionListeners.kt | 3 +- .../thefremenniktrials/ThorvaldDialogue.kt | 12 +- .../quest/thefremenniktrials/YrsaDialogue.kt | 11 +- .../arena/dialogue/KhazardBarmanDialogue.kt | 13 +- .../feldip/quest/chompybird/ChompyBirdNPC.kt | 2 +- .../scorpioncatcher/SCThormacDialogue.kt | 12 +- .../quest/scorpioncatcher/ScorpionCatcher.kt | 4 +- .../ScorpionCatcherUseListener.kt | 382 +++--------------- .../kandarin/seers/dialogue/SeerDialogue.kt | 2 +- .../quest/elementalworkshop/EWListeners.kt | 8 +- .../quest/tribaltotem/TribalTotemListeners.kt | 4 +- .../TutorialCombatInstructorDialogue.kt | 4 +- .../dialogue/TutorialMagicTutorDialogue.kt | 4 +- .../dialogue/TutorialMasterChefDialogue.kt | 8 +- .../TutorialMiningInstructorDialogue.kt | 9 +- .../handlers/TutorialFurnaceListener.kt | 13 +- .../stronghold/GiftOfPeaceDialogue.java | 1 - .../stronghold/GrainOfPlentyDialogue.java | 2 +- .../playersafety/ProfessorHenryDialogue.kt | 12 +- .../misthalin/varrock/handlers/ZaffPlugin.kt | 8 +- .../canifis/dialogue/RoavarDialogue.kt | 6 +- .../morytania/handlers/MortMyreGhastNPC.kt | 5 +- .../quest/naturespirit/NSListeners.kt | 3 +- .../quest/naturespirit/NSTarlockDialogue.kt | 4 +- .../morytania/quest/naturespirit/NSUtils.kt | 2 +- .../randoms/SantaHolidayRandomDialogue.kt | 1 - 49 files changed, 236 insertions(+), 472 deletions(-) diff --git a/Server/src/main/content/global/handlers/iface/FurClothingInterface.kt b/Server/src/main/content/global/handlers/iface/FurClothingInterface.kt index c395840e6..e9025bad9 100644 --- a/Server/src/main/content/global/handlers/iface/FurClothingInterface.kt +++ b/Server/src/main/content/global/handlers/iface/FurClothingInterface.kt @@ -162,9 +162,10 @@ class FurClothingInterface : ComponentPlugin(){ return } - removeItem(player, requiredFur, Container.INVENTORY) - removeItem(player, coins, Container.INVENTORY) - addItem(player, clothing.product.id, amount) + if (removeItem(player, requiredFur, Container.INVENTORY) && + removeItem(player, coins, Container.INVENTORY)) { + addItem(player, clothing.product.id, amount) + } } override fun newInstance(arg: Any?): Plugin { diff --git a/Server/src/main/content/global/handlers/item/EctophialListener.kt b/Server/src/main/content/global/handlers/item/EctophialListener.kt index 111db96c5..a3ab4d0ca 100644 --- a/Server/src/main/content/global/handlers/item/EctophialListener.kt +++ b/Server/src/main/content/global/handlers/item/EctophialListener.kt @@ -25,8 +25,7 @@ class EctophialListener : InteractionListener { delayEntity(player, fillAnimation.duration) animate(player, fillAnimation) playAudio(player, Sounds.FILL_ECTOPLASM_1132) - if (removeItem(player, Items.ECTOPHIAL_4252)) { - addItem(player, Items.ECTOPHIAL_4251) + if (removeItem(player, Items.ECTOPHIAL_4252) && addItem(player, Items.ECTOPHIAL_4251)) { sendMessage(player, "You refill the ectophial from the Ectofuntus.") } } diff --git a/Server/src/main/content/global/handlers/item/EnchantJewelleryTabListener.kt b/Server/src/main/content/global/handlers/item/EnchantJewelleryTabListener.kt index 1a31d30b5..c2d65fc08 100644 --- a/Server/src/main/content/global/handlers/item/EnchantJewelleryTabListener.kt +++ b/Server/src/main/content/global/handlers/item/EnchantJewelleryTabListener.kt @@ -70,8 +70,7 @@ class EnchantJewelleryTabListener : InteractionListener { for (item in player.inventory.toArray()) { if (item == null) continue val product = items[item.id] ?: continue - if (removeItem(player, node.id) && (removeItem(player, item.id))) { - addItem(player, product) + if (removeItem(player, node.id) && (removeItem(player, item.id)) && addItem(player, product)) { playAudio(player, Sounds.POH_TABLET_BREAK_979) animate(player, 4069, true) break diff --git a/Server/src/main/content/global/handlers/item/withitem/CrystalKeyCreateListener.kt b/Server/src/main/content/global/handlers/item/withitem/CrystalKeyCreateListener.kt index 207102b5f..15d12ca62 100644 --- a/Server/src/main/content/global/handlers/item/withitem/CrystalKeyCreateListener.kt +++ b/Server/src/main/content/global/handlers/item/withitem/CrystalKeyCreateListener.kt @@ -24,9 +24,11 @@ class CrystalKeyCreateListener : InteractionListener { return@onUseWith false } - addItem(player, Items.CRYSTAL_KEY_989) - sendMessage(player, "You join the loop half of a key and the tooth half of a key to make a crystal key.") + if (!addItem(player, Items.CRYSTAL_KEY_989)) { + return@onUseWith false + } + sendMessage(player, "You join the loop half of a key and the tooth half of a key to make a crystal key.") return@onUseWith true } } diff --git a/Server/src/main/content/global/handlers/item/withitem/OilFishingRodListener.kt b/Server/src/main/content/global/handlers/item/withitem/OilFishingRodListener.kt index 1dcd15cd4..318f8a0a8 100644 --- a/Server/src/main/content/global/handlers/item/withitem/OilFishingRodListener.kt +++ b/Server/src/main/content/global/handlers/item/withitem/OilFishingRodListener.kt @@ -15,10 +15,11 @@ class OilFishingRodListener : InteractionListener { override fun pulse(): Boolean { when (counter++) { 1 -> { - removeItem(player, used.asItem()) && removeItem(player, with.asItem()) - addItem(player, Items.VIAL_229) - addItem(player, Items.OILY_FISHING_ROD_1585) - sendMessage(player, "You rub the oil into the fishing rod.") + if (removeItem(player, used.asItem()) && removeItem(player, with.asItem()) && + addItem(player, Items.VIAL_229) && + addItem(player, Items.OILY_FISHING_ROD_1585)) { + sendMessage(player, "You rub the oil into the fishing rod.") + } } } return false @@ -35,9 +36,10 @@ class OilFishingRodListener : InteractionListener { when (counter++) { 0 -> player.animator.animate(Animation(364)) 3 -> { - removeItem(player, Items.THIN_SNAIL_3363) - removeItem(player, Items.SAMPLE_BOTTLE_3377) - addItem(player, Items.BLAMISH_SNAIL_SLIME_1581) + if (removeItem(player, Items.THIN_SNAIL_3363) && + removeItem(player, Items.SAMPLE_BOTTLE_3377)) { + addItem(player, Items.BLAMISH_SNAIL_SLIME_1581) + } } } return false diff --git a/Server/src/main/content/global/handlers/item/withitem/WatermelonOnSack.kt b/Server/src/main/content/global/handlers/item/withitem/WatermelonOnSack.kt index da829ed82..f332b0590 100644 --- a/Server/src/main/content/global/handlers/item/withitem/WatermelonOnSack.kt +++ b/Server/src/main/content/global/handlers/item/withitem/WatermelonOnSack.kt @@ -14,11 +14,10 @@ class WatermelonOnSack : InteractionListener { override fun defineListeners() { onUseWith(IntType.ITEM, SACK, WATERMELON){ player, used, _ -> if(getStatLevel(player, Skills.FARMING) >= 23){ - removeItem(player,SACK, Container.INVENTORY) - removeItem(player,WATERMELON,Container.INVENTORY) - addItem(player, Items.SCARECROW_6059) - rewardXP(player, Skills.FARMING, 25.0) - sendMessage(player, "You stab the watermelon on top of the spear, finishing your scarecrow") + if (removeItem(player,SACK, Container.INVENTORY) && removeItem(player,WATERMELON,Container.INVENTORY) && addItem(player, Items.SCARECROW_6059)) { + rewardXP(player, Skills.FARMING, 25.0) + sendMessage(player, "You stab the watermelon on top of the spear, finishing your scarecrow") + } }else{ sendMessage(player, "Your Farming level is not high enough to do this") } diff --git a/Server/src/main/content/global/handlers/item/withobject/SandSourceListener.kt b/Server/src/main/content/global/handlers/item/withobject/SandSourceListener.kt index 799084610..bbde6bc21 100644 --- a/Server/src/main/content/global/handlers/item/withobject/SandSourceListener.kt +++ b/Server/src/main/content/global/handlers/item/withobject/SandSourceListener.kt @@ -35,8 +35,9 @@ class SandSourceListener : InteractionListener { animate(player, ANIMATION) } - sendMessage(player, "You fill the bucket with sand.") - addItem(player, Items.BUCKET_OF_SAND_1783) + if (addItem(player, Items.BUCKET_OF_SAND_1783)) { + sendMessage(player, "You fill the bucket with sand.") + } } animationTrigger++ diff --git a/Server/src/main/content/global/handlers/item/withobject/SapCollectListener.kt b/Server/src/main/content/global/handlers/item/withobject/SapCollectListener.kt index 3607fda98..13ee4000a 100644 --- a/Server/src/main/content/global/handlers/item/withobject/SapCollectListener.kt +++ b/Server/src/main/content/global/handlers/item/withobject/SapCollectListener.kt @@ -61,8 +61,9 @@ class SapCollectListener : InteractionListener { override fun pulse(): Boolean { if (removeItem(player, Items.BUCKET_1925)) { animate(player, ANIMATION) - sendMessage(player, "You cut the tree and allow its sap to drip down into your bucket.") - addItem(player, Items.BUCKET_OF_SAP_4687) + if (addItem(player, Items.BUCKET_OF_SAP_4687)) { + sendMessage(player, "You cut the tree and allow its sap to drip down into your bucket.") + } return true } return false diff --git a/Server/src/main/content/global/handlers/item/withobject/SpiritShieldBlessListener.kt b/Server/src/main/content/global/handlers/item/withobject/SpiritShieldBlessListener.kt index 45b0dad76..084e7f117 100644 --- a/Server/src/main/content/global/handlers/item/withobject/SpiritShieldBlessListener.kt +++ b/Server/src/main/content/global/handlers/item/withobject/SpiritShieldBlessListener.kt @@ -82,9 +82,11 @@ class SpiritShieldBlessListener : InteractionListener { return@onUseWith false } - addItem(player, Items.BLESSED_SPIRIT_SHIELD_13736) - sendMessage(player, "You successfully bless the shield using the holy elixir.") + if (!addItem(player, Items.BLESSED_SPIRIT_SHIELD_13736)) { + return@onUseWith false + } + sendMessage(player, "You successfully bless the shield using the holy elixir.") return@onUseWith true } diff --git a/Server/src/main/content/global/handlers/npc/SheepBehavior.kt b/Server/src/main/content/global/handlers/npc/SheepBehavior.kt index b329d4622..fbd283e77 100644 --- a/Server/src/main/content/global/handlers/npc/SheepBehavior.kt +++ b/Server/src/main/content/global/handlers/npc/SheepBehavior.kt @@ -105,8 +105,10 @@ class SheepBehavior : NPCBehavior(*sheepIds), InteractionListener { sheep.locks.lockMovement(2) sheep.transform(NPCs.SHEEP_5153) playAudio(player, Sounds.SHEAR_SHEEP_761) + if (!addItem(player, Items.WOOL_1737)) { // 5160 + return@on false + } sendMessage(player, "You get some wool.") - addItem(player, Items.WOOL_1737) // 5160 GameWorld.Pulser.submit(object : Pulse(80, sheep) { override fun pulse(): Boolean { sheep.reTransform() diff --git a/Server/src/main/content/global/handlers/scenery/MillingListener.kt b/Server/src/main/content/global/handlers/scenery/MillingListener.kt index c61a8c82a..f88132cc1 100644 --- a/Server/src/main/content/global/handlers/scenery/MillingListener.kt +++ b/Server/src/main/content/global/handlers/scenery/MillingListener.kt @@ -85,13 +85,15 @@ class MillingListener : InteractionListener { if (removeItem(player, EMPTY_POT)) { if (getAttribute(player, "milling:sweetcorn", 0) > 0) { setAttribute(player, "/save:milling:sweetcorn", (getAttribute(player, "milling:sweetcorn", 0) - 1)) - addItem(player, POT_OF_CORNFLOUR) - sendMessage(player, if (player.getAttribute("milling:sweetcorn", 0) > 0) "You fill a pot with cornflour from the bin." else "You fill a pot with the last of the cornflour in the bin.") + if (addItem(player, POT_OF_CORNFLOUR)) { + sendMessage(player, if (player.getAttribute("milling:sweetcorn", 0) > 0) "You fill a pot with cornflour from the bin." else "You fill a pot with the last of the cornflour in the bin.") + } } else if (getAttribute(player, "milling:grain", 0) > 0) { setAttribute(player, "/save:milling:grain", (getAttribute(player, "milling:grain", 0) - 1)) - addItem(player, POT_OF_FLOUR) - sendMessage(player, if (player.getAttribute("milling:grain", 0) > 0) "You fill a pot with flour from the bin." else "You fill a pot with the last of the flour in the bin.") + if (addItem(player, POT_OF_FLOUR)) { + sendMessage(player, if (player.getAttribute("milling:grain", 0) > 0) "You fill a pot with flour from the bin." else "You fill a pot with the last of the flour in the bin.") + } } if (getAttribute(player, "milling:sweetcorn", 0) + getAttribute(player, "milling:grain", 0) <= 0) { setVarp(player, VARP, 0, true) diff --git a/Server/src/main/content/global/skill/crafting/silver/SilverCraftingPulse.kt b/Server/src/main/content/global/skill/crafting/silver/SilverCraftingPulse.kt index 8bc77007c..338b41b47 100644 --- a/Server/src/main/content/global/skill/crafting/silver/SilverCraftingPulse.kt +++ b/Server/src/main/content/global/skill/crafting/silver/SilverCraftingPulse.kt @@ -31,10 +31,8 @@ class SilverCraftingPulse( animate(player, Animations.HUMAN_FURNACE_SMELTING_3243) playAudio(player, Sounds.FURNACE_2725) - if (removeItem(player, Items.SILVER_BAR_2355, Container.INVENTORY)) { - addItem(player, product.producedItemId, product.amountProduced) + if (removeItem(player, Items.SILVER_BAR_2355, Container.INVENTORY) && addItem(player, product.producedItemId, product.amountProduced)) { rewardXP(player, Skills.CRAFTING, product.xpReward) - player.dispatch( ResourceProducedEvent( product.producedItemId, diff --git a/Server/src/main/content/global/skill/farming/PatchRaker.kt b/Server/src/main/content/global/skill/farming/PatchRaker.kt index 10af95489..21967671b 100644 --- a/Server/src/main/content/global/skill/farming/PatchRaker.kt +++ b/Server/src/main/content/global/skill/farming/PatchRaker.kt @@ -35,7 +35,7 @@ object PatchRaker { } else { patch.getPatchFor(player).currentGrowthStage++ patch.getPatchFor(player).setCurrentState(++patchStage) - addItem(player, Items.WEEDS_6055) + addItem(player, Items.WEEDS_6055) //authentically destroys weeds if inventory was full rewardXP(player, Skills.FARMING, 4.0) } if (patchStage >= 3) { @@ -45,4 +45,4 @@ object PatchRaker { } }) } -} \ No newline at end of file +} diff --git a/Server/src/main/content/global/skill/gather/fishing/FishingPulse.kt b/Server/src/main/content/global/skill/gather/fishing/FishingPulse.kt index ea254e311..58bb1ac02 100644 --- a/Server/src/main/content/global/skill/gather/fishing/FishingPulse.kt +++ b/Server/src/main/content/global/skill/gather/fishing/FishingPulse.kt @@ -127,10 +127,10 @@ class FishingPulse(player: Player?, npc: NPC, private val option: FishingOption? SkillingPets.checkPetDrop(player, SkillingPets.HERON) val item = fish!! if (isActive(SkillcapePerks.GREAT_AIM, player) && RandomFunction.random(100) <= 5) { - addItem(player, item.id) + addItemOrDrop(player, item.id) player.sendMessage(colorize("%RYour expert aim catches you a second fish.")) } - addItem(player, item.id) + addItemOrDrop(player, item.id) var fishCaught = player.getAttribute(STATS_BASE + ":" + STATS_FISH, 0) player.setAttribute("/save:$STATS_BASE:$STATS_FISH", ++fishCaught) player.skills.addExperience(Skills.FISHING, fish!!.experience, true) diff --git a/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt b/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt index 085f447bd..6525f3bc6 100644 --- a/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt +++ b/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt @@ -325,7 +325,10 @@ class ModernListeners : SpellListener("modern"){ } visualizeSpell(player, CHARGE_ORB_ANIM, spell.graphics, spell.sound) removeRunes(player) - addItem(player, spell.chargedOrb) + val success = addItem(player, spell.chargedOrb) + if (!success) { + return@queueScript stopExecuting(player) + } addXP(player, spell.experience) setDelay(player, 3) crafted++ diff --git a/Server/src/main/content/global/skill/summoning/pet/IncubatorHandler.kt b/Server/src/main/content/global/skill/summoning/pet/IncubatorHandler.kt index edb11ca18..4b9969574 100644 --- a/Server/src/main/content/global/skill/summoning/pet/IncubatorHandler.kt +++ b/Server/src/main/content/global/skill/summoning/pet/IncubatorHandler.kt @@ -66,8 +66,9 @@ class IncubatorHandler : InteractionListener { val product = egg.product val name = product.name.lowercase() - sendMessage(player, "You take your $name out of the incubator.") - addItem(player, product.id) + if (addItem(player, product.id)) { + sendMessage(player, "You take your $name out of the incubator.") + } return true } } diff --git a/Server/src/main/content/minigame/barbassault/CaptainCainDialogue.kt b/Server/src/main/content/minigame/barbassault/CaptainCainDialogue.kt index baeb381ba..d4b31bc8e 100644 --- a/Server/src/main/content/minigame/barbassault/CaptainCainDialogue.kt +++ b/Server/src/main/content/minigame/barbassault/CaptainCainDialogue.kt @@ -1,9 +1,6 @@ package content.minigame.barbassault -import core.api.Container -import core.api.addItem -import core.api.inInventory -import core.api.removeItem +import core.api.* import core.game.dialogue.DialoguePlugin import core.game.dialogue.FacialExpression import core.game.node.entity.player.Player @@ -46,7 +43,7 @@ class CaptainCainDialogue(player: Player? = null) : DialoguePlugin(player) { } 10 -> npcl(FacialExpression.FRIENDLY, "Alright, then, that'll be %,d gold please.".format(TORSO_PRICE)).also { stage++ } - 11 -> options("Here you go!","Nevermind.").also { stage++ } + 11 -> options("Here you go!","Never mind.").also { stage++ } 12 -> when(buttonId){ 1 -> if(inInventory(player, 995, TORSO_PRICE)) playerl(FacialExpression.FRIENDLY, "Here you go!").also { stage = 20 } @@ -57,9 +54,13 @@ class CaptainCainDialogue(player: Player? = null) : DialoguePlugin(player) { } 20 -> { - npcl(FacialExpression.FRIENDLY, "Thank you much, kind sir. And here's your torso.") - if(removeItem(player, Item(995, TORSO_PRICE), Container.INVENTORY)) { - addItem(player, Items.FIGHTER_TORSO_10551, 1) + if (hasSpaceFor(player, Item(Items.FIGHTER_TORSO_10551)) || amountInInventory(player, Items.COINS_995) == TORSO_PRICE) { + npcl(FacialExpression.FRIENDLY, "Thank you much, kind sir. And here's your torso.") + if (removeItem(player, Item(Items.COINS_995, TORSO_PRICE), Container.INVENTORY)) { + addItem(player, Items.FIGHTER_TORSO_10551, 1) + } + } else { + npcl(FacialExpression.FRIENDLY, "Sorry, you don't have space for it! Give my regards to Player Name - he made me check this before I take your cash.") } stage = END_DIALOGUE } diff --git a/Server/src/main/content/region/asgarnia/taverley/dialogue/VelrakDialogue.kt b/Server/src/main/content/region/asgarnia/taverley/dialogue/VelrakDialogue.kt index b6ea3c4f3..90063d64c 100644 --- a/Server/src/main/content/region/asgarnia/taverley/dialogue/VelrakDialogue.kt +++ b/Server/src/main/content/region/asgarnia/taverley/dialogue/VelrakDialogue.kt @@ -41,7 +41,7 @@ class VelrakDialogue(player: Player? = null) : DialoguePlugin(player) { 2 -> playerl(FacialExpression.NEUTRAL, "No, it's too dangerous for me too.").also { stage = 15 } } - 14 -> sendItemDialogue(player, Items.DUSTY_KEY_1590, "Velrak reaches somewhere mysterious and passes you a key.").also { addItem(player, Items.DUSTY_KEY_1590, 1); stage = END_DIALOGUE } + 14 -> sendItemDialogue(player, Items.DUSTY_KEY_1590, "Velrak reaches somewhere mysterious and passes you a key.").also { addItemOrDrop(player, Items.DUSTY_KEY_1590, 1); stage = END_DIALOGUE } 15 -> npcl(FacialExpression.FRIENDLY, "I don't blame you!").also { stage = END_DIALOGUE } diff --git a/Server/src/main/content/region/desert/quest/shadowofthestorm/DarklightListener.kt b/Server/src/main/content/region/desert/quest/shadowofthestorm/DarklightListener.kt index 1daba317d..87fc7de02 100644 --- a/Server/src/main/content/region/desert/quest/shadowofthestorm/DarklightListener.kt +++ b/Server/src/main/content/region/desert/quest/shadowofthestorm/DarklightListener.kt @@ -14,8 +14,8 @@ class DarklightListener : InteractionListener { if (!hasRequirement(player, Quests.SHADOW_OF_THE_STORM) || (!player.inventory.contains(Items.BLACK_MUSHROOM_INK_4622, 1) && (!player.inventory.contains(Items.SILVERLIGHT_2402, 1)))) return@onUseWith false if (removeItem(player, used.id) && removeItem(player, with.id)) - addItem(player, Items.DARKLIGHT_6746) - return@onUseWith true + return@onUseWith addItem(player, Items.DARKLIGHT_6746) + return@onUseWith false } } } diff --git a/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/SigliTheHuntsman.kt b/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/SigliTheHuntsman.kt index 3ccd4e421..8fd16e3fc 100644 --- a/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/SigliTheHuntsman.kt +++ b/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/SigliTheHuntsman.kt @@ -9,11 +9,12 @@ import core.plugin.Initializable import core.game.dialogue.DialoguePlugin import core.game.dialogue.FacialExpression import content.data.Quests +import org.rs09.consts.Items @Initializable class SigliTheHuntsman(player: Player? = null) : DialoguePlugin(player){ override fun open(vararg args: Any?): Boolean { - if(player?.inventory?.contains(3702,1) == true){ + if(player?.inventory?.contains(Items.CUSTOM_BOW_STRING_3702, 1) == true){ npcl(FacialExpression.HAPPY,"Greetings outerlander.") stage = 165 return true @@ -104,13 +105,13 @@ class SigliTheHuntsman(player: Player? = null) : DialoguePlugin(player){ //Draugen killed 100 -> player("Thanks!").also { - player.removeAttribute("fremtrials:draugen-killed") - player.setAttribute("/save:fremtrials:sigli-vote",true) - player?.setAttribute("/save:fremtrials:votes",player.getAttribute("fremtrials:votes",0) + 1) - player?.inventory?.remove(Item(3697)) - stage = 1000 - } - + if (player.inventory.remove(Item(Items.HUNTERS_TALISMAN_3697))) { + player.removeAttribute("fremtrials:draugen-killed") + player.setAttribute("/save:fremtrials:sigli-vote", true) + player.setAttribute("/save:fremtrials:votes", player.getAttribute("fremtrials:votes", 0) + 1) + } + stage = 1000 + } 150 -> playerl(FacialExpression.ASKING,"I don't suppose you have any idea where I could find a map to unspoiled hunting grounds, do you?").also { stage++ } 151 -> npcl(FacialExpression.HAPPY,"Well, of course I do. I wouldn't be much of a huntsman if I didn't know where to find my prey now, would I outerlander?").also { stage++ } 152 -> playerl(FacialExpression.ASKING,"No, I guess not. So can I have it?").also { stage++ } @@ -127,9 +128,10 @@ class SigliTheHuntsman(player: Player? = null) : DialoguePlugin(player){ 161 -> npcl(FacialExpression.ANNOYED,"If I knew I would not have asked you to go and get me one, now would I?").also { stage = 1000 } 165 -> playerl(FacialExpression.HAPPY,"Here. I have your bowstring. Give me your map to the hunting grounds.").also { - removeItem(player,3702) - addItem(player,3701) - stage++ + if (removeItem(player, Items.CUSTOM_BOW_STRING_3702)) { + addItem(player, Items.TRACKING_MAP_3701) + stage++ + } } 166 -> npcl(FacialExpression.HAPPY,"Well met, outerlander. I see some hunting potential within you. Here, take my map, I was getting too dependent on it for my skill anyway.").also { stage = 1000 } diff --git a/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/SigmundDialogue.kt b/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/SigmundDialogue.kt index b4804f0d2..947ffee6c 100644 --- a/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/SigmundDialogue.kt +++ b/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/SigmundDialogue.kt @@ -10,6 +10,7 @@ import core.game.node.entity.player.Player import core.plugin.Initializable import org.rs09.consts.Items import content.data.Quests +import core.api.addItemOrDrop @Initializable class SigmundDialogue (player: Player? = null) : DialoguePlugin(player) { @@ -109,8 +110,9 @@ class SigmundDialogue (player: Player? = null) : DialoguePlugin(player) { 36 -> npcl(FacialExpression.ASKING,"I suggest you ask around the other Fremennik in the town. A good merchant will find exactly what their customer needs somewhere.").also { stage++ } 37 -> playerl(FacialExpression.ASKING,"I was making some trades, but then I lost the goods...").also { stage++ } 38 -> npcl(FacialExpression.THINKING,"Hmmm... well try and start again at the beginning. And try to be more careful of your wares in future.").also { - addItem(player, Items.PROMISSORY_NOTE_3709) - stage = 1000 } + addItemOrDrop(player, Items.PROMISSORY_NOTE_3709) + stage = 1000 + } 40 -> npcl(FacialExpression.HAPPY,"Hello again outerlander! I am amazed once more at your apparent skill at merchanting!").also { stage++ } 41 -> playerl(FacialExpression.HAPPY,"So I can count on your vote at the council of elders?").also { stage++ } diff --git a/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/SkulgrimenDialogue.kt b/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/SkulgrimenDialogue.kt index e4ec8ecfb..4ca7edd4f 100644 --- a/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/SkulgrimenDialogue.kt +++ b/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/SkulgrimenDialogue.kt @@ -8,18 +8,19 @@ import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.plugin.Initializable import content.data.Quests +import org.rs09.consts.Items @Initializable class SkulgrimenDialogue(player: Player? = null) : DialoguePlugin(player) { override fun open(vararg args: Any?): Boolean { npc = args[0] as NPC - if(player?.inventory?.contains(3703,1) == true){ + if(player?.inventory?.contains(Items.UNUSUAL_FISH_3703,1) == true){ playerl(FacialExpression.HAPPY,"Hi there. I got your fish, so can I have that bowstring for Sigli now?") stage = 20 return true } - else if(player?.inventory?.contains(3702,1) == true){ + else if(player?.inventory?.contains(Items.CUSTOM_BOW_STRING_3702,1) == true){ playerl(FacialExpression.ASKING,"So about this bowstring... was it hard to make or something?") stage = 25 return true @@ -72,9 +73,9 @@ class SkulgrimenDialogue(player: Player? = null) : DialoguePlugin(player) { 17 -> npcl(FacialExpression.ANNOYED,"Ah. I see. I already told you. Some guy down by the docks was bragging. Best ask there, I reckon.").also { stage = 1000 } 20 -> npcl(FacialExpression.HAPPY,"Ohh... That's a nice fish. Very pleased. Here. Take the bowstring. You fulfilled agreement. Only fair I do same. Good work outerlander.").also { - removeItem(player,3703) - addItem(player,3702) - stage++ + if (removeItem(player, Items.UNUSUAL_FISH_3703) && addItem(player, Items.CUSTOM_BOW_STRING_3702)) { + stage++ + } } 21 -> playerl(FacialExpression.HAPPY,"Thanks!").also { stage = 1000 } diff --git a/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/SwensenTheNavigator.kt b/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/SwensenTheNavigator.kt index b2f5aada9..0f7762d8e 100644 --- a/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/SwensenTheNavigator.kt +++ b/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/SwensenTheNavigator.kt @@ -8,18 +8,19 @@ import core.plugin.Initializable import core.game.dialogue.DialoguePlugin import core.game.dialogue.FacialExpression import content.data.Quests +import org.rs09.consts.Items @Initializable class SwensenTheNavigator(player: Player? = null) : DialoguePlugin(player){ val gender = if (player?.isMale == true){"brother"} else "sister" val fName = player?.getAttribute("fremennikname","doug hug'em") override fun open(vararg args: Any?): Boolean { - if(player?.inventory?.contains(3705,1) == true){ + if(player?.inventory?.contains(Items.WEATHER_FORECAST_3705, 1) == true){ playerl(FacialExpression.HAPPY,"I would like your map of fishing spots.") stage = 120 return true } - else if(player?.inventory?.contains(3704,1) == true){ + else if(player?.inventory?.contains(Items.SEA_FISHING_MAP_3704, 1) == true){ playerl(FacialExpression.ASKING,"If this map of fishing spots is so valuable, why did you give it away to me so easily?") stage = 125 return true @@ -48,7 +49,7 @@ class SwensenTheNavigator(player: Player? = null) : DialoguePlugin(player){ stage = 1000 return true } - else if(player.questRepository.isComplete(Quests.THE_FREMENNIK_TRIALS)){ + else if (player.questRepository.isComplete(Quests.THE_FREMENNIK_TRIALS)){ playerl(FacialExpression.HAPPY,"Hello!") stage = 140 return true @@ -119,9 +120,9 @@ class SwensenTheNavigator(player: Player? = null) : DialoguePlugin(player){ 121 -> playerl(FacialExpression.HAPPY,"What, like this one I have here?").also { stage++ } 122 -> npcl(FacialExpression.AMAZED,"W-what...? I don't believe it! How did you...?").also { stage++ } 123 -> npcl(FacialExpression.HAPPY,"I suppose it doesn't matter, you have my gratitude outerlander! With this forecast I will be able to plan a safe course for our next raiding expedition!").also { - removeItem(player,3705) - addItem(player,3704) - stage++ + if (removeItem(player,Items.WEATHER_FORECAST_3705) && addItem(player, Items.SEA_FISHING_MAP_3704)) { + stage++ + } } 124 -> npcl(FacialExpression.HAPPY,"Here, outerlander; you may take my map of local fishing patterns with my gratitude!").also { stage = 1000 } diff --git a/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/TFTInteractionListeners.kt b/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/TFTInteractionListeners.kt index 51331c124..db98b2314 100644 --- a/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/TFTInteractionListeners.kt +++ b/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/TFTInteractionListeners.kt @@ -84,8 +84,7 @@ class TFTInteractionListeners : InteractionListener { } onUseWith(IntType.ITEM,TINDERBOX,CHERRY_BOMB){ player, _, _ -> - if(removeItem(player,CHERRY_BOMB)){ - addItem(player,LIT_BOMB) + if (removeItem(player,CHERRY_BOMB) && addItem(player,LIT_BOMB)) { sendMessage(player,"You light the strange object.") } return@onUseWith true diff --git a/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/ThorvaldDialogue.kt b/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/ThorvaldDialogue.kt index af0ed440f..e64080988 100644 --- a/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/ThorvaldDialogue.kt +++ b/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/ThorvaldDialogue.kt @@ -5,18 +5,19 @@ import core.api.getQuestStage import core.api.removeItem import core.game.node.entity.player.Player import core.plugin.Initializable +import org.rs09.consts.Items import org.rs09.consts.NPCs import content.data.Quests @Initializable class ThorvaldDialogue(player: Player? = null) : core.game.dialogue.DialoguePlugin(player){ override fun open(vararg args: Any?): Boolean { - if(player?.inventory?.contains(3706,1) == true){ + if(player?.inventory?.contains(Items.CHAMPIONS_TOKEN_3706, 1) == true){ playerl(core.game.dialogue.FacialExpression.HAPPY,"I would like your contract to offer your services as a bodyguard.") stage = 215 return true } - else if(player?.inventory?.contains(3710,1) == true){ + else if(player?.inventory?.contains(Items.WARRIORS_CONTRACT_3710, 1) == true){ playerl(core.game.dialogue.FacialExpression.ASKING,"You didn't take much persuading to 'lower' yourself to a bodyguard.") stage = 220 return true @@ -179,9 +180,10 @@ class ThorvaldDialogue(player: Player? = null) : core.game.dialogue.DialoguePlug 216 -> playerl(core.game.dialogue.FacialExpression.HAPPY,"It's a good thing I have the Champions' Token right here then, isn't it?").also { stage++ } 217 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"Ah... well this is a different matter. With that token I can claim my rightful place as a champion in the Long hall!").also { stage++ } 218 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"Here outerlander, I can suffer the indignity of playing babysitter if it means that I can then revel with my warrior equals in the Long Hall afterwards!").also { - removeItem(player,3706) - addItem(player,3710) - stage++ + if (removeItem(player, Items.CHAMPIONS_TOKEN_3706)) { + addItem(player, Items.WARRIORS_CONTRACT_3710) + stage++ + } } 219 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"Here outerlander, take this contract; I will fulfill it to my utmost.").also { stage = 1000 } diff --git a/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/YrsaDialogue.kt b/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/YrsaDialogue.kt index a515d5dde..3114af8ab 100644 --- a/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/YrsaDialogue.kt +++ b/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/YrsaDialogue.kt @@ -8,18 +8,19 @@ import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.plugin.Initializable import content.data.Quests +import org.rs09.consts.Items @Initializable class YrsaDialogue(player: Player? = null) : DialoguePlugin(player) { override fun open(vararg args: Any?): Boolean { npc = args[0] as NPC - if(player?.inventory?.contains(3708,1) == true){ + if(player?.inventory?.contains(Items.FISCAL_STATEMENT_3708,1) == true){ playerl(FacialExpression.HAPPY,"Hello. Can I have those boots now? Here is a written statement from Brundt outlining future tax burdens upon Fremennik merchants and shopkeepers for the year.") stage = 15 return true } - else if(player?.inventory?.contains(3700,1) == true){ + else if(player?.inventory?.contains(Items.STURDY_BOOTS_3700,1) == true){ playerl(FacialExpression.ASKING,"Hey, these shoes look pretty comfy. Think you could make me a pair like them?") stage = 20 return true @@ -67,9 +68,9 @@ class YrsaDialogue(player: Player? = null) : DialoguePlugin(player) { 10 -> npcl(FacialExpression.NEUTRAL,"Yes I do outerlander. Only the Chieftain may permit such a thing. Talk to him.").also { stage = 1000 } 15 -> npcl(FacialExpression.HAPPY,"Certainly! Let me have a look at what he has written here, just give me a moment...").also { - removeItem(player,3708) - addItem(player,3700) - stage++ + if (removeItem(player, Items.FISCAL_STATEMENT_3708) && addItem(player, Items.STURDY_BOOTS_3700)) { + stage++ + } } 16 -> npcl(FacialExpression.HAPPY,"Yes, that all appears in order. Tell Olaf to come to me next time for shoes!").also { stage = 1000 } diff --git a/Server/src/main/content/region/kandarin/ardougne/quest/arena/dialogue/KhazardBarmanDialogue.kt b/Server/src/main/content/region/kandarin/ardougne/quest/arena/dialogue/KhazardBarmanDialogue.kt index 48ff869ac..96c91c8e1 100644 --- a/Server/src/main/content/region/kandarin/ardougne/quest/arena/dialogue/KhazardBarmanDialogue.kt +++ b/Server/src/main/content/region/kandarin/ardougne/quest/arena/dialogue/KhazardBarmanDialogue.kt @@ -1,10 +1,7 @@ package content.region.kandarin.ardougne.quest.arena.dialogue import content.data.Quests -import core.api.addItem -import core.api.getQuestStage -import core.api.removeItem -import core.api.setQuestStage +import core.api.* import core.game.dialogue.DialogueFile import core.game.dialogue.FacialExpression import core.game.node.entity.npc.NPC @@ -24,7 +21,7 @@ class KhazardBarmanDialogue : DialogueFile() { 1 -> npcl(FacialExpression.FRIENDLY, "There you go, that's two gold coins.").also { stage = 2 } 2 -> if (removeItem(player!!, Item(COINS_995, 2))) { end() - addItem(player!!, Items.BEER_1917, 1) + addItemOrDrop(player!!, Items.BEER_1917, 1) stage = END_DIALOGUE } else { end() @@ -49,7 +46,7 @@ class KhazardBarmanDialogue : DialogueFile() { 7 -> npcl(FacialExpression.FRIENDLY, "There you go, that's five gold coins. I suggest lying down before you drink it. That way you have less distance to collapse.").also { stage = 9 } 8 -> if (removeItem(player!!, Item(COINS_995, 2))){ end() - addItem(player!!, Items.BEER_1917, 1) + addItemOrDrop(player!!, Items.BEER_1917, 1) stage = END_DIALOGUE } else { end() @@ -57,8 +54,8 @@ class KhazardBarmanDialogue : DialogueFile() { } 9 -> if (removeItem(player!!, Item(COINS_995, 5))){ end() - addItem(player!!, Items.KHALI_BREW_77, 1) - setQuestStage(player!!, Quests.FIGHT_ARENA, 60) + addItemOrDrop(player!!, Items.KHALI_BREW_77, 1) + setQuestStage(player!!, Quests.FIGHT_ARENA, 60) stage = END_DIALOGUE } else { end() diff --git a/Server/src/main/content/region/kandarin/feldip/quest/chompybird/ChompyBirdNPC.kt b/Server/src/main/content/region/kandarin/feldip/quest/chompybird/ChompyBirdNPC.kt index 337f30ac2..efff701ef 100644 --- a/Server/src/main/content/region/kandarin/feldip/quest/chompybird/ChompyBirdNPC.kt +++ b/Server/src/main/content/region/kandarin/feldip/quest/chompybird/ChompyBirdNPC.kt @@ -153,7 +153,7 @@ class ChompyBirdNPC : AbstractNPC, InteractionListener { val bird = node.asNpc() if (!bird.getAttribute("plucked", false)) { - addItem(player, Items.FEATHER_314, RandomFunction.random(25, 32)) + addItemOrDrop(player, Items.FEATHER_314, RandomFunction.random(25, 32)) produceGroundItem(player, Items.BONES_526, 1, bird.location) produceGroundItem(player, Items.RAW_CHOMPY_2876, 1, bird.location) bird.clear() diff --git a/Server/src/main/content/region/kandarin/quest/scorpioncatcher/SCThormacDialogue.kt b/Server/src/main/content/region/kandarin/quest/scorpioncatcher/SCThormacDialogue.kt index bd4b91b78..9422c9d06 100644 --- a/Server/src/main/content/region/kandarin/quest/scorpioncatcher/SCThormacDialogue.kt +++ b/Server/src/main/content/region/kandarin/quest/scorpioncatcher/SCThormacDialogue.kt @@ -61,8 +61,11 @@ class SCThormacDialogue(val questStage: Int) : DialogueFile() { WHY_SHOULD_I_START -> npcl(FacialExpression.WORRIED, "Well I suppose I can aid you with my skills as a staff sorcerer. " + "Most battlestaffs around here are a bit puny. I can beef them up for you a bit.").also { // Need to recheck the quest stage since it may have been changed in this dialogue - if(getQuestStage(player!!, Quests.SCORPION_CATCHER) == 0) stage++ - else stage = END_DIALOGUE + if (getQuestStage(player!!, Quests.SCORPION_CATCHER) == 0) { + stage++ + } else { + stage = END_DIALOGUE + } } WHY_SHOULD_I_START+1 -> showTopics( Topic(FacialExpression.ASKING, "So how would I go about catching them then?", HOW_TO_CATCH), @@ -77,7 +80,7 @@ class SCThormacDialogue(val questStage: Int) : DialogueFile() { HOW_TO_CATCH+1 -> { sendItemDialogue(player!!, Items.SCORPION_CAGE_456, "Thormac gives you a cage.").also { stage++ } startQuest(player!!, Quests.SCORPION_CATCHER) - addItem(player!!, Items.SCORPION_CAGE_456) + addItemOrDrop(player!!, Items.SCORPION_CAGE_456) } HOW_TO_CATCH+2 -> npcl(FacialExpression.WORRIED, "If you go up to the village of Seers, to the North of " + "here, one of them will be able to tell you where the scorpions are now.").also { stage++ } @@ -92,8 +95,7 @@ class SCThormacDialogue(val questStage: Int) : DialogueFile() { Items.SCORPION_CAGE_459, Items.SCORPION_CAGE_460, Items.SCORPION_CAGE_461, Items.SCORPION_CAGE_462), false).exists()){ playerl(FacialExpression.SAD, "I've lost my cage.").also { stage = GIVE_ANOTHER_CAGE } - } - else{ + } else { playerl(FacialExpression.NEUTRAL, "I've not caught all the scorpions yet.").also { stage++ } } } diff --git a/Server/src/main/content/region/kandarin/quest/scorpioncatcher/ScorpionCatcher.kt b/Server/src/main/content/region/kandarin/quest/scorpioncatcher/ScorpionCatcher.kt index ba9cc4046..2c314f0d6 100644 --- a/Server/src/main/content/region/kandarin/quest/scorpioncatcher/ScorpionCatcher.kt +++ b/Server/src/main/content/region/kandarin/quest/scorpioncatcher/ScorpionCatcher.kt @@ -17,7 +17,7 @@ class ScorpionCatcher : Quest(Quests.SCORPION_CATCHER, 108, 107, 1, 76, 0, 1, 6 const val QUEST_STATE_PEKSA_HELP = 40 const val QUEST_STATE_DONE = 100 - const val ATTRIBUTE_TAVERLY = "scorpion_catcher:caught_taverly" + const val ATTRIBUTE_TAVERLEY = "scorpion_catcher:caught_taverly" const val ATTRIBUTE_BARB = "scorpion_catcher:caught_barb" const val ATTRIBUTE_MONK = "scorpion_catcher:caught_monk" @@ -32,7 +32,7 @@ class ScorpionCatcher : Quest(Quests.SCORPION_CATCHER, 108, 107, 1, 76, 0, 1, 6 var ln = 12 - val caughtTaverly = player!!.getAttribute(ATTRIBUTE_TAVERLY, false) + val caughtTaverly = player!!.getAttribute(ATTRIBUTE_TAVERLEY, false) val caughtBarb = player.getAttribute(ATTRIBUTE_BARB, false) val caughtMonk = player.getAttribute(ATTRIBUTE_MONK, false) diff --git a/Server/src/main/content/region/kandarin/quest/scorpioncatcher/ScorpionCatcherUseListener.kt b/Server/src/main/content/region/kandarin/quest/scorpioncatcher/ScorpionCatcherUseListener.kt index 8759e2459..3b95da47b 100644 --- a/Server/src/main/content/region/kandarin/quest/scorpioncatcher/ScorpionCatcherUseListener.kt +++ b/Server/src/main/content/region/kandarin/quest/scorpioncatcher/ScorpionCatcherUseListener.kt @@ -1,342 +1,88 @@ package content.region.kandarin.quest.scorpioncatcher -import content.region.kandarin.quest.scorpioncatcher.ScorpionCatcher.Companion.ATTRIBUTE_TAVERLY +import content.region.kandarin.quest.scorpioncatcher.ScorpionCatcher.Companion.ATTRIBUTE_TAVERLEY import content.region.kandarin.quest.scorpioncatcher.ScorpionCatcher.Companion.ATTRIBUTE_BARB import content.region.kandarin.quest.scorpioncatcher.ScorpionCatcher.Companion.ATTRIBUTE_MONK -import core.api.addItem +import core.api.* import core.game.node.item.Item -import core.api.removeItem -import core.api.runTask import core.game.interaction.IntType import core.game.interaction.InteractionListener +import core.game.node.Node +import core.game.node.entity.player.Player import core.game.system.config.NPCConfigParser import core.game.world.GameWorld +import core.tools.Log import org.rs09.consts.Items import org.rs09.consts.NPCs - class ScorpionCatcherUseListener : InteractionListener { - override fun defineListeners() { - /** - * List of cages - * Talvery Barbarian Monk - * TBM - * 456 --- - * 457 O-- - * 458 00- - * 459 -0- - * 460 -00 - * 461 --0 - * 462 0-0 - * 463 000 - * - * Scorpions - * 385 - Barbarian - * 386 - Taverly - * 387 - Monastery - */ + val scorpToAttr = mapOf( + /* 385 - Barbarian + * 386 - Taverley + * 387 - Monastery + */ + NPCs.KHARID_SCORPION_385 to ATTRIBUTE_BARB, + NPCs.KHARID_SCORPION_386 to ATTRIBUTE_TAVERLEY, + NPCs.KHARID_SCORPION_387 to ATTRIBUTE_MONK + ) + val cageToScorps = mapOf( + /* Taverley(386) Barbarian(385) Monastery(387) + * TBM + * 456 --- + * 457 O-- + * 458 00- + * 459 -0- + * 460 -00 + * 461 --0 + * 462 0-0 + * 463 000 + */ + Items.SCORPION_CAGE_456 to setOf(), + Items.SCORPION_CAGE_457 to setOf(NPCs.KHARID_SCORPION_386), + Items.SCORPION_CAGE_458 to setOf(NPCs.KHARID_SCORPION_386, NPCs.KHARID_SCORPION_385), + Items.SCORPION_CAGE_459 to setOf(NPCs.KHARID_SCORPION_385), + Items.SCORPION_CAGE_460 to setOf(NPCs.KHARID_SCORPION_385, NPCs.KHARID_SCORPION_387), + Items.SCORPION_CAGE_461 to setOf(NPCs.KHARID_SCORPION_387), + Items.SCORPION_CAGE_462 to setOf(NPCs.KHARID_SCORPION_386, NPCs.KHARID_SCORPION_387), + Items.SCORPION_CAGE_463 to setOf(NPCs.KHARID_SCORPION_386, NPCs.KHARID_SCORPION_385, NPCs.KHARID_SCORPION_387) + ) - /** - * Good captures - */ - - /** - * Empty cage on Taverly scorpion - */ - - // todo check this message - val haveAlready = "You already have this scorpion in this cage." - val catchMessage = "You catch a scorpion!" - - onUseWith(IntType.NPC, Items.SCORPION_CAGE_456, NPCs.KHARID_SCORPION_386){ player, used, with -> - removeItem(player, Item(used.id, 1)) - addItem(player, Items.SCORPION_CAGE_457) - player.sendMessage(catchMessage) - // This is the first time taverly has been caught - if (!player.getAttribute(ATTRIBUTE_TAVERLY, false)){ - player.setAttribute("/save:$ATTRIBUTE_TAVERLY", true) + fun catchScorpion(player: Player, item: Node, scorpion: Node): Boolean { + val haveInCage = cageToScorps[item.id] ?: return false + if (scorpion.id in haveInCage) { + sendMessage(player, "You already have this scorpion in this cage.") //TODO check this message + return true } - runTask(player, 2) { - with.asNpc().respawnTick = - GameWorld.ticks + with.asNpc().definition.getConfiguration(NPCConfigParser.RESPAWN_DELAY, 34) + val newScorpionSet = haveInCage + setOf(scorpion.id) + var newItem: Int? = null + for ((cage, scorps) in cageToScorps) { + if (scorps == newScorpionSet) { + newItem = cage + } } - return@onUseWith true - } - /** - * Barbarian cage on Taverly scorpion - */ - onUseWith(IntType.NPC, Items.SCORPION_CAGE_459, NPCs.KHARID_SCORPION_386){ player, used, with -> - removeItem(player, Item(used.id, 1)) - addItem(player, Items.SCORPION_CAGE_458) - player.sendMessage(catchMessage) - // This is the first time taverly has been caught - if (!player.getAttribute(ATTRIBUTE_TAVERLY, false)){ - player.setAttribute("/save:$ATTRIBUTE_TAVERLY", true) + if (newItem == null) { + log(this::class.java, Log.ERR, "Error looking up new scorpion cage item - this isn't possible") + return false } - runTask(player, 2) { - with.asNpc().respawnTick = - GameWorld.ticks + with.asNpc().definition.getConfiguration(NPCConfigParser.RESPAWN_DELAY, 34) + val attribute = scorpToAttr[scorpion.id] + if (removeItem(player, Item(item.id, 1)) && addItem(player, newItem)) { + sendMessage(player, "You catch a scorpion!") + setAttribute(player, "/save:$attribute", true) + runTask(player, 2) { + scorpion.asNpc().respawnTick = GameWorld.ticks + scorpion.asNpc().definition.getConfiguration(NPCConfigParser.RESPAWN_DELAY, 34) + } + return true } - return@onUseWith true - } - /** - * Monk cage on Taverly scorpion - */ - onUseWith(IntType.NPC, Items.SCORPION_CAGE_461, NPCs.KHARID_SCORPION_386){ player, used, with -> - removeItem(player, Item(used.id, 1)) - addItem(player, Items.SCORPION_CAGE_462) - player.sendMessage(catchMessage) - // This is the first time taverly has been caught - if (!player.getAttribute(ATTRIBUTE_TAVERLY, false)){ - player.setAttribute("/save:$ATTRIBUTE_TAVERLY", true) - } - runTask(player, 2) { - with.asNpc().respawnTick = - GameWorld.ticks + with.asNpc().definition.getConfiguration(NPCConfigParser.RESPAWN_DELAY, 34) - } - return@onUseWith true - } - /** - * Others on Taverly scorpion - */ - onUseWith(IntType.NPC, Items.SCORPION_CAGE_460, NPCs.KHARID_SCORPION_386){ player, used, with -> - removeItem(player, Item(used.id, 1)) - addItem(player, Items.SCORPION_CAGE_463) - player.sendMessage(catchMessage) - // This is the first time taverly has been caught - if (!player.getAttribute(ATTRIBUTE_TAVERLY, false)){ - player.setAttribute("/save:$ATTRIBUTE_TAVERLY", true) - } - runTask(player, 2) { - with.asNpc().respawnTick = - GameWorld.ticks + with.asNpc().definition.getConfiguration(NPCConfigParser.RESPAWN_DELAY, 34) - } - return@onUseWith true + return false } - /** - * Empty cage on barbarian agility course scorpion - */ - onUseWith(IntType.NPC, Items.SCORPION_CAGE_456, NPCs.KHARID_SCORPION_385){ player, used, with -> - removeItem(player, Item(used.id, 1)) - addItem(player, Items.SCORPION_CAGE_459) - player.sendMessage(catchMessage) - // This is the first time barbarian has been caught - if (!player.getAttribute(ATTRIBUTE_BARB, false)){ - player.setAttribute("/save:$ATTRIBUTE_BARB", true) + for (scorp in scorpToAttr.keys) { + for (cage in cageToScorps.keys) { + onUseWith(IntType.NPC, cage, scorp) { player, usedCage, usedScorp -> + return@onUseWith catchScorpion(player, usedCage, usedScorp) + } } - runTask(player, 2) { - with.asNpc().respawnTick = - GameWorld.ticks + with.asNpc().definition.getConfiguration(NPCConfigParser.RESPAWN_DELAY, 34) - } - return@onUseWith true } - /** - * Cage with Taverly scorpion on barbarian scorpion - */ - onUseWith(IntType.NPC, Items.SCORPION_CAGE_457, NPCs.KHARID_SCORPION_385) { player, used, with -> - removeItem(player, Item(used.id, 1)) - addItem(player, Items.SCORPION_CAGE_458) - player.sendMessage(catchMessage) - // This is the first time barbarian has been caught - if (!player.getAttribute(ATTRIBUTE_BARB, false)){ - player.setAttribute("/save:$ATTRIBUTE_BARB", true) - } - runTask(player, 2) { - with.asNpc().respawnTick = - GameWorld.ticks + with.asNpc().definition.getConfiguration(NPCConfigParser.RESPAWN_DELAY, 34) - } - return@onUseWith true - } - /** - * Cage with Monk scorpion on barbarian scorpion - */ - onUseWith(IntType.NPC, Items.SCORPION_CAGE_461, NPCs.KHARID_SCORPION_385) { player, used, with -> - removeItem(player, Item(used.id, 1)) - addItem(player, Items.SCORPION_CAGE_460) - player.sendMessage(catchMessage) - // This is the first time barbarian has been caught - if (!player.getAttribute(ATTRIBUTE_BARB, false)){ - player.setAttribute("/save:$ATTRIBUTE_BARB", true) - } - runTask(player, 2) { - with.asNpc().respawnTick = - GameWorld.ticks + with.asNpc().definition.getConfiguration(NPCConfigParser.RESPAWN_DELAY, 34) - } - return@onUseWith true - } - - /** - * Others on barbarian scorpion - */ - onUseWith(IntType.NPC, Items.SCORPION_CAGE_462, NPCs.KHARID_SCORPION_385) { player, used, with -> - removeItem(player, Item(used.id, 1)) - addItem(player, Items.SCORPION_CAGE_463) - player.sendMessage(catchMessage) - // This is the first time barbarian has been caught - if (!player.getAttribute(ATTRIBUTE_BARB, false)){ - player.setAttribute("/save:$ATTRIBUTE_BARB", true) - } - runTask(player, 2) { - with.asNpc().respawnTick = - GameWorld.ticks + with.asNpc().definition.getConfiguration(NPCConfigParser.RESPAWN_DELAY, 34) - } - return@onUseWith true - } - - - /** - * Empty on Monk scorpion - */ - onUseWith(IntType.NPC, Items.SCORPION_CAGE_456, NPCs.KHARID_SCORPION_387) { player, used, with -> - removeItem(player, Item(used.id, 1)) - addItem(player, Items.SCORPION_CAGE_461) - player.sendMessage(catchMessage) - // This is the first time the monastery has been caught - if (!player.getAttribute(ATTRIBUTE_MONK, false)){ - player.setAttribute("/save:$ATTRIBUTE_MONK", true) - } - runTask(player, 2) { - with.asNpc().respawnTick = - GameWorld.ticks + with.asNpc().definition.getConfiguration(NPCConfigParser.RESPAWN_DELAY, 34) - } - return@onUseWith true - } - - /** - * Taverly cage on Monk scorpion - */ - onUseWith(IntType.NPC, Items.SCORPION_CAGE_457, NPCs.KHARID_SCORPION_387) { player, used, with -> - removeItem(player, Item(used.id, 1)) - addItem(player, Items.SCORPION_CAGE_462) - player.sendMessage(catchMessage) - // This is the first time the monastery has been caught - if (!player.getAttribute(ATTRIBUTE_MONK, false)){ - player.setAttribute("/save:$ATTRIBUTE_MONK", true) - } - runTask(player, 2) { - with.asNpc().respawnTick = - GameWorld.ticks + with.asNpc().definition.getConfiguration(NPCConfigParser.RESPAWN_DELAY, 34) - } - return@onUseWith true - } - - /** - * Barbarian cage on Monk scorpion - */ - onUseWith(IntType.NPC, Items.SCORPION_CAGE_459, NPCs.KHARID_SCORPION_387) { player, used, with -> - removeItem(player, Item(used.id, 1)) - addItem(player, Items.SCORPION_CAGE_460) - player.sendMessage(catchMessage) - // This is the first time the monastery has been caught - if (!player.getAttribute(ATTRIBUTE_MONK, false)){ - player.setAttribute("/save:$ATTRIBUTE_MONK", true) - } - runTask(player, 2) { - with.asNpc().respawnTick = - GameWorld.ticks + with.asNpc().definition.getConfiguration(NPCConfigParser.RESPAWN_DELAY, 34) - } - return@onUseWith true - } - - /** - * Others on Monk scorpion - */ - onUseWith(IntType.NPC, Items.SCORPION_CAGE_458, NPCs.KHARID_SCORPION_387) { player, used, with -> - removeItem(player, Item(used.id, 1)) - addItem(player, Items.SCORPION_CAGE_463) - player.sendMessage(catchMessage) - // This is the first time the monastery has been caught - if (!player.getAttribute(ATTRIBUTE_MONK, false)){ - player.setAttribute("/save:$ATTRIBUTE_MONK", true) - } - runTask(player, 2) { - with.asNpc().respawnTick = - GameWorld.ticks + with.asNpc().definition.getConfiguration(NPCConfigParser.RESPAWN_DELAY, 34) - } - return@onUseWith true - } - - - /** - * Player being stupid and trying to recatch one they have already - */ - - /** - * Taverly - */ - // Just Taverly - onUseWith(IntType.NPC, Items.SCORPION_CAGE_457, NPCs.KHARID_SCORPION_386){ player, _, _ -> - player.sendMessage(haveAlready) - return@onUseWith true - } - // Taverly and Barb - onUseWith(IntType.NPC, Items.SCORPION_CAGE_458, NPCs.KHARID_SCORPION_386){ player, _, _ -> - player.sendMessage(haveAlready) - return@onUseWith true - } - // Taverly and Monk - onUseWith(IntType.NPC, Items.SCORPION_CAGE_462, NPCs.KHARID_SCORPION_386){ player, _, _ -> - player.sendMessage(haveAlready) - return@onUseWith true - } - // All - onUseWith(IntType.NPC, Items.SCORPION_CAGE_463, NPCs.KHARID_SCORPION_386){ player, _, _ -> - player.sendMessage(haveAlready) - return@onUseWith true - } - - - /** - * Barbarian - */ - // Just Barb - onUseWith(IntType.NPC, Items.SCORPION_CAGE_459, NPCs.KHARID_SCORPION_385){ player, _, _ -> - player.sendMessage(haveAlready) - return@onUseWith true - } - // Barb and Taverly - onUseWith(IntType.NPC, Items.SCORPION_CAGE_458, NPCs.KHARID_SCORPION_385){ player, _, _ -> - player.sendMessage(haveAlready) - return@onUseWith true - } - // Barb and Monk - onUseWith(IntType.NPC, Items.SCORPION_CAGE_460, NPCs.KHARID_SCORPION_385){ player, _, _ -> - player.sendMessage(haveAlready) - return@onUseWith true - } - // All - onUseWith(IntType.NPC, Items.SCORPION_CAGE_463, NPCs.KHARID_SCORPION_385){ player, _, _ -> - player.sendMessage(haveAlready) - return@onUseWith true - } - - /** - * Monastery - */ - // Just Monk - onUseWith(IntType.NPC, Items.SCORPION_CAGE_461, NPCs.KHARID_SCORPION_387){ player, _, _ -> - player.sendMessage(haveAlready) - return@onUseWith true - } - // Monk and Taverly - onUseWith(IntType.NPC, Items.SCORPION_CAGE_462, NPCs.KHARID_SCORPION_387){ player, _, _ -> - player.sendMessage(haveAlready) - return@onUseWith true - } - // Monk and Barb - onUseWith(IntType.NPC, Items.SCORPION_CAGE_460, NPCs.KHARID_SCORPION_387){ player, _, _ -> - player.sendMessage(haveAlready) - return@onUseWith true - } - // All - onUseWith(IntType.NPC, Items.SCORPION_CAGE_463, NPCs.KHARID_SCORPION_387){ player, _, _ -> - player.sendMessage(haveAlready) - return@onUseWith true - } - } - - -} \ No newline at end of file +} diff --git a/Server/src/main/content/region/kandarin/seers/dialogue/SeerDialogue.kt b/Server/src/main/content/region/kandarin/seers/dialogue/SeerDialogue.kt index c4d8a4e93..e241c34f2 100644 --- a/Server/src/main/content/region/kandarin/seers/dialogue/SeerDialogue.kt +++ b/Server/src/main/content/region/kandarin/seers/dialogue/SeerDialogue.kt @@ -75,7 +75,7 @@ class SeerDialogue(player: Player? = null) : DialoguePlugin(player) { ) } else if ((scorpionCatcherQuestStage == ScorpionCatcher.QUEST_STATE_DARK_PLACE) and - getAttribute(player!!, ScorpionCatcher.ATTRIBUTE_TAVERLY, false) + getAttribute(player!!, ScorpionCatcher.ATTRIBUTE_TAVERLEY, false) ) { playerl( FacialExpression.NEUTRAL, diff --git a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt index f992cb72e..f43ac6bed 100644 --- a/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt +++ b/Server/src/main/content/region/kandarin/seers/quest/elementalworkshop/EWListeners.kt @@ -103,7 +103,7 @@ class EWListeners : InteractionListener { } // Player needs to receive a battered book sendItemDialogue(player, Item(Items.BATTERED_BOOK_2886), "You find a book titled 'The Elemental Shield'.") - addItem(player, batteredBook.id) + addItemOrDrop(player, batteredBook.id) return@on true } @@ -120,7 +120,7 @@ class EWListeners : InteractionListener { } sendItemDialogue(player, Item(Items.SLASHED_BOOK_9715), "You find a book titled 'The Elemental Shield'.") - addItem(player, slashedBook.id) + addItemOrDrop(player, slashedBook.id) if (player.inventory.addIfDoesntHave(batteredKey)) { sendItemDialogue(player, Item(Items.BATTERED_KEY_2887),"You also find a key.") } @@ -237,7 +237,7 @@ class EWListeners : InteractionListener { on(Scenery.CRATE_3400, IntType.SCENERY, "search") { player, _ -> if (!getAttribute(player, "/save:ew1:got_needle", false)) { setAttribute(player, "/save:ew1:got_needle", true) - addItem(player, Items.NEEDLE_1733) + addItemOrDrop(player, Items.NEEDLE_1733) sendMessage(player, "You find a needle.") } else { sendMessage(player, "You search the crate but find nothing.") @@ -249,7 +249,7 @@ class EWListeners : InteractionListener { on(Scenery.CRATE_3394, IntType.SCENERY, "search") { player, _ -> if (!getAttribute(player, "/save:ew1:got_leather", false)) { setAttribute(player, "/save:ew1:got_leather", true) - addItem(player, Items.LEATHER_1741) + addItemOrDrop(player, Items.LEATHER_1741) sendMessage(player, "You find some leather.") } else { sendMessage(player, "You search the crate but find nothing.") diff --git a/Server/src/main/content/region/karamja/quest/tribaltotem/TribalTotemListeners.kt b/Server/src/main/content/region/karamja/quest/tribaltotem/TribalTotemListeners.kt index 33ab1eb7e..bf48bc464 100644 --- a/Server/src/main/content/region/karamja/quest/tribaltotem/TribalTotemListeners.kt +++ b/Server/src/main/content/region/karamja/quest/tribaltotem/TribalTotemListeners.kt @@ -33,7 +33,7 @@ class TribalTotemListeners : InteractionListener { on(realCrate, IntType.SCENERY, "Investigate"){ player, node -> if(player.questRepository.getStage(Quests.TRIBAL_TOTEM) in 1..19 && !player.inventory.containsAtLeastOneItem(Items.ADDRESS_LABEL_1858)){ sendDialogue(player,"There is a label on this crate. It says; To Lord Handelmort, Handelmort Mansion Ardogune.You carefully peel it off and take it.") - addItem(player,Items.ADDRESS_LABEL_1858,1) + addItemOrDrop(player,Items.ADDRESS_LABEL_1858,1) } else if(player.questRepository.getStage(Quests.TRIBAL_TOTEM) in 1..19 && player.inventory.containsAtLeastOneItem(Items.ADDRESS_LABEL_1858)){ sendDialogue(player,"There was a label on this crate, but it's gone now since you took it!") @@ -96,7 +96,7 @@ class TribalTotemListeners : InteractionListener { on(openChest, IntType.SCENERY, "Search"){ player, node -> if(!player.inventory.containsAtLeastOneItem(Items.TOTEM_1857)){ sendDialogue(player,"Inside the chest you find the tribal totem.") - addItem(player,Items.TOTEM_1857) + addItemOrDrop(player,Items.TOTEM_1857) } else{ sendDialogue(player,"Inside the chest you don't find anything because you already took the totem!") diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialCombatInstructorDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialCombatInstructorDialogue.kt index 4e73a799d..3513aaeb8 100644 --- a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialCombatInstructorDialogue.kt +++ b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialCombatInstructorDialogue.kt @@ -31,9 +31,9 @@ class TutorialCombatInstructorDialogue(player: Player? = null) : DialoguePlugin( 54 -> { player.dialogueInterpreter.sendDoubleItemMessage(Items.SHORTBOW_841, Items.BRONZE_ARROW_882, "The Combat Guide gives you some bronze arrows and a shortbow!") if(!inInventory(player, Items.SHORTBOW_841) && !inEquipment(player, Items.SHORTBOW_841)) - addItem(player, Items.SHORTBOW_841) + addItemOrDrop(player, Items.SHORTBOW_841) if(!inInventory(player, Items.BRONZE_ARROW_882) && !inEquipment(player, Items.BRONZE_ARROW_882)) - addItem(player, Items.BRONZE_ARROW_882, 30) + addItemOrDrop(player, Items.BRONZE_ARROW_882, 30) } } return true diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMagicTutorDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMagicTutorDialogue.kt index a92d70916..6c15f7449 100644 --- a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMagicTutorDialogue.kt +++ b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMagicTutorDialogue.kt @@ -59,8 +59,8 @@ class TutorialMagicTutorDialogue(player: Player? = null) : core.game.dialogue.Di 70 -> if(!inInventory(player, Items.AIR_RUNE_556) && !inInventory(player, Items.MIND_RUNE_558)) { player.dialogueInterpreter.sendDoubleItemMessage(Items.AIR_RUNE_556, Items.MIND_RUNE_558, "You receive some spare runes.") - addItem(player, Items.AIR_RUNE_556, 15) - addItem(player, Items.MIND_RUNE_558, 15) + addItemOrDrop(player, Items.AIR_RUNE_556, 15) + addItemOrDrop(player, Items.MIND_RUNE_558, 15) return false } 71 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Alright, last thing. Are you interested in being an ironman or changing your experience rate?") diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMasterChefDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMasterChefDialogue.kt index 2db699406..ad7d90bcb 100644 --- a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMasterChefDialogue.kt +++ b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMasterChefDialogue.kt @@ -42,14 +42,14 @@ class TutorialMasterChefDialogue(player: Player? = null) : DialoguePlugin(player if(!inInventory(player, Items.BUCKET_OF_WATER_1929)) { sendItemDialogue(player, Items.BUCKET_OF_WATER_1929, "The Master Chef gives you another bucket of water.") - addItem(player, Items.BUCKET_OF_WATER_1929) + addItemOrDrop(player, Items.BUCKET_OF_WATER_1929) TutorialStage.load(player, 19) return false } if(!inInventory(player, Items.POT_OF_FLOUR_1933)) { sendItemDialogue(player, Items.POT_OF_FLOUR_1933, "The Master Chef gives you another pot of flour.") - addItem(player, Items.POT_OF_FLOUR_1933) + addItemOrDrop(player, Items.POT_OF_FLOUR_1933) TutorialStage.load(player, 19) return false } @@ -102,8 +102,8 @@ class TutorialMasterChefDialogue(player: Player? = null) : DialoguePlugin(player "The Cooking Guide gives you a bucket of water and a pot of flour." ) ) - addItem(player, Items.BUCKET_OF_WATER_1929) - addItem(player, Items.POT_OF_FLOUR_1933) + addItemOrDrop(player, Items.BUCKET_OF_WATER_1929) + addItemOrDrop(player, Items.POT_OF_FLOUR_1933) stage++ } 4 -> { diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMiningInstructorDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMiningInstructorDialogue.kt index ad9f6c77e..85a8577fa 100644 --- a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMiningInstructorDialogue.kt +++ b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMiningInstructorDialogue.kt @@ -1,9 +1,5 @@ package content.region.misc.tutisland.dialogue -import core.api.addItem -import core.api.getAttribute -import core.api.inInventory -import core.api.setAttribute import core.game.dialogue.DialoguePlugin import core.game.dialogue.FacialExpression import core.game.node.entity.npc.NPC @@ -12,6 +8,7 @@ import core.plugin.Initializable import org.rs09.consts.Items import org.rs09.consts.NPCs import content.region.misc.tutisland.handlers.TutorialStage +import core.api.* /** * Handles the mining tutor's dialogue @@ -30,7 +27,7 @@ class TutorialMiningInstructorDialogue(player: Player? = null) : DialoguePlugin( 34 -> playerl(FacialExpression.FRIENDLY, "I prospected both types of rock! One set contains tin and the other has copper ore inside.") 35 -> { if(!inInventory(player, Items.BRONZE_PICKAXE_1265)) { - addItem(player, Items.BRONZE_PICKAXE_1265) + addItemOrDrop(player, Items.BRONZE_PICKAXE_1265) player.dialogueInterpreter.sendItemMessage(Items.BRONZE_PICKAXE_1265, "Dezzick gives you a bronze pickaxe!") stage = 3 } @@ -41,7 +38,7 @@ class TutorialMiningInstructorDialogue(player: Player? = null) : DialoguePlugin( 40 -> playerl(FacialExpression.ASKING, "How do I make a weapon out of this?") 41 -> { if(!inInventory(player, Items.HAMMER_2347)) { - addItem(player, Items.HAMMER_2347) + addItemOrDrop(player, Items.HAMMER_2347) player.dialogueInterpreter.sendItemMessage(Items.HAMMER_2347, "Dezzick gives you a hammer!") stage = 3 } diff --git a/Server/src/main/content/region/misc/tutisland/handlers/TutorialFurnaceListener.kt b/Server/src/main/content/region/misc/tutisland/handlers/TutorialFurnaceListener.kt index c4139d6f5..b53335a9d 100644 --- a/Server/src/main/content/region/misc/tutisland/handlers/TutorialFurnaceListener.kt +++ b/Server/src/main/content/region/misc/tutisland/handlers/TutorialFurnaceListener.kt @@ -35,12 +35,13 @@ class TutorialFurnaceListener : InteractionListener { animate(player, ANIMATION) submitIndividualPulse(player, object: Pulse(2) { override fun pulse(): Boolean { - removeItem(player, Items.TIN_ORE_438) - removeItem(player, Items.COPPER_ORE_436) - addItem(player, Items.BRONZE_BAR_2349) - rewardXP(player, Skills.SMITHING, Bar.BRONZE.experience) - player.dispatch(ResourceProducedEvent(Items.BRONZE_BAR_2349, 1, player, Items.COPPER_ORE_436)) - return true + if (removeItem(player, Items.TIN_ORE_438) && removeItem(player, Items.COPPER_ORE_436)) { + addItem(player, Items.BRONZE_BAR_2349) + rewardXP(player, Skills.SMITHING, Bar.BRONZE.experience) + player.dispatch(ResourceProducedEvent(Items.BRONZE_BAR_2349, 1, player, Items.COPPER_ORE_436)) + return true + } + return false } }) diff --git a/Server/src/main/content/region/misthalin/barbvillage/stronghold/GiftOfPeaceDialogue.java b/Server/src/main/content/region/misthalin/barbvillage/stronghold/GiftOfPeaceDialogue.java index 1adee0589..0608d0fea 100644 --- a/Server/src/main/content/region/misthalin/barbvillage/stronghold/GiftOfPeaceDialogue.java +++ b/Server/src/main/content/region/misthalin/barbvillage/stronghold/GiftOfPeaceDialogue.java @@ -4,7 +4,6 @@ import core.api.Container; import core.game.dialogue.DialoguePlugin; import core.game.node.entity.player.Player; import core.game.node.entity.player.link.emote.Emotes; -import core.game.node.item.Item; import org.rs09.consts.Items; import static core.api.ContentAPIKt.addItem; diff --git a/Server/src/main/content/region/misthalin/barbvillage/stronghold/GrainOfPlentyDialogue.java b/Server/src/main/content/region/misthalin/barbvillage/stronghold/GrainOfPlentyDialogue.java index 8cef49bf4..1135cefd5 100644 --- a/Server/src/main/content/region/misthalin/barbvillage/stronghold/GrainOfPlentyDialogue.java +++ b/Server/src/main/content/region/misthalin/barbvillage/stronghold/GrainOfPlentyDialogue.java @@ -56,7 +56,7 @@ public final class GrainOfPlentyDialogue extends DialoguePlugin { break; } player.getSavedData().getGlobalData().getStrongHoldRewards()[1] = true; - interpreter.sendDialogue("...congratualtions adventurer, you have been deemed worthy of this", "reward. You have also unlocked the Slap Head emote!"); + interpreter.sendDialogue("...congratulations adventurer, you have been deemed worthy of this", "reward. You have also unlocked the Slap Head emote!"); stage = 1; player.getEmoteManager().unlock(Emotes.SLAP_HEAD); break; diff --git a/Server/src/main/content/region/misthalin/barbvillage/stronghold/playersafety/ProfessorHenryDialogue.kt b/Server/src/main/content/region/misthalin/barbvillage/stronghold/playersafety/ProfessorHenryDialogue.kt index 50b4b0406..3361fedce 100644 --- a/Server/src/main/content/region/misthalin/barbvillage/stronghold/playersafety/ProfessorHenryDialogue.kt +++ b/Server/src/main/content/region/misthalin/barbvillage/stronghold/playersafety/ProfessorHenryDialogue.kt @@ -32,7 +32,7 @@ class ProfessorHenryDialogue(player : Player? = null) : DialoguePlugin(player){ } else if (player.savedData.globalData.testStage >= 3){ // The player has already had their test marked and taken - npcl(FacialExpression.HAPPY, "Good job ${player.name}, you completed the test!").also { stage = END_DIALOGUE } + npcl(FacialExpression.HAPPY, "Good job ${player.username}, you completed the test!").also { stage = END_DIALOGUE } return true } else{ @@ -83,7 +83,7 @@ class ProfessorHenryDialogue(player : Player? = null) : DialoguePlugin(player){ GET_TEST + 1 -> playerl(FacialExpression.HALF_GUILTY, "Okay, thanks.").also { stage = END_DIALOGUE } HAND_IN_TEST -> npcl(FacialExpression.HAPPY, - "Ah, ${player.name}. How's the test going?").also { stage ++ } + "Ah, ${player.username}. How's the test going?").also { stage ++ } HAND_IN_TEST + 1 -> playerl(FacialExpression.NEUTRAL, "I think I've finished.").also { stage++ } HAND_IN_TEST + 2 -> npcl(FacialExpression.HAPPY, "Excellent! Let me just mark the paper for you then.").also { stage++ } HAND_IN_TEST + 3 -> npcl(FacialExpression.HAPPY, "Hmm. Uh-huh, yes I see. Good! Yes, that's right.").also{ stage++ } @@ -118,8 +118,9 @@ class ProfessorHenryDialogue(player : Player? = null) : DialoguePlugin(player){ setVarp(player, 1203, 1 shl 29, true) player.savedData.globalData.testStage = 3 - removeItem(player, Items.TEST_PAPER_12626) - addItem(player, Items.ANTIQUE_LAMP_4447, 2) + if (removeItem(player, Items.TEST_PAPER_12626)) { + addItem(player, Items.ANTIQUE_LAMP_4447, 2) + } player.emoteManager.unlock(Emotes.SAFETY_FIRST) openInterface(player, iFace) @@ -137,8 +138,7 @@ class ProfessorHenryDialogue(player : Player? = null) : DialoguePlugin(player){ player.packetDispatch.sendString("Player Safety Dungeon", iFace, 11) player.packetDispatch.sendString("The Safety First' emote", iFace, 12) - sendItemZoomOnInterface(player, iFace, 5, Items.TEST_PAPER_12626) } -} \ No newline at end of file +} diff --git a/Server/src/main/content/region/misthalin/varrock/handlers/ZaffPlugin.kt b/Server/src/main/content/region/misthalin/varrock/handlers/ZaffPlugin.kt index 1af2980f0..22243ca0b 100644 --- a/Server/src/main/content/region/misthalin/varrock/handlers/ZaffPlugin.kt +++ b/Server/src/main/content/region/misthalin/varrock/handlers/ZaffPlugin.kt @@ -392,16 +392,18 @@ class ZaffPlugin : OptionHandler() { ammount = getStoreFile().getInt(player.username.toLowerCase()) var amt = value as Int if(amt > maxStaffs - ammount) amt = maxStaffs - ammount + if(amt == 0){ + return@sendInputDialogue + } val coinage = amt * 7000 if(!inInventory(player, Items.COINS_995, coinage)){ sendDialogue(player, "You can't afford that many.") return@sendInputDialogue } - - if(amt == 0){ + if(!hasSpaceFor(player, Item(Items.BATTLESTAFF_1392, amt)) && amountInInventory(player, Items.COINS_995) > coinage){ + sendDialogue(player, "You don't have enough inventory space.") return@sendInputDialogue } - if(removeItem(player, Item(Items.COINS_995, coinage), Container.INVENTORY)){ addItem(player, Items.BATTLESTAFF_1392, amt) getStoreFile()[player.username.toLowerCase()] = amt + ammount diff --git a/Server/src/main/content/region/morytania/canifis/dialogue/RoavarDialogue.kt b/Server/src/main/content/region/morytania/canifis/dialogue/RoavarDialogue.kt index edb3fbdda..41b7314a2 100644 --- a/Server/src/main/content/region/morytania/canifis/dialogue/RoavarDialogue.kt +++ b/Server/src/main/content/region/morytania/canifis/dialogue/RoavarDialogue.kt @@ -36,7 +36,7 @@ class RoavarDialogue (player: Player? = null) : DialoguePlugin(player) { 1 -> showTopics( Topic(FacialExpression.HALF_GUILTY, "Can I buy a beer?", 10, false), - Topic(FacialExpression.HALF_GUILTY, "Can I hear some gossip", 20, false), + Topic(FacialExpression.HALF_GUILTY, "Can I hear some gossip?", 20, false), IfTopic(FacialExpression.HALF_GUILTY, "Can I buy something to eat?", RoavarDialogueFile(1), player.getQuestRepository().getQuest(Quests.CREATURE_OF_FENKENSTRAIN).getStage(player) == 2, false), Topic(FacialExpression.HALF_GUILTY, "Nothing thanks.", 40, false) ) @@ -81,7 +81,7 @@ class RoavarDialogue (player: Player? = null) : DialoguePlugin(player) { } 21 -> end() - 30 -> stage = if (inInventory(player, 2963, 1)) { + 30 -> stage = if (inInventory(player, Items.SILVER_SICKLEB_2963, 1)) { npc(FacialExpression.HALF_GUILTY, "I don't have a spare lying around, sorry friend.", "Hopefully you'll find something else that can protect you", "against ghasts!") 31 } else { @@ -101,7 +101,7 @@ class RoavarDialogue (player: Player? = null) : DialoguePlugin(player) { npc(FacialExpression.HALF_GUILTY, "Oh, nevermind. It seems your backpack is full.") } else { sendDialogue(player, "The bartender hands you a silver sickle.") - addItem(player, 2963) + addItemOrDrop(player, Items.SILVER_SICKLEB_2963) } stage = 31 } diff --git a/Server/src/main/content/region/morytania/handlers/MortMyreGhastNPC.kt b/Server/src/main/content/region/morytania/handlers/MortMyreGhastNPC.kt index 6748596f5..cb80df540 100644 --- a/Server/src/main/content/region/morytania/handlers/MortMyreGhastNPC.kt +++ b/Server/src/main/content/region/morytania/handlers/MortMyreGhastNPC.kt @@ -70,8 +70,9 @@ class MortMyreGhastNPC : AbstractNPC { val consumable = Consumables.getConsumableById(i.id) if(consumable != null && consumable.consumable is Food) { hasFood = true - removeItem(player, i, Container.INVENTORY) - addItem(player, Items.ROTTEN_FOOD_2959) + if (removeItem(player, i, Container.INVENTORY)) { + addItem(player, Items.ROTTEN_FOOD_2959) + } sendMessage(player, "You feel something attacking your backpack, and smell a terrible stench.") break } diff --git a/Server/src/main/content/region/morytania/quest/naturespirit/NSListeners.kt b/Server/src/main/content/region/morytania/quest/naturespirit/NSListeners.kt index 0be90563f..b580aa4f3 100644 --- a/Server/src/main/content/region/morytania/quest/naturespirit/NSListeners.kt +++ b/Server/src/main/content/region/morytania/quest/naturespirit/NSListeners.kt @@ -132,8 +132,7 @@ class NSListeners : InteractionListener { } on(SPELLCARD, IntType.ITEM, "cast"){ player, node -> - if(NSUtils.castBloom(player)){ - removeItem(player, node.asItem(), Container.INVENTORY) + if (NSUtils.castBloom(player) && removeItem(player, node.asItem(), Container.INVENTORY)) { addItem(player, Items.A_USED_SPELL_2969) } return@on true diff --git a/Server/src/main/content/region/morytania/quest/naturespirit/NSTarlockDialogue.kt b/Server/src/main/content/region/morytania/quest/naturespirit/NSTarlockDialogue.kt index d7bdaba72..7559ff085 100644 --- a/Server/src/main/content/region/morytania/quest/naturespirit/NSTarlockDialogue.kt +++ b/Server/src/main/content/region/morytania/quest/naturespirit/NSTarlockDialogue.kt @@ -127,7 +127,7 @@ class NSTarlockDialogue(player: Player? = null) : DialoguePlugin(player) { 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 { addItem(player, Items.DRUIDIC_SPELL_2968); stage = END_DIALOGUE } + 75 -> sendDialogue("The spirit of Filliman Tarlock gives you","another bloom spell.").also { addItemOrDrop(player, Items.DRUIDIC_SPELL_2968); stage = END_DIALOGUE } //has fungus 80 -> sendDialogue("You show the fungus to Filliman.").also { stage++ } @@ -190,7 +190,7 @@ class NSTarlockDialogue(player: Player? = null) : DialoguePlugin(player) { npcl(FacialExpression.NEUTRAL, "No, you've already got one!").also { stage = END_DIALOGUE } } else { npcl(FacialExpression.NEUTRAL, "Sure, but look after this one.") - addItem(player, Items.DRUIDIC_SPELL_2968) + addItemOrDrop(player, Items.DRUIDIC_SPELL_2968) stage = END_DIALOGUE } diff --git a/Server/src/main/content/region/morytania/quest/naturespirit/NSUtils.kt b/Server/src/main/content/region/morytania/quest/naturespirit/NSUtils.kt index 03263c6c4..1a59aa8df 100644 --- a/Server/src/main/content/region/morytania/quest/naturespirit/NSUtils.kt +++ b/Server/src/main/content/region/morytania/quest/naturespirit/NSUtils.kt @@ -58,7 +58,7 @@ object NSUtils { if(pouchAmt == 1) shouldAddEmptyPouch = true if(pouchAmt > 0 && removeItem(player, Items.DRUID_POUCH_2958, Container.INVENTORY)){ if(shouldAddEmptyPouch){ - addItem(player, Items.DRUID_POUCH_2957) + addItemOrDrop(player, Items.DRUID_POUCH_2957) } spawnProjectile(player, attacker, 268) submitWorldPulse(object : Pulse(){ diff --git a/Server/src/main/core/game/worldevents/holiday/christmas/randoms/SantaHolidayRandomDialogue.kt b/Server/src/main/core/game/worldevents/holiday/christmas/randoms/SantaHolidayRandomDialogue.kt index 79991e5bb..f0b5a76c3 100644 --- a/Server/src/main/core/game/worldevents/holiday/christmas/randoms/SantaHolidayRandomDialogue.kt +++ b/Server/src/main/core/game/worldevents/holiday/christmas/randoms/SantaHolidayRandomDialogue.kt @@ -1,6 +1,5 @@ package core.game.worldevents.holiday.christmas.randoms -import core.api.addItem import core.api.addItemOrDrop import core.api.getAttribute import core.game.dialogue.DialogueFile From 7c74bfcb7198f6dda807bc73a030a0918e94c022 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Sun, 6 Apr 2025 08:18:17 +0000 Subject: [PATCH 019/117] Fixed the bug where an NPC would show up on an item dialogue Refactored both item and double item dialogues --- .../quest/rovingelves/IslwynDialogue.java | 2 +- Server/src/main/core/api/ContentAPI.kt | 4 +- .../game/dialogue/DialogueInterpreter.java | 134 +++++++++--------- .../entity/player/link/PacketDispatch.java | 4 + 4 files changed, 73 insertions(+), 71 deletions(-) diff --git a/Server/src/main/content/region/tirranwn/quest/rovingelves/IslwynDialogue.java b/Server/src/main/content/region/tirranwn/quest/rovingelves/IslwynDialogue.java index 9dbb4a6e0..2d111e7cd 100644 --- a/Server/src/main/content/region/tirranwn/quest/rovingelves/IslwynDialogue.java +++ b/Server/src/main/content/region/tirranwn/quest/rovingelves/IslwynDialogue.java @@ -172,7 +172,7 @@ public class IslwynDialogue extends DialoguePlugin { stage = 21; break; case 21: - interpreter.sendDoubleItemMessage(RovingElves.CRYSTAL_BOW_FULL, RovingElves.CRYSTAL_SHIELD_FULL, "Islwyn shows you a crystal bow and a crystal shield."); + interpreter.sendDoubleItemMessage(RovingElves.CRYSTAL_BOW_FULL.getId(), RovingElves.CRYSTAL_SHIELD_FULL.getId(), "Islwyn shows you a crystal bow and a crystal shield."); stage = 22; break; case 22: diff --git a/Server/src/main/core/api/ContentAPI.kt b/Server/src/main/core/api/ContentAPI.kt index f11ca49ac..ae01af745 100644 --- a/Server/src/main/core/api/ContentAPI.kt +++ b/Server/src/main/core/api/ContentAPI.kt @@ -1842,8 +1842,8 @@ fun sendItemDialogue(player: Player, item: Any, message: String) { */ fun sendDoubleItemDialogue(player: Player, item1: Any, item2: Any, message: String) { when (item1) { - is Item -> player.dialogueInterpreter.sendDoubleItemMessage(item1, item2 as Item, message) - is Int -> player.dialogueInterpreter.sendDoubleItemMessage(item1, item2 as Int, message) + is Item -> player.dialogueInterpreter.sendDoubleItemMessage(item1, item2 as Item, *splitLines(message)) + is Int -> player.dialogueInterpreter.sendDoubleItemMessage(item1, item2 as Int, *splitLines(message)) } } diff --git a/Server/src/main/core/game/dialogue/DialogueInterpreter.java b/Server/src/main/core/game/dialogue/DialogueInterpreter.java index b37a610db..43b4566d6 100644 --- a/Server/src/main/core/game/dialogue/DialogueInterpreter.java +++ b/Server/src/main/core/game/dialogue/DialogueInterpreter.java @@ -326,61 +326,37 @@ public final class DialogueInterpreter { * @param itemId The item id. */ public Component sendItemMessage(int itemId, String... messages) { - if(1 <= messages.length && messages.length < 4) { - ArrayList packedMessages = new ArrayList(); - for(int i = 0; i < messages.length/2; i++) { - packedMessages.add(messages[i] + "
" + messages[i+1]); - } - if(messages.length % 2 == 1) { - packedMessages.add(messages[messages.length-1]); - } - - int interfaceId = 241 + (packedMessages.size() - 1); - player.getInterfaceManager().openChatbox(interfaceId); - player.getPacketDispatch().sendItemOnInterface(itemId, 1, interfaceId, 1); - ItemDefinition itemDef = ItemDefinition.forId(itemId); - player.getPacketDispatch().sendAngleOnInterface(interfaceId, 1, itemDef.getModelZoom() / 2, itemDef.getModelRotationX(), itemDef.getModelRotationY()); - player.getPacketDispatch().sendString("", interfaceId, 3); - for(int i = 0; i < packedMessages.size(); i++) { - //System.out.printf("sendItemMessage[%d]: %s\n", i, packedMessages[i]); - player.getPacketDispatch().sendString(packedMessages.get(i), interfaceId, 4+i); - } - } else { - int interfaceId = 131; - //int interfaceId = 173; - //int interfaceId = 519; - //int interfaceId = 757; - //int interfaceId = 760; - player.getInterfaceManager().openChatbox(interfaceId); - String message = messages[0]; - for (int i = 1; i < messages.length; i++) { - message += "
" + messages[i]; - } - switch(interfaceId) { - case 131: - player.getPacketDispatch().sendString(message, 131, 1); - player.getPacketDispatch().sendItemOnInterface(itemId, 1, 131, 2); - break; - case 173: - player.getPacketDispatch().sendString(message, 173, 4); - player.getPacketDispatch().sendString("", 173, 3); - player.getPacketDispatch().sendItemOnInterface(itemId, 1, 173, 1); - break; - case 519: - player.getPacketDispatch().sendString(message, 519, 1); - player.getPacketDispatch().sendItemOnInterface(itemId, 1, 519, 0); - break; - case 757: - player.getPacketDispatch().sendString(message, 757, 2); - player.getPacketDispatch().sendString("", 757, 1); - player.getPacketDispatch().sendItemOnInterface(itemId, 1, 757, 0); - break; - case 760: - player.getPacketDispatch().sendString(message, 760, 0); - player.getPacketDispatch().sendItemOnInterface(itemId, 1, 760, 1); - break; - } + // Select interface based on number of messages - 241 (1 line) to 244 (4 lines) + int interfaceId = 240 + messages.length; + player.getInterfaceManager().openChatbox(interfaceId); + player.getPacketDispatch().sendInterfaceConfig(interfaceId, 1, false); + player.getPacketDispatch().sendInterfaceConfig(interfaceId, 2, false); + // Hide or empty the title, since double item messages do not have them. (Child 3) + player.getPacketDispatch().sendString("", interfaceId, 3); + // Loop and print messages on Child 4,5,6,7 based on messages count. + for(int i = 0; i < messages.length; i++) { + player.getPacketDispatch().sendString(messages[i], interfaceId, 4+i); } + // Set the first Item (child 1) + ItemDefinition itemDef = ItemDefinition.forId(itemId); + player.getPacketDispatch().sendItemOnInterface(itemId, 1, interfaceId, 2); + player.getPacketDispatch().sendAngleOnInterface(interfaceId, 2, itemDef.getModelZoom() / 2, itemDef.getModelRotationX(), itemDef.getModelRotationY()); + player.getPacketDispatch().sendRepositionOnInterface(interfaceId, 2, 45, 46); + // Hide the second item which seems to be used for double items (child 1) + player.getPacketDispatch().sendInterfaceConfig(interfaceId, 1, true); + // These are old interfaces which made no sense to use them. +// player.getPacketDispatch().sendString(message, 131, 1); +// player.getPacketDispatch().sendItemOnInterface(itemId, 1, 131, 2); +// player.getPacketDispatch().sendString(message, 173, 4); +// player.getPacketDispatch().sendString("", 173, 3); +// player.getPacketDispatch().sendItemOnInterface(itemId, 1, 173, 1); +// player.getPacketDispatch().sendString(message, 519, 1); +// player.getPacketDispatch().sendItemOnInterface(itemId, 1, 519, 0); +// player.getPacketDispatch().sendString(message, 757, 2); +// player.getPacketDispatch().sendString("", 757, 1); +// player.getPacketDispatch().sendItemOnInterface(itemId, 1, 757, 0); +// player.getPacketDispatch().sendString(message, 760, 0); +// player.getPacketDispatch().sendItemOnInterface(itemId, 1, 760, 1); return player.getInterfaceManager().getChatbox(); } @@ -395,23 +371,40 @@ public final class DialogueInterpreter { * Send a message with an item next to it. * @param message The message. */ - public Component sendDoubleItemMessage(int first, int second, String message) { - player.getInterfaceManager().openChatbox(131); - player.getPacketDispatch().sendString(message, 131, 1); - player.getPacketDispatch().sendItemOnInterface(first, 1, 131, 0); - player.getPacketDispatch().sendItemOnInterface(second, 1, 131, 2); - return player.getInterfaceManager().getChatbox(); + public Component sendDoubleItemMessage(int first, int second, String... message) { + return sendDoubleItemMessage(new Item(first), new Item(second), message); } /** - * Send a message with an item next to it. - * @param message The message. + * Send a message with two items next to it. + * Note that interface 241 to 244 contains 2 childs for images, which is used for sendDoubleItemMessage. + * @param first The first item to display. + * @param second The second item to display. + * @param messages The array of messages, one message per line. */ - public Component sendDoubleItemMessage(Item first, Item second, String message) { - player.getInterfaceManager().openChatbox(131); - player.getPacketDispatch().sendString(message, 131, 1); - player.getPacketDispatch().sendItemOnInterface(first.getId(), first.getAmount(), 131, 0); - player.getPacketDispatch().sendItemOnInterface(second.getId(), second.getAmount(), 131, 2); + public Component sendDoubleItemMessage(Item first, Item second, String... messages) { + // Select interface based on number of messages - 241 (1 line) to 244 (4 lines) + int interfaceId = 240 + messages.length; + player.getInterfaceManager().openChatbox(interfaceId); + player.getPacketDispatch().sendInterfaceConfig(interfaceId, 1, false); + player.getPacketDispatch().sendInterfaceConfig(interfaceId, 2, false); + // Hide or empty the title, since double item messages do not have them. + player.getPacketDispatch().sendString("", interfaceId, 3); + // Loop and print messages on child 4,5,6,7 based on messages count. + for(int i = 0; i < messages.length; i++) { + player.getPacketDispatch().sendString(messages[i], interfaceId, 4+i); + } + // Set the first item + ItemDefinition itemDef = ItemDefinition.forId(first.getId()); + player.getPacketDispatch().sendItemOnInterface(first.getId(), first.getAmount(), interfaceId, 1); + player.getPacketDispatch().sendAngleOnInterface(interfaceId, 1, (int)(itemDef.getModelZoom() / 1.5), itemDef.getModelRotationX(), itemDef.getModelRotationY()); + player.getPacketDispatch().sendRepositionOnInterface(interfaceId, 1, 40, 40); + // Set the second items + ItemDefinition itemDef2 = ItemDefinition.forId(second.getId()); + player.getPacketDispatch().sendItemOnInterface(second.getId(), second.getAmount(), interfaceId, 2); + player.getPacketDispatch().sendAngleOnInterface(interfaceId, 2, (int)(itemDef2.getModelZoom() / 1.5), itemDef2.getModelRotationX(), itemDef2.getModelRotationY()); + player.getPacketDispatch().sendRepositionOnInterface(interfaceId, 2, 60, 65); + return player.getInterfaceManager().getChatbox(); } @@ -525,19 +518,24 @@ public final class DialogueInterpreter { if (expression == -1) { expression = FacialExpression.HALF_GUILTY.getAnimationId(); } + player.getInterfaceManager().openChatbox(interfaceId); + player.getPacketDispatch().sendInterfaceConfig(interfaceId, 1, true); + player.getPacketDispatch().sendInterfaceConfig(interfaceId, 2, false); player.getPacketDispatch().sendAnimationInterface(expression, interfaceId, 2); player.getPacketDispatch().sendItemOnInterface(-1, 1, interfaceId, 1); if (npc) { + player.getPacketDispatch().sendItemOnInterface(-1, 1, interfaceId, 1); + player.getPacketDispatch().sendRepositionOnInterface(interfaceId, 2, 45, 45); player.getPacketDispatch().sendNpcOnInterface(npcId, interfaceId, 2); player.getPacketDispatch().sendString(NPCDefinition.forId(npcId).getName(), interfaceId, 3); } else { + player.getPacketDispatch().sendRepositionOnInterface(interfaceId, 2, 426, 45); // 423 is 47 * 9 player.getPacketDispatch().sendPlayerOnInterface(interfaceId, 2); player.getPacketDispatch().sendString(player.getUsername(), interfaceId, 3); } for (int i = 0; i < messages.length; i++) { player.getPacketDispatch().sendString(doSubstitutions(player, messages[i]), interfaceId, (i + 4)); } - player.getInterfaceManager().openChatbox(interfaceId); player.getPacketDispatch().sendInterfaceConfig(player.getInterfaceManager().getChatbox().getId(), 3, false); return player.getInterfaceManager().getChatbox(); } diff --git a/Server/src/main/core/game/node/entity/player/link/PacketDispatch.java b/Server/src/main/core/game/node/entity/player/link/PacketDispatch.java index 53dd6819e..302b19400 100644 --- a/Server/src/main/core/game/node/entity/player/link/PacketDispatch.java +++ b/Server/src/main/core/game/node/entity/player/link/PacketDispatch.java @@ -224,6 +224,10 @@ public final class PacketDispatch { PacketRepository.send(DisplayModel.class, new DisplayModelContext(player, ModelType.MODEL, modelID,zoom,interfaceId,childId,new Object())); } + public void sendRepositionOnInterface(int interfaceId, int childId, int positionX, int positionY){ + PacketRepository.send(RepositionChild.class, new ChildPositionContext(player, interfaceId, childId, positionX, positionY)); + } + public void sendAngleOnInterface(int interfaceId, int childId, int zoom, int pitch, int yaw){ PacketRepository.send(InterfaceSetAngle.class, new DefaultContext(player, pitch, zoom, yaw, interfaceId, childId)); } From 02c18350162f1b73f2d44d6c52e0ce5ac34dcc6d Mon Sep 17 00:00:00 2001 From: Ceikry Date: Sun, 6 Apr 2025 08:29:06 +0000 Subject: [PATCH 020/117] Fixed bots not picking up loot --- Server/src/main/core/game/node/entity/combat/ImpactHandler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Server/src/main/core/game/node/entity/combat/ImpactHandler.java b/Server/src/main/core/game/node/entity/combat/ImpactHandler.java index 4065a0d80..6e49734ec 100644 --- a/Server/src/main/core/game/node/entity/combat/ImpactHandler.java +++ b/Server/src/main/core/game/node/entity/combat/ImpactHandler.java @@ -255,6 +255,7 @@ public final class ImpactHandler { if (entity instanceof Player) { return killer; } + if (killer instanceof AIPlayer) return killer; int damage = -1; if (playerImpactLog.isEmpty()) { From 3a7dfab6280d2366e5cdb8fe62597e349ee4fc92 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Sun, 6 Apr 2025 09:01:29 +0000 Subject: [PATCH 021/117] Crumble undead now more effective against zogres and skogres --- .../global/skill/magic/modern/CrumbleUndead.java | 15 +++++++++++++++ .../quest/zogreflesheaters/SkogreBehavior.kt | 11 ++++++++++- .../quest/zogreflesheaters/SlashBashBehavior.kt | 8 ++++++++ .../quest/zogreflesheaters/ZogreBehavior.kt | 11 ++++++++++- 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/Server/src/main/content/global/skill/magic/modern/CrumbleUndead.java b/Server/src/main/content/global/skill/magic/modern/CrumbleUndead.java index ec23e258f..582795a81 100644 --- a/Server/src/main/content/global/skill/magic/modern/CrumbleUndead.java +++ b/Server/src/main/content/global/skill/magic/modern/CrumbleUndead.java @@ -1,5 +1,7 @@ package content.global.skill.magic.modern; +import content.region.kandarin.feldip.quest.zogreflesheaters.SkogreBehavior; +import content.region.kandarin.feldip.quest.zogreflesheaters.ZogreBehavior; import core.game.node.entity.combat.spell.Runes; import core.game.node.Node; import core.game.node.entity.Entity; @@ -15,8 +17,12 @@ import core.game.world.update.flag.context.Animation; import core.game.world.update.flag.context.Graphics; import core.plugin.Initializable; import core.plugin.Plugin; +import org.rs09.consts.NPCs; import org.rs09.consts.Sounds; +import java.util.Arrays; +import java.util.List; + /** * Handles the crumble undead spell. * @author Emperor @@ -35,6 +41,15 @@ public final class CrumbleUndead extends CombatSpell { @Override public boolean cast(Entity entity, Node target) { NPC npc = target instanceof NPC ? (NPC) target : null; + // Exception for Zogres and Skogres because they are classified as OGRES in npc.getTask() as not undead, + // so you couldn't use crumble undead on it. This bypasses that. + if ( + Arrays.stream(ZogreBehavior.zogreIds).anyMatch(i -> i == npc.getId()) || + Arrays.stream(SkogreBehavior.skogreIds).anyMatch(i -> i == npc.getId()) || + npc.getId() == NPCs.SLASH_BASH_2060 + ) { + return super.cast(entity, target); + } if (npc == null || npc.getTask() == null || !npc.getTask().undead) { ((Player) entity).getPacketDispatch().sendMessage("This spell only affects the undead."); return false; diff --git a/Server/src/main/content/region/kandarin/feldip/quest/zogreflesheaters/SkogreBehavior.kt b/Server/src/main/content/region/kandarin/feldip/quest/zogreflesheaters/SkogreBehavior.kt index 4b309bbc9..54363e66d 100644 --- a/Server/src/main/content/region/kandarin/feldip/quest/zogreflesheaters/SkogreBehavior.kt +++ b/Server/src/main/content/region/kandarin/feldip/quest/zogreflesheaters/SkogreBehavior.kt @@ -4,6 +4,7 @@ import core.api.getOrStartTimer import core.api.inEquipment import core.game.node.entity.Entity import core.game.node.entity.combat.BattleState +import core.game.node.entity.combat.spell.SpellType import core.game.node.entity.npc.NPC import core.game.node.entity.npc.NPCBehavior import core.game.node.entity.player.Player @@ -13,7 +14,8 @@ import org.rs09.consts.NPCs class SkogreBehavior : NPCBehavior(*skogreIds) { companion object { - private val skogreIds = intArrayOf( + @JvmField + val skogreIds = intArrayOf( NPCs.SKOGRE_2050, NPCs.SKOGRE_2056, NPCs.SKOGRE_2057, @@ -25,6 +27,13 @@ class SkogreBehavior : NPCBehavior(*skogreIds) { if (inEquipment(attacker, Items.COMP_OGRE_BOW_4827)) { return } + if (state.spell != null && state.spell.type == SpellType.CRUMBLE_UNDEAD) { + state.estimatedHit = (state.estimatedHit * 0.5).toInt() + if (state.secondaryHit > 0) { + state.secondaryHit = (state.secondaryHit * 0.5).toInt() + } + return + } state.estimatedHit = (state.estimatedHit * 0.25).toInt() if (state.secondaryHit > 0) { state.secondaryHit = (state.secondaryHit * 0.25).toInt() diff --git a/Server/src/main/content/region/kandarin/feldip/quest/zogreflesheaters/SlashBashBehavior.kt b/Server/src/main/content/region/kandarin/feldip/quest/zogreflesheaters/SlashBashBehavior.kt index 169984ce0..95fab62bc 100644 --- a/Server/src/main/content/region/kandarin/feldip/quest/zogreflesheaters/SlashBashBehavior.kt +++ b/Server/src/main/content/region/kandarin/feldip/quest/zogreflesheaters/SlashBashBehavior.kt @@ -8,6 +8,7 @@ import core.game.node.entity.combat.CombatStyle import core.game.node.entity.combat.CombatSwingHandler import core.game.node.entity.combat.MultiSwingHandler import core.game.node.entity.combat.equipment.SwitchAttack +import core.game.node.entity.combat.spell.SpellType import core.game.node.entity.npc.NPC import core.game.node.entity.npc.NPCBehavior import core.game.node.entity.player.Player @@ -42,6 +43,13 @@ class SlashBashBehavior : NPCBehavior(NPCs.SLASH_BASH_2060) { if (inEquipment(attacker, Items.COMP_OGRE_BOW_4827)) { return } + if (state.spell != null && state.spell.type == SpellType.CRUMBLE_UNDEAD) { + state.estimatedHit = (state.estimatedHit * 0.5).toInt() + if (state.secondaryHit > 0) { + state.secondaryHit = (state.secondaryHit * 0.5).toInt() + } + return + } state.estimatedHit = (state.estimatedHit * 0.25).toInt() if (state.secondaryHit > 0) { state.secondaryHit = (state.secondaryHit * 0.25).toInt() diff --git a/Server/src/main/content/region/kandarin/feldip/quest/zogreflesheaters/ZogreBehavior.kt b/Server/src/main/content/region/kandarin/feldip/quest/zogreflesheaters/ZogreBehavior.kt index 1e00271d5..871266c92 100644 --- a/Server/src/main/content/region/kandarin/feldip/quest/zogreflesheaters/ZogreBehavior.kt +++ b/Server/src/main/content/region/kandarin/feldip/quest/zogreflesheaters/ZogreBehavior.kt @@ -4,6 +4,7 @@ import core.api.getOrStartTimer import core.api.inEquipment import core.game.node.entity.Entity import core.game.node.entity.combat.BattleState +import core.game.node.entity.combat.spell.SpellType import core.game.node.entity.npc.NPC import core.game.node.entity.npc.NPCBehavior import core.game.node.entity.player.Player @@ -13,7 +14,8 @@ import org.rs09.consts.NPCs class ZogreBehavior : NPCBehavior(*zogreIds) { companion object { - private val zogreIds = intArrayOf( + @JvmField + val zogreIds = intArrayOf( NPCs.ZOGRE_2044, NPCs.ZOGRE_2045, NPCs.ZOGRE_2046, @@ -33,6 +35,13 @@ class ZogreBehavior : NPCBehavior(*zogreIds) { if (inEquipment(attacker, Items.COMP_OGRE_BOW_4827)) { return } + if (state.spell != null && state.spell.type == SpellType.CRUMBLE_UNDEAD) { + state.estimatedHit = (state.estimatedHit * 0.5).toInt() + if (state.secondaryHit > 0) { + state.secondaryHit = (state.secondaryHit * 0.5).toInt() + } + return + } state.estimatedHit = (state.estimatedHit * 0.25).toInt() if (state.secondaryHit > 0) { state.secondaryHit = (state.secondaryHit * 0.25).toInt() From 53c90ad6d613ad04f7151e702f28fcf31531e294 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Sun, 6 Apr 2025 09:17:16 +0000 Subject: [PATCH 022/117] Fixed maze reward chest amount Fixed ToG quest point requirements --- .../src/main/content/global/ame/events/maze/MazeInterface.kt | 2 +- .../misthalin/lumbridge/quest/tearsofguthix/TearsOfGuthix.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Server/src/main/content/global/ame/events/maze/MazeInterface.kt b/Server/src/main/content/global/ame/events/maze/MazeInterface.kt index f408b1507..d42743118 100644 --- a/Server/src/main/content/global/ame/events/maze/MazeInterface.kt +++ b/Server/src/main/content/global/ame/events/maze/MazeInterface.kt @@ -160,7 +160,7 @@ class MazeInterface : InteractionListener, EventHook, MapArea { animate(player, 536) // val actualScenery = RegionManager.getObject(node.location.z, node.location.x, node.location.y, 3626) val tableRoll = CHEST_REWARDS.roll() - addItemOrBank(player, tableRoll[0].id) + addItemOrBank(player, tableRoll[0].id, tableRoll[0].amount) when (tableRoll[0].id){ Items.AIR_RUNE_556 -> sendItemDialogue(player, Items.AIR_RUNE_556, "You've found some air runes!") Items.WATER_RUNE_555 -> sendItemDialogue(player, Items.WATER_RUNE_555, "You've found some water runes!") diff --git a/Server/src/main/content/region/misthalin/lumbridge/quest/tearsofguthix/TearsOfGuthix.kt b/Server/src/main/content/region/misthalin/lumbridge/quest/tearsofguthix/TearsOfGuthix.kt index 52a859bb3..072c108a8 100644 --- a/Server/src/main/content/region/misthalin/lumbridge/quest/tearsofguthix/TearsOfGuthix.kt +++ b/Server/src/main/content/region/misthalin/lumbridge/quest/tearsofguthix/TearsOfGuthix.kt @@ -51,6 +51,7 @@ class TearsOfGuthix : Quest(Quests.TEARS_OF_GUTHIX, 120, 119, 1, 449, 451, 0, 1, fun hasRequirements(player: Player): Boolean { return arrayOf( + getQuestPoints(player) >= 43, hasLevelStat(player, Skills.FIREMAKING, 49), hasLevelStat(player, Skills.CRAFTING, 20), hasLevelStat(player, Skills.MINING, 20), @@ -71,7 +72,7 @@ class TearsOfGuthix : Quest(Quests.TEARS_OF_GUTHIX, 120, 119, 1, 449, 451, 0, 1, line(player, "Level 49 firemaking", line++, hasLevelStat(player, Skills.FIREMAKING, 49)) line(player, "!!Level 20 crafting??", line++, hasLevelStat(player, Skills.CRAFTING, 20)) line(player, "!!Level 20 mining??", line++, hasLevelStat(player, Skills.MINING, 20)) - line(player, "!!43 quest points??", line++, getQuestPoints(player) >= 55) + line(player, "!!43 quest points??", line++, getQuestPoints(player) >= 43) line(player, "!!Level 49 crafting would be an advantage??", line++, hasLevelStat(player, Skills.CRAFTING, 49)) line(player, "!!Level 49 smithing would be an advantage??", line++, hasLevelStat(player, Skills.SMITHING, 49)) } else if (stage < 100) { From 1bdf1ae2048d3fd45e5ebef263dc2be17c3795ad Mon Sep 17 00:00:00 2001 From: Tom Vanlaer Date: Sat, 12 Apr 2025 00:21:11 +0000 Subject: [PATCH 023/117] Corrected spider HP to 2 points --- Server/data/configs/npc_configs.json | 14704 ++++++++++++------------- 1 file changed, 7334 insertions(+), 7370 deletions(-) diff --git a/Server/data/configs/npc_configs.json b/Server/data/configs/npc_configs.json index 71d194eab..a3a5c43fe 100644 --- a/Server/data/configs/npc_configs.json +++ b/Server/data/configs/npc_configs.json @@ -1167,7 +1167,7 @@ "name": "Spider", "defence_level": "1", "safespot": null, - "lifepoints": "1", + "lifepoints": "2", "strength_level": "1", "id": "61", "aggressive": "true", @@ -12905,12 +12905,12 @@ "attack_level": "52" }, { - "examine": "It looks very hungry!", - "melee_animation": "1264", "slayer_exp": "40", + "examine": "It looks very hungry!", "name": "Vampire", "defence_level": "1", "safespot": null, + "melee_animation": "1264", "lifepoints": "50", "strength_level": "1", "id": "1223", @@ -52176,7 +52176,7 @@ "lifepoints": "60", "strength_level": "10", "id": "6027", - "bonuses": "0,0,0,0,0,-21,-21,-21,-21,-21,0,0,0,0,0", + "bonuses": "0,0,0,0,0,-21,-21,-21,-21,-21,0,0,0,0,0", "range_level": "1", "attack_level": "10" }, @@ -52195,7 +52195,7 @@ "strength_level": "10", "id": "6028", "bonuses": "0,0,0,0,0,-21,-21,-21,-21,-21,0,0,0,0,0", - "range_level": "1", + "range_level": "1", "attack_level": "10" }, { @@ -52213,7 +52213,7 @@ "strength_level": "10", "id": "6029", "bonuses": "0,0,0,0,0,-21,-21,-21,-21,-21,0,0,0,0,0", - "range_level": "1", + "range_level": "1", "attack_level": "10" }, { @@ -52231,7 +52231,7 @@ "strength_level": "10", "id": "6030", "bonuses": "0,0,0,0,0,-21,-21,-21,-21,-21,0,0,0,0,0", - "range_level": "1", + "range_level": "1", "attack_level": "10" }, { @@ -52249,7 +52249,7 @@ "strength_level": "10", "id": "6031", "bonuses": "0,0,0,0,0,-21,-21,-21,-21,-21,0,0,0,0,0", - "range_level": "1", + "range_level": "1", "attack_level": "10" }, { @@ -52267,7 +52267,7 @@ "strength_level": "10", "id": "6032", "bonuses": "0,0,0,0,0,-21,-21,-21,-21,-21,0,0,0,0,0", - "range_level": "1", + "range_level": "1", "attack_level": "10" }, { @@ -52285,7 +52285,7 @@ "strength_level": "10", "id": "6033", "bonuses": "0,0,0,0,0,-21,-21,-21,-21,-21,0,0,0,0,0", - "range_level": "1", + "range_level": "1", "attack_level": "10" }, { @@ -52303,7 +52303,7 @@ "strength_level": "10", "id": "6034", "bonuses": "0,0,0,0,0,-21,-21,-21,-21,-21,0,0,0,0,0", - "range_level": "1", + "range_level": "1", "attack_level": "10" }, { @@ -52321,7 +52321,7 @@ "strength_level": "10", "id": "6035", "bonuses": "0,0,0,0,0,-21,-21,-21,-21,-21,0,0,0,0,0", - "range_level": "1", + "range_level": "1", "attack_level": "10" }, { @@ -52339,7 +52339,7 @@ "strength_level": "10", "id": "6036", "bonuses": "0,0,0,0,0,-21,-21,-21,-21,-21,0,0,0,0,0", - "range_level": "1", + "range_level": "1", "attack_level": "10" }, { @@ -52357,7 +52357,7 @@ "strength_level": "10", "id": "6037", "bonuses": "0,0,0,0,0,-21,-21,-21,-21,-21,0,0,0,0,0", - "range_level": "1", + "range_level": "1", "attack_level": "10" }, { @@ -52375,7 +52375,7 @@ "strength_level": "10", "id": "6038", "bonuses": "0,0,0,0,0,-21,-21,-21,-21,-21,0,0,0,0,0", - "range_level": "1", + "range_level": "1", "attack_level": "10" }, { @@ -52393,7 +52393,7 @@ "strength_level": "10", "id": "6039", "bonuses": "0,0,0,0,0,-21,-21,-21,-21,-21,0,0,0,0,0", - "range_level": "1", + "range_level": "1", "attack_level": "10" }, { @@ -52411,7 +52411,7 @@ "strength_level": "10", "id": "6040", "bonuses": "0,0,0,0,0,-21,-21,-21,-21,-21,0,0,0,0,0", - "range_level": "1", + "range_level": "1", "attack_level": "10" }, { @@ -52429,7 +52429,7 @@ "strength_level": "10", "id": "6041", "bonuses": "0,0,0,0,0,-21,-21,-21,-21,-21,0,0,0,0,0", - "range_level": "1", + "range_level": "1", "attack_level": "10" }, { @@ -52447,7 +52447,7 @@ "strength_level": "10", "id": "6042", "bonuses": "0,0,0,0,0,-21,-21,-21,-21,-21,0,0,0,0,0", - "range_level": "1", + "range_level": "1", "attack_level": "10" }, { @@ -52465,7 +52465,7 @@ "strength_level": "10", "id": "6043", "bonuses": "0,0,0,0,0,-21,-21,-21,-21,-21,0,0,0,0,0", - "range_level": "1", + "range_level": "1", "attack_level": "10" }, { @@ -52483,7 +52483,7 @@ "strength_level": "10", "id": "6044", "bonuses": "0,0,0,0,0,-21,-21,-21,-21,-21,0,0,0,0,0", - "range_level": "1", + "range_level": "1", "attack_level": "10" }, { @@ -52501,7 +52501,7 @@ "strength_level": "10", "id": "6045", "bonuses": "0,0,0,0,0,-21,-21,-21,-21,-21,0,0,0,0,0", - "range_level": "1", + "range_level": "1", "attack_level": "10" }, { @@ -72960,9 +72960,9 @@ "id": "796" }, { + "examine": "The hat is a dead give away.", "name": "Wizard Cromperty", - "id": "2328", - "examine": "The hat is a dead give away." + "id": "2328" }, { "examine": "An intelligent-looking shop owner.", @@ -73035,14639 +73035,14623 @@ "id": "7047" }, { - "id": "17", - "name": "Schoolgirl" + "name": "Schoolgirl", + "id": "17" }, { - "id": "70", - "name": "Orc" + "name": "Orc", + "id": "70" }, { - "id": "94", - "name": "Skeleton Mage" + "name": "Skeleton Mage", + "id": "94" }, { - "id": "129", - "name": "Skavid" + "name": "Skavid", + "id": "129" }, { - "id": "135", - "name": "Mammoth" + "name": "Mammoth", + "id": "135" }, { - "id": "136", - "name": "Mounted terrorchick gnome" + "name": "Mounted terrorchick gnome", + "id": "136" }, { - "id": "149", - "name": "Gull" + "name": "Gull", + "id": "149" }, { - "id": "150", - "name": "Gull" + "name": "Gull", + "id": "150" }, { - "id": "151", - "name": "Fly trap" + "name": "Fly trap", + "id": "151" }, { - "id": "152", - "name": "Butterfly" + "name": "Butterfly", + "id": "152" }, { - "id": "155", - "name": "Butterfly" + "name": "Butterfly", + "id": "155" }, { - "id": "156", - "name": "Butterfly" + "name": "Butterfly", + "id": "156" }, { - "id": "157", - "name": "Butterfly" + "name": "Butterfly", + "id": "157" }, { - "id": "165", - "name": "Gnome shop keeper" + "name": "Gnome shop keeper", + "id": "165" }, { - "id": "167", - "name": "Gnome baller" + "name": "Gnome baller", + "id": "167" }, { - "id": "171", - "name": "Brimstail" + "name": "Brimstail", + "id": "171" }, { - "id": "177", - "name": "Witch" + "name": "Witch", + "id": "177" }, { - "id": "195", - "name": "Bandit" + "name": "Bandit", + "id": "195" }, { - "id": "197", - "name": "Barbarian guard" + "name": "Barbarian guard", + "id": "197" }, { - "id": "200", - "name": "Lord Daquarius" + "name": "Lord Daquarius", + "id": "200" }, { - "id": "207", - "name": "Lollk" + "name": "Lollk", + "id": "207" }, { - "id": "209", - "name": "Nulodion" + "name": "Nulodion", + "id": "209" }, { - "id": "211", - "name": "Sir Percival" + "name": "Sir Percival", + "id": "211" }, { - "id": "213", - "name": "Merlin" + "name": "Merlin", + "id": "213" }, { - "id": "215", - "name": "Peasant" + "name": "Peasant", + "id": "215" }, { - "id": "217", - "name": "Crone" + "name": "Crone", + "id": "217" }, { - "id": "218", - "name": "Galahad" + "name": "Galahad", + "id": "218" }, { - "id": "225", - "name": "Bonzo" + "name": "Bonzo", + "id": "225" }, { - "id": "226", - "name": "Morris" + "name": "Morris", + "id": "226" }, { - "id": "228", - "name": "Big Dave" + "name": "Big Dave", + "id": "228" }, { - "id": "229", - "name": "Joshua" + "name": "Joshua", + "id": "229" }, { - "id": "232", - "name": "Austri" + "name": "Austri", + "id": "232" }, { - "id": "234", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "234" }, { - "id": "235", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "235" }, { - "id": "236", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "236" }, { - "id": "239", - "name": "Sir Lancelot" + "name": "Sir Lancelot", + "id": "239" }, { - "id": "240", - "name": "Sir Gawain" + "name": "Sir Gawain", + "id": "240" }, { - "id": "241", - "name": "Sir Kay" + "name": "Sir Kay", + "id": "241" }, { - "id": "242", - "name": "Sir Bedivere" + "name": "Sir Bedivere", + "id": "242" }, { - "id": "243", - "name": "Sir Tristram" + "name": "Sir Tristram", + "id": "243" }, { - "id": "244", - "name": "Sir Pelleas" + "name": "Sir Pelleas", + "id": "244" }, { - "id": "245", - "name": "Sir Lucan" + "name": "Sir Lucan", + "id": "245" }, { - "id": "248", - "name": "Morgan Le Faye" + "name": "Morgan Le Faye", + "id": "248" }, { - "id": "249", - "name": "Merlin" + "name": "Merlin", + "id": "249" }, { - "id": "250", - "name": "The Lady of the Lake" + "name": "The Lady of the Lake", + "id": "250" }, { - "id": "252", - "name": "Beggar" + "name": "Beggar", + "id": "252" }, { - "id": "260", - "name": "Kelvin" + "name": "Kelvin", + "id": "260" }, { - "id": "261", - "name": "Joe" + "name": "Joe", + "id": "261" }, { - "id": "263", - "name": "Hengrad" + "name": "Hengrad", + "id": "263" }, { - "id": "276", - "name": "Winelda" + "name": "Winelda", + "id": "276" }, { - "id": "287", - "name": "Ernest" + "name": "Ernest", + "id": "287" }, { - "id": "292", - "name": "Guard" + "name": "Guard", + "id": "292" }, { - "id": "301", - "name": "Twig" + "name": "Twig", + "id": "301" }, { - "id": "302", - "name": "Hadley" + "name": "Hadley", + "id": "302" }, { - "id": "303", - "name": "Gerald" + "name": "Gerald", + "id": "303" }, { - "id": "304", - "name": "Almera" + "name": "Almera", + "id": "304" }, { - "id": "305", - "name": "Hudon" + "name": "Hudon", + "id": "305" }, { - "id": "306", - "name": "Golrie" + "name": "Golrie", + "id": "306" }, { - "id": "309", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "309" }, { - "id": "310", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "310" }, { - "id": "311", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "311" }, { - "id": "312", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "312" }, { - "id": "313", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "313" }, { - "id": "314", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "314" }, { - "id": "315", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "315" }, { - "id": "316", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "316" }, { - "id": "317", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "317" }, { - "id": "318", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "318" }, { - "id": "319", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "319" }, { - "id": "320", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "320" }, { - "id": "321", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "321" }, { - "id": "322", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "322" }, { - "id": "324", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "324" }, { - "id": "325", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "325" }, { - "id": "326", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "326" }, { - "id": "327", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "327" }, { - "id": "328", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "328" }, { - "id": "329", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "329" }, { - "id": "330", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "330" }, { - "id": "331", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "331" }, { - "id": "332", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "332" }, { - "id": "333", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "333" }, { - "id": "334", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "334" }, { - "id": "337", - "name": "Da Vinci" + "name": "Da Vinci", + "id": "337" }, { - "id": "343", - "name": "Guidor" + "name": "Guidor", + "id": "343" }, { - "id": "349", - "name": "Kilron" + "name": "Kilron", + "id": "349" }, { - "id": "350", - "name": "Omart" + "name": "Omart", + "id": "350" }, { - "id": "366", - "name": "Jerico" + "name": "Jerico", + "id": "366" }, { - "id": "379", - "name": "Luthas" + "name": "Luthas", + "id": "379" }, { - "id": "381", - "name": "Dwarf" + "name": "Dwarf", + "id": "381" }, { - "id": "383", - "name": "Stankers" + "name": "Stankers", + "id": "383" }, { - "id": "399", - "name": "Legends guard" + "name": "Legends guard", + "id": "399" }, { - "id": "400", - "name": "Radimus Erkle" + "name": "Radimus Erkle", + "id": "400" }, { - "id": "403", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "403" }, { - "id": "404", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "404" }, { - "id": "405", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "405" }, { - "id": "406", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "406" }, { - "id": "409", - "name": "Genie" + "name": "Genie", + "id": "409" }, { - "id": "410", - "name": "Mysterious Old Man" + "name": "Mysterious Old Man", + "id": "410" }, { - "id": "431", - "name": "Cyrisus" + "name": "Cyrisus", + "id": "431" }, { - "id": "433", - "name": "Cyrisus" + "name": "Cyrisus", + "id": "433" }, { - "id": "434", - "name": "Cyrisus" + "name": "Cyrisus", + "id": "434" }, { - "id": "436", - "name": "Fallen Man" + "name": "Fallen Man", + "id": "436" }, { - "id": "463", - "name": "Murphy" + "name": "Murphy", + "id": "463" }, { - "id": "464", - "name": "Murphy" + "name": "Murphy", + "id": "464" }, { - "id": "465", - "name": "Murphy" + "name": "Murphy", + "id": "465" }, { - "id": "466", - "name": "Murphy" + "name": "Murphy", + "id": "466" }, { - "id": "467", - "name": "Shark" + "name": "Shark", + "id": "467" }, { - "id": "468", - "name": "Shark" + "name": "Shark", + "id": "468" }, { - "id": "471", - "name": "Bolkoy" + "name": "Bolkoy", + "id": "471" }, { - "id": "472", - "name": "Remsai" + "name": "Remsai", + "id": "472" }, { - "id": "473", - "name": "Khazard trooper" + "name": "Khazard trooper", + "id": "473" }, { - "id": "480", - "name": "Gnome troop" + "name": "Gnome troop", + "id": "480" }, { - "id": "484", - "name": "Local Gnome" + "name": "Local Gnome", + "id": "484" }, { - "id": "486", - "name": "Kalron" + "name": "Kalron", + "id": "486" }, { - "id": "488", - "name": "Observatory professor" + "name": "Observatory professor", + "id": "488" }, { - "id": "510", - "name": "Hajedy" + "name": "Hajedy", + "id": "510" }, { - "id": "511", - "name": "Vigroy" + "name": "Vigroy", + "id": "511" }, { - "id": "513", - "name": "Yohnus" + "name": "Yohnus", + "id": "513" }, { - "id": "514", - "name": "Seravel" + "name": "Seravel", + "id": "514" }, { - "id": "536", - "name": "Valaine" + "name": "Valaine", + "id": "536" }, { - "id": "537", - "name": "Scavvo" + "name": "Scavvo", + "id": "537" }, { - "id": "553", - "name": "Aubury" + "name": "Aubury", + "id": "553" }, { - "id": "560", - "name": "Jiminua" + "name": "Jiminua", + "id": "560" }, { - "id": "563", - "name": "Arhein" + "name": "Arhein", + "id": "563" }, { - "id": "565", - "name": "Lunderwin" + "name": "Lunderwin", + "id": "565" }, { - "id": "568", - "name": "Zambo" + "name": "Zambo", + "id": "568" }, { - "id": "575", - "name": "Hickton" + "name": "Hickton", + "id": "575" }, { - "id": "576", - "name": "Harry" + "name": "Harry", + "id": "576" }, { - "id": "578", - "name": "Frincos" + "name": "Frincos", + "id": "578" }, { - "id": "584", - "name": "Herquin" + "name": "Herquin", + "id": "584" }, { - "id": "585", - "name": "Rommik" + "name": "Rommik", + "id": "585" }, { - "id": "588", - "name": "Davon" + "name": "Davon", + "id": "588" }, { - "id": "589", - "name": "Zenesha" + "name": "Zenesha", + "id": "589" }, { - "id": "590", - "name": "Aemad" + "name": "Aemad", + "id": "590" }, { - "id": "591", - "name": "Kortan" + "name": "Kortan", + "id": "591" }, { - "id": "592", - "name": "Roachey" + "name": "Roachey", + "id": "592" }, { - "id": "593", - "name": "Frenita" + "name": "Frenita", + "id": "593" }, { - "id": "594", - "name": "Nurmof" + "name": "Nurmof", + "id": "594" }, { - "id": "597", - "name": "Noterazzo" + "name": "Noterazzo", + "id": "597" }, { - "id": "600", - "name": "Hudo" + "name": "Hudo", + "id": "600" }, { - "id": "601", - "name": "Rometti" + "name": "Rometti", + "id": "601" }, { - "id": "602", - "name": "Gulluck" + "name": "Gulluck", + "id": "602" }, { - "id": "603", - "name": "Heckel Funch" + "name": "Heckel Funch", + "id": "603" }, { - "id": "607", - "name": "Gunnjorn" + "name": "Gunnjorn", + "id": "607" }, { - "id": "608", - "name": "Sir Amik Varze" + "name": "Sir Amik Varze", + "id": "608" }, { - "id": "612", - "name": "Greldo" + "name": "Greldo", + "id": "612" }, { - "id": "622", - "name": "Gnome baller" + "name": "Gnome baller", + "id": "622" }, { - "id": "623", - "name": "Gnome baller" + "name": "Gnome baller", + "id": "623" }, { - "id": "624", - "name": "Gnome baller" + "name": "Gnome baller", + "id": "624" }, { - "id": "625", - "name": "Gnome baller" + "name": "Gnome baller", + "id": "625" }, { - "id": "626", - "name": "Gnome baller" + "name": "Gnome baller", + "id": "626" }, { - "id": "627", - "name": "Gnome baller" + "name": "Gnome baller", + "id": "627" }, { - "id": "628", - "name": "Gnome baller" + "name": "Gnome baller", + "id": "628" }, { - "id": "629", - "name": "Gnome baller" + "name": "Gnome baller", + "id": "629" }, { - "id": "630", - "name": "Gnome baller" + "name": "Gnome baller", + "id": "630" }, { - "id": "631", - "name": "Gnome baller" + "name": "Gnome baller", + "id": "631" }, { - "id": "632", - "name": "Gnome baller" + "name": "Gnome baller", + "id": "632" }, { - "id": "634", - "name": "Gnome winger" + "name": "Gnome winger", + "id": "634" }, { - "id": "636", - "name": "Cheerleader" + "name": "Cheerleader", + "id": "636" }, { - "id": "642", - "name": "Katrine" + "name": "Katrine", + "id": "642" }, { - "id": "644", - "name": "Straven" + "name": "Straven", + "id": "644" }, { - "id": "647", - "name": "King Roald" + "name": "King Roald", + "id": "647" }, { - "id": "653", - "name": "Fairy Queen" + "name": "Fairy Queen", + "id": "653" }, { - "id": "654", - "name": "Shamus" + "name": "Shamus", + "id": "654" }, { - "id": "661", - "name": "Megan" + "name": "Megan", + "id": "661" }, { - "id": "662", - "name": "Lucy" + "name": "Lucy", + "id": "662" }, { - "id": "664", - "name": "Boot" + "name": "Boot", + "id": "664" }, { - "id": "666", - "name": "Caleb" + "name": "Caleb", + "id": "666" }, { - "id": "668", - "name": "Johnathon" + "name": "Johnathon", + "id": "668" }, { - "id": "669", - "name": "Hazelmere" + "name": "Hazelmere", + "id": "669" }, { - "id": "671", - "name": "Glough" + "name": "Glough", + "id": "671" }, { - "id": "672", - "name": "Anita" + "name": "Anita", + "id": "672" }, { - "id": "673", - "name": "Charlie" + "name": "Charlie", + "id": "673" }, { - "id": "675", - "name": "Shipyard worker" + "name": "Shipyard worker", + "id": "675" }, { - "id": "676", - "name": "Femi" + "name": "Femi", + "id": "676" }, { - "id": "685", - "name": "Tower Advisor" + "name": "Tower Advisor", + "id": "685" }, { - "id": "686", - "name": "Tower Advisor" + "name": "Tower Advisor", + "id": "686" }, { - "id": "687", - "name": "Tower Advisor" + "name": "Tower Advisor", + "id": "687" }, { - "id": "693", - "name": "Competition Judge" + "name": "Competition Judge", + "id": "693" }, { - "id": "695", - "name": "Bailey" + "name": "Bailey", + "id": "695" }, { - "id": "697", - "name": "Holgart" + "name": "Holgart", + "id": "697" }, { - "id": "699", - "name": "Holgart" + "name": "Holgart", + "id": "699" }, { - "id": "700", - "name": "Holgart" + "name": "Holgart", + "id": "700" }, { - "id": "701", - "name": "Kent" + "name": "Kent", + "id": "701" }, { - "id": "702", - "name": "Fisherman" + "name": "Fisherman", + "id": "702" }, { - "id": "703", - "name": "Fisherman" + "name": "Fisherman", + "id": "703" }, { - "id": "704", - "name": "Fisherman" + "name": "Fisherman", + "id": "704" }, { - "id": "710", - "name": "Alrena" + "name": "Alrena", + "id": "710" }, { - "id": "711", - "name": "Bravek" + "name": "Bravek", + "id": "711" }, { - "id": "712", - "name": "Carla" + "name": "Carla", + "id": "712" }, { - "id": "721", - "name": "Ted Rehnison" + "name": "Ted Rehnison", + "id": "721" }, { - "id": "722", - "name": "Martha Rehnison" + "name": "Martha Rehnison", + "id": "722" }, { - "id": "723", - "name": "Billy Rehnison" + "name": "Billy Rehnison", + "id": "723" }, { - "id": "724", - "name": "Milli Rehnison" + "name": "Milli Rehnison", + "id": "724" }, { - "id": "725", - "name": "Jethick" + "name": "Jethick", + "id": "725" }, { - "id": "740", - "name": "Trufitus" + "name": "Trufitus", + "id": "740" }, { - "id": "746", - "name": "Oracle" + "name": "Oracle", + "id": "746" }, { - "id": "748", - "name": "Angry barbarian spirit" + "name": "Angry barbarian spirit", + "id": "748" }, { - "id": "754", - "name": "Peaceful barbarian spirit" + "name": "Peaceful barbarian spirit", + "id": "754" }, { - "id": "759", - "name": "Kittens" + "name": "Kittens", + "id": "759" }, { - "id": "761", - "name": "Kitten" + "name": "Kitten", + "id": "761" }, { - "id": "762", - "name": "Kitten" + "name": "Kitten", + "id": "762" }, { - "id": "763", - "name": "Kitten" + "name": "Kitten", + "id": "763" }, { - "id": "764", - "name": "Kitten" + "name": "Kitten", + "id": "764" }, { - "id": "765", - "name": "Kitten" + "name": "Kitten", + "id": "765" }, { - "id": "766", - "name": "Kitten" + "name": "Kitten", + "id": "766" }, { - "id": "768", - "name": "Cat" + "name": "Cat", + "id": "768" }, { - "id": "769", - "name": "Cat" + "name": "Cat", + "id": "769" }, { - "id": "770", - "name": "Cat" + "name": "Cat", + "id": "770" }, { - "id": "771", - "name": "Cat" + "name": "Cat", + "id": "771" }, { - "id": "772", - "name": "Cat" + "name": "Cat", + "id": "772" }, { - "id": "773", - "name": "Cat" + "name": "Cat", + "id": "773" }, { - "id": "774", - "name": "Overgrown cat" + "name": "Overgrown cat", + "id": "774" }, { - "id": "775", - "name": "Overgrown cat" + "name": "Overgrown cat", + "id": "775" }, { - "id": "776", - "name": "Overgrown cat" + "name": "Overgrown cat", + "id": "776" }, { - "id": "777", - "name": "Overgrown cat" + "name": "Overgrown cat", + "id": "777" }, { - "id": "779", - "name": "Overgrown cat" + "name": "Overgrown cat", + "id": "779" }, { - "id": "780", - "name": "Gertrude" + "name": "Gertrude", + "id": "780" }, { - "id": "782", - "name": "Philop" + "name": "Philop", + "id": "782" }, { - "id": "784", - "name": "Kanel" + "name": "Kanel", + "id": "784" }, { - "id": "788", - "name": "Garv" + "name": "Garv", + "id": "788" }, { - "id": "789", - "name": "Grubor" + "name": "Grubor", + "id": "789" }, { - "id": "791", - "name": "Seth" + "name": "Seth", + "id": "791" }, { - "id": "792", - "name": "Grip" + "name": "Grip", + "id": "792" }, { - "id": "797", - "name": "Helemos" + "name": "Helemos", + "id": "797" }, { - "id": "807", - "name": "Pierre" + "name": "Pierre", + "id": "807" }, { - "id": "808", - "name": "Hobbes" + "name": "Hobbes", + "id": "808" }, { - "id": "809", - "name": "Louisa" + "name": "Louisa", + "id": "809" }, { - "id": "810", - "name": "Mary" + "name": "Mary", + "id": "810" }, { - "id": "811", - "name": "Stanford" + "name": "Stanford", + "id": "811" }, { - "id": "814", - "name": "Anna" + "name": "Anna", + "id": "814" }, { - "id": "815", - "name": "Bob" + "name": "Bob", + "id": "815" }, { - "id": "816", - "name": "Carol" + "name": "Carol", + "id": "816" }, { - "id": "817", - "name": "David" + "name": "David", + "id": "817" }, { - "id": "818", - "name": "Elizabeth" + "name": "Elizabeth", + "id": "818" }, { - "id": "819", - "name": "Frank" + "name": "Frank", + "id": "819" }, { - "id": "822", - "name": "Ana" + "name": "Ana", + "id": "822" }, { - "id": "823", - "name": "Ana" + "name": "Ana", + "id": "823" }, { - "id": "824", - "name": "Female slave" + "name": "Female slave", + "id": "824" }, { - "id": "826", - "name": "Escaping slave" + "name": "Escaping slave", + "id": "826" }, { - "id": "828", - "name": "Shanty Claws" + "name": "Shanty Claws", + "id": "828" }, { - "id": "829", - "name": "Mercenary Captain" + "name": "Mercenary Captain", + "id": "829" }, { - "id": "832", - "name": "Al Shabim" + "name": "Al Shabim", + "id": "832" }, { - "id": "844", - "name": "Horacio" + "name": "Horacio", + "id": "844" }, { - "id": "846", - "name": "Kangai Mau" + "name": "Kangai Mau", + "id": "846" }, { - "id": "848", - "name": "Blurberry" + "name": "Blurberry", + "id": "848" }, { - "id": "849", - "name": "Barman" + "name": "Barman", + "id": "849" }, { - "id": "853", - "name": "Og" + "name": "Og", + "id": "853" }, { - "id": "854", - "name": "Grew" + "name": "Grew", + "id": "854" }, { - "id": "855", - "name": "Toban" + "name": "Toban", + "id": "855" }, { - "id": "857", - "name": "Ogre guard" + "name": "Ogre guard", + "id": "857" }, { - "id": "860", - "name": "Ogre guard" + "name": "Ogre guard", + "id": "860" }, { - "id": "861", - "name": "Ogre guard" + "name": "Ogre guard", + "id": "861" }, { - "id": "865", - "name": "Skavid" + "name": "Skavid", + "id": "865" }, { - "id": "866", - "name": "Skavid" + "name": "Skavid", + "id": "866" }, { - "id": "867", - "name": "Skavid" + "name": "Skavid", + "id": "867" }, { - "id": "868", - "name": "Skavid" + "name": "Skavid", + "id": "868" }, { - "id": "869", - "name": "Skavid" + "name": "Skavid", + "id": "869" }, { - "id": "871", - "name": "Watchtower Wizard" + "name": "Watchtower Wizard", + "id": "871" }, { - "id": "876", - "name": "Ogre trader" + "name": "Ogre trader", + "id": "876" }, { - "id": "880", - "name": "Weakened Delrith" + "name": "Weakened Delrith", + "id": "880" }, { - "id": "886", - "name": "Claus the chef" + "name": "Claus the chef", + "id": "886" }, { - "id": "888", - "name": "Philipe Carnillean" + "name": "Philipe Carnillean", + "id": "888" }, { - "id": "889", - "name": "Henryeta Carnillean" + "name": "Henryeta Carnillean", + "id": "889" }, { - "id": "890", - "name": "Butler Jones" + "name": "Butler Jones", + "id": "890" }, { - "id": "891", - "name": "Alomone" + "name": "Alomone", + "id": "891" }, { - "id": "892", - "name": "Hazeel" + "name": "Hazeel", + "id": "892" }, { - "id": "896", - "name": "Nora T. Hagg" + "name": "Nora T. Hagg", + "id": "896" }, { - "id": "901", - "name": "Mouse" + "name": "Mouse", + "id": "901" }, { - "id": "902", - "name": "Gundai" + "name": "Gundai", + "id": "902" }, { - "id": "903", - "name": "Lundail" + "name": "Lundail", + "id": "903" }, { - "id": "906", - "name": "Kolodion" + "name": "Kolodion", + "id": "906" }, { - "id": "918", - "name": "Ned" + "name": "Ned", + "id": "918" }, { - "id": "921", - "name": "Prince Ali" + "name": "Prince Ali", + "id": "921" }, { - "id": "926", - "name": "Border Guard" + "name": "Border Guard", + "id": "926" }, { - "id": "927", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "927" }, { - "id": "928", - "name": "Gujuo" + "name": "Gujuo", + "id": "928" }, { - "id": "929", - "name": "Ungadulu" + "name": "Ungadulu", + "id": "929" }, { - "id": "930", - "name": "Ungadulu" + "name": "Ungadulu", + "id": "930" }, { - "id": "933", - "name": "Siegfried Erkle" + "name": "Siegfried Erkle", + "id": "933" }, { - "id": "935", - "name": "Viyeldi" + "name": "Viyeldi", + "id": "935" }, { - "id": "936", - "name": "San Tojalon" + "name": "San Tojalon", + "id": "936" }, { - "id": "937", - "name": "Irvig Senay" + "name": "Irvig Senay", + "id": "937" }, { - "id": "940", - "name": "Echned Zekin" + "name": "Echned Zekin", + "id": "940" }, { - "id": "952", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "952" }, { - "id": "953", - "name": "Banker" + "name": "Banker", + "id": "953" }, { - "id": "992", - "name": "Kardia" + "name": "Kardia", + "id": "992" }, { - "id": "994", - "name": "Niloof" + "name": "Niloof", + "id": "994" }, { - "id": "1003", - "name": "Lord Iban" + "name": "Lord Iban", + "id": "1003" }, { - "id": "1008", - "name": "Hamid" + "name": "Hamid", + "id": "1008" }, { - "id": "1011", - "name": "Fycie" + "name": "Fycie", + "id": "1011" }, { - "id": "1012", - "name": "Bugs" + "name": "Bugs", + "id": "1012" }, { - "id": "1014", - "name": "Bloated Toad" + "name": "Bloated Toad", + "id": "1014" }, { - "id": "1016", - "name": "Chompy bird" + "name": "Chompy bird", + "id": "1016" }, { - "id": "1024", - "name": "Baby impling" + "name": "Baby impling", + "id": "1024" }, { - "id": "1036", - "name": "Banker" + "name": "Banker", + "id": "1036" }, { - "id": "1038", - "name": "Rufus" + "name": "Rufus", + "id": "1038" }, { - "id": "1039", - "name": "Barker" + "name": "Barker", + "id": "1039" }, { - "id": "1040", - "name": "Fidelio" + "name": "Fidelio", + "id": "1040" }, { - "id": "1041", - "name": "Sbott" + "name": "Sbott", + "id": "1041" }, { - "id": "1042", - "name": "Roavar" + "name": "Roavar", + "id": "1042" }, { - "id": "1043", - "name": "Will o' the wisp" + "name": "Will o' the wisp", + "id": "1043" }, { - "id": "1047", - "name": "Drezel" + "name": "Drezel", + "id": "1047" }, { - "id": "1056", - "name": "Mime" + "name": "Mime", + "id": "1056" }, { - "id": "1070", - "name": "Saba" + "name": "Saba", + "id": "1070" }, { - "id": "1071", - "name": "Tenzing" + "name": "Tenzing", + "id": "1071" }, { - "id": "1075", - "name": "Archer" + "name": "Archer", + "id": "1075" }, { - "id": "1091", - "name": "Bob" + "name": "Bob", + "id": "1091" }, { - "id": "1093", - "name": "Billy" + "name": "Billy", + "id": "1093" }, { - "id": "1094", - "name": "Mountain goat" + "name": "Mountain goat", + "id": "1094" }, { - "id": "1113", - "name": "Eadgar" + "name": "Eadgar", + "id": "1113" }, { - "id": "1114", - "name": "Godric" + "name": "Godric", + "id": "1114" }, { - "id": "1159", - "name": "Kalphite Queen" + "name": "Kalphite Queen", + "id": "1159" }, { - "id": "1162", - "name": "Timfraku" + "name": "Timfraku", + "id": "1162" }, { - "id": "1163", - "name": "Tiadeche" + "name": "Tiadeche", + "id": "1163" }, { - "id": "1164", - "name": "Tiadeche" + "name": "Tiadeche", + "id": "1164" }, { - "id": "1165", - "name": "Tinsay" + "name": "Tinsay", + "id": "1165" }, { - "id": "1166", - "name": "Tinsay" + "name": "Tinsay", + "id": "1166" }, { - "id": "1167", - "name": "Tamayu" + "name": "Tamayu", + "id": "1167" }, { - "id": "1168", - "name": "Tamayu" + "name": "Tamayu", + "id": "1168" }, { - "id": "1169", - "name": "Tamayu" + "name": "Tamayu", + "id": "1169" }, { - "id": "1170", - "name": "Tamayu" + "name": "Tamayu", + "id": "1170" }, { - "id": "1171", - "name": "Lubufu" + "name": "Lubufu", + "id": "1171" }, { - "id": "1173", - "name": "The Shaikahan" + "name": "The Shaikahan", + "id": "1173" }, { - "id": "1174", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "1174" }, { - "id": "1175", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "1175" }, { - "id": "1177", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "1177" }, { - "id": "1178", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "1178" }, { - "id": "1180", - "name": "Cormorant" + "name": "Cormorant", + "id": "1180" }, { - "id": "1181", - "name": "Pelican" + "name": "Pelican", + "id": "1181" }, { - "id": "1182", - "name": "Lord Iorwerth" + "name": "Lord Iorwerth", + "id": "1182" }, { - "id": "1186", - "name": "Idris" + "name": "Idris", + "id": "1186" }, { - "id": "1187", - "name": "Essyllt" + "name": "Essyllt", + "id": "1187" }, { - "id": "1188", - "name": "Morvran" + "name": "Morvran", + "id": "1188" }, { - "id": "1189", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "1189" }, { - "id": "1190", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "1190" }, { - "id": "1191", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "1191" }, { - "id": "1202", - "name": "Arianwyn" + "name": "Arianwyn", + "id": "1202" }, { - "id": "1205", - "name": "Tyras guard" + "name": "Tyras guard", + "id": "1205" }, { - "id": "1207", - "name": "Quartermaster" + "name": "Quartermaster", + "id": "1207" }, { - "id": "1221", - "name": "Spider" + "name": "Spider", + "id": "1221" }, { - "id": "1222", - "name": "Mist" + "name": "Mist", + "id": "1222" }, { - "id": "1224", - "name": "Vampyric hound" + "name": "Vampyric hound", + "id": "1224" }, { - "id": "1226", - "name": "Tree" + "name": "Tree", + "id": "1226" }, { - "id": "1236", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "1236" }, { - "id": "1237", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "1237" }, { - "id": "1238", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "1238" }, { - "id": "1242", - "name": "Shade Spirit" + "name": "Shade Spirit", + "id": "1242" }, { - "id": "1251", - "name": "Afflicted(Ulsquire)" + "name": "Afflicted(Ulsquire)", + "id": "1251" }, { - "id": "1252", - "name": "Ulsquire Shauncy" + "name": "Ulsquire Shauncy", + "id": "1252" }, { - "id": "1253", - "name": "Afflicted(Razmire)" + "name": "Afflicted(Razmire)", + "id": "1253" }, { - "id": "1254", - "name": "Razmire Keelgan" + "name": "Razmire Keelgan", + "id": "1254" }, { - "id": "1255", - "name": "Mort'ton Local" + "name": "Mort'ton Local", + "id": "1255" }, { - "id": "1256", - "name": "Mort'ton Local" + "name": "Mort'ton Local", + "id": "1256" }, { - "id": "1259", - "name": "Mort'ton local" + "name": "Mort'ton local", + "id": "1259" }, { - "id": "1260", - "name": "Mort'ton local" + "name": "Mort'ton local", + "id": "1260" }, { - "id": "1280", - "name": "Butterfly" + "name": "Butterfly", + "id": "1280" }, { - "id": "1284", - "name": "Bjorn" + "name": "Bjorn", + "id": "1284" }, { - "id": "1285", - "name": "Eldgrim" + "name": "Eldgrim", + "id": "1285" }, { - "id": "1295", - "name": "Askeladden" + "name": "Askeladden", + "id": "1295" }, { - "id": "1299", - "name": "Town Guard" + "name": "Town Guard", + "id": "1299" }, { - "id": "1310", - "name": "Freygerd" + "name": "Freygerd", + "id": "1310" }, { - "id": "1311", - "name": "Lensa" + "name": "Lensa", + "id": "1311" }, { - "id": "1312", - "name": "Jennella" + "name": "Jennella", + "id": "1312" }, { - "id": "1322", - "name": "Gull" + "name": "Gull", + "id": "1322" }, { - "id": "1323", - "name": "Gull" + "name": "Gull", + "id": "1323" }, { - "id": "1324", - "name": "Gull" + "name": "Gull", + "id": "1324" }, { - "id": "1325", - "name": "Gull" + "name": "Gull", + "id": "1325" }, { - "id": "1331", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "1331" }, { - "id": "1332", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "1332" }, { - "id": "1333", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "1333" }, { - "id": "1334", - "name": "Jossik" + "name": "Jossik", + "id": "1334" }, { - "id": "1335", - "name": "Jossik" + "name": "Jossik", + "id": "1335" }, { - "id": "1336", - "name": "Larrissa" + "name": "Larrissa", + "id": "1336" }, { - "id": "1337", - "name": "Larrissa" + "name": "Larrissa", + "id": "1337" }, { - "id": "1348", - "name": "Dagannoth mother" + "name": "Dagannoth mother", + "id": "1348" }, { - "id": "1349", - "name": "Dagannoth mother" + "name": "Dagannoth mother", + "id": "1349" }, { - "id": "1350", - "name": "Dagannoth mother" + "name": "Dagannoth mother", + "id": "1350" }, { - "id": "1359", - "name": "Queen Sigrid" + "name": "Queen Sigrid", + "id": "1359" }, { - "id": "1361", - "name": "Arnor" + "name": "Arnor", + "id": "1361" }, { - "id": "1362", - "name": "Haming" + "name": "Haming", + "id": "1362" }, { - "id": "1363", - "name": "Moldof" + "name": "Moldof", + "id": "1363" }, { - "id": "1364", - "name": "Helga" + "name": "Helga", + "id": "1364" }, { - "id": "1365", - "name": "Matilda" + "name": "Matilda", + "id": "1365" }, { - "id": "1366", - "name": "Ashild" + "name": "Ashild", + "id": "1366" }, { - "id": "1371", - "name": "Prince Brand" + "name": "Prince Brand", + "id": "1371" }, { - "id": "1372", - "name": "Princess Astrid" + "name": "Princess Astrid", + "id": "1372" }, { - "id": "1373", - "name": "King Vargas" + "name": "King Vargas", + "id": "1373" }, { - "id": "1375", - "name": "Advisor Ghrim" + "name": "Advisor Ghrim", + "id": "1375" }, { - "id": "1383", - "name": "Halla" + "name": "Halla", + "id": "1383" }, { - "id": "1384", - "name": "Yrsa" + "name": "Yrsa", + "id": "1384" }, { - "id": "1387", - "name": "Thora" + "name": "Thora", + "id": "1387" }, { - "id": "1399", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "1399" }, { - "id": "1400", - "name": "Gull" + "name": "Gull", + "id": "1400" }, { - "id": "1405", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "1405" }, { - "id": "1406", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "1406" }, { - "id": "1407", - "name": "Daero" + "name": "Daero", + "id": "1407" }, { - "id": "1408", - "name": "Waydar" + "name": "Waydar", + "id": "1408" }, { - "id": "1409", - "name": "Waydar" + "name": "Waydar", + "id": "1409" }, { - "id": "1410", - "name": "Waydar" + "name": "Waydar", + "id": "1410" }, { - "id": "1411", - "name": "Garkor" + "name": "Garkor", + "id": "1411" }, { - "id": "1412", - "name": "Garkor" + "name": "Garkor", + "id": "1412" }, { - "id": "1413", - "name": "Lumo" + "name": "Lumo", + "id": "1413" }, { - "id": "1414", - "name": "Lumo" + "name": "Lumo", + "id": "1414" }, { - "id": "1415", - "name": "Bunkdo" + "name": "Bunkdo", + "id": "1415" }, { - "id": "1416", - "name": "Bunkdo" + "name": "Bunkdo", + "id": "1416" }, { - "id": "1417", - "name": "Carado" + "name": "Carado", + "id": "1417" }, { - "id": "1418", - "name": "Carado" + "name": "Carado", + "id": "1418" }, { - "id": "1420", - "name": "Karam" + "name": "Karam", + "id": "1420" }, { - "id": "1421", - "name": "Karam" + "name": "Karam", + "id": "1421" }, { - "id": "1422", - "name": "Karam" + "name": "Karam", + "id": "1422" }, { - "id": "1423", - "name": "Bunkwicket" + "name": "Bunkwicket", + "id": "1423" }, { - "id": "1424", - "name": "Waymottin" + "name": "Waymottin", + "id": "1424" }, { - "id": "1425", - "name": "Zooknock" + "name": "Zooknock", + "id": "1425" }, { - "id": "1426", - "name": "Zooknock" + "name": "Zooknock", + "id": "1426" }, { - "id": "1428", - "name": "G.L.O. Caranock" + "name": "G.L.O. Caranock", + "id": "1428" }, { - "id": "1429", - "name": "Dugopul" + "name": "Dugopul", + "id": "1429" }, { - "id": "1430", - "name": "Salenab" + "name": "Salenab", + "id": "1430" }, { - "id": "1431", - "name": "Trefaji" + "name": "Trefaji", + "id": "1431" }, { - "id": "1432", - "name": "Aberab" + "name": "Aberab", + "id": "1432" }, { - "id": "1433", - "name": "Solihib" + "name": "Solihib", + "id": "1433" }, { - "id": "1434", - "name": "Daga" + "name": "Daga", + "id": "1434" }, { - "id": "1435", - "name": "Tutab" + "name": "Tutab", + "id": "1435" }, { - "id": "1436", - "name": "Ifaba" + "name": "Ifaba", + "id": "1436" }, { - "id": "1437", - "name": "Hamab" + "name": "Hamab", + "id": "1437" }, { - "id": "1438", - "name": "Hafuba" + "name": "Hafuba", + "id": "1438" }, { - "id": "1439", - "name": "Denadu" + "name": "Denadu", + "id": "1439" }, { - "id": "1440", - "name": "Lofu" + "name": "Lofu", + "id": "1440" }, { - "id": "1441", - "name": "Kruk" + "name": "Kruk", + "id": "1441" }, { - "id": "1448", - "name": "Awowogei" + "name": "Awowogei", + "id": "1448" }, { - "id": "1449", - "name": "Uwogo" + "name": "Uwogo", + "id": "1449" }, { - "id": "1450", - "name": "Muruwoi" + "name": "Muruwoi", + "id": "1450" }, { - "id": "1462", - "name": "Elder Guard" + "name": "Elder Guard", + "id": "1462" }, { - "id": "1468", - "name": "Bonzara" + "name": "Bonzara", + "id": "1468" }, { - "id": "1470", - "name": "Foreman" + "name": "Foreman", + "id": "1470" }, { - "id": "1474", - "name": "Spider" + "name": "Spider", + "id": "1474" }, { - "id": "1482", - "name": "Gorilla" + "name": "Gorilla", + "id": "1482" }, { - "id": "1488", - "name": "Dummy" + "name": "Dummy", + "id": "1488" }, { - "id": "1489", - "name": "Dummy" + "name": "Dummy", + "id": "1489" }, { - "id": "1490", - "name": "Dummy" + "name": "Dummy", + "id": "1490" }, { - "id": "1491", - "name": "Dummy" + "name": "Dummy", + "id": "1491" }, { - "id": "1492", - "name": "Dummy" + "name": "Dummy", + "id": "1492" }, { - "id": "1493", - "name": "Dummy" + "name": "Dummy", + "id": "1493" }, { - "id": "1494", - "name": "Dummy" + "name": "Dummy", + "id": "1494" }, { - "id": "1495", - "name": "Dummy" + "name": "Dummy", + "id": "1495" }, { - "id": "1496", - "name": "Dummy" + "name": "Dummy", + "id": "1496" }, { - "id": "1497", - "name": "Dummy" + "name": "Dummy", + "id": "1497" }, { - "id": "1498", - "name": "Dummy" + "name": "Dummy", + "id": "1498" }, { - "id": "1499", - "name": "Dummy" + "name": "Dummy", + "id": "1499" }, { - "id": "1500", - "name": "Dummy" + "name": "Dummy", + "id": "1500" }, { - "id": "1501", - "name": "Dummy" + "name": "Dummy", + "id": "1501" }, { - "id": "1502", - "name": "Dummy" + "name": "Dummy", + "id": "1502" }, { - "id": "1503", - "name": "Dummy" + "name": "Dummy", + "id": "1503" }, { - "id": "1504", - "name": "Dummy" + "name": "Dummy", + "id": "1504" }, { - "id": "1505", - "name": "Dummy" + "name": "Dummy", + "id": "1505" }, { - "id": "1506", - "name": "Dummy" + "name": "Dummy", + "id": "1506" }, { - "id": "1507", - "name": "Dummy" + "name": "Dummy", + "id": "1507" }, { - "id": "1508", - "name": "Forester" + "name": "Forester", + "id": "1508" }, { - "id": "1509", - "name": "Woman-at-arms" + "name": "Woman-at-arms", + "id": "1509" }, { - "id": "1510", - "name": "Apprentice" + "name": "Apprentice", + "id": "1510" }, { - "id": "1511", - "name": "Ranger" + "name": "Ranger", + "id": "1511" }, { - "id": "1512", - "name": "Adventurer" + "name": "Adventurer", + "id": "1512" }, { - "id": "1513", - "name": "Mage" + "name": "Mage", + "id": "1513" }, { - "id": "1515", - "name": "Nail beast" + "name": "Nail beast", + "id": "1515" }, { - "id": "1522", - "name": "Nail beast" + "name": "Nail beast", + "id": "1522" }, { - "id": "1523", - "name": "Nail beast" + "name": "Nail beast", + "id": "1523" }, { - "id": "1525", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "1525" }, { - "id": "1526", - "name": "Lanthus" + "name": "Lanthus", + "id": "1526" }, { - "id": "1527", - "name": "Mine cart" + "name": "Mine cart", + "id": "1527" }, { - "id": "1529", - "name": "Sheep" + "name": "Sheep", + "id": "1529" }, { - "id": "1530", - "name": "Rabbit" + "name": "Rabbit", + "id": "1530" }, { - "id": "1542", - "name": "Loading crane" + "name": "Loading crane", + "id": "1542" }, { - "id": "1543", - "name": "Innocent-looking key" + "name": "Innocent-looking key", + "id": "1543" }, { - "id": "1544", - "name": "Mine cart" + "name": "Mine cart", + "id": "1544" }, { - "id": "1545", - "name": "Mine cart" + "name": "Mine cart", + "id": "1545" }, { - "id": "1546", - "name": "Mine cart" + "name": "Mine cart", + "id": "1546" }, { - "id": "1547", - "name": "Mine cart" + "name": "Mine cart", + "id": "1547" }, { - "id": "1548", - "name": "Mine cart" + "name": "Mine cart", + "id": "1548" }, { - "id": "1552", - "name": "Santa" + "name": "Santa", + "id": "1552" }, { - "id": "1554", - "name": "Aga" + "name": "Aga", + "id": "1554" }, { - "id": "1555", - "name": "Arrg" + "name": "Arrg", + "id": "1555" }, { - "id": "1559", - "name": "Ice wolf" + "name": "Ice wolf", + "id": "1559" }, { - "id": "1568", - "name": "Curpile Fyod" + "name": "Curpile Fyod", + "id": "1568" }, { - "id": "1569", - "name": "Veliaf Hurtz" + "name": "Veliaf Hurtz", + "id": "1569" }, { - "id": "1570", - "name": "Sani Piliu" + "name": "Sani Piliu", + "id": "1570" }, { - "id": "1571", - "name": "Harold Evans" + "name": "Harold Evans", + "id": "1571" }, { - "id": "1572", - "name": "Radigad Ponfit" + "name": "Radigad Ponfit", + "id": "1572" }, { - "id": "1573", - "name": "Polmafi Ferdygris" + "name": "Polmafi Ferdygris", + "id": "1573" }, { - "id": "1574", - "name": "Ivan Strom" + "name": "Ivan Strom", + "id": "1574" }, { - "id": "1575", - "name": "Skeleton Hellhound" + "name": "Skeleton Hellhound", + "id": "1575" }, { - "id": "1576", - "name": "Stranger" + "name": "Stranger", + "id": "1576" }, { - "id": "1577", - "name": "Vanstrom Klause" + "name": "Vanstrom Klause", + "id": "1577" }, { - "id": "1578", - "name": "Mist" + "name": "Mist", + "id": "1578" }, { - "id": "1579", - "name": "Vanstrom Klause" + "name": "Vanstrom Klause", + "id": "1579" }, { - "id": "1580", - "name": "Vanstrom Klause" + "name": "Vanstrom Klause", + "id": "1580" }, { - "id": "1581", - "name": "Vanstrom Klause" + "name": "Vanstrom Klause", + "id": "1581" }, { - "id": "1595", - "name": "Saniboch" + "name": "Saniboch", + "id": "1595" }, { - "id": "1596", - "name": "Vannaka" + "name": "Vannaka", + "id": "1596" }, { - "id": "1599", - "name": "Cave crawler" + "name": "Cave crawler", + "id": "1599" }, { - "id": "1659", - "name": "Skullball" + "name": "Skullball", + "id": "1659" }, { - "id": "1664", - "name": "Agility Trainer" + "name": "Agility Trainer", + "id": "1664" }, { - "id": "1666", - "name": "Dr Fenkenstrain" + "name": "Dr Fenkenstrain", + "id": "1666" }, { - "id": "1671", - "name": "Fenkenstrain's Monster" + "name": "Fenkenstrain's Monster", + "id": "1671" }, { - "id": "1674", - "name": "Lord Rologarth" + "name": "Lord Rologarth", + "id": "1674" }, { - "id": "1679", - "name": "Eluned" + "name": "Eluned", + "id": "1679" }, { - "id": "1680", - "name": "Islwyn" + "name": "Islwyn", + "id": "1680" }, { - "id": "1682", - "name": "Golrie" + "name": "Golrie", + "id": "1682" }, { - "id": "1683", - "name": "Velorina" + "name": "Velorina", + "id": "1683" }, { - "id": "1685", - "name": "Gravingas" + "name": "Gravingas", + "id": "1685" }, { - "id": "1687", - "name": "Ak-Haranu" + "name": "Ak-Haranu", + "id": "1687" }, { - "id": "1689", - "name": "Undead cow" + "name": "Undead cow", + "id": "1689" }, { - "id": "1694", - "name": "Robin" + "name": "Robin", + "id": "1694" }, { - "id": "1709", - "name": "Johanhus Ulsbrecht" + "name": "Johanhus Ulsbrecht", + "id": "1709" }, { - "id": "1719", - "name": "Tree" + "name": "Tree", + "id": "1719" }, { - "id": "1720", - "name": "Tree" + "name": "Tree", + "id": "1720" }, { - "id": "1721", - "name": "Tree" + "name": "Tree", + "id": "1721" }, { - "id": "1722", - "name": "Dead tree" + "name": "Dead tree", + "id": "1722" }, { - "id": "1723", - "name": "Dead tree" + "name": "Dead tree", + "id": "1723" }, { - "id": "1724", - "name": "Dead tree" + "name": "Dead tree", + "id": "1724" }, { - "id": "1725", - "name": "Dead tree" + "name": "Dead tree", + "id": "1725" }, { - "id": "1726", - "name": "Dead tree" + "name": "Dead tree", + "id": "1726" }, { - "id": "1727", - "name": "Dead tree" + "name": "Dead tree", + "id": "1727" }, { - "id": "1728", - "name": "Dead tree" + "name": "Dead tree", + "id": "1728" }, { - "id": "1729", - "name": "Dead tree" + "name": "Dead tree", + "id": "1729" }, { - "id": "1730", - "name": "Dead tree" + "name": "Dead tree", + "id": "1730" }, { - "id": "1731", - "name": "Dead tree" + "name": "Dead tree", + "id": "1731" }, { - "id": "1732", - "name": "Dramen tree" + "name": "Dramen tree", + "id": "1732" }, { - "id": "1734", - "name": "Magic tree" + "name": "Magic tree", + "id": "1734" }, { - "id": "1735", - "name": "Maple tree" + "name": "Maple tree", + "id": "1735" }, { - "id": "1736", - "name": "Willow" + "name": "Willow", + "id": "1736" }, { - "id": "1737", - "name": "Willow" + "name": "Willow", + "id": "1737" }, { - "id": "1738", - "name": "Willow" + "name": "Willow", + "id": "1738" }, { - "id": "1749", - "name": "Hollow tree" + "name": "Hollow tree", + "id": "1749" }, { - "id": "1750", - "name": "Hollow tree" + "name": "Hollow tree", + "id": "1750" }, { - "id": "1753", - "name": "Mounted terrorbird gnome" + "name": "Mounted terrorbird gnome", + "id": "1753" }, { - "id": "1756", - "name": "Crow" + "name": "Crow", + "id": "1756" }, { - "id": "1762", - "name": "Sheep" + "name": "Sheep", + "id": "1762" }, { - "id": "1764", - "name": "Sheep" + "name": "Sheep", + "id": "1764" }, { - "id": "1765", - "name": "Sheep" + "name": "Sheep", + "id": "1765" }, { - "id": "1777", - "name": "Ilfeen" + "name": "Ilfeen", + "id": "1777" }, { - "id": "1778", - "name": "William" + "name": "William", + "id": "1778" }, { - "id": "1779", - "name": "Ian" + "name": "Ian", + "id": "1779" }, { - "id": "1780", - "name": "Larry" + "name": "Larry", + "id": "1780" }, { - "id": "1781", - "name": "Darren" + "name": "Darren", + "id": "1781" }, { - "id": "1782", - "name": "Edward" + "name": "Edward", + "id": "1782" }, { - "id": "1784", - "name": "Neil" + "name": "Neil", + "id": "1784" }, { - "id": "1786", - "name": "Simon" + "name": "Simon", + "id": "1786" }, { - "id": "1787", - "name": "Sam" + "name": "Sam", + "id": "1787" }, { - "id": "1788", - "name": "Lumdo" + "name": "Lumdo", + "id": "1788" }, { - "id": "1789", - "name": "Bunkwicket" + "name": "Bunkwicket", + "id": "1789" }, { - "id": "1790", - "name": "Waymottin" + "name": "Waymottin", + "id": "1790" }, { - "id": "1791", - "name": "Jungle Tree" + "name": "Jungle Tree", + "id": "1791" }, { - "id": "1792", - "name": "Jungle Tree" + "name": "Jungle Tree", + "id": "1792" }, { - "id": "1793", - "name": "Tassie Slipcast" + "name": "Tassie Slipcast", + "id": "1793" }, { - "id": "1798", - "name": "Phantuwti Fanstuwi Farsight" + "name": "Phantuwti Fanstuwi Farsight", + "id": "1798" }, { - "id": "1799", - "name": "Tindel Marchant" + "name": "Tindel Marchant", + "id": "1799" }, { - "id": "1800", - "name": "Gnormadium Avlafrim" + "name": "Gnormadium Avlafrim", + "id": "1800" }, { - "id": "1801", - "name": "Petra Fiyed" + "name": "Petra Fiyed", + "id": "1801" }, { - "id": "1804", - "name": "Slagilith" + "name": "Slagilith", + "id": "1804" }, { - "id": "1808", - "name": "Ragnar" + "name": "Ragnar", + "id": "1808" }, { - "id": "1809", - "name": "Svidi" + "name": "Svidi", + "id": "1809" }, { - "id": "1810", - "name": "Jokul" + "name": "Jokul", + "id": "1810" }, { - "id": "1811", - "name": "The Kendal" + "name": "The Kendal", + "id": "1811" }, { - "id": "1821", - "name": "Bald Headed Eagle" + "name": "Bald Headed Eagle", + "id": "1821" }, { - "id": "1826", - "name": "Zombie" + "name": "Frog", + "id": "1830" }, { - "id": "1830", - "name": "Frog" + "name": "Easter Bunny", + "id": "1835" }, { - "id": "1835", - "name": "Easter Bunny" + "name": "Dondakan the Dwarf", + "id": "1836" }, { - "id": "1836", - "name": "Dondakan the Dwarf" + "name": "Dondakan the Dwarf", + "id": "1838" }, { - "id": "1838", - "name": "Dondakan the Dwarf" + "name": "Dondakan the Dwarf", + "id": "1839" }, { - "id": "1839", - "name": "Dondakan the Dwarf" + "name": "Rolad", + "id": "1841" }, { - "id": "1841", - "name": "Rolad" + "name": "Dwarven Boatman", + "id": "1845" }, { - "id": "1845", - "name": "Dwarven Boatman" + "name": "Miodvetnir", + "id": "1847" }, { - "id": "1847", - "name": "Miodvetnir" + "name": "Dernu", + "id": "1848" }, { - "id": "1848", - "name": "Dernu" + "name": "Derni", + "id": "1849" }, { - "id": "1849", - "name": "Derni" + "name": "Brian", + "id": "1860" }, { - "id": "1860", - "name": "Brian" + "name": "Ali Morrisane", + "id": "1862" }, { - "id": "1862", - "name": "Ali Morrisane" + "name": "Ali the Mayor", + "id": "1869" }, { - "id": "1869", - "name": "Ali the Mayor" + "name": "Bandit Leader", + "id": "1876" }, { - "id": "1876", - "name": "Bandit Leader" + "name": "Bandit", + "id": "1879" }, { - "id": "1879", - "name": "Bandit" + "name": "Bandit", + "id": "1881" }, { - "id": "1881", - "name": "Bandit" + "name": "Sir Palomedes", + "id": "1882" }, { - "id": "1882", - "name": "Sir Palomedes" + "name": "Sir Palomedes", + "id": "1883" }, { - "id": "1883", - "name": "Sir Palomedes" + "name": "Trobert", + "id": "1884" }, { - "id": "1884", - "name": "Trobert" + "name": "Villager", + "id": "1887" }, { - "id": "1887", - "name": "Villager" + "name": "Villager", + "id": "1889" }, { - "id": "1889", - "name": "Villager" + "name": "Villager", + "id": "1890" }, { - "id": "1890", - "name": "Villager" + "name": "Villager", + "id": "1891" }, { - "id": "1891", - "name": "Villager" + "name": "Villager", + "id": "1893" }, { - "id": "1893", - "name": "Villager" + "name": "Villager", + "id": "1894" }, { - "id": "1894", - "name": "Villager" + "name": "Villager", + "id": "1895" }, { - "id": "1895", - "name": "Villager" + "name": "Villager", + "id": "1897" }, { - "id": "1897", - "name": "Villager" + "name": "Villager", + "id": "1898" }, { - "id": "1898", - "name": "Villager" + "name": "Menaphite Leader", + "id": "1899" }, { - "id": "1899", - "name": "Menaphite Leader" + "name": "Menaphite Thug", + "id": "1903" }, { - "id": "1903", - "name": "Menaphite Thug" + "name": "Broken clay golem", + "id": "1907" }, { - "id": "1907", - "name": "Broken clay golem" + "name": "Damaged clay golem", + "id": "1909" }, { - "id": "1909", - "name": "Damaged clay golem" + "name": "Clay golem", + "id": "1910" }, { - "id": "1910", - "name": "Clay golem" + "name": "Elissa", + "id": "1912" }, { - "id": "1912", - "name": "Elissa" + "name": "Eblis", + "id": "1922" }, { - "id": "1922", - "name": "Eblis" + "name": "Eblis", + "id": "1924" }, { - "id": "1924", - "name": "Eblis" + "name": "Bandit", + "id": "1927" }, { - "id": "1927", - "name": "Bandit" + "name": "Bandit", + "id": "1928" }, { - "id": "1928", - "name": "Bandit" + "name": "Bandit", + "id": "1929" }, { - "id": "1929", - "name": "Bandit" + "name": "Bandit", + "id": "1930" }, { - "id": "1930", - "name": "Bandit" + "name": "Troll child", + "id": "1932" }, { - "id": "1932", - "name": "Troll child" + "name": "Troll child", + "id": "1934" }, { - "id": "1934", - "name": "Troll child" + "name": "Mummy", + "id": "1957" }, { - "id": "1943", - "name": "Ice block" + "name": "Mummy", + "id": "1959" }, { - "id": "1945", - "name": "Ice block" + "name": "Mummy ashes", + "id": "1960" }, { - "id": "1957", - "name": "Mummy" + "name": "Mummy", + "id": "1966" }, { - "id": "1959", - "name": "Mummy" + "name": "Mummy", + "id": "1968" }, { - "id": "1960", - "name": "Mummy ashes" + "name": "Azzanadra", + "id": "1970" }, { - "id": "1966", - "name": "Mummy" + "name": "Raetul", + "id": "1982" }, { - "id": "1968", - "name": "Mummy" + "name": "Siamun", + "id": "1983" }, { - "id": "1970", - "name": "Azzanadra" + "name": "High Priest", + "id": "1984" }, { - "id": "1982", - "name": "Raetul" + "name": "Priest", + "id": "1987" }, { - "id": "1983", - "name": "Siamun" + "name": "Priest", + "id": "1989" }, { - "id": "1984", - "name": "High Priest" + "name": "Sphinx", + "id": "1990" }, { - "id": "1987", - "name": "Priest" + "name": "Neite", + "id": "1992" }, { - "id": "1989", - "name": "Priest" + "name": "Vulture", + "id": "1996" }, { - "id": "1990", - "name": "Sphinx" + "name": "Plague cow", + "id": "2000" }, { - "id": "1992", - "name": "Neite" + "name": "Wanderer", + "id": "2002" }, { - "id": "1996", - "name": "Vulture" + "name": "Wanderer", + "id": "2006" }, { - "id": "2000", - "name": "Plague cow" + "name": "Het", + "id": "2007" }, { - "id": "2002", - "name": "Wanderer" + "name": "Apmeken", + "id": "2008" }, { - "id": "2006", - "name": "Wanderer" + "name": "Scabaras", + "id": "2009" }, { - "id": "2007", - "name": "Het" + "name": "Crondis", + "id": "2010" }, { - "id": "2008", - "name": "Apmeken" + "name": "Icthlarin", + "id": "2011" }, { - "id": "2009", - "name": "Scabaras" + "name": "Klenter", + "id": "2013" }, { - "id": "2010", - "name": "Crondis" + "name": "Light creature", + "id": "2020" }, { - "id": "2011", - "name": "Icthlarin" + "name": "Light creature", + "id": "2022" }, { - "id": "2013", - "name": "Klenter" + "name": "Juna", + "id": "2023" }, { - "id": "2020", - "name": "Light creature" + "name": "Grish", + "id": "2038" }, { - "id": "2022", - "name": "Light creature" + "name": "Uglug Nar", + "id": "2039" }, { - "id": "2023", - "name": "Juna" + "name": "Pilg", + "id": "2040" }, { - "id": "2038", - "name": "Grish" + "name": "Grug", + "id": "2041" }, { - "id": "2039", - "name": "Uglug Nar" + "name": "Ogre guard", + "id": "2042" }, { - "id": "2040", - "name": "Pilg" + "name": "Ogre guard", + "id": "2043" }, { - "id": "2041", - "name": "Grug" + "name": "Zavistic Rarve", + "id": "2059" }, { - "id": "2042", - "name": "Ogre guard" + "name": "Sithik Ints", + "id": "2061" }, { - "id": "2043", - "name": "Ogre guard" + "name": "Sithik Ints", + "id": "2062" }, { - "id": "2059", - "name": "Zavistic Rarve" + "name": "Gargh", + "id": "2063" }, { - "id": "2061", - "name": "Sithik Ints" + "name": "Scarg", + "id": "2064" }, { - "id": "2062", - "name": "Sithik Ints" + "name": "Gruh", + "id": "2065" }, { - "id": "2063", - "name": "Gargh" + "name": "Irwin Feaselbaum", + "id": "2066" }, { - "id": "2064", - "name": "Scarg" + "name": "Fishing spot", + "id": "2068" }, { - "id": "2065", - "name": "Gruh" + "name": "Sigmund", + "id": "2079" }, { - "id": "2066", - "name": "Irwin Feaselbaum" + "name": "Sigmund", + "id": "2083" }, { - "id": "2068", - "name": "Fishing spot" + "name": "Mistag", + "id": "2084" }, { - "id": "2079", - "name": "Sigmund" + "name": "Kazgar", + "id": "2085" }, { - "id": "2083", - "name": "Sigmund" + "name": "Ur-tag", + "id": "2087" }, { - "id": "2084", - "name": "Mistag" + "name": "Duke Horacio", + "id": "2088" }, { - "id": "2085", - "name": "Kazgar" + "name": "Mistag", + "id": "2089" }, { - "id": "2087", - "name": "Ur-tag" + "name": "Sigmund", + "id": "2090" }, { - "id": "2088", - "name": "Duke Horacio" + "name": "Red Axe Secretary", + "id": "2099" }, { - "id": "2089", - "name": "Mistag" + "name": "Red Axe Director", + "id": "2107" }, { - "id": "2090", - "name": "Sigmund" + "name": "Red Axe Cat", + "id": "2108" }, { - "id": "2099", - "name": "Red Axe Secretary" + "name": "Trader", + "id": "2123" }, { - "id": "2107", - "name": "Red Axe Director" + "name": "Trader", + "id": "2125" }, { - "id": "2108", - "name": "Red Axe Cat" + "name": "Supreme Commander", + "id": "2128" }, { - "id": "2123", - "name": "Trader" + "name": "Commander Veldaban", + "id": "2129" }, { - "id": "2125", - "name": "Trader" + "name": "Gnome emissary", + "id": "2137" }, { - "id": "2128", - "name": "Supreme Commander" + "name": "Riki the sculptor's model", + "id": "2142" }, { - "id": "2129", - "name": "Commander Veldaban" + "name": "Riki the sculptor's model", + "id": "2144" }, { - "id": "2137", - "name": "Gnome emissary" + "name": "Riki the sculptor's model", + "id": "2145" }, { - "id": "2142", - "name": "Riki the sculptor's model" + "name": "Riki the sculptor's model", + "id": "2146" }, { - "id": "2144", - "name": "Riki the sculptor's model" + "name": "Riki the sculptor's model", + "id": "2147" }, { - "id": "2145", - "name": "Riki the sculptor's model" + "name": "Riki the sculptor's model", + "id": "2148" }, { - "id": "2146", - "name": "Riki the sculptor's model" + "name": "Riki the sculptor's model", + "id": "2149" }, { - "id": "2147", - "name": "Riki the sculptor's model" + "name": "Riki the sculptor's model", + "id": "2150" }, { - "id": "2148", - "name": "Riki the sculptor's model" + "name": "Vigr", + "id": "2151" }, { - "id": "2149", - "name": "Riki the sculptor's model" + "name": "Santiri", + "id": "2152" }, { - "id": "2150", - "name": "Riki the sculptor's model" + "name": "Saro", + "id": "2153" }, { - "id": "2151", - "name": "Vigr" + "name": "Wemund", + "id": "2155" }, { - "id": "2152", - "name": "Santiri" + "name": "Randivor", + "id": "2156" }, { - "id": "2153", - "name": "Saro" + "name": "Hervi", + "id": "2157" }, { - "id": "2155", - "name": "Wemund" + "name": "Gulldamar", + "id": "2159" }, { - "id": "2156", - "name": "Randivor" + "name": "Agmundi", + "id": "2161" }, { - "id": "2157", - "name": "Hervi" + "name": "Vermundi", + "id": "2162" }, { - "id": "2159", - "name": "Gulldamar" + "name": "Assistant", + "id": "2166" }, { - "id": "2161", - "name": "Agmundi" + "name": "Dromund", + "id": "2169" }, { - "id": "2162", - "name": "Vermundi" + "name": "Inn Keeper", + "id": "2176" }, { - "id": "2166", - "name": "Assistant" + "name": "Barman", + "id": "2179" }, { - "id": "2169", - "name": "Dromund" + "name": "Hegir", + "id": "2188" }, { - "id": "2176", - "name": "Inn Keeper" + "name": "Haera", + "id": "2189" }, { - "id": "2179", - "name": "Barman" + "name": "Reinald", + "id": "2194" }, { - "id": "2188", - "name": "Hegir" + "name": "Gauss", + "id": "2196" }, { - "id": "2189", - "name": "Haera" + "name": "Myndill", + "id": "2197" }, { - "id": "2194", - "name": "Reinald" + "name": "Tombar", + "id": "2199" }, { - "id": "2196", - "name": "Gauss" + "name": "Odmar", + "id": "2200" }, { - "id": "2197", - "name": "Myndill" + "name": "Drunken Dwarf", + "id": "2204" }, { - "id": "2199", - "name": "Tombar" + "name": "Dwarven Miner", + "id": "2208" }, { - "id": "2200", - "name": "Odmar" + "name": "Dwarven Miner", + "id": "2209" }, { - "id": "2204", - "name": "Drunken Dwarf" + "name": "Dwarven Miner", + "id": "2210" }, { - "id": "2208", - "name": "Dwarven Miner" + "name": "Dwarven Miner", + "id": "2211" }, { - "id": "2209", - "name": "Dwarven Miner" + "name": "Dwarven Miner", + "id": "2212" }, { - "id": "2210", - "name": "Dwarven Miner" + "name": "Dwarven Miner", + "id": "2213" }, { - "id": "2211", - "name": "Dwarven Miner" + "name": "Dwarven Miner", + "id": "2214" }, { - "id": "2212", - "name": "Dwarven Miner" + "name": "Dwarven Miner", + "id": "2215" }, { - "id": "2213", - "name": "Dwarven Miner" + "name": "Dwarven Miner", + "id": "2216" }, { - "id": "2214", - "name": "Dwarven Miner" + "name": "Dwarven Miner", + "id": "2217" }, { - "id": "2215", - "name": "Dwarven Miner" + "name": "Dwarven Miner", + "id": "2218" }, { - "id": "2216", - "name": "Dwarven Miner" + "name": "Purple Pewter Director", + "id": "2219" }, { - "id": "2217", - "name": "Dwarven Miner" + "name": "Purple Pewter Director", + "id": "2220" }, { - "id": "2218", - "name": "Dwarven Miner" + "name": "Blue Opal Director", + "id": "2221" }, { - "id": "2219", - "name": "Purple Pewter Director" + "name": "Yellow Fortune Director", + "id": "2222" }, { - "id": "2220", - "name": "Purple Pewter Director" + "name": "Green Gemstone Director", + "id": "2223" }, { - "id": "2221", - "name": "Blue Opal Director" + "name": "White Chisel Director", + "id": "2224" }, { - "id": "2222", - "name": "Yellow Fortune Director" + "name": "Silver Cog Director", + "id": "2225" }, { - "id": "2223", - "name": "Green Gemstone Director" + "name": "Brown Engine Director", + "id": "2226" }, { - "id": "2224", - "name": "White Chisel Director" + "name": "Red Axe Director", + "id": "2227" }, { - "id": "2225", - "name": "Silver Cog Director" + "name": "Commander Veldaban", + "id": "2228" }, { - "id": "2226", - "name": "Brown Engine Director" + "name": "Red Axe Cat", + "id": "2229" }, { - "id": "2227", - "name": "Red Axe Director" + "name": "Red Axe Cat", + "id": "2230" }, { - "id": "2228", - "name": "Commander Veldaban" + "name": "Black Guard Berserker", + "id": "2231" }, { - "id": "2229", - "name": "Red Axe Cat" + "name": "Olivia", + "id": "2233" }, { - "id": "2230", - "name": "Red Axe Cat" + "name": "Donie", + "id": "2238" }, { - "id": "2231", - "name": "Black Guard Berserker" + "name": "Pig", + "id": "2239" }, { - "id": "2233", - "name": "Olivia" + "name": "Gnome", + "id": "2251" }, { - "id": "2238", - "name": "Donie" + "name": "Crow", + "id": "2252" }, { - "id": "2239", - "name": "Pig" + "name": "Bed", + "id": "2254" }, { - "id": "2251", - "name": "Gnome" + "name": "Thing under the bed", + "id": "2255" }, { - "id": "2252", - "name": "Crow" + "name": "Mage of Zamorak", + "id": "2257" }, { - "id": "2254", - "name": "Bed" + "name": "Mage of Zamorak", + "id": "2259" }, { - "id": "2255", - "name": "Thing under the bed" + "name": "Mage of Zamorak", + "id": "2260" }, { - "id": "2257", - "name": "Mage of Zamorak" + "name": "Brian O'Richard", + "id": "2266" }, { - "id": "2259", - "name": "Mage of Zamorak" + "name": "Rogue Guard", + "id": "2268" }, { - "id": "2260", - "name": "Mage of Zamorak" + "name": "Martin Thwait", + "id": "2270" }, { - "id": "2266", - "name": "Brian O'Richard" + "name": "Spin Blades", + "id": "2273" }, { - "id": "2268", - "name": "Rogue Guard" + "name": "Rug Merchant", + "id": "2295" }, { - "id": "2270", - "name": "Martin Thwait" + "name": "Rug Merchant", + "id": "2297" }, { - "id": "2273", - "name": "Spin Blades" + "name": "Rug Station Attendant", + "id": "2299" }, { - "id": "2295", - "name": "Rug Merchant" + "name": "Sarah", + "id": "2302" }, { - "id": "2297", - "name": "Rug Merchant" + "name": "Vanessa", + "id": "2305" }, { - "id": "2299", - "name": "Rug Station Attendant" + "name": "Richard", + "id": "2306" }, { - "id": "2302", - "name": "Sarah" + "name": "Alice", + "id": "2307" }, { - "id": "2305", - "name": "Vanessa" + "name": "Capt' Arnav", + "id": "2308" }, { - "id": "2306", - "name": "Richard" + "name": "Metarialus", + "id": "2322" }, { - "id": "2307", - "name": "Alice" + "name": "Sick-looking sheep (1)", + "id": "2345" }, { - "id": "2308", - "name": "Capt' Arnav" + "name": "Sick-looking sheep (2)", + "id": "2346" }, { - "id": "2322", - "name": "Metarialus" + "name": "Sick-looking sheep (3)", + "id": "2347" }, { - "id": "2345", - "name": "Sick-looking sheep (1)" + "name": "Sick-looking sheep (4)", + "id": "2348" }, { - "id": "2346", - "name": "Sick-looking sheep (2)" + "name": "Mourner", + "id": "2349" }, { - "id": "2347", - "name": "Sick-looking sheep (3)" + "name": "Mourner", + "id": "2350" }, { - "id": "2348", - "name": "Sick-looking sheep (4)" + "name": "Mourner", + "id": "2351" }, { - "id": "2349", - "name": "Mourner" + "name": "Eudav", + "id": "2352" }, { - "id": "2350", - "name": "Mourner" + "name": "Oronwen", + "id": "2353" }, { - "id": "2351", - "name": "Mourner" + "name": "Dalldav", + "id": "2356" }, { - "id": "2352", - "name": "Eudav" + "name": "Gethin", + "id": "2357" }, { - "id": "2353", - "name": "Oronwen" + "name": "Arianwyn", + "id": "2358" }, { - "id": "2356", - "name": "Dalldav" + "name": "Goreu", + "id": "2363" }, { - "id": "2357", - "name": "Gethin" + "name": "Ysgawyn", + "id": "2364" }, { - "id": "2358", - "name": "Arianwyn" + "name": "Arvel", + "id": "2365" }, { - "id": "2363", - "name": "Goreu" + "name": "Mawrth", + "id": "2366" }, { - "id": "2364", - "name": "Ysgawyn" + "name": "Kelyn", + "id": "2367" }, { - "id": "2365", - "name": "Arvel" + "name": "Eoin", + "id": "2368" }, { - "id": "2366", - "name": "Mawrth" + "name": "Iona", + "id": "2369" }, { - "id": "2367", - "name": "Kelyn" + "name": "Gnome", + "id": "2370" }, { - "id": "2368", - "name": "Eoin" + "name": "Eluned", + "id": "2375" }, { - "id": "2369", - "name": "Iona" + "name": "Sick-looking sheep (1)", + "id": "2377" }, { - "id": "2370", - "name": "Gnome" + "name": "Sick-looking sheep (2)", + "id": "2378" }, { - "id": "2375", - "name": "Eluned" + "name": "Sick-looking sheep (3)", + "id": "2379" }, { - "id": "2377", - "name": "Sick-looking sheep (1)" + "name": "Sick-looking sheep (4)", + "id": "2380" }, { - "id": "2378", - "name": "Sick-looking sheep (2)" + "name": "Cart conductor", + "id": "2403" }, { - "id": "2379", - "name": "Sick-looking sheep (3)" + "name": "Red Axe Director", + "id": "2410" }, { - "id": "2380", - "name": "Sick-looking sheep (4)" + "name": "Red Axe Director", + "id": "2411" }, { - "id": "2381", - "name": "Mysterious ghost" + "name": "Red Axe Henchman", + "id": "2412" }, { - "id": "2403", - "name": "Cart conductor" + "name": "Red Axe Henchman", + "id": "2413" }, { - "id": "2410", - "name": "Red Axe Director" + "name": "Red Axe Henchman", + "id": "2414" }, { - "id": "2411", - "name": "Red Axe Director" + "name": "Colonel Grimsson", + "id": "2415" }, { - "id": "2412", - "name": "Red Axe Henchman" + "name": "Colonel Grimsson", + "id": "2416" }, { - "id": "2413", - "name": "Red Axe Henchman" + "name": "Ogre shaman", + "id": "2417" }, { - "id": "2414", - "name": "Red Axe Henchman" + "name": "Ogre shaman", + "id": "2418" }, { - "id": "2415", - "name": "Colonel Grimsson" + "name": "Grunsh", + "id": "2419" }, { - "id": "2416", - "name": "Colonel Grimsson" + "name": "Gnome emissary", + "id": "2420" }, { - "id": "2417", - "name": "Ogre shaman" + "name": "Gnome companion", + "id": "2421" }, { - "id": "2418", - "name": "Ogre shaman" + "name": "Gnome companion", + "id": "2422" }, { - "id": "2419", - "name": "Grunsh" + "name": "Gunslik", + "id": "2424" }, { - "id": "2420", - "name": "Gnome emissary" + "name": "Nolar", + "id": "2425" }, { - "id": "2421", - "name": "Gnome companion" + "name": "Factory Worker", + "id": "2426" }, { - "id": "2422", - "name": "Gnome companion" + "name": "Cart conductor", + "id": "2427" }, { - "id": "2424", - "name": "Gunslik" + "name": "Gauss", + "id": "2428" }, { - "id": "2425", - "name": "Nolar" + "name": "Drunken Dwarf", + "id": "2429" }, { - "id": "2426", - "name": "Factory Worker" + "name": "Rowdy dwarf", + "id": "2430" }, { - "id": "2427", - "name": "Cart conductor" + "name": "Ulifed", + "id": "2431" }, { - "id": "2428", - "name": "Gauss" + "name": "Red Axe Henchman", + "id": "2432" }, { - "id": "2429", - "name": "Drunken Dwarf" + "name": "Red Axe Henchman", + "id": "2433" }, { - "id": "2430", - "name": "Rowdy dwarf" + "name": "Ogre shaman", + "id": "2434" }, { - "id": "2431", - "name": "Ulifed" + "name": "Jarvald", + "id": "2435" }, { - "id": "2432", - "name": "Red Axe Henchman" + "name": "Jarvald", + "id": "2437" }, { - "id": "2433", - "name": "Red Axe Henchman" + "name": "Jarvald", + "id": "2438" }, { - "id": "2434", - "name": "Ogre shaman" + "name": "Door-support", + "id": "2440" }, { - "id": "2435", - "name": "Jarvald" + "name": "Door", + "id": "2441" }, { - "id": "2437", - "name": "Jarvald" + "name": "Door", + "id": "2442" }, { - "id": "2438", - "name": "Jarvald" + "name": "Door", + "id": "2444" }, { - "id": "2440", - "name": "Door-support" + "name": "Door", + "id": "2445" }, { - "id": "2441", - "name": "Door" + "name": "Door-support", + "id": "2446" }, { - "id": "2442", - "name": "Door" + "name": "Door", + "id": "2447" }, { - "id": "2444", - "name": "Door" + "name": "Door", + "id": "2448" }, { - "id": "2445", - "name": "Door" + "name": "Egg", + "id": "2449" }, { - "id": "2446", - "name": "Door-support" + "name": "Egg", + "id": "2450" }, { - "id": "2447", - "name": "Door" + "name": "Egg", + "id": "2451" }, { - "id": "2448", - "name": "Door" + "name": "Freaky Forester", + "id": "2458" }, { - "id": "2449", - "name": "Egg" + "name": "Frog", + "id": "2469" }, { - "id": "2450", - "name": "Egg" + "name": "Frog", + "id": "2470" }, { - "id": "2451", - "name": "Egg" + "name": "Frog", + "id": "2471" }, { - "id": "2458", - "name": "Freaky Forester" + "name": "Frog", + "id": "2472" }, { - "id": "2469", - "name": "Frog" + "name": "Frog", + "id": "2473" }, { - "id": "2470", - "name": "Frog" + "name": "Frog prince", + "id": "2474" }, { - "id": "2471", - "name": "Frog" + "name": "Frog princess", + "id": "2475" }, { - "id": "2472", - "name": "Frog" + "name": "Evil Bob", + "id": "2478" }, { - "id": "2473", - "name": "Frog" + "name": "Servant", + "id": "2480" }, { - "id": "2474", - "name": "Frog prince" + "name": "Bush snake", + "id": "2483" }, { - "id": "2475", - "name": "Frog princess" + "name": "Broodoo victim", + "id": "2498" }, { - "id": "2478", - "name": "Evil Bob" + "name": "Broodoo victim", + "id": "2500" }, { - "id": "2480", - "name": "Servant" + "name": "Broodoo victim", + "id": "2502" }, { - "id": "2483", - "name": "Bush snake" + "name": "Sharimika", + "id": "2504" }, { - "id": "2498", - "name": "Broodoo victim" + "name": "Mamma Bufetta", + "id": "2507" }, { - "id": "2500", - "name": "Broodoo victim" + "name": "Layleen", + "id": "2510" }, { - "id": "2502", - "name": "Broodoo victim" + "name": "Karaday", + "id": "2513" }, { - "id": "2504", - "name": "Sharimika" + "name": "Safta Doc", + "id": "2516" }, { - "id": "2507", - "name": "Mamma Bufetta" + "name": "Gabooty", + "id": "2519" }, { - "id": "2510", - "name": "Layleen" + "name": "Fanellaman", + "id": "2522" }, { - "id": "2513", - "name": "Karaday" + "name": "Jagbakoba", + "id": "2525" }, { - "id": "2516", - "name": "Safta Doc" + "name": "Murcaily", + "id": "2528" }, { - "id": "2519", - "name": "Gabooty" + "name": "Rionasta", + "id": "2531" }, { - "id": "2522", - "name": "Fanellaman" + "name": "Mahogany", + "id": "2534" }, { - "id": "2525", - "name": "Jagbakoba" + "name": "Teak", + "id": "2535" }, { - "id": "2528", - "name": "Murcaily" + "name": "Niles", + "id": "2536" }, { - "id": "2531", - "name": "Rionasta" + "name": "Miles", + "id": "2537" }, { - "id": "2534", - "name": "Mahogany" + "name": "Giles", + "id": "2538" }, { - "id": "2535", - "name": "Teak" + "name": "Dr Jekyll", + "id": "2540" }, { - "id": "2536", - "name": "Niles" + "name": "Mr Hyde", + "id": "2541" }, { - "id": "2537", - "name": "Miles" + "name": "Mr Hyde", + "id": "2542" }, { - "id": "2538", - "name": "Giles" + "name": "Mr Hyde", + "id": "2543" }, { - "id": "2540", - "name": "Dr Jekyll" + "name": "Mr Hyde", + "id": "2544" }, { - "id": "2541", - "name": "Mr Hyde" + "name": "Mr Hyde", + "id": "2545" }, { - "id": "2542", - "name": "Mr Hyde" + "name": "Mr Hyde", + "id": "2546" }, { - "id": "2543", - "name": "Mr Hyde" + "name": "Blackjack seller", + "id": "2548" }, { - "id": "2544", - "name": "Mr Hyde" + "name": "Dwarven Miner", + "id": "2550" }, { - "id": "2545", - "name": "Mr Hyde" + "name": "Dwarven Miner", + "id": "2551" }, { - "id": "2546", - "name": "Mr Hyde" + "name": "Dwarven Miner", + "id": "2552" }, { - "id": "2548", - "name": "Blackjack seller" + "name": "Tin ore", + "id": "2554" }, { - "id": "2550", - "name": "Dwarven Miner" + "name": "Copper ore", + "id": "2555" }, { - "id": "2551", - "name": "Dwarven Miner" + "name": "Iron ore", + "id": "2556" }, { - "id": "2552", - "name": "Dwarven Miner" + "name": "Mithril ore", + "id": "2557" }, { - "id": "2554", - "name": "Tin ore" + "name": "Adamantite ore", + "id": "2558" }, { - "id": "2555", - "name": "Copper ore" + "name": "Runite ore", + "id": "2559" }, { - "id": "2556", - "name": "Iron ore" + "name": "Silver ore", + "id": "2560" }, { - "id": "2557", - "name": "Mithril ore" + "name": "Gold ore", + "id": "2561" }, { - "id": "2558", - "name": "Adamantite ore" + "name": "Coal", + "id": "2562" }, { - "id": "2559", - "name": "Runite ore" + "name": "Perfect gold ore", + "id": "2563" }, { - "id": "2560", - "name": "Silver ore" + "name": "Wise Old Man", + "id": "2566" }, { - "id": "2561", - "name": "Gold ore" + "name": "Wise Old Man", + "id": "2567" }, { - "id": "2562", - "name": "Coal" + "name": "Banker", + "id": "2568" }, { - "id": "2563", - "name": "Perfect gold ore" + "name": "Banker", + "id": "2569" }, { - "id": "2566", - "name": "Wise Old Man" + "name": "Banker", + "id": "2570" }, { - "id": "2567", - "name": "Wise Old Man" + "name": "Pillory Guard", + "id": "2573" }, { - "id": "2568", - "name": "Banker" + "name": "Purepker895", + "id": "2575" }, { - "id": "2569", - "name": "Banker" + "name": "Qutiedoll", + "id": "2576" }, { - "id": "2570", - "name": "Banker" + "name": "1337sp34kr", + "id": "2577" }, { - "id": "2573", - "name": "Pillory Guard" + "name": "Elfinlocks", + "id": "2578" }, { - "id": "2575", - "name": "Purepker895" + "name": "Cool Mom227", + "id": "2579" }, { - "id": "2576", - "name": "Qutiedoll" + "name": "Ellamaria", + "id": "2581" }, { - "id": "2577", - "name": "1337sp34kr" + "name": "Trolley", + "id": "2582" }, { - "id": "2578", - "name": "Elfinlocks" + "name": "Trolley", + "id": "2583" }, { - "id": "2579", - "name": "Cool Mom227" + "name": "Trolley", + "id": "2584" }, { - "id": "2581", - "name": "Ellamaria" + "name": "Billy, a guard of Falador", + "id": "2585" }, { - "id": "2582", - "name": "Trolley" + "name": "Bob, another guard of Falador", + "id": "2587" }, { - "id": "2583", - "name": "Trolley" + "name": "PKMaster0036", + "id": "2589" }, { - "id": "2584", - "name": "Trolley" + "name": "King Roald", + "id": "2590" }, { - "id": "2585", - "name": "Billy, a guard of Falador" + "name": "TzHaar-Mej-Jal", + "id": "2617" }, { - "id": "2587", - "name": "Bob, another guard of Falador" + "name": "TzHaar-Mej-Kah", + "id": "2618" }, { - "id": "2589", - "name": "PKMaster0036" + "name": "TzHaar-Ket-Zuh", + "id": "2619" }, { - "id": "2590", - "name": "King Roald" + "name": "TzHaar-Hur-Tel", + "id": "2620" }, { - "id": "2617", - "name": "TzHaar-Mej-Jal" + "name": "TzHaar-Hur-Lek", + "id": "2622" }, { - "id": "2618", - "name": "TzHaar-Mej-Kah" + "name": "TzHaar-Mej-Roh", + "id": "2623" }, { - "id": "2619", - "name": "TzHaar-Ket-Zuh" + "name": "TzHaar-Ket", + "id": "2624" }, { - "id": "2620", - "name": "TzHaar-Hur-Tel" + "name": "TzHaar-Ket", + "id": "2625" }, { - "id": "2622", - "name": "TzHaar-Hur-Lek" + "name": "Rocks", + "id": "2626" }, { - "id": "2623", - "name": "TzHaar-Mej-Roh" + "name": "Tok-Xil", + "id": "2632" }, { - "id": "2624", - "name": "TzHaar-Ket" + "name": "Wise Old Man", + "id": "2633" }, { - "id": "2625", - "name": "TzHaar-Ket" + "name": "Bob", + "id": "2635" }, { - "id": "2626", - "name": "Rocks" + "name": "Bob", + "id": "2636" }, { - "id": "2632", - "name": "Tok-Xil" + "name": "Sphinx", + "id": "2637" }, { - "id": "2633", - "name": "Wise Old Man" + "name": "Neite", + "id": "2638" }, { - "id": "2635", - "name": "Bob" + "name": "Robert the Strong", + "id": "2639" }, { - "id": "2636", - "name": "Bob" + "name": "Odysseus", + "id": "2640" }, { - "id": "2637", - "name": "Sphinx" + "name": "King Black Dragon", + "id": "2642" }, { - "id": "2638", - "name": "Neite" + "name": "R4ng3rNo0b889", + "id": "2643" }, { - "id": "2639", - "name": "Robert the Strong" + "name": "Love Cats", + "id": "2644" }, { - "id": "2640", - "name": "Odysseus" + "name": "Love Cats", + "id": "2645" }, { - "id": "2642", - "name": "King Black Dragon" + "name": "Neite", + "id": "2646" }, { - "id": "2643", - "name": "R4ng3rNo0b889" + "name": "Bob", + "id": "2647" }, { - "id": "2644", - "name": "Love Cats" + "name": "Beite", + "id": "2648" }, { - "id": "2645", - "name": "Love Cats" + "name": "Gnome", + "id": "2649" }, { - "id": "2646", - "name": "Neite" + "name": "Gnome", + "id": "2650" }, { - "id": "2647", - "name": "Bob" + "name": "Odysseus", + "id": "2651" }, { - "id": "2648", - "name": "Beite" + "name": "Neite", + "id": "2652" }, { - "id": "2649", - "name": "Gnome" + "name": "Unferth", + "id": "2654" }, { - "id": "2650", - "name": "Gnome" + "name": "Unferth", + "id": "2656" }, { - "id": "2651", - "name": "Odysseus" + "name": "Unferth", + "id": "2657" }, { - "id": "2652", - "name": "Neite" + "name": "Unferth", + "id": "2658" }, { - "id": "2654", - "name": "Unferth" + "name": "Unferth", + "id": "2659" }, { - "id": "2656", - "name": "Unferth" + "name": "Reldo", + "id": "2661" }, { - "id": "2657", - "name": "Unferth" + "name": "Lazy cat", + "id": "2662" }, { - "id": "2658", - "name": "Unferth" + "name": "Lazy cat", + "id": "2663" }, { - "id": "2659", - "name": "Unferth" + "name": "Lazy cat", + "id": "2664" }, { - "id": "2661", - "name": "Reldo" + "name": "Lazy cat", + "id": "2665" }, { - "id": "2662", - "name": "Lazy cat" + "name": "Lazy cat", + "id": "2666" }, { - "id": "2663", - "name": "Lazy cat" + "name": "Lazy cat", + "id": "2667" }, { - "id": "2664", - "name": "Lazy cat" + "name": "Wily cat", + "id": "2668" }, { - "id": "2665", - "name": "Lazy cat" + "name": "Wily cat", + "id": "2669" }, { - "id": "2666", - "name": "Lazy cat" + "name": "Wily cat", + "id": "2670" }, { - "id": "2667", - "name": "Lazy cat" + "name": "Wily cat", + "id": "2671" }, { - "id": "2668", - "name": "Wily cat" + "name": "Wily cat", + "id": "2672" }, { - "id": "2669", - "name": "Wily cat" + "name": "Wily cat", + "id": "2673" }, { - "id": "2670", - "name": "Wily cat" - }, - { - "id": "2671", - "name": "Wily cat" - }, - { - "id": "2672", - "name": "Wily cat" - }, - { - "id": "2673", - "name": "Wily cat" - }, - { - "id": "2676", - "name": "Make-over Mage" + "name": "Make-over Mage", + "id": "2676" }, { "examine": "So what can one do with a drunken sailor?", - "id": "2692", - "name": "Ahab" + "name": "Ahab", + "id": "2692" }, { - "id": "2708", - "name": "Seagull" + "name": "Seagull", + "id": "2708" }, { - "id": "2719", - "name": "Grum" + "name": "Grum", + "id": "2719" }, { - "id": "2720", - "name": "Gerrant" + "name": "Gerrant", + "id": "2720" }, { - "id": "2721", - "name": "Wydin" + "name": "Wydin", + "id": "2721" }, { - "id": "2722", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "2722" }, { - "id": "2723", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "2723" }, { - "id": "2724", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "2724" }, { - "id": "2727", - "name": "Gull" + "name": "Gull", + "id": "2727" }, { - "id": "2730", - "name": "Monk of Entrana" + "name": "Monk of Entrana", + "id": "2730" }, { - "id": "2747", - "name": "Solus Dellagar" + "name": "Solus Dellagar", + "id": "2747" }, { - "id": "2748", - "name": "Savant" + "name": "Savant", + "id": "2748" }, { - "id": "2749", - "name": "Lord Daquarius" + "name": "Lord Daquarius", + "id": "2749" }, { - "id": "2750", - "name": "Solus Dellagar" + "name": "Solus Dellagar", + "id": "2750" }, { - "id": "2751", - "name": "Black Knight" + "name": "Black Knight", + "id": "2751" }, { - "id": "2752", - "name": "Lord Daquarius" + "name": "Lord Daquarius", + "id": "2752" }, { - "id": "2753", - "name": "Mage of Zamorak" + "name": "Mage of Zamorak", + "id": "2753" }, { - "id": "2754", - "name": "Mage of Zamorak" + "name": "Mage of Zamorak", + "id": "2754" }, { - "id": "2755", - "name": "Mage of Zamorak" + "name": "Mage of Zamorak", + "id": "2755" }, { - "id": "2756", - "name": "Woman" + "name": "Woman", + "id": "2756" }, { - "id": "2777", - "name": "Black Knight" + "name": "Black Knight", + "id": "2777" }, { - "id": "2778", - "name": "Black Knight" + "name": "Black Knight", + "id": "2778" }, { - "id": "2780", - "name": "Solus Dellagar" + "name": "Solus Dellagar", + "id": "2780" }, { - "id": "2781", - "name": "Gnome guard" + "name": "Gnome guard", + "id": "2781" }, { - "id": "2788", - "name": "Thorgel" + "name": "Thorgel", + "id": "2788" }, { - "id": "2791", - "name": "Pillory Guard" + "name": "Pillory Guard", + "id": "2791" }, { - "id": "2793", - "name": "Tramp" + "name": "Tramp", + "id": "2793" }, { - "id": "2794", - "name": "Tramp" + "name": "Tramp", + "id": "2794" }, { - "id": "2795", - "name": "Skippy" + "name": "Skippy", + "id": "2795" }, { - "id": "2797", - "name": "Skippy" + "name": "Skippy", + "id": "2797" }, { - "id": "2798", - "name": "Skippy" + "name": "Skippy", + "id": "2798" }, { - "id": "2799", - "name": "Skippy" + "name": "Skippy", + "id": "2799" }, { - "id": "2800", - "name": "A pile of broken glass" + "name": "A pile of broken glass", + "id": "2800" }, { - "id": "2813", - "name": "Alice the Camel" + "name": "Alice the Camel", + "id": "2813" }, { - "id": "2816", - "name": "Ali the Smith" + "name": "Ali the Smith", + "id": "2816" }, { - "id": "2821", - "name": "Ali the Farmer" + "name": "Ali the Farmer", + "id": "2821" }, { - "id": "2822", - "name": "Ali the Tailor" + "name": "Ali the Tailor", + "id": "2822" }, { - "id": "2823", - "name": "Ali the Guard" + "name": "Ali the Guard", + "id": "2823" }, { - "id": "2829", - "name": "Davey" + "name": "Davey", + "id": "2829" }, { - "id": "2859", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "2859" }, { - "id": "2862", - "name": "Death" + "name": "Death", + "id": "2862" }, { - "id": "2864", - "name": "Most of a Zombie" + "name": "Most of a Zombie", + "id": "2864" }, { - "id": "2865", - "name": "Most of a Zombie" + "name": "Most of a Zombie", + "id": "2865" }, { - "id": "2867", - "name": "Most of a Zombie" + "name": "Most of a Zombie", + "id": "2867" }, { - "id": "2868", - "name": "Zombie Head" + "name": "Zombie Head", + "id": "2868" }, { - "id": "2870", - "name": "Half-Zombie" + "name": "Half-Zombie", + "id": "2870" }, { - "id": "2871", - "name": "Other Half-Zombie" + "name": "Other Half-Zombie", + "id": "2871" }, { - "id": "2872", - "name": "Child" + "name": "Child", + "id": "2872" }, { - "id": "2873", - "name": "Child" + "name": "Child", + "id": "2873" }, { - "id": "2874", - "name": "Child" + "name": "Child", + "id": "2874" }, { - "id": "2875", - "name": "Child" + "name": "Child", + "id": "2875" }, { - "id": "2876", - "name": "Child" + "name": "Child", + "id": "2876" }, { - "id": "2877", - "name": "Child" + "name": "Child", + "id": "2877" }, { - "id": "2879", - "name": "Bardur" + "name": "Bardur", + "id": "2879" }, { - "id": "2884", - "name": "Wallasalki" + "name": "Wallasalki", + "id": "2884" }, { - "id": "2891", - "name": "Suspicious water" + "name": "Suspicious water", + "id": "2891" }, { - "id": "2893", - "name": "Suspicious water" + "name": "Suspicious water", + "id": "2893" }, { - "id": "2895", - "name": "Suspicious water" + "name": "Suspicious water", + "id": "2895" }, { - "id": "2897", - "name": "Father Reen" + "name": "Father Reen", + "id": "2897" }, { - "id": "2900", - "name": "Father Reen" + "name": "Father Reen", + "id": "2900" }, { - "id": "2901", - "name": "Father Badden" + "name": "Father Badden", + "id": "2901" }, { - "id": "2903", - "name": "Father Badden" + "name": "Father Badden", + "id": "2903" }, { - "id": "2904", - "name": "Denath" + "name": "Denath", + "id": "2904" }, { - "id": "2905", - "name": "Denath" + "name": "Denath", + "id": "2905" }, { - "id": "2906", - "name": "Eric" + "name": "Eric", + "id": "2906" }, { - "id": "2907", - "name": "Eric" + "name": "Eric", + "id": "2907" }, { - "id": "2908", - "name": "Evil Dave" + "name": "Evil Dave", + "id": "2908" }, { - "id": "2910", - "name": "Evil Dave" + "name": "Evil Dave", + "id": "2910" }, { - "id": "2911", - "name": "Matthew" + "name": "Matthew", + "id": "2911" }, { - "id": "2912", - "name": "Matthew" + "name": "Matthew", + "id": "2912" }, { - "id": "2913", - "name": "Jennifer" + "name": "Jennifer", + "id": "2913" }, { - "id": "2914", - "name": "Jennifer" + "name": "Jennifer", + "id": "2914" }, { - "id": "2915", - "name": "Tanya" + "name": "Tanya", + "id": "2915" }, { - "id": "2916", - "name": "Tanya" + "name": "Tanya", + "id": "2916" }, { - "id": "2917", - "name": "Patrick" + "name": "Patrick", + "id": "2917" }, { - "id": "2918", - "name": "Patrick" + "name": "Patrick", + "id": "2918" }, { - "id": "2920", - "name": "Sand storm" + "name": "Sand storm", + "id": "2920" }, { - "id": "2922", - "name": "Clay golem" + "name": "Clay golem", + "id": "2922" }, { - "id": "2928", - "name": "Clay golem" + "name": "Clay golem", + "id": "2928" }, { - "id": "2929", - "name": "Ghost" + "name": "Ghost", + "id": "2929" }, { - "id": "2932", - "name": "Jorral" + "name": "Jorral", + "id": "2932" }, { - "id": "2933", - "name": "Melina" + "name": "Melina", + "id": "2933" }, { - "id": "2935", - "name": "Melina" + "name": "Melina", + "id": "2935" }, { - "id": "2936", - "name": "Droalak" + "name": "Droalak", + "id": "2936" }, { - "id": "2938", - "name": "Droalak" + "name": "Droalak", + "id": "2938" }, { - "id": "2939", - "name": "Dron" + "name": "Dron", + "id": "2939" }, { - "id": "2940", - "name": "Blanin" + "name": "Blanin", + "id": "2940" }, { - "id": "2943", - "name": "Pox" + "name": "Pox", + "id": "2943" }, { - "id": "2944", - "name": "Pox" + "name": "Pox", + "id": "2944" }, { "examine": "Cracking personality.", - "id": "2946", - "name": "Grimesquit" + "name": "Grimesquit", + "id": "2946" }, { "examine": "Lovely girl, shame about the smell.", - "id": "2947", - "name": "Phingspet" + "name": "Phingspet", + "id": "2947" }, { - "id": "2951", - "name": "Felkrash" + "name": "Felkrash", + "id": "2951" }, { - "id": "2953", - "name": "Ceril Carnillean" + "name": "Ceril Carnillean", + "id": "2953" }, { - "id": "2954", - "name": "Councillor Halgrive" + "name": "Councillor Halgrive", + "id": "2954" }, { - "id": "2955", - "name": "Spice seller" + "name": "Spice seller", + "id": "2955" }, { - "id": "2956", - "name": "Fur trader" + "name": "Fur trader", + "id": "2956" }, { - "id": "2957", - "name": "Gem merchant" + "name": "Gem merchant", + "id": "2957" }, { - "id": "2959", - "name": "Silk merchant" + "name": "Silk merchant", + "id": "2959" }, { - "id": "2960", - "name": "Zenesha" + "name": "Zenesha", + "id": "2960" }, { - "id": "2961", - "name": "Ali Morrisane" + "name": "Ali Morrisane", + "id": "2961" }, { - "id": "2974", - "name": "Rat" + "name": "Rat", + "id": "2974" }, { - "id": "2983", - "name": "Turbogroomer" + "name": "Turbogroomer", + "id": "2983" }, { - "id": "2985", - "name": "Loki" + "name": "Loki", + "id": "2985" }, { - "id": "2987", - "name": "Treacle" + "name": "Treacle", + "id": "2987" }, { - "id": "2989", - "name": "Claude" + "name": "Claude", + "id": "2989" }, { - "id": "2991", - "name": "Rauborn" + "name": "Rauborn", + "id": "2991" }, { - "id": "2992", - "name": "Vaeringk" + "name": "Vaeringk", + "id": "2992" }, { - "id": "2993", - "name": "Oxi" + "name": "Oxi", + "id": "2993" }, { - "id": "2994", - "name": "Fior" + "name": "Fior", + "id": "2994" }, { - "id": "2995", - "name": "Sagira" + "name": "Sagira", + "id": "2995" }, { - "id": "2996", - "name": "Anleif" + "name": "Anleif", + "id": "2996" }, { - "id": "2999", - "name": "Gambler" + "name": "Gambler", + "id": "2999" }, { - "id": "3000", - "name": "Barman" + "name": "Barman", + "id": "3000" }, { - "id": "3019", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "3019" }, { - "id": "3020", - "name": "Rug Merchant" + "name": "Rug Merchant", + "id": "3020" }, { - "id": "3023", - "name": "Nirrie" + "name": "Nirrie", + "id": "3023" }, { - "id": "3024", - "name": "Tirrie" + "name": "Tirrie", + "id": "3024" }, { - "id": "3025", - "name": "Hallak" + "name": "Hallak", + "id": "3025" }, { - "id": "3031", - "name": "Usi" + "name": "Usi", + "id": "3031" }, { - "id": "3032", - "name": "Nkuku" + "name": "Nkuku", + "id": "3032" }, { - "id": "3033", - "name": "Garai" + "name": "Garai", + "id": "3033" }, { - "id": "3034", - "name": "Habibah" + "name": "Habibah", + "id": "3034" }, { - "id": "3035", - "name": "Meskhenet" + "name": "Meskhenet", + "id": "3035" }, { - "id": "3036", - "name": "Zahra" + "name": "Zahra", + "id": "3036" }, { - "id": "3037", - "name": "Zahur" + "name": "Zahur", + "id": "3037" }, { - "id": "3038", - "name": "Seddu" + "name": "Seddu", + "id": "3038" }, { - "id": "3039", - "name": "Kazemde" + "name": "Kazemde", + "id": "3039" }, { - "id": "3041", - "name": "Tarik" + "name": "Tarik", + "id": "3041" }, { - "id": "3045", - "name": "Rokuh" + "name": "Rokuh", + "id": "3045" }, { - "id": "3047", - "name": "Target" + "name": "Target", + "id": "3047" }, { - "id": "3048", - "name": "Target" + "name": "Target", + "id": "3048" }, { - "id": "3049", - "name": "Larxus" + "name": "Larxus", + "id": "3049" }, { - "id": "3076", - "name": "Dead Monk" + "name": "Dead Monk", + "id": "3076" }, { - "id": "3078", - "name": "High Priest" + "name": "High Priest", + "id": "3078" }, { - "id": "3081", - "name": "Assassin" + "name": "Assassin", + "id": "3081" }, { - "id": "3082", - "name": "Rosie" + "name": "Rosie", + "id": "3082" }, { - "id": "3083", - "name": "Sorcha" + "name": "Sorcha", + "id": "3083" }, { - "id": "3084", - "name": "Cait" + "name": "Cait", + "id": "3084" }, { - "id": "3085", - "name": "Cormac" + "name": "Cormac", + "id": "3085" }, { - "id": "3086", - "name": "Fionn" + "name": "Fionn", + "id": "3086" }, { - "id": "3087", - "name": "Donnacha" + "name": "Donnacha", + "id": "3087" }, { - "id": "3088", - "name": "Ronan" + "name": "Ronan", + "id": "3088" }, { - "id": "3093", - "name": "Flying Book" + "name": "Flying Book", + "id": "3093" }, { - "id": "3095", - "name": "Flying Book" + "name": "Flying Book", + "id": "3095" }, { - "id": "3096", - "name": "Pizzaz Hat" + "name": "Pizzaz Hat", + "id": "3096" }, { - "id": "3107", - "name": "Charmed Warrior" + "name": "Charmed Warrior", + "id": "3107" }, { - "id": "3108", - "name": "Bert" + "name": "Bert", + "id": "3108" }, { - "id": "3110", - "name": "Sandy" + "name": "Sandy", + "id": "3110" }, { - "id": "3113", - "name": "Sandy" + "name": "Sandy", + "id": "3113" }, { - "id": "3114", - "name": "Mazion" + "name": "Mazion", + "id": "3114" }, { - "id": "3116", - "name": "Reeso" + "name": "Reeso", + "id": "3116" }, { - "id": "3118", - "name": "Prison Pete" + "name": "Prison Pete", + "id": "3118" }, { - "id": "3119", - "name": "Balloon Animal" + "name": "Balloon Animal", + "id": "3119" }, { - "id": "3120", - "name": "Balloon Animal" + "name": "Balloon Animal", + "id": "3120" }, { - "id": "3124", - "name": "Pyramid block" + "name": "Pyramid block", + "id": "3124" }, { - "id": "3125", - "name": "Pyramid block" + "name": "Pyramid block", + "id": "3125" }, { - "id": "3126", - "name": "Pentyn" + "name": "Pentyn", + "id": "3126" }, { - "id": "3127", - "name": "Aristarchus" + "name": "Aristarchus", + "id": "3127" }, { - "id": "3128", - "name": "Boneguard" + "name": "Boneguard", + "id": "3128" }, { - "id": "3130", - "name": "Pile of bones" + "name": "Pile of bones", + "id": "3130" }, { - "id": "3131", - "name": "Desert Spirit" + "name": "Desert Spirit", + "id": "3131" }, { - "id": "3132", - "name": "Crust of ice" + "name": "Crust of ice", + "id": "3132" }, { - "id": "3134", - "name": "Furnace grate" + "name": "Furnace grate", + "id": "3134" }, { - "id": "3136", - "name": "Enakhra" + "name": "Enakhra", + "id": "3136" }, { - "id": "3138", - "name": "Enakhra" + "name": "Enakhra", + "id": "3138" }, { - "id": "3139", - "name": "Boneguard" + "name": "Boneguard", + "id": "3139" }, { - "id": "3141", - "name": "Akthanakos" + "name": "Akthanakos", + "id": "3141" }, { - "id": "3142", - "name": "Akthanakos" + "name": "Akthanakos", + "id": "3142" }, { - "id": "3143", - "name": "Lazim" + "name": "Lazim", + "id": "3143" }, { - "id": "3148", - "name": "Enakhra" + "name": "Enakhra", + "id": "3148" }, { - "id": "3149", - "name": "Akthanakos" + "name": "Akthanakos", + "id": "3149" }, { - "id": "3152", - "name": "Harpie Bug Swarm" + "name": "Harpie Bug Swarm", + "id": "3152" }, { - "id": "3154", - "name": "Count Draynor" + "name": "Count Draynor", + "id": "3154" }, { - "id": "3156", - "name": "Bill Teach" + "name": "Bill Teach", + "id": "3156" }, { - "id": "3157", - "name": "Bill Teach" + "name": "Bill Teach", + "id": "3157" }, { - "id": "3158", - "name": "Bill Teach" + "name": "Bill Teach", + "id": "3158" }, { - "id": "3159", - "name": "Bill Teach" + "name": "Bill Teach", + "id": "3159" }, { - "id": "3160", - "name": "Bill Teach" + "name": "Bill Teach", + "id": "3160" }, { - "id": "3161", - "name": "Charley" + "name": "Charley", + "id": "3161" }, { - "id": "3162", - "name": "Smith" + "name": "Smith", + "id": "3162" }, { - "id": "3163", - "name": "Joe" + "name": "Joe", + "id": "3163" }, { - "id": "3164", - "name": "Mama" + "name": "Mama", + "id": "3164" }, { - "id": "3165", - "name": "Mama" + "name": "Mama", + "id": "3165" }, { - "id": "3197", - "name": "Gull" + "name": "Gull", + "id": "3197" }, { - "id": "3205", - "name": "Romily Weaklax" + "name": "Romily Weaklax", + "id": "3205" }, { - "id": "3206", - "name": "Priest" + "name": "Priest", + "id": "3206" }, { - "id": "3207", - "name": "Pious Pete" + "name": "Pious Pete", + "id": "3207" }, { - "id": "3208", - "name": "Taper" + "name": "Taper", + "id": "3208" }, { - "id": "3210", - "name": "Alrena" + "name": "Alrena", + "id": "3210" }, { - "id": "3211", - "name": "Alrena" + "name": "Alrena", + "id": "3211" }, { - "id": "3212", - "name": "Bravek" + "name": "Bravek", + "id": "3212" }, { - "id": "3218", - "name": "Tina" + "name": "Tina", + "id": "3218" }, { - "id": "3254", - "name": "Hunding" + "name": "Hunding", + "id": "3254" }, { - "id": "3281", - "name": "Engineering assistant" + "name": "Engineering assistant", + "id": "3281" }, { - "id": "3284", - "name": "Squirrel" + "name": "Squirrel", + "id": "3284" }, { - "id": "3285", - "name": "Squirrel" + "name": "Squirrel", + "id": "3285" }, { - "id": "3287", - "name": "Raccoon" + "name": "Raccoon", + "id": "3287" }, { - "id": "3289", - "name": "Skeleton" + "name": "Skeleton", + "id": "3289" }, { - "id": "3292", - "name": "Witch" + "name": "Witch", + "id": "3292" }, { - "id": "3300", - "name": "Frog" + "name": "Frog", + "id": "3300" }, { - "id": "3301", - "name": "Storm cloud" + "name": "Storm cloud", + "id": "3301" }, { - "id": "3303", - "name": "Fairy Nuff" + "name": "Fairy Nuff", + "id": "3303" }, { - "id": "3305", - "name": "Slim Louie" + "name": "Slim Louie", + "id": "3305" }, { - "id": "3306", - "name": "Fat Rocco" + "name": "Fat Rocco", + "id": "3306" }, { - "id": "3308", - "name": "Zandar Horfyre" + "name": "Zandar Horfyre", + "id": "3308" }, { - "id": "3311", - "name": "Sheep" + "name": "Sheep", + "id": "3311" }, { - "id": "3312", - "name": "Zanaris choir" + "name": "Zanaris choir", + "id": "3312" }, { - "id": "3314", - "name": "Baby tanglefoot" + "name": "Baby tanglefoot", + "id": "3314" }, { - "id": "3321", - "name": "Gatekeeper" + "name": "Gatekeeper", + "id": "3321" }, { - "id": "3323", - "name": "Draul Leptoc" + "name": "Draul Leptoc", + "id": "3323" }, { - "id": "3326", - "name": "Martina Scorsby" + "name": "Martina Scorsby", + "id": "3326" }, { - "id": "3328", - "name": "Tarquin" + "name": "Tarquin", + "id": "3328" }, { - "id": "3329", - "name": "Sigurd" + "name": "Sigurd", + "id": "3329" }, { - "id": "3330", - "name": "Hari" + "name": "Hari", + "id": "3330" }, { - "id": "3332", - "name": "Trees" + "name": "Trees", + "id": "3332" }, { - "id": "3333", - "name": "Trees" + "name": "Trees", + "id": "3333" }, { - "id": "3335", - "name": "Bullrush" + "name": "Bullrush", + "id": "3335" }, { - "id": "3336", - "name": "Bullrush" + "name": "Bullrush", + "id": "3336" }, { - "id": "3337", - "name": "Cave scenery" + "name": "Cave scenery", + "id": "3337" }, { - "id": "3338", - "name": "Cave scenery" + "name": "Cave scenery", + "id": "3338" }, { - "id": "3339", - "name": "Cave scenery" + "name": "Cave scenery", + "id": "3339" }, { - "id": "3351", - "name": "Genie" + "name": "Genie", + "id": "3351" }, { - "id": "3352", - "name": "Mysterious Old Man" + "name": "Mysterious Old Man", + "id": "3352" }, { - "id": "3353", - "name": "Swarm" + "name": "Swarm", + "id": "3353" }, { - "id": "3354", - "name": "Cap'n Hand" + "name": "Cap'n Hand", + "id": "3354" }, { - "id": "3355", - "name": "Rick Turpentine" + "name": "Rick Turpentine", + "id": "3355" }, { - "id": "3356", - "name": "Niles" + "name": "Niles", + "id": "3356" }, { - "id": "3357", - "name": "Miles" + "name": "Miles", + "id": "3357" }, { - "id": "3358", - "name": "Giles" + "name": "Giles", + "id": "3358" }, { - "id": "3359", - "name": "Dr Jekyll" + "name": "Dr Jekyll", + "id": "3359" }, { - "id": "3360", - "name": "Mr Hyde" + "name": "Mr Hyde", + "id": "3360" }, { - "id": "3361", - "name": "Mr Hyde" + "name": "Mr Hyde", + "id": "3361" }, { - "id": "3362", - "name": "Mr Hyde" + "name": "Mr Hyde", + "id": "3362" }, { - "id": "3363", - "name": "Mr Hyde" + "name": "Mr Hyde", + "id": "3363" }, { - "id": "3364", - "name": "Mr Hyde" + "name": "Mr Hyde", + "id": "3364" }, { - "id": "3365", - "name": "Mr Hyde" + "name": "Mr Hyde", + "id": "3365" }, { - "id": "3372", - "name": "Sir Amik Varze" + "name": "Sir Amik Varze", + "id": "3372" }, { - "id": "3373", - "name": "Sir Amik Varze" + "name": "Sir Amik Varze", + "id": "3373" }, { - "id": "3377", - "name": "K'klik" + "name": "K'klik", + "id": "3377" }, { - "id": "3379", - "name": "Evil Dave" + "name": "Evil Dave", + "id": "3379" }, { - "id": "3381", - "name": "Doris" + "name": "Doris", + "id": "3381" }, { - "id": "3383", - "name": "Gypsy" + "name": "Gypsy", + "id": "3383" }, { - "id": "3386", - "name": "Gypsy" + "name": "Gypsy", + "id": "3386" }, { - "id": "3387", - "name": "Culinaromancer" + "name": "Culinaromancer", + "id": "3387" }, { - "id": "3388", - "name": "Osman" + "name": "Osman", + "id": "3388" }, { - "id": "3395", - "name": "Sir Amik Varze" + "name": "Sir Amik Varze", + "id": "3395" }, { - "id": "3396", - "name": "Awowogei" + "name": "Awowogei", + "id": "3396" }, { - "id": "3397", - "name": "Awowogei" + "name": "Awowogei", + "id": "3397" }, { - "id": "3398", - "name": "Skrach Uglogwee" + "name": "Skrach Uglogwee", + "id": "3398" }, { - "id": "3399", - "name": "Culinaromancer" + "name": "Culinaromancer", + "id": "3399" }, { - "id": "3401", - "name": "An old Dwarf" + "name": "An old Dwarf", + "id": "3401" }, { - "id": "3403", - "name": "Rohak" + "name": "Rohak", + "id": "3403" }, { - "id": "3417", - "name": "Pirate Pete" + "name": "Pirate Pete", + "id": "3417" }, { - "id": "3428", - "name": "Fish" + "name": "Fish", + "id": "3428" }, { - "id": "3429", - "name": "Fish" + "name": "Fish", + "id": "3429" }, { - "id": "3430", - "name": "Fish" + "name": "Fish", + "id": "3430" }, { - "id": "3440", - "name": "Fish" + "name": "Fish", + "id": "3440" }, { - "id": "3441", - "name": "Fish" + "name": "Fish", + "id": "3441" }, { - "id": "3442", - "name": "Fish" + "name": "Fish", + "id": "3442" }, { - "id": "3446", - "name": "Fish" + "name": "Fish", + "id": "3446" }, { - "id": "3447", - "name": "Fish" + "name": "Fish", + "id": "3447" }, { - "id": "3448", - "name": "Fish" + "name": "Fish", + "id": "3448" }, { - "id": "3452", - "name": "? ? ? ?" + "name": "? ? ? ?", + "id": "3452" }, { - "id": "3453", - "name": "? ? ? ?" + "name": "? ? ? ?", + "id": "3453" }, { - "id": "3454", - "name": "? ? ? ?" + "name": "? ? ? ?", + "id": "3454" }, { - "id": "3455", - "name": "? ? ? ?" + "name": "? ? ? ?", + "id": "3455" }, { - "id": "3456", - "name": "? ? ? ?" + "name": "? ? ? ?", + "id": "3456" }, { - "id": "3457", - "name": "? ? ? ?" + "name": "? ? ? ?", + "id": "3457" }, { - "id": "3458", - "name": "? ? ? ?" + "name": "? ? ? ?", + "id": "3458" }, { - "id": "3459", - "name": "? ? ? ?" + "name": "? ? ? ?", + "id": "3459" }, { - "id": "3460", - "name": "? ? ? ?" + "name": "? ? ? ?", + "id": "3460" }, { - "id": "3461", - "name": "? ? ? ?" + "name": "? ? ? ?", + "id": "3461" }, { - "id": "3462", - "name": "Skrach Uglogwee" + "name": "Skrach Uglogwee", + "id": "3462" }, { - "id": "3464", - "name": "Skrach Uglogwee" + "name": "Skrach Uglogwee", + "id": "3464" }, { - "id": "3465", - "name": "Nung" + "name": "Nung", + "id": "3465" }, { - "id": "3466", - "name": "Ogre" + "name": "Ogre", + "id": "3466" }, { - "id": "3467", - "name": "Rantz" + "name": "Rantz", + "id": "3467" }, { - "id": "3468", - "name": "Rantz" + "name": "Rantz", + "id": "3468" }, { - "id": "3469", - "name": "Ogre boat" + "name": "Ogre boat", + "id": "3469" }, { - "id": "3472", - "name": "Ogre boat" + "name": "Ogre boat", + "id": "3472" }, { - "id": "3473", - "name": "Balloon Toad" + "name": "Balloon Toad", + "id": "3473" }, { - "id": "3474", - "name": "Balloon Toad" + "name": "Balloon Toad", + "id": "3474" }, { - "id": "3475", - "name": "Balloon Toad" + "name": "Balloon Toad", + "id": "3475" }, { - "id": "3477", - "name": "Jubbly bird" + "name": "Jubbly bird", + "id": "3477" }, { - "id": "3479", - "name": "King Awowogei" + "name": "King Awowogei", + "id": "3479" }, { - "id": "3481", - "name": "Mizaru" + "name": "Mizaru", + "id": "3481" }, { - "id": "3482", - "name": "Kikazaru" + "name": "Kikazaru", + "id": "3482" }, { - "id": "3483", - "name": "Iwazaru" + "name": "Iwazaru", + "id": "3483" }, { - "id": "3485", - "name": "Culinaromancer" + "name": "Culinaromancer", + "id": "3485" }, { - "id": "3486", - "name": "Culinaromancer" + "name": "Culinaromancer", + "id": "3486" }, { - "id": "3487", - "name": "Culinaromancer" + "name": "Culinaromancer", + "id": "3487" }, { - "id": "3488", - "name": "Culinaromancer" + "name": "Culinaromancer", + "id": "3488" }, { - "id": "3489", - "name": "Culinaromancer" + "name": "Culinaromancer", + "id": "3489" }, { - "id": "3490", - "name": "Culinaromancer" + "name": "Culinaromancer", + "id": "3490" }, { - "id": "3492", - "name": "Culinaromancer" + "name": "Culinaromancer", + "id": "3492" }, { - "id": "3503", - "name": "Overgrown hellcat" + "name": "Overgrown hellcat", + "id": "3503" }, { - "id": "3504", - "name": "Hellcat" + "name": "Hellcat", + "id": "3504" }, { - "id": "3505", - "name": "Hell-kitten" + "name": "Hell-kitten", + "id": "3505" }, { - "id": "3506", - "name": "Lazy hellcat" + "name": "Lazy hellcat", + "id": "3506" }, { - "id": "3507", - "name": "Wily hellcat" + "name": "Wily hellcat", + "id": "3507" }, { - "id": "3508", - "name": "Leo" + "name": "Leo", + "id": "3508" }, { - "id": "3510", - "name": "Wiskit" + "name": "Wiskit", + "id": "3510" }, { - "id": "3512", - "name": "Vampyre Juvinate" + "name": "Vampyre Juvinate", + "id": "3512" }, { - "id": "3515", - "name": "Vampyre Juvinate" + "name": "Vampyre Juvinate", + "id": "3515" }, { - "id": "3516", - "name": "Gadderanks" + "name": "Gadderanks", + "id": "3516" }, { - "id": "3518", - "name": "Gadderanks" + "name": "Gadderanks", + "id": "3518" }, { - "id": "3519", - "name": "Gadderanks" + "name": "Gadderanks", + "id": "3519" }, { - "id": "3520", - "name": "Vampyre Juvinate" + "name": "Vampyre Juvinate", + "id": "3520" }, { - "id": "3528", - "name": "Vampyre Juvinate" + "name": "Vampyre Juvinate", + "id": "3528" }, { - "id": "3529", - "name": "Vampyre Juvinate" + "name": "Vampyre Juvinate", + "id": "3529" }, { - "id": "3530", - "name": "Mist" + "name": "Mist", + "id": "3530" }, { - "id": "3535", - "name": "Ivan Strom" + "name": "Ivan Strom", + "id": "3535" }, { - "id": "3536", - "name": "Ivan Strom" + "name": "Ivan Strom", + "id": "3536" }, { - "id": "3537", - "name": "Vampyre Juvinate" + "name": "Vampyre Juvinate", + "id": "3537" }, { - "id": "3538", - "name": "Vampyre Juvinate" + "name": "Vampyre Juvinate", + "id": "3538" }, { - "id": "3539", - "name": "Veliaf Hurtz" + "name": "Veliaf Hurtz", + "id": "3539" }, { - "id": "3540", - "name": "Elisabeta" + "name": "Elisabeta", + "id": "3540" }, { - "id": "3541", - "name": "Aurel" + "name": "Aurel", + "id": "3541" }, { - "id": "3542", - "name": "Sorin" + "name": "Sorin", + "id": "3542" }, { - "id": "3543", - "name": "Luscion" + "name": "Luscion", + "id": "3543" }, { - "id": "3544", - "name": "Sergiu" + "name": "Sergiu", + "id": "3544" }, { - "id": "3545", - "name": "Radu" + "name": "Radu", + "id": "3545" }, { - "id": "3546", - "name": "Grigore" + "name": "Grigore", + "id": "3546" }, { - "id": "3547", - "name": "Ileana" + "name": "Ileana", + "id": "3547" }, { - "id": "3548", - "name": "Valeria" + "name": "Valeria", + "id": "3548" }, { - "id": "3549", - "name": "Emilia" + "name": "Emilia", + "id": "3549" }, { - "id": "3550", - "name": "Florin" + "name": "Florin", + "id": "3550" }, { - "id": "3551", - "name": "Catalina" + "name": "Catalina", + "id": "3551" }, { - "id": "3552", - "name": "Ivan" + "name": "Ivan", + "id": "3552" }, { - "id": "3553", - "name": "Victor" + "name": "Victor", + "id": "3553" }, { - "id": "3554", - "name": "Helena" + "name": "Helena", + "id": "3554" }, { - "id": "3555", - "name": "Teodor" + "name": "Teodor", + "id": "3555" }, { - "id": "3556", - "name": "Marius" + "name": "Marius", + "id": "3556" }, { - "id": "3557", - "name": "Gabriela" + "name": "Gabriela", + "id": "3557" }, { - "id": "3558", - "name": "Vladimir" + "name": "Vladimir", + "id": "3558" }, { - "id": "3559", - "name": "Calin" + "name": "Calin", + "id": "3559" }, { - "id": "3560", - "name": "Mihail" + "name": "Mihail", + "id": "3560" }, { - "id": "3561", - "name": "Nicoleta" + "name": "Nicoleta", + "id": "3561" }, { - "id": "3562", - "name": "Simona" + "name": "Simona", + "id": "3562" }, { - "id": "3563", - "name": "Vasile" + "name": "Vasile", + "id": "3563" }, { - "id": "3564", - "name": "Razvan" + "name": "Razvan", + "id": "3564" }, { - "id": "3565", - "name": "Luminata" + "name": "Luminata", + "id": "3565" }, { - "id": "3566", - "name": "Cornelius" + "name": "Cornelius", + "id": "3566" }, { - "id": "3569", - "name": "Cornelius" + "name": "Cornelius", + "id": "3569" }, { - "id": "3570", - "name": "Benjamin" + "name": "Benjamin", + "id": "3570" }, { - "id": "3571", - "name": "Liam" + "name": "Liam", + "id": "3571" }, { - "id": "3572", - "name": "Miala" + "name": "Miala", + "id": "3572" }, { - "id": "3573", - "name": "Verak" + "name": "Verak", + "id": "3573" }, { - "id": "3574", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "3574" }, { - "id": "3575", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "3575" }, { - "id": "3576", - "name": "Juvinate" + "name": "Juvinate", + "id": "3576" }, { - "id": "3578", - "name": "Juvinate" + "name": "Juvinate", + "id": "3578" }, { - "id": "3580", - "name": "Tentacle" + "name": "Tentacle", + "id": "3580" }, { - "id": "3584", - "name": "Troll" + "name": "Troll", + "id": "3584" }, { - "id": "3592", - "name": "Tok-Xil" + "name": "Tok-Xil", + "id": "3592" }, { - "id": "3594", - "name": "Rocnar" + "name": "Rocnar", + "id": "3594" }, { - "id": "3595", - "name": "Toy Soldier" + "name": "Toy Soldier", + "id": "3595" }, { - "id": "3596", - "name": "Toy Doll" + "name": "Toy Doll", + "id": "3596" }, { - "id": "3597", - "name": "Toy Mouse" + "name": "Toy Mouse", + "id": "3597" }, { - "id": "3598", - "name": "Clockwork cat" + "name": "Clockwork cat", + "id": "3598" }, { - "id": "3606", - "name": "Ghast" + "name": "Ghast", + "id": "3606" }, { - "id": "3607", - "name": "Ghast" + "name": "Ghast", + "id": "3607" }, { - "id": "3608", - "name": "Ghast" + "name": "Ghast", + "id": "3608" }, { - "id": "3609", - "name": "Ghast" + "name": "Ghast", + "id": "3609" }, { - "id": "3610", - "name": "Ghast" + "name": "Ghast", + "id": "3610" }, { - "id": "3611", - "name": "Ghast" + "name": "Ghast", + "id": "3611" }, { - "id": "3612", - "name": "Giant snail" + "name": "Giant snail", + "id": "3612" }, { - "id": "3613", - "name": "Giant snail" + "name": "Giant snail", + "id": "3613" }, { - "id": "3614", - "name": "Giant snail" + "name": "Giant snail", + "id": "3614" }, { - "id": "3615", - "name": "Riyl shadow" + "name": "Riyl shadow", + "id": "3615" }, { - "id": "3616", - "name": "Asyn shadow" + "name": "Asyn shadow", + "id": "3616" }, { - "id": "3617", - "name": "Shade" + "name": "Shade", + "id": "3617" }, { - "id": "3621", - "name": "Tentacle" + "name": "Tentacle", + "id": "3621" }, { - "id": "3623", - "name": "Smiddi Ryak" + "name": "Smiddi Ryak", + "id": "3623" }, { - "id": "3625", - "name": "Rolayne Twickit" + "name": "Rolayne Twickit", + "id": "3625" }, { - "id": "3627", - "name": "Jayene Kliyn" + "name": "Jayene Kliyn", + "id": "3627" }, { - "id": "3629", - "name": "Valantay Eppel" + "name": "Valantay Eppel", + "id": "3629" }, { - "id": "3631", - "name": "Dalcian Fang" + "name": "Dalcian Fang", + "id": "3631" }, { - "id": "3633", - "name": "Fyiona Fray" + "name": "Fyiona Fray", + "id": "3633" }, { - "id": "3635", - "name": "Abidor Crank" + "name": "Abidor Crank", + "id": "3635" }, { - "id": "3636", - "name": "Spirit tree" + "name": "Spirit tree", + "id": "3636" }, { - "id": "3637", - "name": "Spirit tree" + "name": "Spirit tree", + "id": "3637" }, { - "id": "3638", - "name": "Launa" + "name": "Launa", + "id": "3638" }, { - "id": "3640", - "name": "Launa" + "name": "Launa", + "id": "3640" }, { - "id": "3641", - "name": "Brana" + "name": "Brana", + "id": "3641" }, { - "id": "3642", - "name": "Mawnis Burowgar" + "name": "Mawnis Burowgar", + "id": "3642" }, { - "id": "3643", - "name": "Tolna" + "name": "Tolna", + "id": "3643" }, { - "id": "3644", - "name": "Tolna" + "name": "Tolna", + "id": "3644" }, { - "id": "3651", - "name": "Confusion beast" + "name": "Confusion beast", + "id": "3651" }, { - "id": "3652", - "name": "Confusion beast" + "name": "Confusion beast", + "id": "3652" }, { - "id": "3653", - "name": "Confusion beast" + "name": "Confusion beast", + "id": "3653" }, { - "id": "3654", - "name": "Confusion beast" + "name": "Confusion beast", + "id": "3654" }, { - "id": "3656", - "name": "Hopeless creature" + "name": "Hopeless creature", + "id": "3656" }, { - "id": "3657", - "name": "Hopeless creature" + "name": "Hopeless creature", + "id": "3657" }, { - "id": "3658", - "name": "Tolna" + "name": "Tolna", + "id": "3658" }, { - "id": "3659", - "name": "Tolna" + "name": "Tolna", + "id": "3659" }, { - "id": "3660", - "name": "Tolna" + "name": "Tolna", + "id": "3660" }, { - "id": "3668", - "name": "Hopeless beast" + "name": "Hopeless beast", + "id": "3668" }, { - "id": "3669", - "name": "Hopeless beast" + "name": "Hopeless beast", + "id": "3669" }, { - "id": "3677", - "name": "Sinister Stranger" + "name": "Sinister Stranger", + "id": "3677" }, { - "id": "3678", - "name": "Sinister Stranger" + "name": "Sinister Stranger", + "id": "3678" }, { - "id": "3679", - "name": "Vestri" + "name": "Vestri", + "id": "3679" }, { - "id": "3681", - "name": "Nigel" + "name": "Nigel", + "id": "3681" }, { - "id": "3682", - "name": "Egg" + "name": "Egg", + "id": "3682" }, { - "id": "3683", - "name": "Egg" + "name": "Egg", + "id": "3683" }, { - "id": "3684", - "name": "Egg" + "name": "Egg", + "id": "3684" }, { - "id": "3685", - "name": "Egg" + "name": "Egg", + "id": "3685" }, { - "id": "3686", - "name": "Chocolate kebbit" + "name": "Chocolate kebbit", + "id": "3686" }, { - "id": "3687", - "name": "Chocolate kebbit" + "name": "Chocolate kebbit", + "id": "3687" }, { - "id": "3688", - "name": "Easter Bunny" + "name": "Easter Bunny", + "id": "3688" }, { - "id": "3689", - "name": "Egg" + "name": "Egg", + "id": "3689" }, { - "id": "3690", - "name": "Egg" + "name": "Egg", + "id": "3690" }, { - "id": "3691", - "name": "Egg" + "name": "Egg", + "id": "3691" }, { - "id": "3692", - "name": "Egg" + "name": "Egg", + "id": "3692" }, { - "id": "3693", - "name": "Egg" + "name": "Egg", + "id": "3693" }, { - "id": "3694", - "name": "Egg" + "name": "Egg", + "id": "3694" }, { - "id": "3695", - "name": "Volf Olafson" + "name": "Volf Olafson", + "id": "3695" }, { - "id": "3696", - "name": "Ingrid Hradson" + "name": "Ingrid Hradson", + "id": "3696" }, { - "id": "3708", - "name": "Boulder" + "name": "Boulder", + "id": "3708" }, { - "id": "3710", - "name": "Ulfric" + "name": "Ulfric", + "id": "3710" }, { - "id": "3712", - "name": "Zanik" + "name": "Zanik", + "id": "3712" }, { - "id": "3713", - "name": "Sigmund" + "name": "Sigmund", + "id": "3713" }, { - "id": "3714", - "name": "Zanik" + "name": "Zanik", + "id": "3714" }, { - "id": "3716", - "name": "Sigmund" + "name": "Sigmund", + "id": "3716" }, { - "id": "3717", - "name": "Sigmund" + "name": "Sigmund", + "id": "3717" }, { - "id": "3718", - "name": "Sigmund" + "name": "Sigmund", + "id": "3718" }, { - "id": "3719", - "name": "Sigmund" + "name": "Sigmund", + "id": "3719" }, { - "id": "3720", - "name": "Sigmund" + "name": "Sigmund", + "id": "3720" }, { - "id": "3721", - "name": "Zanik" + "name": "Zanik", + "id": "3721" }, { - "id": "3722", - "name": "Zanik" + "name": "Zanik", + "id": "3722" }, { - "id": "3723", - "name": "General Bentnoze" + "name": "General Bentnoze", + "id": "3723" }, { - "id": "3724", - "name": "General Wartface" + "name": "General Wartface", + "id": "3724" }, { - "id": "3725", - "name": "Grubfoot" + "name": "Grubfoot", + "id": "3725" }, { - "id": "3778", - "name": "Arthur" + "name": "Arthur", + "id": "3778" }, { - "id": "3780", - "name": "Squire" + "name": "Squire", + "id": "3780" }, { - "id": "3787", - "name": "Sir Palomedes" + "name": "Sir Palomedes", + "id": "3787" }, { - "id": "3789", - "name": "Void Knight" + "name": "Void Knight", + "id": "3789" }, { - "id": "3803", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "3803" }, { - "id": "3817", - "name": "Lieutenant Schepbur" + "name": "Lieutenant Schepbur", + "id": "3817" }, { - "id": "3820", - "name": "Wise Old Man" + "name": "Wise Old Man", + "id": "3820" }, { - "id": "3825", - "name": "Devin Mendelberg" + "name": "Devin Mendelberg", + "id": "3825" }, { - "id": "3826", - "name": "George Laxmeister" + "name": "George Laxmeister", + "id": "3826" }, { - "id": "3827", - "name": "Ramara du Croissant" + "name": "Ramara du Croissant", + "id": "3827" }, { - "id": "3828", - "name": "Kathy Corkat" + "name": "Kathy Corkat", + "id": "3828" }, { - "id": "3831", - "name": "Kathy Corkat" + "name": "Kathy Corkat", + "id": "3831" }, { - "id": "3832", - "name": "Kalphite Queen" + "name": "Kalphite Queen", + "id": "3832" }, { - "id": "3837", - "name": "Drunken Dwarf" + "name": "Drunken Dwarf", + "id": "3837" }, { - "id": "3838", - "name": "Wise Old Man" + "name": "Wise Old Man", + "id": "3838" }, { - "id": "3839", - "name": "Wise Old Man" + "name": "Wise Old Man", + "id": "3839" }, { - "id": "3841", - "name": "Sea troll" + "name": "Sea troll", + "id": "3841" }, { - "id": "3842", - "name": "Sea troll" + "name": "Sea troll", + "id": "3842" }, { - "id": "3844", - "name": "Skeleton Mage" + "name": "Skeleton Mage", + "id": "3844" }, { - "id": "3845", - "name": "Sea troll" + "name": "Sea troll", + "id": "3845" }, { - "id": "3846", - "name": "Sea Troll General" + "name": "Sea Troll General", + "id": "3846" }, { - "id": "3848", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "3848" }, { - "id": "3850", - "name": "Skeleton Mage" + "name": "Skeleton Mage", + "id": "3850" }, { - "id": "3852", - "name": "Suspect" + "name": "Suspect", + "id": "3852" }, { - "id": "3853", - "name": "Suspect" + "name": "Suspect", + "id": "3853" }, { - "id": "3854", - "name": "Suspect" + "name": "Suspect", + "id": "3854" }, { - "id": "3855", - "name": "Suspect" + "name": "Suspect", + "id": "3855" }, { - "id": "3856", - "name": "Suspect" + "name": "Suspect", + "id": "3856" }, { - "id": "3857", - "name": "Suspect" + "name": "Suspect", + "id": "3857" }, { - "id": "3858", - "name": "Suspect" + "name": "Suspect", + "id": "3858" }, { - "id": "3859", - "name": "Suspect" + "name": "Suspect", + "id": "3859" }, { - "id": "3860", - "name": "Suspect" + "name": "Suspect", + "id": "3860" }, { - "id": "3861", - "name": "Suspect" + "name": "Suspect", + "id": "3861" }, { - "id": "3862", - "name": "Suspect" + "name": "Suspect", + "id": "3862" }, { - "id": "3863", - "name": "Suspect" + "name": "Suspect", + "id": "3863" }, { - "id": "3864", - "name": "Suspect" + "name": "Suspect", + "id": "3864" }, { - "id": "3865", - "name": "Suspect" + "name": "Suspect", + "id": "3865" }, { - "id": "3866", - "name": "Suspect" + "name": "Suspect", + "id": "3866" }, { - "id": "3867", - "name": "Suspect" + "name": "Suspect", + "id": "3867" }, { - "id": "3868", - "name": "Suspect" + "name": "Suspect", + "id": "3868" }, { - "id": "3869", - "name": "Suspect" + "name": "Suspect", + "id": "3869" }, { - "id": "3870", - "name": "Suspect" + "name": "Suspect", + "id": "3870" }, { - "id": "3871", - "name": "Suspect" + "name": "Suspect", + "id": "3871" }, { - "id": "3872", - "name": "Suspect" + "name": "Suspect", + "id": "3872" }, { - "id": "3873", - "name": "Suspect" + "name": "Suspect", + "id": "3873" }, { - "id": "3874", - "name": "Suspect" + "name": "Suspect", + "id": "3874" }, { - "id": "3875", - "name": "Suspect" + "name": "Suspect", + "id": "3875" }, { - "id": "3876", - "name": "Suspect" + "name": "Suspect", + "id": "3876" }, { - "id": "3877", - "name": "Suspect" + "name": "Suspect", + "id": "3877" }, { - "id": "3878", - "name": "Suspect" + "name": "Suspect", + "id": "3878" }, { - "id": "3879", - "name": "Suspect" + "name": "Suspect", + "id": "3879" }, { - "id": "3880", - "name": "Suspect" + "name": "Suspect", + "id": "3880" }, { - "id": "3881", - "name": "Suspect" + "name": "Suspect", + "id": "3881" }, { - "id": "3882", - "name": "Suspect" + "name": "Suspect", + "id": "3882" }, { - "id": "3883", - "name": "Suspect" + "name": "Suspect", + "id": "3883" }, { - "id": "3884", - "name": "Suspect" + "name": "Suspect", + "id": "3884" }, { - "id": "3885", - "name": "Suspect" + "name": "Suspect", + "id": "3885" }, { - "id": "3886", - "name": "Suspect" + "name": "Suspect", + "id": "3886" }, { - "id": "3887", - "name": "Suspect" + "name": "Suspect", + "id": "3887" }, { - "id": "3888", - "name": "Suspect" + "name": "Suspect", + "id": "3888" }, { - "id": "3889", - "name": "Suspect" + "name": "Suspect", + "id": "3889" }, { - "id": "3890", - "name": "Suspect" + "name": "Suspect", + "id": "3890" }, { - "id": "3891", - "name": "Suspect" + "name": "Suspect", + "id": "3891" }, { - "id": "3892", - "name": "Molly" + "name": "Molly", + "id": "3892" }, { - "id": "3893", - "name": "Molly" + "name": "Molly", + "id": "3893" }, { - "id": "3894", - "name": "Molly" + "name": "Molly", + "id": "3894" }, { - "id": "3895", - "name": "Molly" + "name": "Molly", + "id": "3895" }, { - "id": "3896", - "name": "Molly" + "name": "Molly", + "id": "3896" }, { - "id": "3897", - "name": "Molly" + "name": "Molly", + "id": "3897" }, { - "id": "3898", - "name": "Molly" + "name": "Molly", + "id": "3898" }, { - "id": "3899", - "name": "Molly" + "name": "Molly", + "id": "3899" }, { - "id": "3900", - "name": "Molly" + "name": "Molly", + "id": "3900" }, { - "id": "3901", - "name": "Molly" + "name": "Molly", + "id": "3901" }, { - "id": "3902", - "name": "Molly" + "name": "Molly", + "id": "3902" }, { - "id": "3903", - "name": "Molly" + "name": "Molly", + "id": "3903" }, { - "id": "3904", - "name": "Molly" + "name": "Molly", + "id": "3904" }, { - "id": "3905", - "name": "Molly" + "name": "Molly", + "id": "3905" }, { - "id": "3906", - "name": "Molly" + "name": "Molly", + "id": "3906" }, { - "id": "3907", - "name": "Molly" + "name": "Molly", + "id": "3907" }, { - "id": "3908", - "name": "Molly" + "name": "Molly", + "id": "3908" }, { - "id": "3909", - "name": "Molly" + "name": "Molly", + "id": "3909" }, { - "id": "3910", - "name": "Molly" + "name": "Molly", + "id": "3910" }, { - "id": "3911", - "name": "Molly" + "name": "Molly", + "id": "3911" }, { - "id": "3912", - "name": "Flippa" + "name": "Flippa", + "id": "3912" }, { - "id": "3913", - "name": "Tilt" + "name": "Tilt", + "id": "3913" }, { - "id": "3914", - "name": "Gardener" + "name": "Gardener", + "id": "3914" }, { - "id": "3918", - "name": "Prince Brand" + "name": "Prince Brand", + "id": "3918" }, { - "id": "3919", - "name": "Princess Astrid" + "name": "Princess Astrid", + "id": "3919" }, { - "id": "3920", - "name": "Runa" + "name": "Runa", + "id": "3920" }, { - "id": "3923", - "name": "Osvald" + "name": "Osvald", + "id": "3923" }, { - "id": "3924", - "name": "Runolf" + "name": "Runolf", + "id": "3924" }, { - "id": "3926", - "name": "Ingrid" + "name": "Ingrid", + "id": "3926" }, { - "id": "3928", - "name": "Signy" + "name": "Signy", + "id": "3928" }, { - "id": "3929", - "name": "Hild" + "name": "Hild", + "id": "3929" }, { - "id": "3930", - "name": "Armod" + "name": "Armod", + "id": "3930" }, { - "id": "3931", - "name": "Beigarth" + "name": "Beigarth", + "id": "3931" }, { - "id": "3932", - "name": "Reinn" + "name": "Reinn", + "id": "3932" }, { - "id": "3936", - "name": "Thorodin" + "name": "Thorodin", + "id": "3936" }, { - "id": "3944", - "name": "Hangman game" + "name": "Hangman game", + "id": "3944" }, { - "id": "3945", - "name": "Hangman game" + "name": "Hangman game", + "id": "3945" }, { - "id": "3946", - "name": "Hangman game" + "name": "Hangman game", + "id": "3946" }, { - "id": "3947", - "name": "Hangman game" + "name": "Hangman game", + "id": "3947" }, { - "id": "3948", - "name": "Hangman game" + "name": "Hangman game", + "id": "3948" }, { - "id": "3949", - "name": "Hangman game" + "name": "Hangman game", + "id": "3949" }, { - "id": "3950", - "name": "Hangman game" + "name": "Hangman game", + "id": "3950" }, { - "id": "3951", - "name": "Hangman game" + "name": "Hangman game", + "id": "3951" }, { - "id": "3952", - "name": "Hangman game" + "name": "Hangman game", + "id": "3952" }, { - "id": "3953", - "name": "Hangman game" + "name": "Hangman game", + "id": "3953" }, { - "id": "3954", - "name": "Treasure fairy" + "name": "Treasure fairy", + "id": "3954" }, { - "id": "3955", - "name": "Jacky Jester" + "name": "Jacky Jester", + "id": "3955" }, { - "id": "3956", - "name": "Combat stone" + "name": "Combat stone", + "id": "3956" }, { - "id": "3957", - "name": "Combat stone" + "name": "Combat stone", + "id": "3957" }, { - "id": "3958", - "name": "Combat stone" + "name": "Combat stone", + "id": "3958" }, { - "id": "3959", - "name": "Combat stone" + "name": "Combat stone", + "id": "3959" }, { - "id": "3960", - "name": "Combat stone" + "name": "Combat stone", + "id": "3960" }, { - "id": "3961", - "name": "Combat stone" + "name": "Combat stone", + "id": "3961" }, { - "id": "3962", - "name": "Combat stone" + "name": "Combat stone", + "id": "3962" }, { - "id": "3963", - "name": "Combat stone" + "name": "Combat stone", + "id": "3963" }, { - "id": "3964", - "name": "Combat stone" + "name": "Combat stone", + "id": "3964" }, { - "id": "3965", - "name": "Combat stone" + "name": "Combat stone", + "id": "3965" }, { - "id": "3966", - "name": "Combat stone" + "name": "Combat stone", + "id": "3966" }, { - "id": "3967", - "name": "Combat stone" + "name": "Combat stone", + "id": "3967" }, { - "id": "3968", - "name": "Combat stone" + "name": "Combat stone", + "id": "3968" }, { - "id": "3969", - "name": "Combat stone" + "name": "Combat stone", + "id": "3969" }, { - "id": "3970", - "name": "Combat stone" + "name": "Combat stone", + "id": "3970" }, { - "id": "3971", - "name": "Combat stone" + "name": "Combat stone", + "id": "3971" }, { - "id": "3972", - "name": "Combat stone" + "name": "Combat stone", + "id": "3972" }, { - "id": "3973", - "name": "Combat stone" + "name": "Combat stone", + "id": "3973" }, { - "id": "3974", - "name": "Combat stone" + "name": "Combat stone", + "id": "3974" }, { - "id": "3975", - "name": "Combat stone" + "name": "Combat stone", + "id": "3975" }, { - "id": "3976", - "name": "Combat stone" + "name": "Combat stone", + "id": "3976" }, { - "id": "3977", - "name": "Combat stone" + "name": "Combat stone", + "id": "3977" }, { - "id": "3978", - "name": "Combat stone" + "name": "Combat stone", + "id": "3978" }, { - "id": "3979", - "name": "Combat stone" + "name": "Combat stone", + "id": "3979" }, { - "id": "3980", - "name": "Combat stone" + "name": "Combat stone", + "id": "3980" }, { - "id": "3981", - "name": "Combat stone" + "name": "Combat stone", + "id": "3981" }, { - "id": "3982", - "name": "Combat stone" + "name": "Combat stone", + "id": "3982" }, { - "id": "3983", - "name": "Combat stone" + "name": "Combat stone", + "id": "3983" }, { - "id": "3984", - "name": "Combat stone" + "name": "Combat stone", + "id": "3984" }, { - "id": "3985", - "name": "Combat stone" + "name": "Combat stone", + "id": "3985" }, { - "id": "3986", - "name": "Combat stone" + "name": "Combat stone", + "id": "3986" }, { - "id": "3987", - "name": "Combat stone" + "name": "Combat stone", + "id": "3987" }, { - "id": "3988", - "name": "Combat stone" + "name": "Combat stone", + "id": "3988" }, { - "id": "3989", - "name": "Combat stone" + "name": "Combat stone", + "id": "3989" }, { - "id": "3990", - "name": "Combat stone" + "name": "Combat stone", + "id": "3990" }, { - "id": "3991", - "name": "Combat stone" + "name": "Combat stone", + "id": "3991" }, { - "id": "3992", - "name": "Combat stone" + "name": "Combat stone", + "id": "3992" }, { - "id": "3993", - "name": "Combat stone" + "name": "Combat stone", + "id": "3993" }, { - "id": "3994", - "name": "Combat stone" + "name": "Combat stone", + "id": "3994" }, { - "id": "3995", - "name": "Combat stone" + "name": "Combat stone", + "id": "3995" }, { - "id": "3996", - "name": "Combat stone" + "name": "Combat stone", + "id": "3996" }, { - "id": "3997", - "name": "Combat stone" + "name": "Combat stone", + "id": "3997" }, { - "id": "3998", - "name": "Combat stone" + "name": "Combat stone", + "id": "3998" }, { - "id": "3999", - "name": "Combat stone" + "name": "Combat stone", + "id": "3999" }, { - "id": "4000", - "name": "Combat stone" + "name": "Combat stone", + "id": "4000" }, { - "id": "4001", - "name": "Combat stone" + "name": "Combat stone", + "id": "4001" }, { - "id": "4002", - "name": "Combat stone" + "name": "Combat stone", + "id": "4002" }, { - "id": "4003", - "name": "Combat stone" + "name": "Combat stone", + "id": "4003" }, { - "id": "4004", - "name": "Combat stone" + "name": "Combat stone", + "id": "4004" }, { - "id": "4005", - "name": "Combat stone" + "name": "Combat stone", + "id": "4005" }, { - "id": "4006", - "name": "Combat stone" + "name": "Combat stone", + "id": "4006" }, { - "id": "4007", - "name": "Combat stone" + "name": "Combat stone", + "id": "4007" }, { - "id": "4008", - "name": "Combat stone" + "name": "Combat stone", + "id": "4008" }, { - "id": "4009", - "name": "Combat stone" + "name": "Combat stone", + "id": "4009" }, { - "id": "4010", - "name": "Combat stone" + "name": "Combat stone", + "id": "4010" }, { - "id": "4011", - "name": "Combat stone" + "name": "Combat stone", + "id": "4011" }, { - "id": "4012", - "name": "Combat stone" + "name": "Combat stone", + "id": "4012" }, { - "id": "4013", - "name": "Combat stone" + "name": "Combat stone", + "id": "4013" }, { - "id": "4014", - "name": "Combat stone" + "name": "Combat stone", + "id": "4014" }, { - "id": "4015", - "name": "Combat stone" + "name": "Combat stone", + "id": "4015" }, { - "id": "4016", - "name": "Combat stone" + "name": "Combat stone", + "id": "4016" }, { - "id": "4017", - "name": "Combat stone" + "name": "Combat stone", + "id": "4017" }, { - "id": "4018", - "name": "Combat stone" + "name": "Combat stone", + "id": "4018" }, { - "id": "4019", - "name": "Combat stone" + "name": "Combat stone", + "id": "4019" }, { - "id": "4020", - "name": "Combat stone" + "name": "Combat stone", + "id": "4020" }, { - "id": "4021", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4021" }, { - "id": "4022", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4022" }, { - "id": "4023", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4023" }, { - "id": "4024", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4024" }, { - "id": "4025", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4025" }, { - "id": "4026", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4026" }, { - "id": "4027", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4027" }, { - "id": "4028", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4028" }, { - "id": "4029", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4029" }, { - "id": "4030", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4030" }, { - "id": "4031", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4031" }, { - "id": "4032", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4032" }, { - "id": "4033", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4033" }, { - "id": "4034", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4034" }, { - "id": "4035", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4035" }, { - "id": "4036", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4036" }, { - "id": "4037", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4037" }, { - "id": "4038", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4038" }, { - "id": "4039", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4039" }, { - "id": "4040", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4040" }, { - "id": "4041", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4041" }, { - "id": "4042", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4042" }, { - "id": "4043", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4043" }, { - "id": "4044", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4044" }, { - "id": "4045", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4045" }, { - "id": "4046", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4046" }, { - "id": "4047", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4047" }, { - "id": "4048", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4048" }, { - "id": "4049", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4049" }, { - "id": "4050", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4050" }, { - "id": "4051", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4051" }, { - "id": "4052", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4052" }, { - "id": "4053", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4053" }, { - "id": "4054", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4054" }, { - "id": "4055", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4055" }, { - "id": "4056", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4056" }, { - "id": "4057", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4057" }, { - "id": "4058", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4058" }, { - "id": "4059", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4059" }, { - "id": "4060", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4060" }, { - "id": "4061", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4061" }, { - "id": "4062", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4062" }, { - "id": "4063", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4063" }, { - "id": "4064", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4064" }, { - "id": "4065", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4065" }, { - "id": "4066", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4066" }, { - "id": "4067", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4067" }, { - "id": "4068", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4068" }, { - "id": "4069", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4069" }, { - "id": "4070", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4070" }, { - "id": "4071", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4071" }, { - "id": "4072", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4072" }, { - "id": "4073", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4073" }, { - "id": "4074", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4074" }, { - "id": "4075", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4075" }, { - "id": "4076", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4076" }, { - "id": "4077", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4077" }, { - "id": "4078", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4078" }, { - "id": "4079", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4079" }, { - "id": "4080", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4080" }, { - "id": "4081", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4081" }, { - "id": "4082", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4082" }, { - "id": "4083", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4083" }, { - "id": "4084", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4084" }, { - "id": "4085", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4085" }, { - "id": "4086", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4086" }, { - "id": "4087", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4087" }, { - "id": "4088", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4088" }, { - "id": "4089", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4089" }, { - "id": "4090", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4090" }, { - "id": "4091", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4091" }, { - "id": "4092", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4092" }, { - "id": "4093", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4093" }, { - "id": "4094", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4094" }, { - "id": "4095", - "name": "Elemental balance" + "name": "Elemental balance", + "id": "4095" }, { - "id": "4096", - "name": "Combat stone" + "name": "Combat stone", + "id": "4096" }, { - "id": "4097", - "name": "Combat stone" + "name": "Combat stone", + "id": "4097" }, { - "id": "4098", - "name": "Combat stone" + "name": "Combat stone", + "id": "4098" }, { - "id": "4099", - "name": "Combat stone" + "name": "Combat stone", + "id": "4099" }, { - "id": "4100", - "name": "Combat stone" + "name": "Combat stone", + "id": "4100" }, { - "id": "4101", - "name": "Combat stone" + "name": "Combat stone", + "id": "4101" }, { - "id": "4102", - "name": "Combat stone" + "name": "Combat stone", + "id": "4102" }, { - "id": "4103", - "name": "Combat stone" + "name": "Combat stone", + "id": "4103" }, { - "id": "4104", - "name": "Combat stone" + "name": "Combat stone", + "id": "4104" }, { - "id": "4105", - "name": "Combat stone" + "name": "Combat stone", + "id": "4105" }, { - "id": "4106", - "name": "Combat stone" + "name": "Combat stone", + "id": "4106" }, { - "id": "4107", - "name": "Combat stone" + "name": "Combat stone", + "id": "4107" }, { - "id": "4108", - "name": "Combat stone" + "name": "Combat stone", + "id": "4108" }, { - "id": "4109", - "name": "Combat stone" + "name": "Combat stone", + "id": "4109" }, { - "id": "4110", - "name": "Combat stone" + "name": "Combat stone", + "id": "4110" }, { - "id": "4111", - "name": "Combat stone" + "name": "Combat stone", + "id": "4111" }, { - "id": "4112", - "name": "Combat stone" + "name": "Combat stone", + "id": "4112" }, { - "id": "4113", - "name": "Combat stone" + "name": "Combat stone", + "id": "4113" }, { - "id": "4114", - "name": "Combat stone" + "name": "Combat stone", + "id": "4114" }, { - "id": "4115", - "name": "Combat stone" + "name": "Combat stone", + "id": "4115" }, { - "id": "4116", - "name": "Combat stone" + "name": "Combat stone", + "id": "4116" }, { - "id": "4117", - "name": "Combat stone" + "name": "Combat stone", + "id": "4117" }, { - "id": "4118", - "name": "Combat stone" + "name": "Combat stone", + "id": "4118" }, { - "id": "4119", - "name": "Combat stone" + "name": "Combat stone", + "id": "4119" }, { - "id": "4120", - "name": "Combat stone" + "name": "Combat stone", + "id": "4120" }, { - "id": "4121", - "name": "Combat stone" + "name": "Combat stone", + "id": "4121" }, { - "id": "4122", - "name": "Combat stone" + "name": "Combat stone", + "id": "4122" }, { - "id": "4123", - "name": "Combat stone" + "name": "Combat stone", + "id": "4123" }, { - "id": "4124", - "name": "Combat stone" + "name": "Combat stone", + "id": "4124" }, { - "id": "4125", - "name": "Combat stone" + "name": "Combat stone", + "id": "4125" }, { - "id": "4126", - "name": "Combat stone" + "name": "Combat stone", + "id": "4126" }, { - "id": "4127", - "name": "Combat stone" + "name": "Combat stone", + "id": "4127" }, { - "id": "4128", - "name": "Combat stone" + "name": "Combat stone", + "id": "4128" }, { - "id": "4129", - "name": "Combat stone" + "name": "Combat stone", + "id": "4129" }, { - "id": "4130", - "name": "Combat stone" + "name": "Combat stone", + "id": "4130" }, { - "id": "4131", - "name": "Combat stone" + "name": "Combat stone", + "id": "4131" }, { - "id": "4132", - "name": "Combat stone" + "name": "Combat stone", + "id": "4132" }, { - "id": "4133", - "name": "Combat stone" + "name": "Combat stone", + "id": "4133" }, { - "id": "4134", - "name": "Combat stone" + "name": "Combat stone", + "id": "4134" }, { - "id": "4135", - "name": "Combat stone" + "name": "Combat stone", + "id": "4135" }, { - "id": "4136", - "name": "Combat stone" + "name": "Combat stone", + "id": "4136" }, { - "id": "4137", - "name": "Combat stone" + "name": "Combat stone", + "id": "4137" }, { - "id": "4138", - "name": "Combat stone" + "name": "Combat stone", + "id": "4138" }, { - "id": "4139", - "name": "Combat stone" + "name": "Combat stone", + "id": "4139" }, { - "id": "4140", - "name": "Combat stone" + "name": "Combat stone", + "id": "4140" }, { - "id": "4141", - "name": "Combat stone" + "name": "Combat stone", + "id": "4141" }, { - "id": "4142", - "name": "Combat stone" + "name": "Combat stone", + "id": "4142" }, { - "id": "4143", - "name": "Combat stone" + "name": "Combat stone", + "id": "4143" }, { - "id": "4144", - "name": "Combat stone" + "name": "Combat stone", + "id": "4144" }, { - "id": "4145", - "name": "Combat stone" + "name": "Combat stone", + "id": "4145" }, { - "id": "4146", - "name": "Combat stone" + "name": "Combat stone", + "id": "4146" }, { - "id": "4147", - "name": "Combat stone" + "name": "Combat stone", + "id": "4147" }, { - "id": "4148", - "name": "Combat stone" + "name": "Combat stone", + "id": "4148" }, { - "id": "4149", - "name": "Combat stone" + "name": "Combat stone", + "id": "4149" }, { - "id": "4150", - "name": "Combat stone" + "name": "Combat stone", + "id": "4150" }, { - "id": "4151", - "name": "Combat stone" + "name": "Combat stone", + "id": "4151" }, { - "id": "4152", - "name": "Combat stone" + "name": "Combat stone", + "id": "4152" }, { - "id": "4153", - "name": "Combat stone" + "name": "Combat stone", + "id": "4153" }, { - "id": "4154", - "name": "Combat stone" + "name": "Combat stone", + "id": "4154" }, { - "id": "4155", - "name": "Combat stone" + "name": "Combat stone", + "id": "4155" }, { - "id": "4156", - "name": "Combat stone" + "name": "Combat stone", + "id": "4156" }, { - "id": "4157", - "name": "Combat stone" + "name": "Combat stone", + "id": "4157" }, { - "id": "4158", - "name": "Combat stone" + "name": "Combat stone", + "id": "4158" }, { - "id": "4159", - "name": "Combat stone" + "name": "Combat stone", + "id": "4159" }, { - "id": "4160", - "name": "Combat stone" + "name": "Combat stone", + "id": "4160" }, { - "id": "4161", - "name": "Combat stone" + "name": "Combat stone", + "id": "4161" }, { - "id": "4162", - "name": "Combat stone" + "name": "Combat stone", + "id": "4162" }, { - "id": "4163", - "name": "Combat stone" + "name": "Combat stone", + "id": "4163" }, { - "id": "4164", - "name": "Combat stone" + "name": "Combat stone", + "id": "4164" }, { - "id": "4165", - "name": "Combat stone" + "name": "Combat stone", + "id": "4165" }, { - "id": "4166", - "name": "Combat stone" + "name": "Combat stone", + "id": "4166" }, { - "id": "4167", - "name": "Combat stone" + "name": "Combat stone", + "id": "4167" }, { - "id": "4168", - "name": "Combat stone" + "name": "Combat stone", + "id": "4168" }, { - "id": "4169", - "name": "Combat stone" + "name": "Combat stone", + "id": "4169" }, { - "id": "4170", - "name": "Combat stone" + "name": "Combat stone", + "id": "4170" }, { - "id": "4171", - "name": "Combat stone" + "name": "Combat stone", + "id": "4171" }, { - "id": "4172", - "name": "Combat stone" + "name": "Combat stone", + "id": "4172" }, { - "id": "4173", - "name": "Combat stone" + "name": "Combat stone", + "id": "4173" }, { - "id": "4174", - "name": "Combat stone" + "name": "Combat stone", + "id": "4174" }, { - "id": "4175", - "name": "Combat stone" + "name": "Combat stone", + "id": "4175" }, { - "id": "4176", - "name": "Combat stone" + "name": "Combat stone", + "id": "4176" }, { - "id": "4177", - "name": "Combat stone" + "name": "Combat stone", + "id": "4177" }, { - "id": "4178", - "name": "Combat stone" + "name": "Combat stone", + "id": "4178" }, { - "id": "4179", - "name": "Combat stone" + "name": "Combat stone", + "id": "4179" }, { - "id": "4180", - "name": "Combat stone" + "name": "Combat stone", + "id": "4180" }, { - "id": "4181", - "name": "Combat stone" + "name": "Combat stone", + "id": "4181" }, { - "id": "4182", - "name": "Combat stone" + "name": "Combat stone", + "id": "4182" }, { - "id": "4183", - "name": "Combat stone" + "name": "Combat stone", + "id": "4183" }, { - "id": "4184", - "name": "Combat stone" + "name": "Combat stone", + "id": "4184" }, { - "id": "4185", - "name": "Combat stone" + "name": "Combat stone", + "id": "4185" }, { - "id": "4186", - "name": "Combat stone" + "name": "Combat stone", + "id": "4186" }, { - "id": "4187", - "name": "Combat stone" + "name": "Combat stone", + "id": "4187" }, { - "id": "4188", - "name": "Combat stone" + "name": "Combat stone", + "id": "4188" }, { - "id": "4189", - "name": "Combat stone" + "name": "Combat stone", + "id": "4189" }, { - "id": "4190", - "name": "Combat stone" + "name": "Combat stone", + "id": "4190" }, { - "id": "4191", - "name": "Combat stone" + "name": "Combat stone", + "id": "4191" }, { - "id": "4192", - "name": "Combat stone" + "name": "Combat stone", + "id": "4192" }, { - "id": "4193", - "name": "Combat stone" + "name": "Combat stone", + "id": "4193" }, { - "id": "4194", - "name": "Combat stone" + "name": "Combat stone", + "id": "4194" }, { - "id": "4195", - "name": "Combat stone" + "name": "Combat stone", + "id": "4195" }, { - "id": "4196", - "name": "Combat stone" + "name": "Combat stone", + "id": "4196" }, { - "id": "4197", - "name": "Combat stone" + "name": "Combat stone", + "id": "4197" }, { - "id": "4198", - "name": "Combat stone" + "name": "Combat stone", + "id": "4198" }, { - "id": "4199", - "name": "Combat stone" + "name": "Combat stone", + "id": "4199" }, { - "id": "4200", - "name": "Combat stone" + "name": "Combat stone", + "id": "4200" }, { - "id": "4201", - "name": "Combat stone" + "name": "Combat stone", + "id": "4201" }, { - "id": "4202", - "name": "Combat stone" + "name": "Combat stone", + "id": "4202" }, { - "id": "4203", - "name": "Combat stone" + "name": "Combat stone", + "id": "4203" }, { - "id": "4204", - "name": "Combat stone" + "name": "Combat stone", + "id": "4204" }, { - "id": "4205", - "name": "Combat stone" + "name": "Combat stone", + "id": "4205" }, { - "id": "4206", - "name": "Combat stone" + "name": "Combat stone", + "id": "4206" }, { - "id": "4207", - "name": "Combat stone" + "name": "Combat stone", + "id": "4207" }, { - "id": "4208", - "name": "Combat stone" + "name": "Combat stone", + "id": "4208" }, { - "id": "4209", - "name": "Combat stone" + "name": "Combat stone", + "id": "4209" }, { - "id": "4210", - "name": "Combat stone" + "name": "Combat stone", + "id": "4210" }, { - "id": "4211", - "name": "Combat stone" + "name": "Combat stone", + "id": "4211" }, { - "id": "4212", - "name": "Combat stone" + "name": "Combat stone", + "id": "4212" }, { - "id": "4213", - "name": "Combat stone" + "name": "Combat stone", + "id": "4213" }, { - "id": "4214", - "name": "Combat stone" + "name": "Combat stone", + "id": "4214" }, { - "id": "4215", - "name": "Combat stone" + "name": "Combat stone", + "id": "4215" }, { - "id": "4216", - "name": "Combat stone" + "name": "Combat stone", + "id": "4216" }, { - "id": "4217", - "name": "Combat stone" + "name": "Combat stone", + "id": "4217" }, { - "id": "4218", - "name": "Combat stone" + "name": "Combat stone", + "id": "4218" }, { - "id": "4219", - "name": "Combat stone" + "name": "Combat stone", + "id": "4219" }, { - "id": "4220", - "name": "Combat stone" + "name": "Combat stone", + "id": "4220" }, { - "id": "4221", - "name": "Combat stone" + "name": "Combat stone", + "id": "4221" }, { - "id": "4222", - "name": "Combat stone" + "name": "Combat stone", + "id": "4222" }, { - "id": "4223", - "name": "Combat stone" + "name": "Combat stone", + "id": "4223" }, { - "id": "4224", - "name": "Combat stone" + "name": "Combat stone", + "id": "4224" }, { - "id": "4225", - "name": "Combat stone" + "name": "Combat stone", + "id": "4225" }, { - "id": "4226", - "name": "Crawling hand" + "name": "Crawling hand", + "id": "4226" }, { - "id": "4231", - "name": "Left head" + "name": "Left head", + "id": "4231" }, { - "id": "4232", - "name": "Middle head" + "name": "Middle head", + "id": "4232" }, { - "id": "4233", - "name": "Right head" + "name": "Right head", + "id": "4233" }, { - "id": "4234", - "name": "Kalphite Queen" + "name": "Kalphite Queen", + "id": "4234" }, { - "id": "4235", - "name": "Rick" + "name": "Rick", + "id": "4235" }, { - "id": "4236", - "name": "Maid" + "name": "Maid", + "id": "4236" }, { - "id": "4238", - "name": "Cook" + "name": "Cook", + "id": "4238" }, { - "id": "4240", - "name": "Butler" + "name": "Butler", + "id": "4240" }, { - "id": "4242", - "name": "Demon butler" + "name": "Demon butler", + "id": "4242" }, { - "id": "4244", - "name": "Chief servant" + "name": "Chief servant", + "id": "4244" }, { - "id": "4253", - "name": "Guard" + "name": "Guard", + "id": "4253" }, { - "id": "4277", - "name": "Buinn" + "name": "Buinn", + "id": "4277" }, { - "id": "4312", - "name": "Nardok" + "name": "Nardok", + "id": "4312" }, { - "id": "4313", - "name": "Dartog" + "name": "Dartog", + "id": "4313" }, { - "id": "4315", - "name": "Dwarf" + "name": "Dwarf", + "id": "4315" }, { - "id": "4317", - "name": "H.A.M. Member" + "name": "H.A.M. Member", + "id": "4317" }, { - "id": "4319", - "name": "H.A.M. Member" + "name": "H.A.M. Member", + "id": "4319" }, { - "id": "4321", - "name": "Zanik" + "name": "Zanik", + "id": "4321" }, { - "id": "4323", - "name": "Zanik" + "name": "Zanik", + "id": "4323" }, { - "id": "4324", - "name": "Zanik" + "name": "Zanik", + "id": "4324" }, { - "id": "4325", - "name": "Light creature" + "name": "Light creature", + "id": "4325" }, { - "id": "4326", - "name": "Zanik" + "name": "Zanik", + "id": "4326" }, { - "id": "4327", - "name": "HAM member" + "name": "HAM member", + "id": "4327" }, { - "id": "4328", - "name": "Sigmund" + "name": "Sigmund", + "id": "4328" }, { - "id": "4330", - "name": "Johanhus Ulsbrecht" + "name": "Johanhus Ulsbrecht", + "id": "4330" }, { - "id": "4331", - "name": "Sigmund" + "name": "Sigmund", + "id": "4331" }, { - "id": "4332", - "name": "Sigmund" + "name": "Sigmund", + "id": "4332" }, { - "id": "4333", - "name": "Sigmund" + "name": "Sigmund", + "id": "4333" }, { - "id": "4334", - "name": "Sigmund" + "name": "Sigmund", + "id": "4334" }, { - "id": "4335", - "name": "Sigmund" + "name": "Sigmund", + "id": "4335" }, { - "id": "4337", - "name": "Zanik" + "name": "Zanik", + "id": "4337" }, { - "id": "4338", - "name": "Zanik" + "name": "Zanik", + "id": "4338" }, { - "id": "4340", - "name": "Zanik" + "name": "Zanik", + "id": "4340" }, { - "id": "4341", - "name": "Zanik" + "name": "Zanik", + "id": "4341" }, { - "id": "4342", - "name": "Zanik" + "name": "Zanik", + "id": "4342" }, { - "id": "4346", - "name": "Crab" + "name": "Crab", + "id": "4346" }, { - "id": "4358", - "name": "Cavey Davey" + "name": "Cavey Davey", + "id": "4358" }, { - "id": "4360", - "name": "San Fan" + "name": "San Fan", + "id": "4360" }, { - "id": "4364", - "name": "Swarm" + "name": "Swarm", + "id": "4364" }, { - "id": "4365", - "name": "Blue Monkey" + "name": "Blue Monkey", + "id": "4365" }, { - "id": "4374", - "name": "Parrot" + "name": "Parrot", + "id": "4374" }, { - "id": "4377", - "name": "Gate of War" + "name": "Gate of War", + "id": "4377" }, { - "id": "4378", - "name": "Ricketty door" + "name": "Ricketty door", + "id": "4378" }, { - "id": "4379", - "name": "Oozing barrier" + "name": "Oozing barrier", + "id": "4379" }, { - "id": "4380", - "name": "Portal of Death" + "name": "Portal of Death", + "id": "4380" }, { - "id": "4416", - "name": "Bee keeper" + "name": "Bee keeper", + "id": "4416" }, { - "id": "4417", - "name": "Bees!" + "name": "Bees!", + "id": "4417" }, { - "id": "4420", - "name": "Fairy Godfather" + "name": "Fairy Godfather", + "id": "4420" }, { - "id": "4435", - "name": "Fairy Queen" + "name": "Fairy Queen", + "id": "4435" }, { - "id": "4442", - "name": "Fairy Very Wise" + "name": "Fairy Very Wise", + "id": "4442" }, { - "id": "4444", - "name": "Fairy" + "name": "Fairy", + "id": "4444" }, { - "id": "4445", - "name": "Fairy" + "name": "Fairy", + "id": "4445" }, { - "id": "4446", - "name": "Fairy" + "name": "Fairy", + "id": "4446" }, { - "id": "4447", - "name": "Rabbit" + "name": "Rabbit", + "id": "4447" }, { - "id": "4448", - "name": "Rabbit" + "name": "Rabbit", + "id": "4448" }, { - "id": "4449", - "name": "Butterfly" + "name": "Butterfly", + "id": "4449" }, { - "id": "4450", - "name": "Butterfly" + "name": "Butterfly", + "id": "4450" }, { - "id": "4451", - "name": "Starflower" + "name": "Starflower", + "id": "4451" }, { - "id": "4452", - "name": "Starflower" + "name": "Starflower", + "id": "4452" }, { - "id": "4453", - "name": "Fairy Fixit" + "name": "Fairy Fixit", + "id": "4453" }, { - "id": "4456", - "name": "Ork" + "name": "Ork", + "id": "4456" }, { - "id": "4460", - "name": "Fake Man" + "name": "Fake Man", + "id": "4460" }, { - "id": "4461", - "name": "Held vampyre juvinate" + "name": "Held vampyre juvinate", + "id": "4461" }, { - "id": "4462", - "name": "Held vampyre juvinate" + "name": "Held vampyre juvinate", + "id": "4462" }, { - "id": "4463", - "name": "Angry juvinate" + "name": "Angry juvinate", + "id": "4463" }, { - "id": "4464", - "name": "Angry juvinate" + "name": "Angry juvinate", + "id": "4464" }, { - "id": "4465", - "name": "Angry juvinate" + "name": "Angry juvinate", + "id": "4465" }, { - "id": "4466", - "name": "Benjamin" + "name": "Benjamin", + "id": "4466" }, { - "id": "4467", - "name": "Liam" + "name": "Liam", + "id": "4467" }, { - "id": "4468", - "name": "Miala" + "name": "Miala", + "id": "4468" }, { - "id": "4469", - "name": "Verak" + "name": "Verak", + "id": "4469" }, { - "id": "4471", - "name": "Bogrog" + "name": "Bogrog", + "id": "4471" }, { - "id": "4473", - "name": "Woman" + "name": "Woman", + "id": "4473" }, { - "id": "4475", - "name": "Ned" + "name": "Ned", + "id": "4475" }, { - "id": "4477", - "name": "Annoyed guardian mummy" + "name": "Annoyed guardian mummy", + "id": "4477" }, { - "id": "4478", - "name": "Tarik" + "name": "Tarik", + "id": "4478" }, { - "id": "4493", - "name": "General Bentnoze" + "name": "General Bentnoze", + "id": "4493" }, { - "id": "4495", - "name": "Grubfoot" + "name": "Grubfoot", + "id": "4495" }, { - "id": "4497", - "name": "Grubfoot" + "name": "Grubfoot", + "id": "4497" }, { - "id": "4498", - "name": "Grubfoot" + "name": "Grubfoot", + "id": "4498" }, { - "id": "4500", - "name": "Scarab swarm" + "name": "Scarab swarm", + "id": "4500" }, { - "id": "4508", - "name": "Ethereal Mimic" + "name": "Ethereal Mimic", + "id": "4508" }, { - "id": "4513", - "name": "Baba Yaga" + "name": "Baba Yaga", + "id": "4513" }, { - "id": "4514", - "name": "Pauline Polaris" + "name": "Pauline Polaris", + "id": "4514" }, { - "id": "4515", - "name": "Meteora" + "name": "Meteora", + "id": "4515" }, { - "id": "4516", - "name": "Melana Moonlander" + "name": "Melana Moonlander", + "id": "4516" }, { - "id": "4517", - "name": "Selene" + "name": "Selene", + "id": "4517" }, { - "id": "4518", - "name": "Rimae Sirsalis" + "name": "Rimae Sirsalis", + "id": "4518" }, { - "id": "4519", - "name": "Sirsal Banker" + "name": "Sirsal Banker", + "id": "4519" }, { - "id": "4520", - "name": "Clan Guard" + "name": "Clan Guard", + "id": "4520" }, { - "id": "4522", - "name": "Enchanted Broom" + "name": "Enchanted Broom", + "id": "4522" }, { - "id": "4523", - "name": "Enchanted Broom" + "name": "Enchanted Broom", + "id": "4523" }, { - "id": "4525", - "name": "Enchanted Bucket" + "name": "Enchanted Bucket", + "id": "4525" }, { - "id": "4526", - "name": "Bouquet Mac Hyacinth" + "name": "Bouquet Mac Hyacinth", + "id": "4526" }, { - "id": "4535", - "name": "Parrot" + "name": "Parrot", + "id": "4535" }, { - "id": "4536", - "name": "Lokar Searunner" + "name": "Lokar Searunner", + "id": "4536" }, { - "id": "4538", - "name": "Cabin boy" + "name": "Cabin boy", + "id": "4538" }, { - "id": "4544", - "name": "'Bird's-Eye' Jack" + "name": "'Bird's-Eye' Jack", + "id": "4544" }, { - "id": "4552", - "name": "Palmer" + "name": "Palmer", + "id": "4552" }, { - "id": "4553", - "name": "'Betty' B.Boppin" + "name": "'Betty' B.Boppin", + "id": "4553" }, { - "id": "4558", - "name": "Hirko" + "name": "Hirko", + "id": "4558" }, { - "id": "4559", - "name": "Holoy" + "name": "Holoy", + "id": "4559" }, { - "id": "4563", - "name": "Hura" + "name": "Hura", + "id": "4563" }, { - "id": "4569", - "name": "Nick" + "name": "Nick", + "id": "4569" }, { - "id": "4570", - "name": "Crow" + "name": "Crow", + "id": "4570" }, { - "id": "4571", - "name": "Crow" + "name": "Crow", + "id": "4571" }, { - "id": "4572", - "name": "Gianne jnr." + "name": "Gianne jnr.", + "id": "4572" }, { - "id": "4573", - "name": "Timble" + "name": "Timble", + "id": "4573" }, { - "id": "4574", - "name": "Tamble" + "name": "Tamble", + "id": "4574" }, { - "id": "4575", - "name": "Spang" + "name": "Spang", + "id": "4575" }, { - "id": "4576", - "name": "Brambickle" + "name": "Brambickle", + "id": "4576" }, { - "id": "4577", - "name": "Wingstone" + "name": "Wingstone", + "id": "4577" }, { - "id": "4578", - "name": "Penwie" + "name": "Penwie", + "id": "4578" }, { - "id": "4583", - "name": "Professor Manglethorp" + "name": "Professor Manglethorp", + "id": "4583" }, { - "id": "4584", - "name": "Damwin" + "name": "Damwin", + "id": "4584" }, { - "id": "4586", - "name": "Professor Imblewyn" + "name": "Professor Imblewyn", + "id": "4586" }, { - "id": "4587", - "name": "Perrdur" + "name": "Perrdur", + "id": "4587" }, { - "id": "4588", - "name": "Dalila" + "name": "Dalila", + "id": "4588" }, { - "id": "4591", - "name": "Eebel" + "name": "Eebel", + "id": "4591" }, { - "id": "4592", - "name": "Ermin" + "name": "Ermin", + "id": "4592" }, { - "id": "4596", - "name": "Captain Lamdoo" + "name": "Captain Lamdoo", + "id": "4596" }, { - "id": "4597", - "name": "Meegle" + "name": "Meegle", + "id": "4597" }, { - "id": "4598", - "name": "Wurbel" + "name": "Wurbel", + "id": "4598" }, { - "id": "4599", - "name": "Sarble" + "name": "Sarble", + "id": "4599" }, { - "id": "4601", - "name": "Burkor" + "name": "Burkor", + "id": "4601" }, { - "id": "4602", - "name": "Froono" + "name": "Froono", + "id": "4602" }, { - "id": "4609", - "name": "Brimstail" + "name": "Brimstail", + "id": "4609" }, { - "id": "4612", - "name": "Gnome shop keeper" + "name": "Gnome shop keeper", + "id": "4612" }, { - "id": "4613", - "name": "Cute creature" + "name": "Cute creature", + "id": "4613" }, { - "id": "4616", - "name": "Cute creature" + "name": "Cute creature", + "id": "4616" }, { - "id": "4618", - "name": "Evil creature" + "name": "Evil creature", + "id": "4618" }, { - "id": "4619", - "name": "Cute creature" + "name": "Cute creature", + "id": "4619" }, { - "id": "4621", - "name": "Evil creature" + "name": "Evil creature", + "id": "4621" }, { - "id": "4622", - "name": "Cute creature" + "name": "Cute creature", + "id": "4622" }, { - "id": "4624", - "name": "Evil creature" + "name": "Evil creature", + "id": "4624" }, { - "id": "4625", - "name": "Cute creature" + "name": "Cute creature", + "id": "4625" }, { - "id": "4627", - "name": "Evil creature" + "name": "Evil creature", + "id": "4627" }, { - "id": "4628", - "name": "Cute creature" + "name": "Cute creature", + "id": "4628" }, { - "id": "4630", - "name": "Evil creature" + "name": "Evil creature", + "id": "4630" }, { - "id": "4631", - "name": "fluffie" + "name": "fluffie", + "id": "4631" }, { - "id": "4632", - "name": "fluffie" + "name": "fluffie", + "id": "4632" }, { - "id": "4644", - "name": "Oaknock the Engineer" + "name": "Oaknock the Engineer", + "id": "4644" }, { - "id": "4646", - "name": "King Healthorg" + "name": "King Healthorg", + "id": "4646" }, { - "id": "4647", - "name": "Hazelmere" + "name": "Hazelmere", + "id": "4647" }, { - "id": "4648", - "name": "Nisha" + "name": "Nisha", + "id": "4648" }, { - "id": "4649", - "name": "Tyras guard" + "name": "Tyras guard", + "id": "4649" }, { - "id": "4657", - "name": "Sir Prysin" + "name": "Sir Prysin", + "id": "4657" }, { - "id": "4658", - "name": "Dark wizard" + "name": "Dark wizard", + "id": "4658" }, { - "id": "4662", - "name": "Denath" + "name": "Denath", + "id": "4662" }, { - "id": "4663", - "name": "Denath" + "name": "Denath", + "id": "4663" }, { - "id": "4664", - "name": "Wally" + "name": "Wally", + "id": "4664" }, { - "id": "4709", - "name": "Vertida Sefalatis" + "name": "Vertida Sefalatis", + "id": "4709" }, { - "id": "4710", - "name": "Aeonisig Raispher" + "name": "Aeonisig Raispher", + "id": "4710" }, { - "id": "4711", - "name": "Safalaan" + "name": "Safalaan", + "id": "4711" }, { - "id": "4714", - "name": "Sarius Guile" + "name": "Sarius Guile", + "id": "4714" }, { - "id": "4727", - "name": "Meiyerditch citizen" + "name": "Meiyerditch citizen", + "id": "4727" }, { - "id": "4728", - "name": "Meiyerditch citizen" + "name": "Meiyerditch citizen", + "id": "4728" }, { - "id": "4729", - "name": "Meiyerditch citizen" + "name": "Meiyerditch citizen", + "id": "4729" }, { - "id": "4730", - "name": "Meiyerditch citizen" + "name": "Meiyerditch citizen", + "id": "4730" }, { - "id": "4731", - "name": "Meiyerditch citizen" + "name": "Meiyerditch citizen", + "id": "4731" }, { - "id": "4732", - "name": "Meiyerditch citizen" + "name": "Meiyerditch citizen", + "id": "4732" }, { - "id": "4742", - "name": "Meiyerditch citizen" + "name": "Meiyerditch citizen", + "id": "4742" }, { - "id": "4743", - "name": "Meiyerditch citizen" + "name": "Meiyerditch citizen", + "id": "4743" }, { - "id": "4744", - "name": "Meiyerditch citizen" + "name": "Meiyerditch citizen", + "id": "4744" }, { - "id": "4745", - "name": "Meiyerditch citizen" + "name": "Meiyerditch citizen", + "id": "4745" }, { - "id": "4758", - "name": "Meiyerditch miner" + "name": "Meiyerditch miner", + "id": "4758" }, { - "id": "4760", - "name": "Meiyerditch miner" + "name": "Meiyerditch miner", + "id": "4760" }, { - "id": "4761", - "name": "Meiyerditch miner" + "name": "Meiyerditch miner", + "id": "4761" }, { - "id": "4762", - "name": "Shadowy figure" + "name": "Shadowy figure", + "id": "4762" }, { - "id": "4763", - "name": "Shadowy figure" + "name": "Shadowy figure", + "id": "4763" }, { - "id": "4764", - "name": "Shadowy figure" + "name": "Shadowy figure", + "id": "4764" }, { - "id": "4767", - "name": "Stray dog" + "name": "Stray dog", + "id": "4767" }, { - "id": "4769", - "name": "Cat" + "name": "Cat", + "id": "4769" }, { - "id": "4770", - "name": "Boat" + "name": "Boat", + "id": "4770" }, { - "id": "4771", - "name": "Boat" + "name": "Boat", + "id": "4771" }, { - "id": "4781", - "name": "Held vampyre juvinate" + "name": "Held vampyre juvinate", + "id": "4781" }, { - "id": "4782", - "name": "Vampyre juvinate" + "name": "Vampyre juvinate", + "id": "4782" }, { - "id": "4783", - "name": "Former vampyre" + "name": "Former vampyre", + "id": "4783" }, { - "id": "4784", - "name": "Former vampyre" + "name": "Former vampyre", + "id": "4784" }, { - "id": "4785", - "name": "Former vampyre" + "name": "Former vampyre", + "id": "4785" }, { - "id": "4786", - "name": "Former vampyre" + "name": "Former vampyre", + "id": "4786" }, { - "id": "4787", - "name": "Former vampyre" + "name": "Former vampyre", + "id": "4787" }, { - "id": "4788", - "name": "Former vampyre" + "name": "Former vampyre", + "id": "4788" }, { - "id": "4790", - "name": "Angry vampyre" + "name": "Angry vampyre", + "id": "4790" }, { - "id": "4791", - "name": "Vanstrom Klause" + "name": "Vanstrom Klause", + "id": "4791" }, { - "id": "4792", - "name": "Vanstrom Klause" + "name": "Vanstrom Klause", + "id": "4792" }, { - "id": "4794", - "name": "Vanstrom Klause" + "name": "Vanstrom Klause", + "id": "4794" }, { - "id": "4795", - "name": "Vanstrom Klause" + "name": "Vanstrom Klause", + "id": "4795" }, { - "id": "4797", - "name": "Vanescula Drakan" + "name": "Vanescula Drakan", + "id": "4797" }, { - "id": "4798", - "name": "Vanescula Drakan" + "name": "Vanescula Drakan", + "id": "4798" }, { - "id": "4799", - "name": "Vanescula Drakan" + "name": "Vanescula Drakan", + "id": "4799" }, { - "id": "4800", - "name": "Vanescula Drakan" + "name": "Vanescula Drakan", + "id": "4800" }, { - "id": "4801", - "name": "Ranis Drakan" + "name": "Ranis Drakan", + "id": "4801" }, { - "id": "4802", - "name": "Ranis Drakan" + "name": "Ranis Drakan", + "id": "4802" }, { - "id": "4803", - "name": "Ranis Drakan" + "name": "Ranis Drakan", + "id": "4803" }, { - "id": "4804", - "name": "Ranis Drakan" + "name": "Ranis Drakan", + "id": "4804" }, { - "id": "4809", - "name": "Flying female vampire" + "name": "Flying female vampire", + "id": "4809" }, { - "id": "4846", - "name": "Flying female vampire" + "name": "Flying female vampire", + "id": "4846" }, { - "id": "4853", - "name": "Ezekial Lovecraft" + "name": "Ezekial Lovecraft", + "id": "4853" }, { - "id": "4857", - "name": "Sarius Guile" + "name": "Sarius Guile", + "id": "4857" }, { - "id": "4862", - "name": "Vanstrom Klause" + "name": "Vanstrom Klause", + "id": "4862" }, { - "id": "4863", - "name": "Kennith" + "name": "Kennith", + "id": "4863" }, { - "id": "4864", - "name": "Kennith" + "name": "Kennith", + "id": "4864" }, { - "id": "4865", - "name": "Holgart" + "name": "Holgart", + "id": "4865" }, { - "id": "4867", - "name": "Holgart" + "name": "Holgart", + "id": "4867" }, { - "id": "4869", - "name": "Fisherman" + "name": "Fisherman", + "id": "4869" }, { - "id": "4871", - "name": "Col. O'Niall" + "name": "Col. O'Niall", + "id": "4871" }, { - "id": "4873", - "name": "Col. O'Niall" + "name": "Col. O'Niall", + "id": "4873" }, { - "id": "4875", - "name": "Mayor Hobb" + "name": "Mayor Hobb", + "id": "4875" }, { - "id": "4876", - "name": "Brother Maledict" + "name": "Brother Maledict", + "id": "4876" }, { - "id": "4879", - "name": "Brother Maledict" + "name": "Brother Maledict", + "id": "4879" }, { - "id": "4880", - "name": "Witchaven villager" + "name": "Witchaven villager", + "id": "4880" }, { - "id": "4884", - "name": "Witchaven villager" + "name": "Witchaven villager", + "id": "4884" }, { - "id": "4886", - "name": "Witchaven villager" + "name": "Witchaven villager", + "id": "4886" }, { - "id": "4888", - "name": "Witchaven villager" + "name": "Witchaven villager", + "id": "4888" }, { - "id": "4889", - "name": "Mother Mallum" + "name": "Mother Mallum", + "id": "4889" }, { - "id": "4891", - "name": "Giant lobster" + "name": "Giant lobster", + "id": "4891" }, { - "id": "4896", - "name": "Jeb" + "name": "Jeb", + "id": "4896" }, { - "id": "4897", - "name": "Sir Tinley" + "name": "Sir Tinley", + "id": "4897" }, { - "id": "4905", - "name": "Smithing Tutor" + "name": "Smithing Tutor", + "id": "4905" }, { - "id": "4908", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "4908" }, { - "id": "4912", - "name": "Jig cart" + "name": "Jig cart", + "id": "4912" }, { - "id": "4914", - "name": "Jig cart" + "name": "Jig cart", + "id": "4914" }, { - "id": "4915", - "name": "Jig cart" + "name": "Jig cart", + "id": "4915" }, { - "id": "4916", - "name": "Jig cart" + "name": "Jig cart", + "id": "4916" }, { - "id": "4917", - "name": "Jig cart" + "name": "Jig cart", + "id": "4917" }, { - "id": "4918", - "name": "Jig cart" + "name": "Jig cart", + "id": "4918" }, { - "id": "4919", - "name": "Abidor Crank" + "name": "Abidor Crank", + "id": "4919" }, { - "id": "4946", - "name": "Ignatius Vulcan" + "name": "Ignatius Vulcan", + "id": "4946" }, { - "id": "4948", - "name": "My Arm" + "name": "My Arm", + "id": "4948" }, { - "id": "4949", - "name": "My Arm" + "name": "My Arm", + "id": "4949" }, { - "id": "4950", - "name": "My Arm" + "name": "My Arm", + "id": "4950" }, { - "id": "4958", - "name": "My Arm" + "name": "My Arm", + "id": "4958" }, { - "id": "4959", - "name": "My Arm" + "name": "My Arm", + "id": "4959" }, { - "id": "4960", - "name": "Adventurer" + "name": "Adventurer", + "id": "4960" }, { - "id": "4961", - "name": "Captain Barnaby" + "name": "Captain Barnaby", + "id": "4961" }, { - "id": "4963", - "name": "Murcaily" + "name": "Murcaily", + "id": "4963" }, { - "id": "4964", - "name": "Jagbakoba" + "name": "Jagbakoba", + "id": "4964" }, { - "id": "4966", - "name": "Flies" + "name": "Flies", + "id": "4966" }, { - "id": "4968", - "name": "Unnamed troll child" + "name": "Unnamed troll child", + "id": "4968" }, { - "id": "4969", - "name": "Drunken dwarf's leg" + "name": "Drunken dwarf's leg", + "id": "4969" }, { - "id": "4970", - "name": "Baby Roc" + "name": "Baby Roc", + "id": "4970" }, { - "id": "4973", - "name": "Shadow" + "name": "Shadow", + "id": "4973" }, { - "id": "4974", - "name": "Captain Barnaby" + "name": "Captain Barnaby", + "id": "4974" }, { - "id": "4976", - "name": "Male slave" + "name": "Male slave", + "id": "4976" }, { - "id": "4978", - "name": "Female slave" + "name": "Female slave", + "id": "4978" }, { - "id": "4979", - "name": "Cart Camel" + "name": "Cart Camel", + "id": "4979" }, { - "id": "4980", - "name": "Mine Cart" + "name": "Mine Cart", + "id": "4980" }, { - "id": "4981", - "name": "Mine Cart" + "name": "Mine Cart", + "id": "4981" }, { - "id": "4982", - "name": "Ana" + "name": "Ana", + "id": "4982" }, { - "id": "4983", - "name": "Mercenary" + "name": "Mercenary", + "id": "4983" }, { - "id": "4984", - "name": "Irena" + "name": "Irena", + "id": "4984" }, { - "id": "5003", - "name": "Gublinch" + "name": "Gublinch", + "id": "5003" }, { - "id": "5018", - "name": "Gublinch" + "name": "Gublinch", + "id": "5018" }, { - "id": "5019", - "name": "Gublinch" + "name": "Gublinch", + "id": "5019" }, { - "id": "5020", - "name": "Jack" + "name": "Jack", + "id": "5020" }, { - "id": "5024", - "name": "Jill" + "name": "Jill", + "id": "5024" }, { - "id": "5025", - "name": "Jeff" + "name": "Jeff", + "id": "5025" }, { - "id": "5044", - "name": "Penance Fighter" + "name": "Penance Fighter", + "id": "5044" }, { - "id": "5045", - "name": "Penance Fighter" + "name": "Penance Fighter", + "id": "5045" }, { - "id": "5046", - "name": "Jack" + "name": "Jack", + "id": "5046" }, { - "id": "5047", - "name": "Jill" + "name": "Jill", + "id": "5047" }, { - "id": "5049", - "name": "Auguste" + "name": "Auguste", + "id": "5049" }, { - "id": "5050", - "name": "Auguste" + "name": "Auguste", + "id": "5050" }, { - "id": "5051", - "name": "Auguste" + "name": "Auguste", + "id": "5051" }, { - "id": "5052", - "name": "Auguste" + "name": "Auguste", + "id": "5052" }, { - "id": "5053", - "name": "Assistant Serf" + "name": "Assistant Serf", + "id": "5053" }, { - "id": "5054", - "name": "Assistant Brock" + "name": "Assistant Brock", + "id": "5054" }, { - "id": "5055", - "name": "Assistant Marrow" + "name": "Assistant Marrow", + "id": "5055" }, { - "id": "5056", - "name": "Assistant Le Smith" + "name": "Assistant Le Smith", + "id": "5056" }, { - "id": "5057", - "name": "Assistant Stan" + "name": "Assistant Stan", + "id": "5057" }, { - "id": "5058", - "name": "Bob" + "name": "Bob", + "id": "5058" }, { - "id": "5059", - "name": "Curly" + "name": "Curly", + "id": "5059" }, { - "id": "5060", - "name": "Moe" + "name": "Moe", + "id": "5060" }, { - "id": "5061", - "name": "Larry" + "name": "Larry", + "id": "5061" }, { - "id": "5062", - "name": "Shark" + "name": "Shark", + "id": "5062" }, { - "id": "5068", - "name": "Shark" + "name": "Shark", + "id": "5068" }, { - "id": "5069", - "name": "Shark" + "name": "Shark", + "id": "5069" }, { - "id": "5070", - "name": "Tropical wagtail" + "name": "Tropical wagtail", + "id": "5070" }, { - "id": "5077", - "name": "Chinchompa" + "name": "Chinchompa", + "id": "5077" }, { - "id": "5090", - "name": "Matthias" + "name": "Matthias", + "id": "5090" }, { - "id": "5093", - "name": "Matthias" + "name": "Matthias", + "id": "5093" }, { - "id": "5094", - "name": "Gyr Falcon" + "name": "Gyr Falcon", + "id": "5094" }, { - "id": "5095", - "name": "Gyr Falcon" + "name": "Gyr Falcon", + "id": "5095" }, { - "id": "5096", - "name": "Gyr Falcon" + "name": "Gyr Falcon", + "id": "5096" }, { - "id": "5101", - "name": "Sabre-toothed kyatt" + "name": "Sabre-toothed kyatt", + "id": "5101" }, { - "id": "5110", - "name": "Aleck" + "name": "Aleck", + "id": "5110" }, { - "id": "5118", - "name": "Eagle" + "name": "Eagle", + "id": "5118" }, { - "id": "5121", - "name": "Eagle" + "name": "Eagle", + "id": "5121" }, { - "id": "5122", - "name": "Eagle" + "name": "Eagle", + "id": "5122" }, { - "id": "5123", - "name": "Eagle" + "name": "Eagle", + "id": "5123" }, { - "id": "5124", - "name": "Nickolaus" + "name": "Nickolaus", + "id": "5124" }, { - "id": "5127", - "name": "Nickolaus" + "name": "Nickolaus", + "id": "5127" }, { - "id": "5128", - "name": "Nickolaus" + "name": "Nickolaus", + "id": "5128" }, { - "id": "5129", - "name": "Nickolaus" + "name": "Nickolaus", + "id": "5129" }, { - "id": "5134", - "name": "Kebbit" + "name": "Kebbit", + "id": "5134" }, { - "id": "5138", - "name": "Charlie" + "name": "Charlie", + "id": "5138" }, { - "id": "5139", - "name": "Boulder" + "name": "Boulder", + "id": "5139" }, { - "id": "5141", - "name": "Uri" + "name": "Uri", + "id": "5141" }, { - "id": "5142", - "name": "Uri" + "name": "Uri", + "id": "5142" }, { - "id": "5143", - "name": "Uri" + "name": "Uri", + "id": "5143" }, { - "id": "5148", - "name": "Sheep" + "name": "Sheep", + "id": "5148" }, { - "id": "5149", - "name": "Sheep" + "name": "Sheep", + "id": "5149" }, { - "id": "5150", - "name": "Sheep" + "name": "Sheep", + "id": "5150" }, { - "id": "5151", - "name": "Sheep" + "name": "Sheep", + "id": "5151" }, { - "id": "5152", - "name": "Sheep" + "name": "Sheep", + "id": "5152" }, { - "id": "5153", - "name": "Sheep" + "name": "Sheep", + "id": "5153" }, { - "id": "5154", - "name": "Sheep" + "name": "Sheep", + "id": "5154" }, { - "id": "5155", - "name": "Sheep" + "name": "Sheep", + "id": "5155" }, { - "id": "5156", - "name": "Sheep" + "name": "Sheep", + "id": "5156" }, { - "id": "5157", - "name": "Sheep" + "name": "Sheep", + "id": "5157" }, { - "id": "5158", - "name": "Sheep" + "name": "Sheep", + "id": "5158" }, { - "id": "5159", - "name": "Sheep" + "name": "Sheep", + "id": "5159" }, { - "id": "5160", - "name": "Sheep" + "name": "Sheep", + "id": "5160" }, { - "id": "5161", - "name": "Sheep" + "name": "Sheep", + "id": "5161" }, { - "id": "5165", - "name": "Sheep" + "name": "Sheep", + "id": "5165" }, { - "id": "5174", - "name": "Ogre chieftain" + "name": "Ogre chieftain", + "id": "5174" }, { - "id": "5175", - "name": "Ogre shaman" + "name": "Ogre shaman", + "id": "5175" }, { - "id": "5177", - "name": "Ogre shaman" + "name": "Ogre shaman", + "id": "5177" }, { - "id": "5179", - "name": "Elkoy" + "name": "Elkoy", + "id": "5179" }, { - "id": "5180", - "name": "Ogre shaman" + "name": "Ogre shaman", + "id": "5180" }, { - "id": "5182", - "name": "Elkoy" + "name": "Elkoy", + "id": "5182" }, { - "id": "5183", - "name": "Ogre shaman" + "name": "Ogre shaman", + "id": "5183" }, { - "id": "5185", - "name": "Biggleswade" + "name": "Biggleswade", + "id": "5185" }, { - "id": "5186", - "name": "Ogre shaman" + "name": "Ogre shaman", + "id": "5186" }, { - "id": "5189", - "name": "Ogre shaman" + "name": "Ogre shaman", + "id": "5189" }, { - "id": "5191", - "name": "Blaze Sharpeye" + "name": "Blaze Sharpeye", + "id": "5191" }, { - "id": "5192", - "name": "Ogre shaman" + "name": "Ogre shaman", + "id": "5192" }, { - "id": "5194", - "name": "Blaze Sharpeye" + "name": "Blaze Sharpeye", + "id": "5194" }, { - "id": "5199", - "name": "Witch" + "name": "Witch", + "id": "5199" }, { - "id": "5201", - "name": "Alice's husband" + "name": "Alice's husband", + "id": "5201" }, { - "id": "5203", - "name": "Alice's husband" + "name": "Alice's husband", + "id": "5203" }, { - "id": "5205", - "name": "Alice's husband" + "name": "Alice's husband", + "id": "5205" }, { - "id": "5206", - "name": "Tree" + "name": "Tree", + "id": "5206" }, { - "id": "5208", - "name": "Undead tree" + "name": "Undead tree", + "id": "5208" }, { - "id": "5209", - "name": " Sneaky undead fowl" + "name": " Sneaky undead fowl", + "id": "5209" }, { - "id": "5210", - "name": "Cow1337killr" + "name": "Cow1337killr", + "id": "5210" }, { - "id": "5212", - "name": "Alice" + "name": "Alice", + "id": "5212" }, { - "id": "5213", - "name": "Penance Fighter" + "name": "Penance Fighter", + "id": "5213" }, { - "id": "5214", - "name": "Penance Fighter" + "name": "Penance Fighter", + "id": "5214" }, { - "id": "5215", - "name": "Penance Fighter" + "name": "Penance Fighter", + "id": "5215" }, { - "id": "5216", - "name": "Penance Fighter" + "name": "Penance Fighter", + "id": "5216" }, { - "id": "5217", - "name": "Penance Fighter" + "name": "Penance Fighter", + "id": "5217" }, { - "id": "5218", - "name": "Penance Fighter" + "name": "Penance Fighter", + "id": "5218" }, { - "id": "5219", - "name": "Penance Fighter" + "name": "Penance Fighter", + "id": "5219" }, { - "id": "5220", - "name": "Penance Runner" + "name": "Penance Runner", + "id": "5220" }, { - "id": "5221", - "name": "Penance Runner" + "name": "Penance Runner", + "id": "5221" }, { - "id": "5222", - "name": "Penance Runner" + "name": "Penance Runner", + "id": "5222" }, { - "id": "5223", - "name": "Penance Runner" + "name": "Penance Runner", + "id": "5223" }, { - "id": "5224", - "name": "Penance Runner" + "name": "Penance Runner", + "id": "5224" }, { - "id": "5225", - "name": "Penance Runner" + "name": "Penance Runner", + "id": "5225" }, { - "id": "5226", - "name": "Penance Runner" + "name": "Penance Runner", + "id": "5226" }, { - "id": "5227", - "name": "Penance Runner" + "name": "Penance Runner", + "id": "5227" }, { - "id": "5228", - "name": "Penance Runner" + "name": "Penance Runner", + "id": "5228" }, { - "id": "5230", - "name": "Penance Ranger" + "name": "Penance Ranger", + "id": "5230" }, { - "id": "5231", - "name": "Penance Ranger" + "name": "Penance Ranger", + "id": "5231" }, { - "id": "5232", - "name": "Penance Ranger" + "name": "Penance Ranger", + "id": "5232" }, { - "id": "5233", - "name": "Penance Ranger" + "name": "Penance Ranger", + "id": "5233" }, { - "id": "5234", - "name": "Penance Ranger" + "name": "Penance Ranger", + "id": "5234" }, { - "id": "5235", - "name": "Penance Ranger" + "name": "Penance Ranger", + "id": "5235" }, { - "id": "5236", - "name": "Penance Ranger" + "name": "Penance Ranger", + "id": "5236" }, { - "id": "5238", - "name": "Penance Healer" + "name": "Penance Healer", + "id": "5238" }, { - "id": "5239", - "name": "Penance Healer" + "name": "Penance Healer", + "id": "5239" }, { - "id": "5240", - "name": "Penance Healer" + "name": "Penance Healer", + "id": "5240" }, { - "id": "5241", - "name": "Penance Healer" + "name": "Penance Healer", + "id": "5241" }, { - "id": "5242", - "name": "Penance Healer" + "name": "Penance Healer", + "id": "5242" }, { - "id": "5243", - "name": "Penance Healer" + "name": "Penance Healer", + "id": "5243" }, { - "id": "5244", - "name": "Penance Healer" + "name": "Penance Healer", + "id": "5244" }, { - "id": "5245", - "name": "Penance Healer" + "name": "Penance Healer", + "id": "5245" }, { - "id": "5246", - "name": "Penance Healer" + "name": "Penance Healer", + "id": "5246" }, { - "id": "5255", - "name": "Locust rider" + "name": "Locust rider", + "id": "5255" }, { - "id": "5256", - "name": "Locust rider" + "name": "Locust rider", + "id": "5256" }, { - "id": "5257", - "name": "Banker" + "name": "Banker", + "id": "5257" }, { - "id": "5259", - "name": "Banker" + "name": "Banker", + "id": "5259" }, { - "id": "5261", - "name": "Stonemason" + "name": "Stonemason", + "id": "5261" }, { - "id": "5263", - "name": "Nathifa" + "name": "Nathifa", + "id": "5263" }, { - "id": "5265", - "name": "Urbi" + "name": "Urbi", + "id": "5265" }, { - "id": "5267", - "name": "Jamila" + "name": "Jamila", + "id": "5267" }, { - "id": "5269", - "name": "Sophanem guard" + "name": "Sophanem guard", + "id": "5269" }, { - "id": "5271", - "name": "Sophanem guard" + "name": "Sophanem guard", + "id": "5271" }, { - "id": "5273", - "name": "Sophanem guard" + "name": "Sophanem guard", + "id": "5273" }, { - "id": "5275", - "name": "Sophanem guard" + "name": "Sophanem guard", + "id": "5275" }, { - "id": "5278", - "name": "Coenus" + "name": "Coenus", + "id": "5278" }, { - "id": "5279", - "name": "Jex" + "name": "Jex", + "id": "5279" }, { - "id": "5280", - "name": "Maisa" + "name": "Maisa", + "id": "5280" }, { - "id": "5282", - "name": "Osman" + "name": "Osman", + "id": "5282" }, { - "id": "5286", - "name": "Osman" + "name": "Osman", + "id": "5286" }, { - "id": "5287", - "name": "Osman" + "name": "Osman", + "id": "5287" }, { - "id": "5288", - "name": "Embalmer" + "name": "Embalmer", + "id": "5288" }, { - "id": "5289", - "name": "Carpenter" + "name": "Carpenter", + "id": "5289" }, { - "id": "5290", - "name": "Linen worker" + "name": "Linen worker", + "id": "5290" }, { - "id": "5291", - "name": "Priest" + "name": "Priest", + "id": "5291" }, { - "id": "5292", - "name": "Giant scarab" + "name": "Giant scarab", + "id": "5292" }, { - "id": "5360", - "name": "Mummy ashes" + "name": "Mummy ashes", + "id": "5360" }, { - "id": "5383", - "name": "Odovacar" + "name": "Odovacar", + "id": "5383" }, { - "id": "5415", - "name": "Terror dog statue" + "name": "Terror dog statue", + "id": "5415" }, { - "id": "5416", - "name": "Terror dog statue" + "name": "Terror dog statue", + "id": "5416" }, { - "id": "5419", - "name": "Tarn" + "name": "Tarn", + "id": "5419" }, { - "id": "5423", - "name": "Larry" + "name": "Larry", + "id": "5423" }, { - "id": "5425", - "name": "Larry" + "name": "Larry", + "id": "5425" }, { - "id": "5426", - "name": "Larry" + "name": "Larry", + "id": "5426" }, { - "id": "5427", - "name": "Penguin" + "name": "Penguin", + "id": "5427" }, { - "id": "5429", - "name": "Penguin" + "name": "Penguin", + "id": "5429" }, { - "id": "5430", - "name": "Penguin" + "name": "Penguin", + "id": "5430" }, { - "id": "5431", - "name": "KGP Guard" + "name": "KGP Guard", + "id": "5431" }, { - "id": "5432", - "name": "Pescaling Pax" + "name": "Pescaling Pax", + "id": "5432" }, { - "id": "5433", - "name": "Ping" + "name": "Ping", + "id": "5433" }, { - "id": "5434", - "name": "Ping" + "name": "Ping", + "id": "5434" }, { - "id": "5435", - "name": "Pong" + "name": "Pong", + "id": "5435" }, { - "id": "5436", - "name": "Pong" + "name": "Pong", + "id": "5436" }, { - "id": "5437", - "name": "Ping" + "name": "Ping", + "id": "5437" }, { - "id": "5438", - "name": "Pong" + "name": "Pong", + "id": "5438" }, { - "id": "5443", - "name": "Noodle" + "name": "Noodle", + "id": "5443" }, { - "id": "5445", - "name": "Penguin" + "name": "Penguin", + "id": "5445" }, { - "id": "5446", - "name": "Penguin suit" + "name": "Penguin suit", + "id": "5446" }, { - "id": "5449", - "name": "Penguin" + "name": "Penguin", + "id": "5449" }, { - "id": "5450", - "name": "Penguin" + "name": "Penguin", + "id": "5450" }, { - "id": "5451", - "name": "Penguin" + "name": "Penguin", + "id": "5451" }, { - "id": "5456", - "name": "Crusher" + "name": "Crusher", + "id": "5456" }, { - "id": "5457", - "name": "Crusher" + "name": "Crusher", + "id": "5457" }, { - "id": "5458", - "name": "Crusher" + "name": "Crusher", + "id": "5458" }, { - "id": "5459", - "name": "Crusher" + "name": "Crusher", + "id": "5459" }, { - "id": "5460", - "name": "Tree" + "name": "Tree", + "id": "5460" }, { - "id": "5461", - "name": "Jungle Tree" + "name": "Jungle Tree", + "id": "5461" }, { - "id": "5462", - "name": "Tolna" + "name": "Tolna", + "id": "5462" }, { - "id": "5463", - "name": "Honour guard" + "name": "Honour guard", + "id": "5463" }, { - "id": "5464", - "name": "Honour guard" + "name": "Honour guard", + "id": "5464" }, { - "id": "5465", - "name": "Fridleif Shieldson" + "name": "Fridleif Shieldson", + "id": "5465" }, { - "id": "5466", - "name": "Thakkrad Sigmundson" + "name": "Thakkrad Sigmundson", + "id": "5466" }, { - "id": "5467", - "name": "Iceberg" + "name": "Iceberg", + "id": "5467" }, { - "id": "5468", - "name": "Iceberg" + "name": "Iceberg", + "id": "5468" }, { - "id": "5469", - "name": "Arctic Pine" + "name": "Arctic Pine", + "id": "5469" }, { - "id": "5470", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "5470" }, { - "id": "5471", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "5471" }, { - "id": "5477", - "name": "Bork Sigmundson" + "name": "Bork Sigmundson", + "id": "5477" }, { - "id": "5481", - "name": "Mord Gunnars" + "name": "Mord Gunnars", + "id": "5481" }, { - "id": "5482", - "name": "Mord Gunnars" + "name": "Mord Gunnars", + "id": "5482" }, { - "id": "5498", - "name": "Miner" + "name": "Miner", + "id": "5498" }, { - "id": "5502", - "name": "Grundt" + "name": "Grundt", + "id": "5502" }, { - "id": "5503", - "name": "Mawnis Burowgar" + "name": "Mawnis Burowgar", + "id": "5503" }, { - "id": "5504", - "name": "Mawnis Burowgar" + "name": "Mawnis Burowgar", + "id": "5504" }, { - "id": "5505", - "name": "Fridleif Shieldson" + "name": "Fridleif Shieldson", + "id": "5505" }, { - "id": "5506", - "name": "Thakkrad Sigmundson" + "name": "Thakkrad Sigmundson", + "id": "5506" }, { - "id": "5507", - "name": "Maria Gunnars" + "name": "Maria Gunnars", + "id": "5507" }, { - "id": "5508", - "name": "Maria Gunnars" + "name": "Maria Gunnars", + "id": "5508" }, { - "id": "5509", - "name": "Jofridr Mordstatter" + "name": "Jofridr Mordstatter", + "id": "5509" }, { - "id": "5510", - "name": "Morten Holdstrom" + "name": "Morten Holdstrom", + "id": "5510" }, { - "id": "5511", - "name": "Gunnar Holdstrom" + "name": "Gunnar Holdstrom", + "id": "5511" }, { - "id": "5512", - "name": "Anne Isaakson" + "name": "Anne Isaakson", + "id": "5512" }, { - "id": "5513", - "name": "Lisse Isaakson" + "name": "Lisse Isaakson", + "id": "5513" }, { - "id": "5518", - "name": "Kjedelig Uppsen" + "name": "Kjedelig Uppsen", + "id": "5518" }, { - "id": "5519", - "name": "Trogen Konungarde" + "name": "Trogen Konungarde", + "id": "5519" }, { - "id": "5520", - "name": "Slug Hemligssen" + "name": "Slug Hemligssen", + "id": "5520" }, { - "id": "5528", - "name": "Ice troll grunt" + "name": "Ice troll grunt", + "id": "5528" }, { - "id": "5530", - "name": "Sorceress" + "name": "Sorceress", + "id": "5530" }, { - "id": "5560", - "name": "Osman" + "name": "Osman", + "id": "5560" }, { - "id": "5562", - "name": "Del-Monty" + "name": "Del-Monty", + "id": "5562" }, { - "id": "5564", - "name": "Bouncer" + "name": "Bouncer", + "id": "5564" }, { - "id": "5565", - "name": "Bouncer" + "name": "Bouncer", + "id": "5565" }, { - "id": "5566", - "name": "General Khazard" + "name": "General Khazard", + "id": "5566" }, { - "id": "5567", - "name": "Scout" + "name": "Scout", + "id": "5567" }, { - "id": "5568", - "name": "Scout" + "name": "Scout", + "id": "5568" }, { - "id": "5569", - "name": "Scout" + "name": "Scout", + "id": "5569" }, { - "id": "5570", - "name": "Scout" + "name": "Scout", + "id": "5570" }, { - "id": "5573", - "name": "Effigy" + "name": "Effigy", + "id": "5573" }, { - "id": "5579", - "name": "Effigy" + "name": "Effigy", + "id": "5579" }, { - "id": "5580", - "name": "Bonafido" + "name": "Bonafido", + "id": "5580" }, { - "id": "5582", - "name": "Homunculus" + "name": "Homunculus", + "id": "5582" }, { - "id": "5583", - "name": "Homunculus" + "name": "Homunculus", + "id": "5583" }, { - "id": "5585", - "name": "'Transmute' The Alchemist" + "name": "'Transmute' The Alchemist", + "id": "5585" }, { - "id": "5586", - "name": "'Transmute' The Alchemist" + "name": "'Transmute' The Alchemist", + "id": "5586" }, { - "id": "5587", - "name": "'Currency' The Alchemist" + "name": "'Currency' The Alchemist", + "id": "5587" }, { - "id": "5588", - "name": "'Currency' The Alchemist" + "name": "'Currency' The Alchemist", + "id": "5588" }, { - "id": "5592", - "name": "'The Guns'" + "name": "'The Guns'", + "id": "5592" }, { - "id": "5598", - "name": "Unicow" + "name": "Unicow", + "id": "5598" }, { - "id": "5604", - "name": "Elfinlocks" + "name": "Elfinlocks", + "id": "5604" }, { - "id": "5605", - "name": "Clockwork cat" + "name": "Clockwork cat", + "id": "5605" }, { - "id": "5606", - "name": "Clockwork cat" + "name": "Clockwork cat", + "id": "5606" }, { - "id": "5612", - "name": "Rufus" + "name": "Rufus", + "id": "5612" }, { - "id": "5613", - "name": "Mi-Gor" + "name": "Mi-Gor", + "id": "5613" }, { - "id": "5615", - "name": "Puffin" + "name": "Puffin", + "id": "5615" }, { - "id": "5616", - "name": "Brother Tranquility" + "name": "Brother Tranquility", + "id": "5616" }, { - "id": "5617", - "name": "Brother Tranquility" + "name": "Brother Tranquility", + "id": "5617" }, { - "id": "5618", - "name": "Brother Tranquility" + "name": "Brother Tranquility", + "id": "5618" }, { - "id": "5623", - "name": "Zombie monk" + "name": "Zombie monk", + "id": "5623" }, { - "id": "5624", - "name": "Zombie monk" + "name": "Zombie monk", + "id": "5624" }, { - "id": "5625", - "name": "Zombie monk" + "name": "Zombie monk", + "id": "5625" }, { - "id": "5626", - "name": "Zombie monk" + "name": "Zombie monk", + "id": "5626" }, { - "id": "5667", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5667" }, { - "id": "5679", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5679" }, { - "id": "5681", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5681" }, { - "id": "5682", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5682" }, { - "id": "5683", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5683" }, { - "id": "5684", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5684" }, { - "id": "5685", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5685" }, { - "id": "5686", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5686" }, { - "id": "5687", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5687" }, { - "id": "5688", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5688" }, { - "id": "5689", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5689" }, { - "id": "5690", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5690" }, { - "id": "5691", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5691" }, { - "id": "5692", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5692" }, { - "id": "5693", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5693" }, { - "id": "5694", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5694" }, { - "id": "5695", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5695" }, { - "id": "5696", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5696" }, { - "id": "5697", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5697" }, { - "id": "5698", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5698" }, { - "id": "5699", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5699" }, { - "id": "5700", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5700" }, { - "id": "5701", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5701" }, { - "id": "5702", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5702" }, { - "id": "5703", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5703" }, { - "id": "5704", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5704" }, { - "id": "5705", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5705" }, { - "id": "5706", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5706" }, { - "id": "5707", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5707" }, { - "id": "5708", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5708" }, { - "id": "5709", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5709" }, { - "id": "5710", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5710" }, { - "id": "5711", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5711" }, { - "id": "5712", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5712" }, { - "id": "5713", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5713" }, { - "id": "5714", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5714" }, { - "id": "5715", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5715" }, { - "id": "5716", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5716" }, { - "id": "5717", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5717" }, { - "id": "5718", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5718" }, { - "id": "5719", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5719" }, { - "id": "5720", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5720" }, { - "id": "5721", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5721" }, { - "id": "5722", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5722" }, { - "id": "5723", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5723" }, { - "id": "5724", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5724" }, { - "id": "5725", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5725" }, { - "id": "5726", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5726" }, { - "id": "5727", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5727" }, { - "id": "5728", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5728" }, { - "id": "5729", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5729" }, { - "id": "5730", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5730" }, { - "id": "5731", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5731" }, { - "id": "5732", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5732" }, { - "id": "5733", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5733" }, { - "id": "5734", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5734" }, { - "id": "5735", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5735" }, { - "id": "5736", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5736" }, { - "id": "5737", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5737" }, { - "id": "5738", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5738" }, { - "id": "5739", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5739" }, { - "id": "5740", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5740" }, { - "id": "5741", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5741" }, { - "id": "5742", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5742" }, { - "id": "5743", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5743" }, { - "id": "5744", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5744" }, { - "id": "5745", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5745" }, { - "id": "5746", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5746" }, { - "id": "5747", - "name": "Undead Lumberjack" + "name": "Undead Lumberjack", + "id": "5747" }, { - "id": "5748", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "5748" }, { - "id": "5749", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "5749" }, { - "id": "5770", - "name": "Ur-zek" + "name": "Ur-zek", + "id": "5770" }, { - "id": "5771", - "name": "Ur-vass" + "name": "Ur-vass", + "id": "5771" }, { - "id": "5772", - "name": "Ur-taal" + "name": "Ur-taal", + "id": "5772" }, { - "id": "5773", - "name": "Ur-meg" + "name": "Ur-meg", + "id": "5773" }, { - "id": "5774", - "name": "Ur-lun" + "name": "Ur-lun", + "id": "5774" }, { - "id": "5775", - "name": "Ur-pel" + "name": "Ur-pel", + "id": "5775" }, { - "id": "5778", - "name": "Bartak" + "name": "Bartak", + "id": "5778" }, { - "id": "5779", - "name": "Turgall" + "name": "Turgall", + "id": "5779" }, { - "id": "5780", - "name": "Reldak" + "name": "Reldak", + "id": "5780" }, { - "id": "5781", - "name": "Miltog" + "name": "Miltog", + "id": "5781" }, { - "id": "5782", - "name": "Mernik" + "name": "Mernik", + "id": "5782" }, { - "id": "5787", - "name": "Gourmet" + "name": "Gourmet", + "id": "5787" }, { - "id": "5789", - "name": "Gourmet" + "name": "Gourmet", + "id": "5789" }, { - "id": "5790", - "name": "Gourmet" + "name": "Gourmet", + "id": "5790" }, { - "id": "5791", - "name": "Gourmet" + "name": "Gourmet", + "id": "5791" }, { - "id": "5792", - "name": "Turgok" + "name": "Turgok", + "id": "5792" }, { - "id": "5793", - "name": "Markog" + "name": "Markog", + "id": "5793" }, { - "id": "5794", - "name": "Durgok" + "name": "Durgok", + "id": "5794" }, { - "id": "5795", - "name": "Tindar" + "name": "Tindar", + "id": "5795" }, { - "id": "5796", - "name": "Gundik" + "name": "Gundik", + "id": "5796" }, { - "id": "5797", - "name": "Zenkog" + "name": "Zenkog", + "id": "5797" }, { - "id": "5798", - "name": "Lurgon" + "name": "Lurgon", + "id": "5798" }, { - "id": "5799", - "name": "Ur-tag" + "name": "Ur-tag", + "id": "5799" }, { - "id": "5802", - "name": "Young 'un" + "name": "Young 'un", + "id": "5802" }, { - "id": "5804", - "name": "Tyke" + "name": "Tyke", + "id": "5804" }, { - "id": "5806", - "name": "Nipper" + "name": "Nipper", + "id": "5806" }, { - "id": "5825", - "name": "Movario" + "name": "Movario", + "id": "5825" }, { - "id": "5826", - "name": "Darve" + "name": "Darve", + "id": "5826" }, { - "id": "5828", - "name": "Barlak" + "name": "Barlak", + "id": "5828" }, { - "id": "5830", - "name": "Rat Burgiss" + "name": "Rat Burgiss", + "id": "5830" }, { - "id": "5835", - "name": "Surok Magis" + "name": "Surok Magis", + "id": "5835" }, { - "id": "5836", - "name": "Zaff" + "name": "Zaff", + "id": "5836" }, { - "id": "5837", - "name": "Anna Jones" + "name": "Anna Jones", + "id": "5837" }, { - "id": "5838", - "name": "King Roald" + "name": "King Roald", + "id": "5838" }, { - "id": "5839", - "name": "Mishkal'un Dorn" + "name": "Mishkal'un Dorn", + "id": "5839" }, { - "id": "5840", - "name": "Dakh'thoulan Aegis" + "name": "Dakh'thoulan Aegis", + "id": "5840" }, { - "id": "5841", - "name": "Sil'as Dahcsnu" + "name": "Sil'as Dahcsnu", + "id": "5841" }, { - "id": "5853", - "name": "Bench" + "name": "Bench", + "id": "5853" }, { - "id": "5857", - "name": "Zanik" + "name": "Zanik", + "id": "5857" }, { - "id": "5858", - "name": "Ur-tag" + "name": "Ur-tag", + "id": "5858" }, { - "id": "5862", - "name": "Sigmund and Zanik" + "name": "Sigmund and Zanik", + "id": "5862" }, { - "id": "5863", - "name": "Ambassador Alvijar" + "name": "Ambassador Alvijar", + "id": "5863" }, { - "id": "5868", - "name": "Tegdak" + "name": "Tegdak", + "id": "5868" }, { - "id": "5869", - "name": "Zanik" + "name": "Zanik", + "id": "5869" }, { - "id": "5871", - "name": "Sergeant Mossfists" + "name": "Sergeant Mossfists", + "id": "5871" }, { - "id": "5872", - "name": "Sergeant Slimetoes" + "name": "Sergeant Slimetoes", + "id": "5872" }, { - "id": "5887", - "name": "Cyrisus" + "name": "Cyrisus", + "id": "5887" }, { - "id": "5894", - "name": "Cyrisus" + "name": "Cyrisus", + "id": "5894" }, { - "id": "5895", - "name": "Cyrisus" + "name": "Cyrisus", + "id": "5895" }, { - "id": "5896", - "name": "Cyrisus" + "name": "Cyrisus", + "id": "5896" }, { - "id": "5897", - "name": "Cyrisus" + "name": "Cyrisus", + "id": "5897" }, { - "id": "5898", - "name": "'Bird's-Eye' Jack" + "name": "'Bird's-Eye' Jack", + "id": "5898" }, { - "id": "5899", - "name": "The Inadequacy" + "name": "The Inadequacy", + "id": "5899" }, { - "id": "5907", - "name": "The Illusive" + "name": "The Illusive", + "id": "5907" }, { - "id": "5912", - "name": "Banker" + "name": "Banker", + "id": "5912" }, { - "id": "5913", - "name": "Banker" + "name": "Banker", + "id": "5913" }, { - "id": "5915", - "name": "Elsie" + "name": "Elsie", + "id": "5915" }, { - "id": "5918", - "name": "Stray dog" + "name": "Stray dog", + "id": "5918" }, { - "id": "5932", - "name": "Barnabus Hurma" + "name": "Barnabus Hurma", + "id": "5932" }, { - "id": "5933", - "name": "Marius Giste" + "name": "Marius Giste", + "id": "5933" }, { - "id": "5934", - "name": "Caden Azro" + "name": "Caden Azro", + "id": "5934" }, { - "id": "5935", - "name": "Thias Leacke" + "name": "Thias Leacke", + "id": "5935" }, { - "id": "5936", - "name": "Sinco Doar" + "name": "Sinco Doar", + "id": "5936" }, { - "id": "5937", - "name": "Tinse Torpe" + "name": "Tinse Torpe", + "id": "5937" }, { - "id": "5939", - "name": "Torrcs" + "name": "Torrcs", + "id": "5939" }, { - "id": "5940", - "name": "Marfet" + "name": "Marfet", + "id": "5940" }, { - "id": "5941", - "name": "Museum guard" + "name": "Museum guard", + "id": "5941" }, { - "id": "5942", - "name": "Museum guard" + "name": "Museum guard", + "id": "5942" }, { - "id": "5943", - "name": "Museum guard" + "name": "Museum guard", + "id": "5943" }, { - "id": "5946", - "name": "Schoolboy" + "name": "Schoolboy", + "id": "5946" }, { - "id": "5948", - "name": "Teacher and pupil" + "name": "Teacher and pupil", + "id": "5948" }, { - "id": "5949", - "name": "Schoolboy" + "name": "Schoolboy", + "id": "5949" }, { - "id": "5950", - "name": "Teacher" + "name": "Teacher", + "id": "5950" }, { - "id": "5951", - "name": "Schoolgirl" + "name": "Schoolgirl", + "id": "5951" }, { - "id": "5953", - "name": "Workman" + "name": "Workman", + "id": "5953" }, { - "id": "5955", - "name": "Workman" + "name": "Workman", + "id": "5955" }, { - "id": "5957", - "name": "Schoolgirl" + "name": "Schoolgirl", + "id": "5957" }, { - "id": "5964", - "name": "Ed Wood" + "name": "Ed Wood", + "id": "5964" }, { - "id": "5965", - "name": "Orlando Smith" + "name": "Orlando Smith", + "id": "5965" }, { - "id": "5967", - "name": "Natural historian" + "name": "Natural historian", + "id": "5967" }, { - "id": "5968", - "name": "Natural historian" + "name": "Natural historian", + "id": "5968" }, { - "id": "5969", - "name": "Natural historian" + "name": "Natural historian", + "id": "5969" }, { - "id": "5970", - "name": "Natural historian" + "name": "Natural historian", + "id": "5970" }, { - "id": "5983", - "name": "Schoolgirl" + "name": "Schoolgirl", + "id": "5983" }, { - "id": "5984", - "name": "Schoolgirl" + "name": "Schoolgirl", + "id": "5984" }, { - "id": "5985", - "name": "Schoolgirl" + "name": "Schoolgirl", + "id": "5985" }, { - "id": "5988", - "name": "Miazrqa" + "name": "Miazrqa", + "id": "5988" }, { - "id": "5989", - "name": "Grimgnash" + "name": "Grimgnash", + "id": "5989" }, { - "id": "5991", - "name": "Drain pipe" + "name": "Drain pipe", + "id": "5991" }, { - "id": "5995", - "name": "Mouse" + "name": "Mouse", + "id": "5995" }, { - "id": "5997", - "name": "Rupert the Beard" + "name": "Rupert the Beard", + "id": "5997" }, { - "id": "6000", - "name": "Rupert the Beard" + "name": "Rupert the Beard", + "id": "6000" }, { - "id": "6002", - "name": "Gnome" + "name": "Gnome", + "id": "6002" }, { - "id": "6003", - "name": "Winkin" + "name": "Winkin", + "id": "6003" }, { - "id": "6004", - "name": "Gnome" + "name": "Gnome", + "id": "6004" }, { - "id": "6005", - "name": "Cage" + "name": "Cage", + "id": "6005" }, { - "id": "6071", - "name": "Immenizz" + "name": "Immenizz", + "id": "6071" }, { - "id": "6075", - "name": "Cyclops" + "name": "Cyclops", + "id": "6075" }, { - "id": "6082", - "name": "Captain Ned" + "name": "Captain Ned", + "id": "6082" }, { - "id": "6085", - "name": "Cabin boy Jenkins" + "name": "Cabin boy Jenkins", + "id": "6085" }, { - "id": "6086", - "name": "Cabin boy Jenkins" + "name": "Cabin boy Jenkins", + "id": "6086" }, { - "id": "6087", - "name": "Elvarg" + "name": "Elvarg", + "id": "6087" }, { - "id": "6114", - "name": "Drake" + "name": "Drake", + "id": "6114" }, { - "id": "6119", - "name": "Observatory professor" + "name": "Observatory professor", + "id": "6119" }, { - "id": "6130", - "name": "Clothears" + "name": "Clothears", + "id": "6130" }, { - "id": "6138", - "name": "Town crier" + "name": "Town crier", + "id": "6138" }, { - "id": "6139", - "name": "Town crier" + "name": "Town crier", + "id": "6139" }, { - "id": "6158", - "name": "Sir Lucan" + "name": "Sir Lucan", + "id": "6158" }, { - "id": "6160", - "name": "Sir Lancelot" + "name": "Sir Lancelot", + "id": "6160" }, { - "id": "6161", - "name": "Sir Bedivere" + "name": "Sir Bedivere", + "id": "6161" }, { - "id": "6162", - "name": "Sir Tristram" + "name": "Sir Tristram", + "id": "6162" }, { - "id": "6163", - "name": "Sir Pelleas" + "name": "Sir Pelleas", + "id": "6163" }, { - "id": "6164", - "name": "Sir Gawain" + "name": "Sir Gawain", + "id": "6164" }, { - "id": "6165", - "name": "Sir Kay" + "name": "Sir Kay", + "id": "6165" }, { - "id": "6166", - "name": "Sir Pelleas" + "name": "Sir Pelleas", + "id": "6166" }, { - "id": "6167", - "name": "Sir Gawain" + "name": "Sir Gawain", + "id": "6167" }, { - "id": "6168", - "name": "Sir Kay" + "name": "Sir Kay", + "id": "6168" }, { - "id": "6171", - "name": "Sir Kay" + "name": "Sir Kay", + "id": "6171" }, { - "id": "6172", - "name": "Sir Gawain" + "name": "Sir Gawain", + "id": "6172" }, { - "id": "6173", - "name": "Sir Lucan" + "name": "Sir Lucan", + "id": "6173" }, { - "id": "6174", - "name": "Bandit" + "name": "Bandit", + "id": "6174" }, { - "id": "6175", - "name": "Sir Tristram" + "name": "Sir Tristram", + "id": "6175" }, { - "id": "6176", - "name": "Sir Pelleas" + "name": "Sir Pelleas", + "id": "6176" }, { - "id": "6177", - "name": "Sir Bedivere" + "name": "Sir Bedivere", + "id": "6177" }, { - "id": "6178", - "name": "Anna" + "name": "Anna", + "id": "6178" }, { - "id": "6179", - "name": "David" + "name": "David", + "id": "6179" }, { - "id": "6180", - "name": "Anna" + "name": "Anna", + "id": "6180" }, { - "id": "6181", - "name": "Court judge" + "name": "Court judge", + "id": "6181" }, { - "id": "6182", - "name": "Jury" + "name": "Jury", + "id": "6182" }, { - "id": "6185", - "name": "Prosecutor" + "name": "Prosecutor", + "id": "6185" }, { - "id": "6186", - "name": "Morgan Le Faye" + "name": "Morgan Le Faye", + "id": "6186" }, { - "id": "6191", - "name": "Sinclair" + "name": "Sinclair", + "id": "6191" }, { - "id": "6199", - "name": "Banker" + "name": "Banker", + "id": "6199" }, { - "id": "6202", - "name": "K'ril Tsutsaroth" + "name": "K'ril Tsutsaroth", + "id": "6202" }, { - "id": "6284", - "name": "Warped terrorbird" + "name": "Warped terrorbird", + "id": "6284" }, { - "id": "6298", - "name": "Jeffery" + "name": "Jeffery", + "id": "6298" }, { - "id": "6299", - "name": "Big monolith" + "name": "Big monolith", + "id": "6299" }, { - "id": "6300", - "name": "Small monolith" + "name": "Small monolith", + "id": "6300" }, { - "id": "6301", - "name": "Cute creature" + "name": "Cute creature", + "id": "6301" }, { - "id": "6302", - "name": "Evil creature" + "name": "Evil creature", + "id": "6302" }, { - "id": "6303", - "name": "Yewnock the engineer" + "name": "Yewnock the engineer", + "id": "6303" }, { - "id": "6304", - "name": "Bolrie" + "name": "Bolrie", + "id": "6304" }, { - "id": "6305", - "name": "Hazelmere" + "name": "Hazelmere", + "id": "6305" }, { - "id": "6307", - "name": "Advisor" + "name": "Advisor", + "id": "6307" }, { - "id": "6308", - "name": "King Argenthorg" + "name": "King Argenthorg", + "id": "6308" }, { - "id": "6309", - "name": "Prince Argenthorg" + "name": "Prince Argenthorg", + "id": "6309" }, { - "id": "6310", - "name": "Cute creature" + "name": "Cute creature", + "id": "6310" }, { - "id": "6312", - "name": "Evil creature" + "name": "Evil creature", + "id": "6312" }, { - "id": "6313", - "name": "Terrorbird servant" + "name": "Terrorbird servant", + "id": "6313" }, { - "id": "6316", - "name": "Guard no. 21" + "name": "Guard no. 21", + "id": "6316" }, { - "id": "6317", - "name": "Guard no. 72" + "name": "Guard no. 72", + "id": "6317" }, { - "id": "6318", - "name": "Longramble" + "name": "Longramble", + "id": "6318" }, { - "id": "6319", - "name": "Spirit tree" + "name": "Spirit tree", + "id": "6319" }, { - "id": "6321", - "name": "Spirit tree" + "name": "Spirit tree", + "id": "6321" }, { - "id": "6333", - "name": "Child" + "name": "Child", + "id": "6333" }, { - "id": "6340", - "name": "Child" + "name": "Child", + "id": "6340" }, { - "id": "6341", - "name": "Child" + "name": "Child", + "id": "6341" }, { - "id": "6342", - "name": "Child" + "name": "Child", + "id": "6342" }, { - "id": "6343", - "name": "Child" + "name": "Child", + "id": "6343" }, { - "id": "6345", - "name": "Child" + "name": "Child", + "id": "6345" }, { - "id": "6357", - "name": "Street urchin" + "name": "Street urchin", + "id": "6357" }, { - "id": "6359", - "name": "Street urchin" + "name": "Street urchin", + "id": "6359" }, { - "id": "6360", - "name": "Street urchin" + "name": "Street urchin", + "id": "6360" }, { - "id": "6361", - "name": "Street urchin" + "name": "Street urchin", + "id": "6361" }, { - "id": "6362", - "name": "Eniola" + "name": "Eniola", + "id": "6362" }, { - "id": "6373", - "name": "Kennith" + "name": "Kennith", + "id": "6373" }, { - "id": "6375", - "name": "Wizard Cromperty" + "name": "Wizard Cromperty", + "id": "6375" }, { - "id": "6384", - "name": "Karamjan Jungle Eagle" + "name": "Karamjan Jungle Eagle", + "id": "6384" }, { - "id": "6386", - "name": "Bandit" + "name": "Bandit", + "id": "6386" }, { - "id": "6391", - "name": "Glough" + "name": "Glough", + "id": "6391" }, { - "id": "6392", - "name": "Oldak" + "name": "Oldak", + "id": "6392" }, { - "id": "6393", - "name": "Zanik" + "name": "Zanik", + "id": "6393" }, { - "id": "6394", - "name": "Zanik" + "name": "Zanik", + "id": "6394" }, { - "id": "6396", - "name": "Zanik" + "name": "Zanik", + "id": "6396" }, { - "id": "6398", - "name": "Zanik" + "name": "Zanik", + "id": "6398" }, { - "id": "6400", - "name": "Oldak" + "name": "Oldak", + "id": "6400" }, { - "id": "6468", - "name": "Skoblin" + "name": "Skoblin", + "id": "6468" }, { - "id": "6474", - "name": "Snothead" + "name": "Snothead", + "id": "6474" }, { - "id": "6475", - "name": "Snailfeet" + "name": "Snailfeet", + "id": "6475" }, { - "id": "6476", - "name": "Mosschin" + "name": "Mosschin", + "id": "6476" }, { - "id": "6477", - "name": "Redeyes" + "name": "Redeyes", + "id": "6477" }, { - "id": "6478", - "name": "Strongbones" + "name": "Strongbones", + "id": "6478" }, { - "id": "6479", - "name": "Grubfoot" + "name": "Grubfoot", + "id": "6479" }, { - "id": "6480", - "name": "Grubfoot" + "name": "Grubfoot", + "id": "6480" }, { - "id": "6483", - "name": "Priest" + "name": "Priest", + "id": "6483" }, { - "id": "6484", - "name": "Priest" + "name": "Priest", + "id": "6484" }, { - "id": "6485", - "name": "Priest" + "name": "Priest", + "id": "6485" }, { - "id": "6486", - "name": "Priest" + "name": "Priest", + "id": "6486" }, { - "id": "6487", - "name": "Priest" + "name": "Priest", + "id": "6487" }, { - "id": "6489", - "name": "High priest" + "name": "High priest", + "id": "6489" }, { - "id": "6505", - "name": "Registrar 1" + "name": "Registrar 1", + "id": "6505" }, { - "id": "6511", - "name": "Jury" + "name": "Jury", + "id": "6511" }, { - "id": "6513", - "name": "Spectator" + "name": "Spectator", + "id": "6513" }, { - "id": "6515", - "name": "Spectator" + "name": "Spectator", + "id": "6515" }, { - "id": "6517", - "name": "Spectator" + "name": "Spectator", + "id": "6517" }, { - "id": "6519", - "name": "Spectator" + "name": "Spectator", + "id": "6519" }, { - "id": "6520", - "name": "Head Registrar" + "name": "Head Registrar", + "id": "6520" }, { - "id": "6537", - "name": "Mandrith" + "name": "Mandrith", + "id": "6537" }, { - "id": "6538", - "name": "Banker" + "name": "Banker", + "id": "6538" }, { - "id": "6539", - "name": "Charley the Cleaner" + "name": "Charley the Cleaner", + "id": "6539" }, { - "id": "6540", - "name": "Scotty the Cleaner" + "name": "Scotty the Cleaner", + "id": "6540" }, { - "id": "6541", - "name": "Sanchez the Cleaner" + "name": "Sanchez the Cleaner", + "id": "6541" }, { - "id": "6542", - "name": "Summer Bonde" + "name": "Summer Bonde", + "id": "6542" }, { - "id": "6566", - "name": "Broken grave marker" + "name": "Broken grave marker", + "id": "6566" }, { - "id": "6567", - "name": "Collapsing grave marker" + "name": "Collapsing grave marker", + "id": "6567" }, { - "id": "6568", - "name": "Grave marker" + "name": "Grave marker", + "id": "6568" }, { - "id": "6569", - "name": "Broken grave marker" + "name": "Broken grave marker", + "id": "6569" }, { - "id": "6570", - "name": "Collapsing grave marker" + "name": "Collapsing grave marker", + "id": "6570" }, { - "id": "6571", - "name": "Gravestone" + "name": "Gravestone", + "id": "6571" }, { - "id": "6572", - "name": "Broken gravestone" + "name": "Broken gravestone", + "id": "6572" }, { - "id": "6573", - "name": "Collapsing gravestone" + "name": "Collapsing gravestone", + "id": "6573" }, { - "id": "6574", - "name": "Gravestone" + "name": "Gravestone", + "id": "6574" }, { - "id": "6575", - "name": "Broken gravestone" + "name": "Broken gravestone", + "id": "6575" }, { - "id": "6576", - "name": "Collapsing gravestone" + "name": "Collapsing gravestone", + "id": "6576" }, { - "id": "6577", - "name": "Gravestone" + "name": "Gravestone", + "id": "6577" }, { - "id": "6578", - "name": "Broken gravestone" + "name": "Broken gravestone", + "id": "6578" }, { - "id": "6579", - "name": "Collapsing gravestone" + "name": "Collapsing gravestone", + "id": "6579" }, { - "id": "6580", - "name": "Stele" + "name": "Stele", + "id": "6580" }, { - "id": "6581", - "name": "Broken stele" + "name": "Broken stele", + "id": "6581" }, { - "id": "6582", - "name": "Collapsing stele" + "name": "Collapsing stele", + "id": "6582" }, { - "id": "6583", - "name": "Saradomin symbol" + "name": "Saradomin symbol", + "id": "6583" }, { - "id": "6584", - "name": "Broken Saradomin symbol" + "name": "Broken Saradomin symbol", + "id": "6584" }, { - "id": "6585", - "name": "Collapsing Saradomin symbol" + "name": "Collapsing Saradomin symbol", + "id": "6585" }, { - "id": "6586", - "name": "Zamorak symbol" + "name": "Zamorak symbol", + "id": "6586" }, { - "id": "6587", - "name": "Broken Zamorak symbol" + "name": "Broken Zamorak symbol", + "id": "6587" }, { - "id": "6588", - "name": "Collapsing Zamorak symbol" + "name": "Collapsing Zamorak symbol", + "id": "6588" }, { - "id": "6589", - "name": "Guthix symbol" + "name": "Guthix symbol", + "id": "6589" }, { - "id": "6590", - "name": "Broken Guthix symbol" + "name": "Broken Guthix symbol", + "id": "6590" }, { - "id": "6591", - "name": "Collapsing Guthix symbol" + "name": "Collapsing Guthix symbol", + "id": "6591" }, { - "id": "6592", - "name": "Bandos symbol" + "name": "Bandos symbol", + "id": "6592" }, { - "id": "6593", - "name": "Broken Bandos symbol" + "name": "Broken Bandos symbol", + "id": "6593" }, { - "id": "6594", - "name": "Collapsing Bandos symbol" + "name": "Collapsing Bandos symbol", + "id": "6594" }, { - "id": "6595", - "name": "Armadyl symbol" + "name": "Armadyl symbol", + "id": "6595" }, { - "id": "6596", - "name": "Broken Armadyl symbol" + "name": "Broken Armadyl symbol", + "id": "6596" }, { - "id": "6597", - "name": "Collapsing Armadyl symbol" + "name": "Collapsing Armadyl symbol", + "id": "6597" }, { - "id": "6598", - "name": "Memorial stone" + "name": "Memorial stone", + "id": "6598" }, { - "id": "6599", - "name": "Broken memorial stone" + "name": "Broken memorial stone", + "id": "6599" }, { - "id": "6600", - "name": "Collapsing memorial stone" + "name": "Collapsing memorial stone", + "id": "6600" }, { - "id": "6601", - "name": "Memorial stone" + "name": "Memorial stone", + "id": "6601" }, { - "id": "6602", - "name": "Broken memorial stone" + "name": "Broken memorial stone", + "id": "6602" }, { - "id": "6603", - "name": "Collapsing memorial stone" + "name": "Collapsing memorial stone", + "id": "6603" }, { - "id": "6732", - "name": "Snow imp" + "name": "Snow imp", + "id": "6732" }, { - "id": "6733", - "name": "Snow imp" + "name": "Snow imp", + "id": "6733" }, { - "id": "6734", - "name": "Snow imp" + "name": "Snow imp", + "id": "6734" }, { - "id": "6735", - "name": "Snow imp" + "name": "Snow imp", + "id": "6735" }, { - "id": "6736", - "name": "Snow imp" + "name": "Snow imp", + "id": "6736" }, { - "id": "6737", - "name": "Snow imp" + "name": "Snow imp", + "id": "6737" }, { - "id": "6738", - "name": "Snow imp" + "name": "Snow imp", + "id": "6738" }, { - "id": "6741", - "name": "Snow" + "name": "Snow", + "id": "6741" }, { - "id": "6746", - "name": "Snowman" + "name": "Snowman", + "id": "6746" }, { - "id": "6751", - "name": "Bulldog" + "name": "Bulldog", + "id": "6751" }, { - "id": "6753", - "name": "Mummy" + "name": "Mummy", + "id": "6753" }, { - "id": "6754", - "name": "Mummy" + "name": "Mummy", + "id": "6754" }, { - "id": "6755", - "name": "Mummy" + "name": "Mummy", + "id": "6755" }, { - "id": "6756", - "name": "Mummy" + "name": "Mummy", + "id": "6756" }, { - "id": "6757", - "name": "Mummy" + "name": "Mummy", + "id": "6757" }, { - "id": "6758", - "name": "Mummy" + "name": "Mummy", + "id": "6758" }, { - "id": "6759", - "name": "Mummy" + "name": "Mummy", + "id": "6759" }, { - "id": "6760", - "name": "Mummy" + "name": "Mummy", + "id": "6760" }, { - "id": "6771", - "name": "Giant scarab" + "name": "Giant scarab", + "id": "6771" }, { - "id": "6775", - "name": "Scabaras priest" + "name": "Scabaras priest", + "id": "6775" }, { - "id": "6782", - "name": "Maisa" + "name": "Maisa", + "id": "6782" }, { - "id": "6783", - "name": "Maisa" + "name": "Maisa", + "id": "6783" }, { - "id": "6784", - "name": "Priest" + "name": "Priest", + "id": "6784" }, { - "id": "6791", - "name": "High Priest of Scabaras" + "name": "High Priest of Scabaras", + "id": "6791" }, { - "id": "6792", - "name": "Vulture" + "name": "Vulture", + "id": "6792" }, { - "id": "6793", - "name": "Spirit terrorbird" + "name": "Spirit terrorbird", + "id": "6793" }, { - "id": "6808", - "name": "Beaver" + "name": "Beaver", + "id": "6808" }, { - "id": "6898", - "name": "Pet shop owner" + "name": "Pet shop owner", + "id": "6898" }, { - "id": "6899", - "name": "Bulldog" + "name": "Bulldog", + "id": "6899" }, { - "id": "6908", - "name": "Baby penguin" + "name": "Baby penguin", + "id": "6908" }, { - "id": "6909", - "name": "Penguin" + "name": "Penguin", + "id": "6909" }, { - "id": "6910", - "name": "Penguin" + "name": "Penguin", + "id": "6910" }, { - "id": "6911", - "name": "Raven chick" + "name": "Raven chick", + "id": "6911" }, { - "id": "6912", - "name": "Raven" + "name": "Raven", + "id": "6912" }, { - "id": "6913", - "name": "Baby raccoon" + "name": "Baby raccoon", + "id": "6913" }, { - "id": "6914", - "name": "Raccoon" + "name": "Raccoon", + "id": "6914" }, { - "id": "6915", - "name": "Baby gecko" + "name": "Baby gecko", + "id": "6915" }, { - "id": "6916", - "name": "Gecko" + "name": "Gecko", + "id": "6916" }, { - "id": "6918", - "name": "Gecko" + "name": "Gecko", + "id": "6918" }, { - "id": "6919", - "name": "Baby squirrel" + "name": "Baby squirrel", + "id": "6919" }, { - "id": "6920", - "name": "Squirrel" + "name": "Squirrel", + "id": "6920" }, { - "id": "6922", - "name": "Baby chameleon" + "name": "Baby chameleon", + "id": "6922" }, { - "id": "6923", - "name": "Chameleon" + "name": "Chameleon", + "id": "6923" }, { - "id": "6924", - "name": "Baby chameleon" + "name": "Baby chameleon", + "id": "6924" }, { - "id": "6925", - "name": "Chameleon" + "name": "Chameleon", + "id": "6925" }, { - "id": "6926", - "name": "Baby chameleon" + "name": "Baby chameleon", + "id": "6926" }, { - "id": "6927", - "name": "Chameleon" + "name": "Chameleon", + "id": "6927" }, { - "id": "6928", - "name": "Baby chameleon" + "name": "Baby chameleon", + "id": "6928" }, { - "id": "6929", - "name": "Chameleon" + "name": "Chameleon", + "id": "6929" }, { - "id": "6930", - "name": "Baby chameleon" + "name": "Baby chameleon", + "id": "6930" }, { - "id": "6931", - "name": "Chameleon" + "name": "Chameleon", + "id": "6931" }, { - "id": "6932", - "name": "Baby chameleon" + "name": "Baby chameleon", + "id": "6932" }, { - "id": "6933", - "name": "Chameleon" + "name": "Chameleon", + "id": "6933" }, { - "id": "6934", - "name": "Baby chameleon" + "name": "Baby chameleon", + "id": "6934" }, { - "id": "6935", - "name": "Chameleon" + "name": "Chameleon", + "id": "6935" }, { - "id": "6936", - "name": "Baby chameleon" + "name": "Baby chameleon", + "id": "6936" }, { - "id": "6937", - "name": "Chameleon" + "name": "Chameleon", + "id": "6937" }, { - "id": "6938", - "name": "Baby chameleon" + "name": "Baby chameleon", + "id": "6938" }, { - "id": "6939", - "name": "Chameleon" + "name": "Chameleon", + "id": "6939" }, { - "id": "6940", - "name": "Baby chameleon" + "name": "Baby chameleon", + "id": "6940" }, { - "id": "6941", - "name": "Chameleon" + "name": "Chameleon", + "id": "6941" }, { - "id": "6945", - "name": "Vulture chick" + "name": "Vulture chick", + "id": "6945" }, { - "id": "6946", - "name": "Vulture" + "name": "Vulture", + "id": "6946" }, { - "id": "6947", - "name": "Baby giant crab" + "name": "Baby giant crab", + "id": "6947" }, { - "id": "6948", - "name": "Giant crab" + "name": "Giant crab", + "id": "6948" }, { - "id": "6949", - "name": "Saradomin chick" + "name": "Saradomin chick", + "id": "6949" }, { - "id": "6950", - "name": "Saradomin bird" + "name": "Saradomin bird", + "id": "6950" }, { - "id": "6951", - "name": "Saradomin owl" + "name": "Saradomin owl", + "id": "6951" }, { - "id": "6952", - "name": "Zamorak chick" + "name": "Zamorak chick", + "id": "6952" }, { - "id": "6953", - "name": "Zamorak bird" + "name": "Zamorak bird", + "id": "6953" }, { - "id": "6954", - "name": "Zamorak hawk" + "name": "Zamorak hawk", + "id": "6954" }, { - "id": "6955", - "name": "Guthix chick" + "name": "Guthix chick", + "id": "6955" }, { - "id": "6956", - "name": "Guthix bird" + "name": "Guthix bird", + "id": "6956" }, { - "id": "6957", - "name": "Guthix raptor" + "name": "Guthix raptor", + "id": "6957" }, { - "id": "6970", - "name": "Pikkupstix" + "name": "Pikkupstix", + "id": "6970" }, { - "id": "6988", - "name": "Giant wolpertinger" + "name": "Giant wolpertinger", + "id": "6988" }, { - "id": "6991", - "name": "Ibis" + "name": "Ibis", + "id": "6991" }, { - "id": "6996", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "6996" }, { - "id": "7000", - "name": "Dark Squall" + "name": "Dark Squall", + "id": "7000" }, { - "id": "7002", - "name": "Surok Magis" + "name": "Surok Magis", + "id": "7002" }, { - "id": "7006", - "name": "Grenwall" + "name": "Grenwall", + "id": "7006" }, { - "id": "7015", - "name": "Platypus" + "name": "Platypus", + "id": "7015" }, { - "id": "7016", - "name": "Platypus" + "name": "Platypus", + "id": "7016" }, { - "id": "7017", - "name": "Platypus" + "name": "Platypus", + "id": "7017" }, { - "id": "7018", - "name": "Baby platypus" + "name": "Baby platypus", + "id": "7018" }, { - "id": "7019", - "name": "Baby platypus" + "name": "Baby platypus", + "id": "7019" }, { - "id": "7020", - "name": "Baby platypus" + "name": "Baby platypus", + "id": "7020" }, { - "id": "7022", - "name": "Platypus" + "name": "Platypus", + "id": "7022" }, { - "id": "7023", - "name": "Platypus" + "name": "Platypus", + "id": "7023" }, { - "id": "7025", - "name": "Baby platypus" + "name": "Baby platypus", + "id": "7025" }, { - "id": "7026", - "name": "Baby platypus" + "name": "Baby platypus", + "id": "7026" }, { - "id": "7027", - "name": "Patrick" + "name": "Patrick", + "id": "7027" }, { - "id": "7028", - "name": "Penelope" + "name": "Penelope", + "id": "7028" }, { - "id": "7029", - "name": "Peter" + "name": "Peter", + "id": "7029" }, { - "id": "7030", - "name": "Peanut" + "name": "Peanut", + "id": "7030" }, { - "id": "7032", - "name": "Diseased kebbit" + "name": "Diseased kebbit", + "id": "7032" }, { - "id": "7040", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "7040" }, { - "id": "7045", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "7045" }, { - "id": "7046", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "7046" }, { - "id": "7048", - "name": "Frawd" + "name": "Frawd", + "id": "7048" }, { - "id": "7050", - "name": "Ogress banker" + "name": "Ogress banker", + "id": "7050" }, { - "id": "7052", - "name": "Seegud" + "name": "Seegud", + "id": "7052" }, { - "id": "7053", - "name": "Chargurr" + "name": "Chargurr", + "id": "7053" }, { - "id": "7054", - "name": "Chargurr" + "name": "Chargurr", + "id": "7054" }, { - "id": "7055", - "name": "Snurgh" + "name": "Snurgh", + "id": "7055" }, { - "id": "7058", - "name": "Kringk" + "name": "Kringk", + "id": "7058" }, { - "id": "7059", - "name": "Thump" + "name": "Thump", + "id": "7059" }, { - "id": "7060", - "name": "Massage table" + "name": "Massage table", + "id": "7060" }, { - "id": "7061", - "name": "Thump" + "name": "Thump", + "id": "7061" }, { - "id": "7062", - "name": "Muggh" + "name": "Muggh", + "id": "7062" }, { - "id": "7063", - "name": "Kringk" + "name": "Kringk", + "id": "7063" }, { - "id": "7064", - "name": "Hairdryer" + "name": "Hairdryer", + "id": "7064" }, { - "id": "7065", - "name": "Snert" + "name": "Snert", + "id": "7065" }, { - "id": "7068", - "name": "Tyke" + "name": "Tyke", + "id": "7068" }, { - "id": "7069", - "name": "Snarrl" + "name": "Snarrl", + "id": "7069" }, { - "id": "7070", - "name": "Snarrk" + "name": "Snarrk", + "id": "7070" }, { - "id": "7071", - "name": "I'rk" + "name": "I'rk", + "id": "7071" }, { - "id": "7072", - "name": "Thuddley" + "name": "Thuddley", + "id": "7072" }, { - "id": "7073", - "name": "Grr'bah" + "name": "Grr'bah", + "id": "7073" }, { - "id": "7074", - "name": "Chomp" + "name": "Chomp", + "id": "7074" }, { - "id": "7075", - "name": "Grubb" + "name": "Grubb", + "id": "7075" }, { - "id": "7076", - "name": "Grunther" + "name": "Grunther", + "id": "7076" }, { - "id": "7077", - "name": "Glum" + "name": "Glum", + "id": "7077" }, { - "id": "7083", - "name": "Flying bugs" + "name": "Flying bugs", + "id": "7083" }, { - "id": "7087", - "name": "Balnea" + "name": "Balnea", + "id": "7087" }, { - "id": "7088", - "name": "Chief Tess" + "name": "Chief Tess", + "id": "7088" }, { - "id": "7089", - "name": "Chargurr" + "name": "Chargurr", + "id": "7089" }, { - "id": "7090", - "name": "Wise Old Man" + "name": "Wise Old Man", + "id": "7090" }, { - "id": "7091", - "name": "Dawg" + "name": "Dawg", + "id": "7091" }, { - "id": "7116", - "name": "Drunken sailor" + "name": "Drunken sailor", + "id": "7116" }, { - "id": "7117", - "name": "Drunken sailor" + "name": "Drunken sailor", + "id": "7117" }, { - "id": "7118", - "name": "Catapult engineer" + "name": "Catapult engineer", + "id": "7118" }, { - "id": "7119", - "name": "Tyras guard" + "name": "Tyras guard", + "id": "7119" }, { - "id": "7121", - "name": "General Hining" + "name": "General Hining", + "id": "7121" }, { - "id": "7122", - "name": "Githan" + "name": "Githan", + "id": "7122" }, { - "id": "7123", - "name": "Amulet of Nature" + "name": "Amulet of Nature", + "id": "7123" }, { - "id": "7124", - "name": "Mud" + "name": "Mud", + "id": "7124" }, { - "id": "7136", - "name": "Surok Magis" + "name": "Surok Magis", + "id": "7136" }, { - "id": "7143", - "name": "Professor Henry" + "name": "Professor Henry", + "id": "7143" }, { - "id": "7163", - "name": "Villager" + "name": "Villager", + "id": "7163" }, { - "id": "7165", - "name": "Villager" + "name": "Villager", + "id": "7165" }, { - "id": "7166", - "name": "Villager" + "name": "Villager", + "id": "7166" }, { - "id": "7167", - "name": "Villager" + "name": "Villager", + "id": "7167" }, { - "id": "7169", - "name": "Kimberly" + "name": "Kimberly", + "id": "7169" }, { - "id": "7170", - "name": "Kimberly" + "name": "Kimberly", + "id": "7170" }, { - "id": "7171", - "name": "Kennith" + "name": "Kennith", + "id": "7171" }, { - "id": "7172", - "name": "Kennith" + "name": "Kennith", + "id": "7172" }, { - "id": "7173", - "name": "Kennith" + "name": "Kennith", + "id": "7173" }, { - "id": "7174", - "name": "Kennith" + "name": "Kennith", + "id": "7174" }, { - "id": "7175", - "name": "Kennith" + "name": "Kennith", + "id": "7175" }, { - "id": "7176", - "name": "Villager" + "name": "Villager", + "id": "7176" }, { - "id": "7184", - "name": "Ezekial Lovecraft" + "name": "Ezekial Lovecraft", + "id": "7184" }, { - "id": "7185", - "name": "Clive" + "name": "Clive", + "id": "7185" }, { - "id": "7186", - "name": "Katherine" + "name": "Katherine", + "id": "7186" }, { - "id": "7187", - "name": "Katherine" + "name": "Katherine", + "id": "7187" }, { - "id": "7188", - "name": "Clive" + "name": "Clive", + "id": "7188" }, { - "id": "7189", - "name": "Kent" + "name": "Kent", + "id": "7189" }, { - "id": "7190", - "name": "Rabbit hole" + "name": "Rabbit hole", + "id": "7190" }, { - "id": "7197", - "name": "Easter Bunny" + "name": "Easter Bunny", + "id": "7197" }, { - "id": "7198", - "name": "Bunny" + "name": "Bunny", + "id": "7198" }, { - "id": "7199", - "name": "Bunny" + "name": "Bunny", + "id": "7199" }, { - "id": "7200", - "name": "Guard bunny" + "name": "Guard bunny", + "id": "7200" }, { - "id": "7201", - "name": "Chocatrice" + "name": "Chocatrice", + "id": "7201" }, { - "id": "7203", - "name": "Void Knight" + "name": "Void Knight", + "id": "7203" }, { - "id": "7205", - "name": "Mouse" + "name": "Mouse", + "id": "7205" }, { - "id": "7208", - "name": "Rabbit" + "name": "Rabbit", + "id": "7208" }, { - "id": "7261", - "name": "Raven chick" + "name": "Raven chick", + "id": "7261" }, { - "id": "7262", - "name": "Raven" + "name": "Raven", + "id": "7262" }, { - "id": "7263", - "name": "Raven chick" + "name": "Raven chick", + "id": "7263" }, { - "id": "7264", - "name": "Raven" + "name": "Raven", + "id": "7264" }, { - "id": "7265", - "name": "Raven chick" + "name": "Raven chick", + "id": "7265" }, { - "id": "7266", - "name": "Raven" + "name": "Raven", + "id": "7266" }, { - "id": "7267", - "name": "Raven chick" + "name": "Raven chick", + "id": "7267" }, { - "id": "7268", - "name": "Raven" + "name": "Raven", + "id": "7268" }, { - "id": "7269", - "name": "Raven chick" + "name": "Raven chick", + "id": "7269" }, { - "id": "7270", - "name": "Raven" + "name": "Raven", + "id": "7270" }, { - "id": "7271", - "name": "Baby raccoon" + "name": "Baby raccoon", + "id": "7271" }, { - "id": "7272", - "name": "Raccoon" + "name": "Raccoon", + "id": "7272" }, { - "id": "7273", - "name": "Baby raccoon" + "name": "Baby raccoon", + "id": "7273" }, { - "id": "7274", - "name": "Raccoon" + "name": "Raccoon", + "id": "7274" }, { - "id": "7276", - "name": "Baby raccoon" + "name": "Baby raccoon", + "id": "7276" }, { - "id": "7277", - "name": "Baby gecko" + "name": "Baby gecko", + "id": "7277" }, { - "id": "7278", - "name": "Baby gecko" + "name": "Baby gecko", + "id": "7278" }, { - "id": "7279", - "name": "Baby gecko" + "name": "Baby gecko", + "id": "7279" }, { - "id": "7280", - "name": "Baby gecko" + "name": "Baby gecko", + "id": "7280" }, { - "id": "7281", - "name": "Gecko" + "name": "Gecko", + "id": "7281" }, { - "id": "7282", - "name": "Gecko" + "name": "Gecko", + "id": "7282" }, { - "id": "7283", - "name": "Gecko" + "name": "Gecko", + "id": "7283" }, { - "id": "7284", - "name": "Gecko" + "name": "Gecko", + "id": "7284" }, { - "id": "7286", - "name": "Baby gecko" + "name": "Baby gecko", + "id": "7286" }, { - "id": "7287", - "name": "Baby gecko" + "name": "Baby gecko", + "id": "7287" }, { - "id": "7288", - "name": "Baby gecko" + "name": "Baby gecko", + "id": "7288" }, { - "id": "7289", - "name": "Gecko" + "name": "Gecko", + "id": "7289" }, { - "id": "7290", - "name": "Gecko" + "name": "Gecko", + "id": "7290" }, { - "id": "7291", - "name": "Gecko" + "name": "Gecko", + "id": "7291" }, { - "id": "7292", - "name": "Gecko" + "name": "Gecko", + "id": "7292" }, { - "id": "7293", - "name": "Baby giant crab" + "name": "Baby giant crab", + "id": "7293" }, { - "id": "7294", - "name": "Giant crab" + "name": "Giant crab", + "id": "7294" }, { - "id": "7295", - "name": "Baby giant crab" + "name": "Baby giant crab", + "id": "7295" }, { - "id": "7296", - "name": "Giant crab" + "name": "Giant crab", + "id": "7296" }, { - "id": "7297", - "name": "Baby giant crab" + "name": "Baby giant crab", + "id": "7297" }, { - "id": "7298", - "name": "Giant crab" + "name": "Giant crab", + "id": "7298" }, { - "id": "7299", - "name": "Baby giant crab" + "name": "Baby giant crab", + "id": "7299" }, { - "id": "7300", - "name": "Giant crab" + "name": "Giant crab", + "id": "7300" }, { - "id": "7301", - "name": "Baby squirrel" + "name": "Baby squirrel", + "id": "7301" }, { - "id": "7302", - "name": "Squirrel" + "name": "Squirrel", + "id": "7302" }, { - "id": "7303", - "name": "Baby squirrel" + "name": "Baby squirrel", + "id": "7303" }, { - "id": "7304", - "name": "Squirrel" + "name": "Squirrel", + "id": "7304" }, { - "id": "7305", - "name": "Baby squirrel" + "name": "Baby squirrel", + "id": "7305" }, { - "id": "7306", - "name": "Squirrel" + "name": "Squirrel", + "id": "7306" }, { - "id": "7307", - "name": "Baby squirrel" + "name": "Baby squirrel", + "id": "7307" }, { - "id": "7308", - "name": "Squirrel" + "name": "Squirrel", + "id": "7308" }, { - "id": "7309", - "name": "Baby squirrel" + "name": "Baby squirrel", + "id": "7309" }, { - "id": "7310", - "name": "Baby squirrel" + "name": "Baby squirrel", + "id": "7310" }, { - "id": "7311", - "name": "Baby squirrel" + "name": "Baby squirrel", + "id": "7311" }, { - "id": "7312", - "name": "Baby squirrel" + "name": "Baby squirrel", + "id": "7312" }, { - "id": "7313", - "name": "Baby penguin" + "name": "Baby penguin", + "id": "7313" }, { - "id": "7314", - "name": "Penguin" + "name": "Penguin", + "id": "7314" }, { - "id": "7315", - "name": "Penguin" + "name": "Penguin", + "id": "7315" }, { - "id": "7316", - "name": "Baby penguin" + "name": "Baby penguin", + "id": "7316" }, { - "id": "7317", - "name": "Penguin" + "name": "Penguin", + "id": "7317" }, { - "id": "7318", - "name": "Penguin" + "name": "Penguin", + "id": "7318" }, { - "id": "7319", - "name": "Vulture chick" + "name": "Vulture chick", + "id": "7319" }, { - "id": "7320", - "name": "Vulture" + "name": "Vulture", + "id": "7320" }, { - "id": "7321", - "name": "Vulture chick" + "name": "Vulture chick", + "id": "7321" }, { - "id": "7322", - "name": "Vulture" + "name": "Vulture", + "id": "7322" }, { - "id": "7323", - "name": "Vulture chick" + "name": "Vulture chick", + "id": "7323" }, { - "id": "7324", - "name": "Vulture" + "name": "Vulture", + "id": "7324" }, { - "id": "7325", - "name": "Vulture chick" + "name": "Vulture chick", + "id": "7325" }, { - "id": "7326", - "name": "Vulture" + "name": "Vulture", + "id": "7326" }, { - "id": "7327", - "name": "Vulture chick" + "name": "Vulture chick", + "id": "7327" }, { - "id": "7328", - "name": "Vulture" + "name": "Vulture", + "id": "7328" }, { - "id": "7369", - "name": "Void shifter" + "name": "Void shifter", + "id": "7369" }, { - "id": "7372", - "name": "Ravenous locust" + "name": "Ravenous locust", + "id": "7372" }, { - "id": "7383", - "name": "Uberlass" + "name": "Uberlass", + "id": "7383" }, { - "id": "7384", - "name": "Uberlass" + "name": "Uberlass", + "id": "7384" }, { - "id": "7385", - "name": "Uberlass" + "name": "Uberlass", + "id": "7385" }, { - "id": "7386", - "name": "Uberlass" + "name": "Uberlass", + "id": "7386" }, { - "id": "7387", - "name": "Sannytea" + "name": "Sannytea", + "id": "7387" }, { - "id": "7388", - "name": "Sannytea" + "name": "Sannytea", + "id": "7388" }, { - "id": "7389", - "name": "Irollsixes" + "name": "Irollsixes", + "id": "7389" }, { - "id": "7390", - "name": "Irollsixes" + "name": "Irollsixes", + "id": "7390" }, { - "id": "7391", - "name": "Foxyhunter" + "name": "Foxyhunter", + "id": "7391" }, { - "id": "7392", - "name": "Foxyhunter" + "name": "Foxyhunter", + "id": "7392" }, { - "id": "7393", - "name": "Foxyhunter" + "name": "Foxyhunter", + "id": "7393" }, { - "id": "7394", - "name": "Gabriela" + "name": "Gabriela", + "id": "7394" }, { - "id": "7395", - "name": "Teodor" + "name": "Teodor", + "id": "7395" }, { - "id": "7396", - "name": "Aurel" + "name": "Aurel", + "id": "7396" }, { - "id": "7397", - "name": "Cornelius" + "name": "Cornelius", + "id": "7397" }, { - "id": "7399", - "name": "Sorin" + "name": "Sorin", + "id": "7399" }, { - "id": "7400", - "name": "Luscion" + "name": "Luscion", + "id": "7400" }, { - "id": "7401", - "name": "Sergiu" + "name": "Sergiu", + "id": "7401" }, { - "id": "7402", - "name": "Radu" + "name": "Radu", + "id": "7402" }, { - "id": "7403", - "name": "Grigore" + "name": "Grigore", + "id": "7403" }, { - "id": "7404", - "name": "Ileana" + "name": "Ileana", + "id": "7404" }, { - "id": "7405", - "name": "Valeria" + "name": "Valeria", + "id": "7405" }, { - "id": "7406", - "name": "Emilia" + "name": "Emilia", + "id": "7406" }, { - "id": "7407", - "name": "Florin" + "name": "Florin", + "id": "7407" }, { - "id": "7408", - "name": "Catalina" + "name": "Catalina", + "id": "7408" }, { - "id": "7409", - "name": "Ivan" + "name": "Ivan", + "id": "7409" }, { - "id": "7410", - "name": "Victor" + "name": "Victor", + "id": "7410" }, { - "id": "7411", - "name": "Helena" + "name": "Helena", + "id": "7411" }, { - "id": "7412", - "name": "Mihail" + "name": "Mihail", + "id": "7412" }, { - "id": "7413", - "name": "Nicoleta" + "name": "Nicoleta", + "id": "7413" }, { - "id": "7414", - "name": "Vasile" + "name": "Vasile", + "id": "7414" }, { - "id": "7415", - "name": "Razvan" + "name": "Razvan", + "id": "7415" }, { - "id": "7416", - "name": "Luminata" + "name": "Luminata", + "id": "7416" }, { - "id": "7418", - "name": "Juvinate" + "name": "Juvinate", + "id": "7418" }, { - "id": "7419", - "name": "Held vampyre juvinate" + "name": "Held vampyre juvinate", + "id": "7419" }, { - "id": "7420", - "name": "Hieronymus Avlafrim" + "name": "Hieronymus Avlafrim", + "id": "7420" }, { - "id": "7421", - "name": "Sasquine Huburns" + "name": "Sasquine Huburns", + "id": "7421" }, { - "id": "7422", - "name": "Sasquine Huburns" + "name": "Sasquine Huburns", + "id": "7422" }, { - "id": "7423", - "name": "Lollery" + "name": "Lollery", + "id": "7423" }, { - "id": "7424", - "name": "Wigglewoo" + "name": "Wigglewoo", + "id": "7424" }, { - "id": "7427", - "name": "Orangeowns" + "name": "Orangeowns", + "id": "7427" }, { - "id": "7428", - "name": "I like m0m" + "name": "I like m0m", + "id": "7428" }, { - "id": "7429", - "name": "I like m0m" + "name": "I like m0m", + "id": "7429" }, { - "id": "7430", - "name": "Qutiedoll" + "name": "Qutiedoll", + "id": "7430" }, { - "id": "7431", - "name": "Goreu" + "name": "Goreu", + "id": "7431" }, { - "id": "7432", - "name": "Ysgawyn" + "name": "Ysgawyn", + "id": "7432" }, { - "id": "7433", - "name": "Arvel" + "name": "Arvel", + "id": "7433" }, { - "id": "7434", - "name": "Mawrth" + "name": "Mawrth", + "id": "7434" }, { - "id": "7435", - "name": "Kelyn" + "name": "Kelyn", + "id": "7435" }, { - "id": "7436", - "name": "Eoin" + "name": "Eoin", + "id": "7436" }, { - "id": "7437", - "name": "Iona" + "name": "Iona", + "id": "7437" }, { - "id": "7442", - "name": "Arianwyn" + "name": "Arianwyn", + "id": "7442" }, { - "id": "7444", - "name": "Oronwen" + "name": "Oronwen", + "id": "7444" }, { - "id": "7445", - "name": "Banker" + "name": "Banker", + "id": "7445" }, { - "id": "7446", - "name": "Banker" + "name": "Banker", + "id": "7446" }, { - "id": "7447", - "name": "Dalldav" + "name": "Dalldav", + "id": "7447" }, { - "id": "7448", - "name": "Gethin" + "name": "Gethin", + "id": "7448" }, { - "id": "7449", - "name": "Amaethwr" + "name": "Amaethwr", + "id": "7449" }, { - "id": "7450", - "name": "Teclyn" + "name": "Teclyn", + "id": "7450" }, { - "id": "7451", - "name": "Butterfly" + "name": "Butterfly", + "id": "7451" }, { - "id": "7452", - "name": "Lapsang" + "name": "Lapsang", + "id": "7452" }, { - "id": "7453", - "name": "Lapsang" + "name": "Lapsang", + "id": "7453" }, { - "id": "7454", - "name": "Souchong" + "name": "Souchong", + "id": "7454" }, { - "id": "7455", - "name": "Souchong" + "name": "Souchong", + "id": "7455" }, { - "id": "7456", - "name": "Earlgrey" + "name": "Earlgrey", + "id": "7456" }, { - "id": "7457", - "name": "Earlgrey" + "name": "Earlgrey", + "id": "7457" }, { - "id": "7458", - "name": "Fairtrade" + "name": "Fairtrade", + "id": "7458" }, { - "id": "7464", - "name": "Sliceoflemon" + "name": "Sliceoflemon", + "id": "7464" }, { - "id": "7465", - "name": "Sliceoflemon" + "name": "Sliceoflemon", + "id": "7465" }, { - "id": "7466", - "name": "Milknosugar" + "name": "Milknosugar", + "id": "7466" }, { - "id": "7467", - "name": "Milknosugar" + "name": "Milknosugar", + "id": "7467" }, { - "id": "7468", - "name": "Randomdood" + "name": "Randomdood", + "id": "7468" }, { - "id": "7469", - "name": "Employedman" + "name": "Employedman", + "id": "7469" }, { - "id": "7470", - "name": "Heidiggle" + "name": "Heidiggle", + "id": "7470" }, { - "id": "7471", - "name": "Dodgy Penny" + "name": "Dodgy Penny", + "id": "7471" }, { - "id": "7472", - "name": "Shadydude98" + "name": "Shadydude98", + "id": "7472" }, { - "id": "7473", - "name": "Madam C" + "name": "Madam C", + "id": "7473" }, { - "id": "7474", - "name": "M0m Online" + "name": "M0m Online", + "id": "7474" }, { - "id": "7478", - "name": "Learking" + "name": "Learking", + "id": "7478" }, { - "id": "7489", - "name": "Moglewee" + "name": "Moglewee", + "id": "7489" }, { - "id": "7490", - "name": "Moglewee" + "name": "Moglewee", + "id": "7490" }, { - "id": "7491", - "name": "Sarah Domin" + "name": "Sarah Domin", + "id": "7491" }, { - "id": "7498", - "name": "Snowy knight" + "name": "Snowy knight", + "id": "7498" }, { - "id": "7499", - "name": "Sapphire glacialis" + "name": "Sapphire glacialis", + "id": "7499" }, { - "id": "7509", - "name": "Evil Wibbler" + "name": "Evil Wibbler", + "id": "7509" }, { - "id": "7516", - "name": "Heresjohnny" + "name": "Heresjohnny", + "id": "7516" }, { - "id": "7517", - "name": "Mogglewump" + "name": "Mogglewump", + "id": "7517" }, { - "id": "7521", - "name": "Renderorder" + "name": "Renderorder", + "id": "7521" }, { - "id": "7522", - "name": "Boolean" + "name": "Boolean", + "id": "7522" }, { - "id": "7523", - "name": "Stress Diva" + "name": "Stress Diva", + "id": "7523" }, { - "id": "7524", - "name": "Treadsoftly" + "name": "Treadsoftly", + "id": "7524" }, { - "id": "7525", - "name": "Helphelphelp" + "name": "Helphelphelp", + "id": "7525" }, { - "id": "7526", - "name": "Doorbellpl0x" + "name": "Doorbellpl0x", + "id": "7526" }, { - "id": "7527", - "name": "Fixmydoorup" + "name": "Fixmydoorup", + "id": "7527" }, { - "id": "7529", - "name": "Wallscaler" + "name": "Wallscaler", + "id": "7529" }, { - "id": "7530", - "name": "2scompany" + "name": "2scompany", + "id": "7530" }, { - "id": "7531", - "name": "2scompany" + "name": "2scompany", + "id": "7531" }, { - "id": "7533", - "name": "4sjustsilly" + "name": "4sjustsilly", + "id": "7533" }, { - "id": "7534", - "name": "Roadblocked" + "name": "Roadblocked", + "id": "7534" }, { - "id": "7535", - "name": "Barricade" + "name": "Barricade", + "id": "7535" }, { - "id": "7536", - "name": "Barricade" + "name": "Barricade", + "id": "7536" }, { - "id": "7537", - "name": "Yoinker" + "name": "Yoinker", + "id": "7537" }, { - "id": "7538", - "name": "Yoinker" + "name": "Yoinker", + "id": "7538" }, { - "id": "7539", - "name": "Stopthief" + "name": "Stopthief", + "id": "7539" }, { - "id": "7540", - "name": "Stopthief" + "name": "Stopthief", + "id": "7540" }, { - "id": "7541", - "name": "Knickknack" + "name": "Knickknack", + "id": "7541" }, { - "id": "7542", - "name": "Paddywhack" + "name": "Paddywhack", + "id": "7542" }, { - "id": "7543", - "name": "Giveadog" + "name": "Giveadog", + "id": "7543" }, { - "id": "7544", - "name": "Spacebadgers" + "name": "Spacebadgers", + "id": "7544" }, { - "id": "7545", - "name": "Freakypeaky" + "name": "Freakypeaky", + "id": "7545" }, { - "id": "7546", - "name": "Rollinghome" + "name": "Rollinghome", + "id": "7546" }, { - "id": "7547", - "name": "Nullpointer" + "name": "Nullpointer", + "id": "7547" }, { - "id": "7548", - "name": "Badgerfreak" + "name": "Badgerfreak", + "id": "7548" }, { - "id": "7549", - "name": "Windstrike32" + "name": "Windstrike32", + "id": "7549" }, { - "id": "7550", - "name": "Void Knight" + "name": "Void Knight", + "id": "7550" }, { - "id": "7567", - "name": "Wraithboss" + "name": "Wraithboss", + "id": "7567" }, { - "id": "7568", - "name": "What Goudron" + "name": "What Goudron", + "id": "7568" }, { - "id": "7574", - "name": "Lvzyoda" + "name": "Lvzyoda", + "id": "7574" }, { - "id": "7575", - "name": "Airstriker" + "name": "Airstriker", + "id": "7575" }, { - "id": "7576", - "name": "Droepedoff" + "name": "Droepedoff", + "id": "7576" }, { - "id": "7577", - "name": "Plzpudding" + "name": "Plzpudding", + "id": "7577" }, { - "id": "7578", - "name": "Assassin10" + "name": "Assassin10", + "id": "7578" }, { - "id": "7579", - "name": "Steallake" + "name": "Steallake", + "id": "7579" }, { - "id": "7581", - "name": "Deepkiwi" + "name": "Deepkiwi", + "id": "7581" }, { - "id": "7584", - "name": "Bigface Oz" + "name": "Bigface Oz", + "id": "7584" }, { - "id": "7587", - "name": "Torcher" + "name": "Torcher", + "id": "7587" }, { - "id": "7588", - "name": "Torcher" + "name": "Torcher", + "id": "7588" }, { - "id": "7589", - "name": "Defiler" + "name": "Defiler", + "id": "7589" }, { - "id": "7590", - "name": "Defiler" + "name": "Defiler", + "id": "7590" }, { - "id": "7591", - "name": "Shifter" + "name": "Shifter", + "id": "7591" }, { - "id": "7592", - "name": "Shifter" + "name": "Shifter", + "id": "7592" }, { - "id": "7594", - "name": "Shifter" + "name": "Shifter", + "id": "7594" }, { - "id": "7595", - "name": "Splatter" + "name": "Splatter", + "id": "7595" }, { - "id": "7596", - "name": "Splatter" + "name": "Splatter", + "id": "7596" }, { - "id": "7597", - "name": "Splatter" + "name": "Splatter", + "id": "7597" }, { - "id": "7598", - "name": "Spinner" + "name": "Spinner", + "id": "7598" }, { - "id": "7599", - "name": "Ravager" + "name": "Ravager", + "id": "7599" }, { - "id": "7600", - "name": "Fiara" + "name": "Fiara", + "id": "7600" }, { - "id": "7601", - "name": "Reggie" + "name": "Reggie", + "id": "7601" }, { - "id": "7602", - "name": "Getorix" + "name": "Getorix", + "id": "7602" }, { - "id": "7603", - "name": "Pontimer" + "name": "Pontimer", + "id": "7603" }, { - "id": "7604", - "name": "Alran" + "name": "Alran", + "id": "7604" }, { - "id": "7610", - "name": "Flying female vampire" + "name": "Flying female vampire", + "id": "7610" }, { - "id": "7611", - "name": "Flying female vampire" + "name": "Flying female vampire", + "id": "7611" }, { - "id": "7612", - "name": "Flying female vampire" + "name": "Flying female vampire", + "id": "7612" }, { - "id": "7613", - "name": "Flying female vampire" + "name": "Flying female vampire", + "id": "7613" }, { - "id": "7622", - "name": "Vyrewatch" + "name": "Vyrewatch", + "id": "7622" }, { - "id": "7623", - "name": "Vyrewatch" + "name": "Vyrewatch", + "id": "7623" }, { - "id": "7624", - "name": "Vyrewatch" + "name": "Vyrewatch", + "id": "7624" }, { - "id": "7625", - "name": "Vyrewatch" + "name": "Vyrewatch", + "id": "7625" }, { - "id": "7630", - "name": "Vyrewatch" + "name": "Vyrewatch", + "id": "7630" }, { - "id": "7633", - "name": "Vyrewatch" + "name": "Vyrewatch", + "id": "7633" }, { - "id": "7636", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "7636" }, { - "id": "7637", - "name": "Skeletal hand" + "name": "Skeletal hand", + "id": "7637" }, { - "id": "7652", - "name": "Zaromark Sliver" + "name": "Zaromark Sliver", + "id": "7652" }, { - "id": "7653", - "name": "Zaromark Sliver" + "name": "Zaromark Sliver", + "id": "7653" }, { - "id": "7662", - "name": "Fistandantilus" + "name": "Fistandantilus", + "id": "7662" }, { - "id": "7663", - "name": "Fistandantilus" + "name": "Fistandantilus", + "id": "7663" }, { - "id": "7664", - "name": "Mercenary Adventurer" + "name": "Mercenary Adventurer", + "id": "7664" }, { - "id": "7665", - "name": "Ivan Strom" + "name": "Ivan Strom", + "id": "7665" }, { - "id": "7666", - "name": "Mysterious person" + "name": "Mysterious person", + "id": "7666" }, { - "id": "7667", - "name": "Mysterious person" + "name": "Mysterious person", + "id": "7667" }, { - "id": "7668", - "name": "Mysterious person" + "name": "Mysterious person", + "id": "7668" }, { - "id": "7669", - "name": "Mysterious person" + "name": "Mysterious person", + "id": "7669" }, { - "id": "7670", - "name": "Mysterious person" + "name": "Mysterious person", + "id": "7670" }, { - "id": "7671", - "name": "Mysterious person" + "name": "Mysterious person", + "id": "7671" }, { - "id": "7672", - "name": "Flaygian Screwte" + "name": "Flaygian Screwte", + "id": "7672" }, { - "id": "7673", - "name": "Mekritus A'hara" + "name": "Mekritus A'hara", + "id": "7673" }, { - "id": "7674", - "name": "Andiess Juip" + "name": "Andiess Juip", + "id": "7674" }, { - "id": "7675", - "name": "Kael Forshaw" + "name": "Kael Forshaw", + "id": "7675" }, { - "id": "7676", - "name": "Andiess Juip" + "name": "Andiess Juip", + "id": "7676" }, { - "id": "7677", - "name": "Kael Forshaw" + "name": "Kael Forshaw", + "id": "7677" }, { - "id": "7678", - "name": "Safalaan" + "name": "Safalaan", + "id": "7678" }, { - "id": "7679", - "name": "Andiess Juip" + "name": "Andiess Juip", + "id": "7679" }, { - "id": "7680", - "name": "Kael Forshaw" + "name": "Kael Forshaw", + "id": "7680" }, { - "id": "7681", - "name": "Safalaan" + "name": "Safalaan", + "id": "7681" }, { - "id": "7684", - "name": "Vyrewatch" + "name": "Vyrewatch", + "id": "7684" }, { - "id": "7685", - "name": "Vyrewatch" + "name": "Vyrewatch", + "id": "7685" }, { - "id": "7686", - "name": "Safalaan" + "name": "Safalaan", + "id": "7686" }, { - "id": "7687", - "name": "Spectral Vyrewatch" + "name": "Spectral Vyrewatch", + "id": "7687" }, { - "id": "7688", - "name": "Drezel" + "name": "Drezel", + "id": "7688" }, { - "id": "7708", - "name": "Temple guardian" + "name": "Temple guardian", + "id": "7708" }, { - "id": "7712", - "name": "Baby icefiend" + "name": "Baby icefiend", + "id": "7712" }, { - "id": "7718", - "name": "Brother Bordiss" + "name": "Brother Bordiss", + "id": "7718" }, { - "id": "7719", - "name": "Brother Althric" + "name": "Brother Althric", + "id": "7719" }, { - "id": "7720", - "name": "Drorkar" + "name": "Drorkar", + "id": "7720" }, { - "id": "7721", - "name": "Nurmof" + "name": "Nurmof", + "id": "7721" }, { - "id": "7722", - "name": "Lakki the delivery dwarf" + "name": "Lakki the delivery dwarf", + "id": "7722" }, { - "id": "7723", - "name": "Drorkar" + "name": "Drorkar", + "id": "7723" }, { - "id": "7724", - "name": "Brother Bordiss" + "name": "Brother Bordiss", + "id": "7724" }, { - "id": "7725", - "name": "Professor Arblenap" + "name": "Professor Arblenap", + "id": "7725" }, { - "id": "7726", - "name": "Assistant" + "name": "Assistant", + "id": "7726" }, { - "id": "7728", - "name": "Baby icefiend" + "name": "Baby icefiend", + "id": "7728" }, { - "id": "7737", - "name": "Will" + "name": "Will", + "id": "7737" }, { - "id": "7738", - "name": "Will" + "name": "Will", + "id": "7738" }, { - "id": "7739", - "name": "Phil" + "name": "Phil", + "id": "7739" }, { - "id": "7742", - "name": "Fluffs" + "name": "Fluffs", + "id": "7742" }, { - "id": "7743", - "name": "Fluffs" + "name": "Fluffs", + "id": "7743" }, { - "id": "7744", - "name": "Kittens" + "name": "Kittens", + "id": "7744" }, { - "id": "7746", - "name": "TzHaar-Mej-Malk" + "name": "TzHaar-Mej-Malk", + "id": "7746" }, { - "id": "7748", - "name": "TzHaar-Hur-Frok" + "name": "TzHaar-Hur-Frok", + "id": "7748" }, { - "id": "7749", - "name": "TzHaar-Ket-Grol" + "name": "TzHaar-Ket-Grol", + "id": "7749" }, { - "id": "7750", - "name": "TzHaar-Ket-Rok" + "name": "TzHaar-Ket-Rok", + "id": "7750" }, { - "id": "7751", - "name": "TzHaar-Ket-Lurk" + "name": "TzHaar-Ket-Lurk", + "id": "7751" }, { - "id": "7753", - "name": "TzHaar-Mej" + "name": "TzHaar-Mej", + "id": "7753" }, { - "id": "7754", - "name": "TzHaar-Hur" + "name": "TzHaar-Hur", + "id": "7754" }, { - "id": "7755", - "name": "TzHaar-Xil" + "name": "TzHaar-Xil", + "id": "7755" }, { - "id": "7757", - "name": "TzHaar-Hur-Brekt" + "name": "TzHaar-Hur-Brekt", + "id": "7757" }, { - "id": "7758", - "name": "TzHaar-Hur-Brekt" + "name": "TzHaar-Hur-Brekt", + "id": "7758" }, { - "id": "7759", - "name": "TzHaar-Ket-Jok" + "name": "TzHaar-Ket-Jok", + "id": "7759" }, { - "id": "7760", - "name": "TzHaar-Ket-Jok" + "name": "TzHaar-Ket-Jok", + "id": "7760" }, { - "id": "7761", - "name": "TzHaar-Xil-Mor" + "name": "TzHaar-Xil-Mor", + "id": "7761" }, { - "id": "7762", - "name": "TzHaar-Xil-Mor" + "name": "TzHaar-Xil-Mor", + "id": "7762" }, { - "id": "7763", - "name": "TzHaar-Mej-Kol" + "name": "TzHaar-Mej-Kol", + "id": "7763" }, { - "id": "7764", - "name": "TzHaar-Mej-Kol" + "name": "TzHaar-Mej-Kol", + "id": "7764" }, { - "id": "7765", - "name": "TzHaar-Hur-Klag" + "name": "TzHaar-Hur-Klag", + "id": "7765" }, { - "id": "7766", - "name": "TzHaar-Hur-Klag" + "name": "TzHaar-Hur-Klag", + "id": "7766" }, { - "id": "7770", - "name": "TokTz-Ket-Dill" + "name": "TokTz-Ket-Dill", + "id": "7770" }, { - "id": "7771", - "name": "TokTz-Ket-Dill" + "name": "TokTz-Ket-Dill", + "id": "7771" }, { - "id": "7774", - "name": "Skeleton" + "name": "Skeleton", + "id": "7774" }, { - "id": "7775", - "name": "Skeleton" + "name": "Skeleton", + "id": "7775" }, { - "id": "7776", - "name": "Skeleton" + "name": "Skeleton", + "id": "7776" }, { - "id": "7777", - "name": "Skeleton" + "name": "Skeleton", + "id": "7777" }, { - "id": "7778", - "name": "Skeleton" + "name": "Skeleton", + "id": "7778" }, { - "id": "7779", - "name": "Sumona" + "name": "Sumona", + "id": "7779" }, { - "id": "7781", - "name": "Jesmona" + "name": "Jesmona", + "id": "7781" }, { - "id": "7782", - "name": "Catolax" + "name": "Catolax", + "id": "7782" }, { - "id": "7783", - "name": "Ali Cat" + "name": "Ali Cat", + "id": "7783" }, { - "id": "7787", - "name": "Cave crawler" + "name": "Cave crawler", + "id": "7787" }, { - "id": "7788", - "name": "Skeleton" + "name": "Skeleton", + "id": "7788" }, { - "id": "7789", - "name": "Zombie" + "name": "Zombie", + "id": "7789" }, { - "id": "7790", - "name": "Zombie" + "name": "Zombie", + "id": "7790" }, { - "id": "7791", - "name": "Zombie" + "name": "Zombie", + "id": "7791" }, { - "id": "7792", - "name": "Zombie" + "name": "Zombie", + "id": "7792" }, { - "id": "7793", - "name": "Banshee mistress" + "name": "Banshee mistress", + "id": "7793" }, { - "id": "7794", - "name": "Banshee mistress" + "name": "Banshee mistress", + "id": "7794" }, { - "id": "7795", - "name": "Insectoid assassin" + "name": "Insectoid assassin", + "id": "7795" }, { - "id": "7796", - "name": "Insectoid assassin" + "name": "Insectoid assassin", + "id": "7796" }, { - "id": "7797", - "name": "Kurask overlord" + "name": "Kurask overlord", + "id": "7797" }, { - "id": "7798", - "name": "Monstrous cave crawler" + "name": "Monstrous cave crawler", + "id": "7798" }, { - "id": "7799", - "name": "Basilisk boss" + "name": "Basilisk boss", + "id": "7799" }, { - "id": "7800", - "name": "Mightiest turoth" + "name": "Mightiest turoth", + "id": "7800" }, { - "id": "7801", - "name": "Aberrant spectre" + "name": "Aberrant spectre", + "id": "7801" }, { - "id": "7802", - "name": "Aberrant spectre" + "name": "Aberrant spectre", + "id": "7802" }, { - "id": "7803", - "name": "Aberrant spectre" + "name": "Aberrant spectre", + "id": "7803" }, { - "id": "7804", - "name": "Aberrant spectre" + "name": "Aberrant spectre", + "id": "7804" }, { - "id": "7805", - "name": "Kurask minion" + "name": "Kurask minion", + "id": "7805" }, { - "id": "7806", - "name": "Mummy warrior" + "name": "Mummy warrior", + "id": "7806" }, { - "id": "7807", - "name": "Mummy warrior" + "name": "Mummy warrior", + "id": "7807" }, { - "id": "7808", - "name": "Mummy warrior" + "name": "Mummy warrior", + "id": "7808" }, { - "id": "7809", - "name": "Cat" + "name": "Cat", + "id": "7809" }, { - "id": "7810", - "name": "Banshee" + "name": "Banshee", + "id": "7810" }, { - "id": "7811", - "name": "Kurask" + "name": "Kurask", + "id": "7811" }, { - "id": "7812", - "name": "Cave crawler" + "name": "Cave crawler", + "id": "7812" }, { - "id": "7813", - "name": "Basilisk" + "name": "Basilisk", + "id": "7813" }, { - "id": "7814", - "name": "Turoth" + "name": "Turoth", + "id": "7814" }, { - "id": "7815", - "name": "Skeleton" + "name": "Skeleton", + "id": "7815" }, { - "id": "7816", - "name": "Master" + "name": "Master", + "id": "7816" }, { - "id": "7817", - "name": "Zombie" + "name": "Zombie", + "id": "7817" }, { - "id": "7818", - "name": "Zombie" + "name": "Zombie", + "id": "7818" }, { - "id": "7819", - "name": "Zombie" + "name": "Zombie", + "id": "7819" }, { - "id": "7820", - "name": "Zombie" + "name": "Zombie", + "id": "7820" }, { - "id": "7821", - "name": "A wanderer." + "name": "A wanderer.", + "id": "7821" }, { - "id": "7822", - "name": "A wanderer." + "name": "A wanderer.", + "id": "7822" }, { - "id": "7824", - "name": "Osman" + "name": "Osman", + "id": "7824" }, { - "id": "7825", - "name": "50 Ships Mufassah" + "name": "50 Ships Mufassah", + "id": "7825" }, { - "id": "7826", - "name": "Karamthulhu" + "name": "Karamthulhu", + "id": "7826" }, { - "id": "7827", - "name": "Karamthulhu" + "name": "Karamthulhu", + "id": "7827" }, { - "id": "7828", - "name": "Giant lobster" + "name": "Giant lobster", + "id": "7828" }, { - "id": "7829", - "name": "Giant crab" + "name": "Giant crab", + "id": "7829" }, { - "id": "7830", - "name": "Customs Sergeant" + "name": "Customs Sergeant", + "id": "7830" }, { - "id": "7831", - "name": "Customs Sergeant" + "name": "Customs Sergeant", + "id": "7831" }, { - "id": "7832", - "name": "Young Ralph" + "name": "Young Ralph", + "id": "7832" }, { - "id": "7833", - "name": "Young Ralph" + "name": "Young Ralph", + "id": "7833" }, { - "id": "7834", - "name": "Young Ralph" + "name": "Young Ralph", + "id": "7834" }, { - "id": "7835", - "name": "Young Ralph" + "name": "Young Ralph", + "id": "7835" }, { - "id": "7836", - "name": "Young Ralph" + "name": "Young Ralph", + "id": "7836" }, { - "id": "7837", - "name": "Customs Officer" + "name": "Customs Officer", + "id": "7837" }, { - "id": "7838", - "name": "Customs Officer" + "name": "Customs Officer", + "id": "7838" }, { - "id": "7839", - "name": "Customs Officer" + "name": "Customs Officer", + "id": "7839" }, { - "id": "7840", - "name": "Heavy-Handed Harry" + "name": "Heavy-Handed Harry", + "id": "7840" }, { - "id": "7841", - "name": "Player" + "name": "Player", + "id": "7841" }, { - "id": "7842", - "name": "Locker Officer" + "name": "Locker Officer", + "id": "7842" }, { - "id": "7843", - "name": "Locker Officer" + "name": "Locker Officer", + "id": "7843" }, { - "id": "7844", - "name": "Ex-ex-parrot" + "name": "Ex-ex-parrot", + "id": "7844" }, { - "id": "7845", - "name": "Pirate impling" + "name": "Pirate impling", + "id": "7845" }, { - "id": "7846", - "name": "Pirate impling" + "name": "Pirate impling", + "id": "7846" }, { - "id": "7847", - "name": "Captain Rabid Jack" + "name": "Captain Rabid Jack", + "id": "7847" }, { - "id": "7848", - "name": "Jack" + "name": "Jack", + "id": "7848" }, { - "id": "7849", - "name": "Bosun Giles" + "name": "Bosun Giles", + "id": "7849" }, { - "id": "7850", - "name": "Pirate" + "name": "Pirate", + "id": "7850" }, { - "id": "7851", - "name": "Lizzie" + "name": "Lizzie", + "id": "7851" }, { - "id": "7852", - "name": "Cap'n Izzy No-Beard" + "name": "Cap'n Izzy No-Beard", + "id": "7852" }, { - "id": "7853", - "name": "Ralph" + "name": "Ralph", + "id": "7853" }, { - "id": "7854", - "name": "Brass Hand Harry" + "name": "Brass Hand Harry", + "id": "7854" }, { - "id": "7855", - "name": "Bill Teach" + "name": "Bill Teach", + "id": "7855" }, { - "id": "7856", - "name": "Pirate" + "name": "Pirate", + "id": "7856" }, { - "id": "7857", - "name": "Brass Hand Harry" + "name": "Brass Hand Harry", + "id": "7857" }, { - "id": "7858", - "name": "Gull" + "name": "Gull", + "id": "7858" }, { - "id": "7861", - "name": "Jack" + "name": "Jack", + "id": "7861" }, { - "id": "7862", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "7862" }, { - "id": "7863", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "7863" }, { - "id": "7864", - "name": "Fishing spot" + "name": "Fishing spot", + "id": "7864" }, { - "id": "7865", - "name": "Guardsman Dante" + "name": "Guardsman Dante", + "id": "7865" }, { - "id": "7866", - "name": "Guardsman DeShawn" + "name": "Guardsman DeShawn", + "id": "7866" }, { - "id": "7867", - "name": "Guardsman Brawn" + "name": "Guardsman Brawn", + "id": "7867" }, { - "id": "7868", - "name": "Iain" + "name": "Iain", + "id": "7868" }, { - "id": "7869", - "name": "Julian" + "name": "Julian", + "id": "7869" }, { - "id": "7870", - "name": "Lachtopher" + "name": "Lachtopher", + "id": "7870" }, { - "id": "7871", - "name": "Samuel" + "name": "Samuel", + "id": "7871" }, { - "id": "7872", - "name": "Victoria" + "name": "Victoria", + "id": "7872" }, { - "id": "7873", - "name": "Man" + "name": "Man", + "id": "7873" }, { - "id": "7874", - "name": "Man" + "name": "Man", + "id": "7874" }, { - "id": "7875", - "name": "Man" + "name": "Man", + "id": "7875" }, { - "id": "7876", - "name": "Man" + "name": "Man", + "id": "7876" }, { - "id": "7877", - "name": "Man" + "name": "Man", + "id": "7877" }, { - "id": "7878", - "name": "Man" + "name": "Man", + "id": "7878" }, { - "id": "7879", - "name": "Man" + "name": "Man", + "id": "7879" }, { - "id": "7880", - "name": "Woman" + "name": "Woman", + "id": "7880" }, { - "id": "7881", - "name": "Woman" + "name": "Woman", + "id": "7881" }, { - "id": "7882", - "name": "Woman" + "name": "Woman", + "id": "7882" }, { - "id": "7883", - "name": "Woman" + "name": "Woman", + "id": "7883" }, { - "id": "7884", - "name": "Woman" + "name": "Woman", + "id": "7884" }, { - "id": "7885", - "name": "Guardsman Dante" + "name": "Guardsman Dante", + "id": "7885" }, { - "id": "7886", - "name": "Guardsman DeShawn" + "name": "Guardsman DeShawn", + "id": "7886" }, { - "id": "7887", - "name": "Guardsman Brawn" + "name": "Guardsman Brawn", + "id": "7887" }, { - "id": "7888", - "name": "Sergeant Abram" + "name": "Sergeant Abram", + "id": "7888" }, { - "id": "7889", - "name": "Guardsman Pazel" + "name": "Guardsman Pazel", + "id": "7889" }, { - "id": "7890", - "name": "Guardsman Peale" + "name": "Guardsman Peale", + "id": "7890" }, { - "id": "7892", - "name": "General Wartface" + "name": "General Wartface", + "id": "7892" }, { - "id": "7893", - "name": "General Bentnoze" + "name": "General Bentnoze", + "id": "7893" }, { - "id": "7894", - "name": "General Wartface" + "name": "General Wartface", + "id": "7894" }, { - "id": "7896", - "name": "General Bentnoze" + "name": "General Bentnoze", + "id": "7896" }, { - "id": "7898", - "name": "Loar Shadow" + "name": "Loar Shadow", + "id": "7898" }, { - "id": "7901", - "name": "Loar Shadow" + "name": "Loar Shadow", + "id": "7901" }, { - "id": "7902", - "name": "Sergeant Abram" + "name": "Sergeant Abram", + "id": "7902" }, { - "id": "7903", - "name": "Guardsman Pazel" + "name": "Guardsman Pazel", + "id": "7903" }, { - "id": "7904", - "name": "Guardsman Peale" + "name": "Guardsman Peale", + "id": "7904" }, { - "id": "7905", - "name": "Barricade guard" + "name": "Barricade guard", + "id": "7905" }, { - "id": "7906", - "name": "Barricade guard" + "name": "Barricade guard", + "id": "7906" }, { - "id": "7907", - "name": "Barricade guard" + "name": "Barricade guard", + "id": "7907" }, { - "id": "7908", - "name": "Barricade guard" + "name": "Barricade guard", + "id": "7908" }, { - "id": "7909", - "name": "Barricade guard" + "name": "Barricade guard", + "id": "7909" }, { - "id": "7910", - "name": "Barricade guard" + "name": "Barricade guard", + "id": "7910" }, { - "id": "7911", - "name": "Barricade guard" + "name": "Barricade guard", + "id": "7911" }, { - "id": "7912", - "name": "Border guard" + "name": "Border guard", + "id": "7912" }, { - "id": "7913", - "name": "Iain" + "name": "Iain", + "id": "7913" }, { - "id": "7914", - "name": "Julian" + "name": "Julian", + "id": "7914" }, { - "id": "7915", - "name": "Lachtopher" + "name": "Lachtopher", + "id": "7915" }, { - "id": "7916", - "name": "Samuel" + "name": "Samuel", + "id": "7916" }, { - "id": "7917", - "name": "Victoria" + "name": "Victoria", + "id": "7917" }, { - "id": "7918", - "name": "Man" + "name": "Man", + "id": "7918" }, { - "id": "7919", - "name": "Man" + "name": "Man", + "id": "7919" }, { - "id": "7920", - "name": "Man" + "name": "Man", + "id": "7920" }, { - "id": "7921", - "name": "Man" + "name": "Man", + "id": "7921" }, { - "id": "7922", - "name": "Man" + "name": "Man", + "id": "7922" }, { - "id": "7923", - "name": "Man" + "name": "Man", + "id": "7923" }, { - "id": "7924", - "name": "Man" + "name": "Man", + "id": "7924" }, { - "id": "7925", - "name": "Woman" + "name": "Woman", + "id": "7925" }, { - "id": "7926", - "name": "Woman" + "name": "Woman", + "id": "7926" }, { - "id": "7927", - "name": "Woman" + "name": "Woman", + "id": "7927" }, { - "id": "7928", - "name": "Woman" + "name": "Woman", + "id": "7928" }, { - "id": "7929", - "name": "Lumbridge Guide" + "name": "Lumbridge Guide", + "id": "7929" }, { - "id": "7930", - "name": "Doomsayer" + "name": "Doomsayer", + "id": "7930" }, { - "id": "7931", - "name": "Donie" + "name": "Donie", + "id": "7931" }, { - "id": "7932", - "name": "Gee" + "name": "Gee", + "id": "7932" }, { - "id": "7933", - "name": "Duke Horacio" + "name": "Duke Horacio", + "id": "7933" }, { - "id": "7934", - "name": "Sigmund" + "name": "Sigmund", + "id": "7934" }, { - "id": "7935", - "name": "Hans" + "name": "Hans", + "id": "7935" }, { - "id": "7936", - "name": "Cook" + "name": "Cook", + "id": "7936" }, { - "id": "7937", - "name": "Father Aereck" + "name": "Father Aereck", + "id": "7937" }, { - "id": "7938", - "name": "Sir Vant" + "name": "Sir Vant", + "id": "7938" }, { - "id": "7939", - "name": "Sir Vant" + "name": "Sir Vant", + "id": "7939" }, { - "id": "7940", - "name": "Sir Vant" + "name": "Sir Vant", + "id": "7940" }, { - "id": "7941", - "name": "Sir Vant" + "name": "Sir Vant", + "id": "7941" }, { - "id": "7942", - "name": "Sir Vant" + "name": "Sir Vant", + "id": "7942" }, { - "id": "7943", - "name": "Dragon" + "name": "Dragon", + "id": "7943" }, { - "id": "7944", - "name": "Dragon" + "name": "Dragon", + "id": "7944" }, { - "id": "7945", - "name": "Dragon" + "name": "Dragon", + "id": "7945" }, { - "id": "7946", - "name": "Dragon" + "name": "Dragon", + "id": "7946" }, { - "id": "7947", - "name": "Dragon" + "name": "Dragon", + "id": "7947" }, { - "id": "7948", - "name": "Dragon" + "name": "Dragon", + "id": "7948" }, { - "id": "7949", - "name": "Lumbridge Guide" + "name": "Lumbridge Guide", + "id": "7949" }, { - "id": "7950", - "name": "Melee Tutor" + "name": "Melee Tutor", + "id": "7950" }, { - "id": "7951", - "name": "Ranged Tutor" + "name": "Ranged Tutor", + "id": "7951" }, { - "id": "7952", - "name": "Magic Tutor" + "name": "Magic Tutor", + "id": "7952" }, { - "id": "7953", - "name": "Cooking Tutor" + "name": "Cooking Tutor", + "id": "7953" }, { - "id": "7954", - "name": "Crafting Tutor" + "name": "Crafting Tutor", + "id": "7954" }, { - "id": "7955", - "name": "Fishing Tutor" + "name": "Fishing Tutor", + "id": "7955" }, { - "id": "7956", - "name": "Mining Tutor" + "name": "Mining Tutor", + "id": "7956" }, { - "id": "7957", - "name": "Prayer Tutor" + "name": "Prayer Tutor", + "id": "7957" }, { - "id": "7960", - "name": "Woodcutting Tutor" + "name": "Woodcutting Tutor", + "id": "7960" }, { - "id": "7961", - "name": "Bank Tutor" + "name": "Bank Tutor", + "id": "7961" }, { - "id": "7962", - "name": "Gee" + "name": "Gee", + "id": "7962" }, { - "id": "7963", - "name": "Spirit" + "name": "Spirit", + "id": "7963" }, { - "id": "7964", - "name": "Goblin" + "name": "Goblin", + "id": "7964" }, { - "id": "7965", - "name": "Goblin" + "name": "Goblin", + "id": "7965" }, { - "id": "7966", - "name": "Chaos druid" + "name": "Chaos druid", + "id": "7966" }, { - "id": "7967", - "name": "Shopkeeper" + "name": "Shopkeeper", + "id": "7967" }, { - "id": "7968", - "name": "Explorer Jack" + "name": "Explorer Jack", + "id": "7968" }, { - "id": "7970", - "name": "Smithing Tutor" + "name": "Smithing Tutor", + "id": "7970" }, { - "id": "7971", - "name": "Mining Tutor" + "name": "Mining Tutor", + "id": "7971" }, { - "id": "7972", - "name": "Spirit" + "name": "Spirit", + "id": "7972" }, { - "id": "7977", - "name": "Spirit" + "name": "Spirit", + "id": "7977" }, { - "id": "7978", - "name": "Spirit" + "name": "Spirit", + "id": "7978" }, { - "id": "7979", - "name": "Spirit" + "name": "Spirit", + "id": "7979" }, { - "id": "7980", - "name": "Spirit" + "name": "Spirit", + "id": "7980" }, { - "id": "7981", - "name": "Spirit" + "name": "Spirit", + "id": "7981" }, { - "id": "7982", - "name": "Spirit" + "name": "Spirit", + "id": "7982" }, { - "id": "7983", - "name": "Spirit" + "name": "Spirit", + "id": "7983" }, { - "id": "7984", - "name": "Spirit" + "name": "Spirit", + "id": "7984" }, { - "id": "7985", - "name": "Summer Bonde" + "name": "Summer Bonde", + "id": "7985" }, { - "id": "7987", - "name": "Spirit" + "name": "Spirit", + "id": "7987" }, { - "id": "7988", - "name": "Spirit" + "name": "Spirit", + "id": "7988" }, { - "id": "7989", - "name": "Erik Bonde" + "name": "Erik Bonde", + "id": "7989" }, { - "id": "7990", - "name": "Spirit" + "name": "Spirit", + "id": "7990" }, { - "id": "7991", - "name": "Jallek Lenkin" + "name": "Jallek Lenkin", + "id": "7991" }, { - "id": "7992", - "name": "Spirit" + "name": "Spirit", + "id": "7992" }, { - "id": "7993", - "name": "Meranek Thanatos" + "name": "Meranek Thanatos", + "id": "7993" }, { - "id": "7994", - "name": "Ghostly warrior" + "name": "Ghostly warrior", + "id": "7994" }, { - "id": "7995", - "name": "Spirit Beast" + "name": "Spirit Beast", + "id": "7995" }, { - "id": "7996", - "name": "Spirit Beast" + "name": "Spirit Beast", + "id": "7996" }, { - "id": "7997", - "name": "Spirit Beast" + "name": "Spirit Beast", + "id": "7997" }, { - "id": "7998", - "name": "Ghost" + "name": "Ghost", + "id": "7998" }, { - "id": "7999", - "name": "Ghost" + "name": "Ghost", + "id": "7999" }, { - "id": "8000", - "name": "Goth leprechaun" + "name": "Goth leprechaun", + "id": "8000" }, { - "id": "8001", - "name": "Jack" + "name": "Jack", + "id": "8001" }, { - "id": "8003", - "name": "Sarah" + "name": "Sarah", + "id": "8003" }, { - "id": "8004", - "name": "Laura" + "name": "Laura", + "id": "8004" }, { - "id": "8005", - "name": "Laura" + "name": "Laura", + "id": "8005" }, { - "id": "8006", - "name": "Roger" + "name": "Roger", + "id": "8006" }, { - "id": "8007", - "name": "Jorral" + "name": "Jorral", + "id": "8007" }, { - "id": "8008", - "name": "Guthix" + "name": "Guthix", + "id": "8008" }, { - "id": "8009", - "name": "Max the traveller" + "name": "Max the traveller", + "id": "8009" }, { - "id": "8010", - "name": "Man" + "name": "Man", + "id": "8010" }, { - "id": "8011", - "name": "Man" + "name": "Man", + "id": "8011" }, { - "id": "8012", - "name": "Woman" + "name": "Woman", + "id": "8012" }, { - "id": "8013", - "name": "Woman" + "name": "Woman", + "id": "8013" }, { - "id": "8014", - "name": "Haluned" + "name": "Haluned", + "id": "8014" }, { - "id": "8015", - "name": "Jack" + "name": "Jack", + "id": "8015" }, { - "id": "8016", - "name": "Baby Sarah" + "name": "Baby Sarah", + "id": "8016" }, { - "id": "8017", - "name": "Snowy" + "name": "Snowy", + "id": "8017" }, { - "id": "8018", - "name": "Roger" + "name": "Roger", + "id": "8018" }, { - "id": "8019", - "name": "Portal" + "name": "Portal", + "id": "8019" }, { - "id": "8020", - "name": "Portal" + "name": "Portal", + "id": "8020" }, { - "id": "8021", - "name": "Yellow orb" + "name": "Yellow orb", + "id": "8021" }, { - "id": "8023", - "name": "Yellow orb" + "name": "Yellow orb", + "id": "8023" }, { - "id": "8024", - "name": "Yellow orb" + "name": "Yellow orb", + "id": "8024" }, { - "id": "8025", - "name": "Green orb" + "name": "Green orb", + "id": "8025" }, { - "id": "8027", - "name": "Green orb" + "name": "Green orb", + "id": "8027" }, { - "id": "8028", - "name": "Green orb" + "name": "Green orb", + "id": "8028" }, { - "id": "8029", - "name": "Wizard Korvak" + "name": "Wizard Korvak", + "id": "8029" }, { - "id": "8030", - "name": "Wizard Vief" + "name": "Wizard Vief", + "id": "8030" }, { - "id": "8031", - "name": "Wizard Acantha" + "name": "Wizard Acantha", + "id": "8031" }, { - "id": "8032", - "name": "Wizard Elriss" + "name": "Wizard Elriss", + "id": "8032" }, { - "id": "8033", - "name": "Wizard" + "name": "Wizard", + "id": "8033" }, { - "id": "8034", - "name": "Wizard" + "name": "Wizard", + "id": "8034" }, { - "id": "8035", - "name": "Wizard" + "name": "Wizard", + "id": "8035" }, { - "id": "8036", - "name": "Wizard" + "name": "Wizard", + "id": "8036" }, { - "id": "8037", - "name": "Wizard" + "name": "Wizard", + "id": "8037" }, { - "id": "8038", - "name": "Wizard" + "name": "Wizard", + "id": "8038" }, { - "id": "8039", - "name": "Wizard" + "name": "Wizard", + "id": "8039" }, { - "id": "8040", - "name": "Wizard" + "name": "Wizard", + "id": "8040" }, { - "id": "8042", - "name": "Squire Fyre" + "name": "Squire Fyre", + "id": "8042" }, { - "id": "8043", - "name": "Squire Fyre" + "name": "Squire Fyre", + "id": "8043" }, { - "id": "8044", - "name": "Orry" + "name": "Orry", + "id": "8044" }, { - "id": "8045", - "name": "Orry" + "name": "Orry", + "id": "8045" }, { - "id": "8046", - "name": "Ube" + "name": "Ube", + "id": "8046" }, { - "id": "8047", - "name": "Ube" + "name": "Ube", + "id": "8047" }, { - "id": "8048", - "name": "Waldo" + "name": "Waldo", + "id": "8048" }, { - "id": "8049", - "name": "Waldo" + "name": "Waldo", + "id": "8049" }, { - "id": "8050", - "name": "Gjalp" + "name": "Gjalp", + "id": "8050" }, { - "id": "8051", - "name": "Gjalp" + "name": "Gjalp", + "id": "8051" }, { - "id": "8052", - "name": "Brother Fintan" + "name": "Brother Fintan", + "id": "8052" }, { - "id": "8053", - "name": "Brother Fintan" + "name": "Brother Fintan", + "id": "8053" }, { - "id": "8054", - "name": "Stubthumb" + "name": "Stubthumb", + "id": "8054" }, { - "id": "8055", - "name": "Stubthumb" + "name": "Stubthumb", + "id": "8055" }, { - "id": "8056", - "name": "Stubthumb" + "name": "Stubthumb", + "id": "8056" }, { - "id": "8057", - "name": "Doronbol" + "name": "Doronbol", + "id": "8057" }, { - "id": "8058", - "name": "Doronbol" + "name": "Doronbol", + "id": "8058" }, { - "id": "8059", - "name": "Crate" + "name": "Crate", + "id": "8059" }, { - "id": "8060", - "name": "Crate" + "name": "Crate", + "id": "8060" }, { - "id": "8061", - "name": "Egg" + "name": "Egg", + "id": "8061" }, { - "id": "8062", - "name": "Egg" + "name": "Egg", + "id": "8062" }, { - "id": "8063", - "name": "Nanuq" + "name": "Nanuq", + "id": "8063" }, { - "id": "8064", - "name": "Nanuq" + "name": "Nanuq", + "id": "8064" }, { - "id": "8065", - "name": "Pumpkin" + "name": "Pumpkin", + "id": "8065" }, { - "id": "8078", - "name": "Maggie" + "name": "Maggie", + "id": "8078" }, { - "id": "8079", - "name": "Circus barker" + "name": "Circus barker", + "id": "8079" }, { - "id": "8080", - "name": "Circus barker" + "name": "Circus barker", + "id": "8080" }, { - "id": "8081", - "name": "Circus barker" + "name": "Circus barker", + "id": "8081" }, { - "id": "8082", - "name": "Agility assistant" + "name": "Agility assistant", + "id": "8082" }, { - "id": "8083", - "name": "Magic assistant" + "name": "Magic assistant", + "id": "8083" }, { - "id": "8084", - "name": "Ranged assistant" + "name": "Ranged assistant", + "id": "8084" }, { - "id": "8085", - "name": "Ringmaster" + "name": "Ringmaster", + "id": "8085" }, { - "id": "8086", - "name": "Audience" + "name": "Audience", + "id": "8086" }, { - "id": "8087", - "name": "Audience" + "name": "Audience", + "id": "8087" }, { - "id": "8088", - "name": "Ticket vendor" + "name": "Ticket vendor", + "id": "8088" }, { - "id": "8089", - "name": "Rock" + "name": "Rock", + "id": "8089" }, { - "id": "8091", - "name": "Star sprite" + "name": "Star sprite", + "id": "8091" }, { - "id": "8092", - "name": "Shooting star shadow" + "name": "Shooting star shadow", + "id": "8092" }, { - "id": "8093", - "name": "Penguin" + "name": "Penguin", + "id": "8093" }, { - "id": "8094", - "name": "Penguin" + "name": "Penguin", + "id": "8094" }, { - "id": "8095", - "name": "Penguin" + "name": "Penguin", + "id": "8095" }, { - "id": "8096", - "name": "Penguin" + "name": "Penguin", + "id": "8096" }, { - "id": "8097", - "name": "Penguin" + "name": "Penguin", + "id": "8097" }, { - "id": "8098", - "name": "Penguin" + "name": "Penguin", + "id": "8098" }, { - "id": "8099", - "name": "Penguin" + "name": "Penguin", + "id": "8099" }, { - "id": "8100", - "name": "Penguin" + "name": "Penguin", + "id": "8100" }, { - "id": "8101", - "name": "Penguin" + "name": "Penguin", + "id": "8101" }, { - "id": "8102", - "name": "Penguin" + "name": "Penguin", + "id": "8102" }, { - "id": "8103", - "name": "Penguin" + "name": "Penguin", + "id": "8103" }, { - "id": "8104", - "name": "Barrel" + "name": "Barrel", + "id": "8104" }, { - "id": "8105", - "name": "Bush" + "name": "Bush", + "id": "8105" }, { - "id": "8106", - "name": "Bush" + "name": "Bush", + "id": "8106" }, { - "id": "8107", - "name": "Cactus" + "name": "Cactus", + "id": "8107" }, { - "id": "8108", - "name": "Crate" + "name": "Crate", + "id": "8108" }, { - "id": "8109", - "name": "Rock" + "name": "Rock", + "id": "8109" }, { - "id": "8110", - "name": "Toadstool" + "name": "Toadstool", + "id": "8110" }, { - "id": "8111", - "name": "Calladin" + "name": "Calladin", + "id": "8111" }, { - "id": "8112", - "name": "Summer Bonde" + "name": "Summer Bonde", + "id": "8112" }, { - "id": "8114", - "name": "Summer Bonde" + "name": "Summer Bonde", + "id": "8114" }, { - "id": "8115", - "name": "Erik Bonde" + "name": "Erik Bonde", + "id": "8115" }, { - "id": "8116", - "name": "Erik Bonde" + "name": "Erik Bonde", + "id": "8116" }, { - "id": "8117", - "name": "Jallek Lenkin" + "name": "Jallek Lenkin", + "id": "8117" }, { - "id": "8118", - "name": "Jallek Lenkin" + "name": "Jallek Lenkin", + "id": "8118" }, { - "id": "8119", - "name": "Meranek Thanatos" + "name": "Meranek Thanatos", + "id": "8119" }, { - "id": "8120", - "name": "Meranek Thanatos" + "name": "Meranek Thanatos", + "id": "8120" }, { - "id": "8121", - "name": "Spirit" + "name": "Spirit", + "id": "8121" }, { - "id": "8122", - "name": "Rogue" + "name": "Rogue", + "id": "8122" }, { - "id": "8123", - "name": "Tormented wraith" + "name": "Tormented wraith", + "id": "8123" }, { - "id": "8126", - "name": "Dark energy core" + "name": "Dark energy core", + "id": "8126" }, { - "id": "8127", - "name": "Dark energy core" + "name": "Dark energy core", + "id": "8127" }, { - "id": "8128", - "name": "Spirit Beast" + "name": "Spirit Beast", + "id": "8128" }, { - "id": "8129", - "name": "Spirit Beast" + "name": "Spirit Beast", + "id": "8129" }, { - "id": "8130", - "name": "Spirit Beast" + "name": "Spirit Beast", + "id": "8130" }, { - "id": "8131", - "name": "Spirit Beast" + "name": "Spirit Beast", + "id": "8131" }, { - "id": "8132", - "name": "Spirit Beast" + "name": "Spirit Beast", + "id": "8132" }, { - "id": "8134", - "name": "Spirit" + "name": "Spirit", + "id": "8134" }, { - "id": "8135", - "name": "Summer Bonde" + "name": "Summer Bonde", + "id": "8135" }, { - "id": "8136", - "name": "Erik Bonde" + "name": "Erik Bonde", + "id": "8136" }, { - "id": "8137", - "name": "Jallek Lenkin" + "name": "Jallek Lenkin", + "id": "8137" }, { - "id": "8138", - "name": "Meranek Thanatos" + "name": "Meranek Thanatos", + "id": "8138" }, { - "id": "8139", - "name": "Hartwin" + "name": "Hartwin", + "id": "8139" }, { - "id": "8140", - "name": "Hartwin" + "name": "Hartwin", + "id": "8140" }, { - "id": "8141", - "name": "Zombie" + "name": "Zombie", + "id": "8141" }, { - "id": "8143", - "name": "Zombie" + "name": "Zombie", + "id": "8143" }, { - "id": "8144", - "name": "Zombie" + "name": "Zombie", + "id": "8144" }, { - "id": "8145", - "name": "Zombie" + "name": "Zombie", + "id": "8145" }, { - "id": "8146", - "name": "Zemouregal" + "name": "Zemouregal", + "id": "8146" }, { - "id": "8147", - "name": "Zemouregal" + "name": "Zemouregal", + "id": "8147" }, { - "id": "8152", - "name": "Armoured zombie" + "name": "Armoured zombie", + "id": "8152" }, { - "id": "8153", - "name": "Armoured zombie" + "name": "Armoured zombie", + "id": "8153" }, { - "id": "8154", - "name": "Armoured zombie" + "name": "Armoured zombie", + "id": "8154" }, { - "id": "8155", - "name": "Armoured zombie" + "name": "Armoured zombie", + "id": "8155" }, { - "id": "8156", - "name": "Armoured zombie" + "name": "Armoured zombie", + "id": "8156" }, { - "id": "8157", - "name": "Armoured zombie" + "name": "Armoured zombie", + "id": "8157" }, { - "id": "8158", - "name": "Armoured zombie" + "name": "Armoured zombie", + "id": "8158" }, { - "id": "8159", - "name": "Armoured zombie" + "name": "Armoured zombie", + "id": "8159" }, { - "id": "8160", - "name": "Armoured zombie" + "name": "Armoured zombie", + "id": "8160" }, { - "id": "8161", - "name": "Armoured zombie" + "name": "Armoured zombie", + "id": "8161" }, { - "id": "8162", - "name": "Armoured zombie" + "name": "Armoured zombie", + "id": "8162" }, { - "id": "8163", - "name": "Armoured zombie" + "name": "Armoured zombie", + "id": "8163" }, { - "id": "8164", - "name": "Armoured zombie" + "name": "Armoured zombie", + "id": "8164" }, { - "id": "8165", - "name": "Sharathteerk" + "name": "Sharathteerk", + "id": "8165" }, { - "id": "8166", - "name": "Sharathteerk" + "name": "Sharathteerk", + "id": "8166" }, { - "id": "8168", - "name": "Arrav" + "name": "Arrav", + "id": "8168" }, { - "id": "8169", - "name": "Arrav" + "name": "Arrav", + "id": "8169" }, { - "id": "8170", - "name": "Ramarno" + "name": "Ramarno", + "id": "8170" }, { - "id": "8171", - "name": "Dimintheis" + "name": "Dimintheis", + "id": "8171" }, { - "id": "8172", - "name": "Dimintheis" + "name": "Dimintheis", + "id": "8172" }, { - "id": "8173", - "name": "Guard" + "name": "Guard", + "id": "8173" }, { - "id": "8174", - "name": "Clive" + "name": "Clive", + "id": "8174" }, { - "id": "8175", - "name": "Ellamaria" + "name": "Ellamaria", + "id": "8175" }, { - "id": "8176", - "name": "Fish" + "name": "Fish", + "id": "8176" }, { - "id": "8177", - "name": "Fish" + "name": "Fish", + "id": "8177" }, { - "id": "8178", - "name": "Monk of Zamorak" + "name": "Monk of Zamorak", + "id": "8178" }, { - "id": "8179", - "name": "Ellamaria" + "name": "Ellamaria", + "id": "8179" }, { - "id": "8180", - "name": "Traiborn" + "name": "Traiborn", + "id": "8180" }, { - "id": "8181", - "name": "Fenkenstrain's Monster" + "name": "Fenkenstrain's Monster", + "id": "8181" }, { - "id": "8182", - "name": "Wise Old Man" + "name": "Wise Old Man", + "id": "8182" }, { - "id": "8183", - "name": "Acrobat" + "name": "Acrobat", + "id": "8183" }, { - "id": "8186", - "name": "Acrobat" + "name": "Acrobat", + "id": "8186" }, { - "id": "8187", - "name": "Acrobat" + "name": "Acrobat", + "id": "8187" }, { - "id": "8188", - "name": "Acrobat" + "name": "Acrobat", + "id": "8188" }, { - "id": "8189", - "name": "Acrobat" + "name": "Acrobat", + "id": "8189" }, { - "id": "8190", - "name": "Acrobat" + "name": "Acrobat", + "id": "8190" }, { - "id": "8191", - "name": "Acrobat" + "name": "Acrobat", + "id": "8191" }, { - "id": "8192", - "name": "Acrobat" + "name": "Acrobat", + "id": "8192" }, { - "id": "8193", - "name": "Acrobat" + "name": "Acrobat", + "id": "8193" }, { - "id": "8194", - "name": "Acrobat" + "name": "Acrobat", + "id": "8194" }, { - "id": "8195", - "name": "Acrobat" + "name": "Acrobat", + "id": "8195" }, { - "id": "8196", - "name": "Acrobat" + "name": "Acrobat", + "id": "8196" }, { - "id": "8197", - "name": "Acrobat" + "name": "Acrobat", + "id": "8197" }, { - "id": "8198", - "name": "Acrobat" + "name": "Acrobat", + "id": "8198" }, { - "id": "8199", - "name": "Acrobat" + "name": "Acrobat", + "id": "8199" }, { - "id": "8200", - "name": "Acrobat" + "name": "Acrobat", + "id": "8200" }, { - "id": "8201", - "name": "Wendy" + "name": "Wendy", + "id": "8201" }, { - "id": "8202", - "name": "Trogs" + "name": "Trogs", + "id": "8202" }, { - "id": "8203", - "name": "Norman" + "name": "Norman", + "id": "8203" }, { - "id": "8204", - "name": "Babe" + "name": "Babe", + "id": "8204" }, { - "id": "8205", - "name": "Gus" + "name": "Gus", + "id": "8205" }, { - "id": "8206", - "name": "Lottie" + "name": "Lottie", + "id": "8206" }, { - "id": "8207", - "name": "Aggie" + "name": "Aggie", + "id": "8207" }, { - "id": "8208", - "name": "Bat" + "name": "Bat", + "id": "8208" }, { - "id": "8209", - "name": "Rat" + "name": "Rat", + "id": "8209" }, { - "id": "8210", - "name": "Lizard" + "name": "Lizard", + "id": "8210" }, { - "id": "8211", - "name": "Blackbird" + "name": "Blackbird", + "id": "8211" }, { - "id": "8212", - "name": "Spider" + "name": "Spider", + "id": "8212" }, { - "id": "8213", - "name": "Snail" + "name": "Snail", + "id": "8213" }, { - "id": "8214", - "name": "Cat" + "name": "Cat", + "id": "8214" }, { - "id": "8215", - "name": "Lazy cat" + "name": "Lazy cat", + "id": "8215" }, { - "id": "8216", - "name": "Overgrown cat" + "name": "Overgrown cat", + "id": "8216" }, { - "id": "8217", - "name": "Kitten" + "name": "Kitten", + "id": "8217" }, { - "id": "8218", - "name": "Wily cat" + "name": "Wily cat", + "id": "8218" }, { - "id": "8219", - "name": "Guardian of Armadyl" + "name": "Guardian of Armadyl", + "id": "8219" }, { - "id": "8227", - "name": "Head mystic" + "name": "Head mystic", + "id": "8227" }, { - "id": "8228", - "name": "Rewards mystic" + "name": "Rewards mystic", + "id": "8228" }, { - "id": "8229", - "name": "Mystic" + "name": "Mystic", + "id": "8229" }, { - "id": "8230", - "name": "Mystic" + "name": "Mystic", + "id": "8230" }, { - "id": "8231", - "name": "Mystic" + "name": "Mystic", + "id": "8231" }, { - "id": "8232", - "name": "Mystic" + "name": "Mystic", + "id": "8232" }, { - "id": "8233", - "name": "Mystic" + "name": "Mystic", + "id": "8233" }, { - "id": "8234", - "name": "Mystic" + "name": "Mystic", + "id": "8234" }, { - "id": "8235", - "name": "Mystic" + "name": "Mystic", + "id": "8235" }, { - "id": "8236", - "name": "Mystic" + "name": "Mystic", + "id": "8236" }, { - "id": "8237", - "name": "Mystic" + "name": "Mystic", + "id": "8237" }, { - "id": "8238", - "name": "Mystic" + "name": "Mystic", + "id": "8238" }, { - "id": "8239", - "name": "Head mystic" + "name": "Head mystic", + "id": "8239" }, { - "id": "8240", - "name": "Clay familiar (class 1)" + "name": "Clay familiar (class 1)", + "id": "8240" }, { - "id": "8241", - "name": "Clay familiar (class 1)" + "name": "Clay familiar (class 1)", + "id": "8241" }, { - "id": "8242", - "name": "Clay familiar (class 2)" + "name": "Clay familiar (class 2)", + "id": "8242" }, { - "id": "8243", - "name": "Clay familiar (class 2)" + "name": "Clay familiar (class 2)", + "id": "8243" }, { - "id": "8244", - "name": "Clay familiar (class 3)" + "name": "Clay familiar (class 3)", + "id": "8244" }, { - "id": "8245", - "name": "Clay familiar (class 3)" + "name": "Clay familiar (class 3)", + "id": "8245" }, { - "id": "8246", - "name": "Clay familiar (class 4)" + "name": "Clay familiar (class 4)", + "id": "8246" }, { - "id": "8247", - "name": "Clay familiar (class 4)" + "name": "Clay familiar (class 4)", + "id": "8247" }, { - "id": "8248", - "name": "Clay familiar (class 5)" + "name": "Clay familiar (class 5)", + "id": "8248" }, { - "id": "8249", - "name": "Clay familiar (class 5)" + "name": "Clay familiar (class 5)", + "id": "8249" }, { - "id": "8250", - "name": "Grindxplox" + "name": "Grindxplox", + "id": "8250" }, { - "id": "8251", - "name": "Grindxplox" + "name": "Grindxplox", + "id": "8251" }, { - "id": "8252", - "name": "Stealing Creation team" + "name": "Stealing Creation team", + "id": "8252" }, { - "id": "8253", - "name": "Resource NPC" + "name": "Resource NPC", + "id": "8253" }, { - "id": "8254", - "name": "Game NPC" + "name": "Game NPC", + "id": "8254" }, { - "id": "8255", - "name": "Resource NPC" + "name": "Resource NPC", + "id": "8255" }, { - "id": "8257", - "name": "Harrallak Menarous" + "name": "Harrallak Menarous", + "id": "8257" }, { - "id": "8269", - "name": "Yadech Strongarm" + "name": "Yadech Strongarm", + "id": "8269" }, { - "id": "8271", - "name": "Turael" + "name": "Turael", + "id": "8271" }, { - "id": "8274", - "name": "Mazchna" + "name": "Mazchna", + "id": "8274" }, { - "id": "8275", - "name": "Duradel" + "name": "Duradel", + "id": "8275" }, { - "id": "8276", - "name": "Cyrisus" + "name": "Cyrisus", + "id": "8276" }, { - "id": "8280", - "name": "Sloane" + "name": "Sloane", + "id": "8280" }, { - "id": "8281", - "name": "Balance Elemental" + "name": "Balance Elemental", + "id": "8281" }, { - "id": "8282", - "name": "Balance Elemental" + "name": "Balance Elemental", + "id": "8282" }, { - "id": "8283", - "name": "Balance Elemental" + "name": "Balance Elemental", + "id": "8283" }, { - "id": "8284", - "name": "Balance Elemental" + "name": "Balance Elemental", + "id": "8284" }, { - "id": "8285", - "name": "Balance Elemental" + "name": "Balance Elemental", + "id": "8285" }, { - "id": "8286", - "name": "Undead hero" + "name": "Undead hero", + "id": "8286" }, { - "id": "8287", - "name": "Undead hero" + "name": "Undead hero", + "id": "8287" }, { - "id": "8288", - "name": "Undead hero" + "name": "Undead hero", + "id": "8288" }, { - "id": "8289", - "name": "Undead hero" + "name": "Undead hero", + "id": "8289" }, { - "id": "8290", - "name": "Undead hero" + "name": "Undead hero", + "id": "8290" }, { - "id": "8291", - "name": "Undead hero" + "name": "Undead hero", + "id": "8291" }, { - "id": "8292", - "name": "Undead hero" + "name": "Undead hero", + "id": "8292" }, { - "id": "8293", - "name": "Undead hero" + "name": "Undead hero", + "id": "8293" }, { - "id": "8294", - "name": "Undead hero" + "name": "Undead hero", + "id": "8294" }, { - "id": "8295", - "name": "Undead hero" + "name": "Undead hero", + "id": "8295" }, { - "id": "8296", - "name": "Undead hero" + "name": "Undead hero", + "id": "8296" }, { - "id": "8297", - "name": "Undead hero" + "name": "Undead hero", + "id": "8297" }, { - "id": "8298", - "name": "Stone block" + "name": "Stone block", + "id": "8298" }, { - "id": "8299", - "name": "Harrallak Menarous" + "name": "Harrallak Menarous", + "id": "8299" }, { - "id": "8300", - "name": "Harrallak Menarous" + "name": "Harrallak Menarous", + "id": "8300" }, { - "id": "8301", - "name": "Harrallak Menarous" + "name": "Harrallak Menarous", + "id": "8301" }, { - "id": "8302", - "name": "Turael" + "name": "Turael", + "id": "8302" }, { - "id": "8303", - "name": "Turael" + "name": "Turael", + "id": "8303" }, { - "id": "8304", - "name": "Cyrisus" + "name": "Cyrisus", + "id": "8304" }, { - "id": "8305", - "name": "Cyrisus" + "name": "Cyrisus", + "id": "8305" }, { - "id": "8306", - "name": "Ghommal" + "name": "Ghommal", + "id": "8306" }, { - "id": "8307", - "name": "Mazchna" + "name": "Mazchna", + "id": "8307" }, { - "id": "8308", - "name": "Mazchna" + "name": "Mazchna", + "id": "8308" }, { - "id": "8309", - "name": "Mazchna" + "name": "Mazchna", + "id": "8309" }, { - "id": "8310", - "name": "Duradel" + "name": "Duradel", + "id": "8310" }, { - "id": "8311", - "name": "Sloane" + "name": "Sloane", + "id": "8311" }, { - "id": "8317", - "name": "Elite Dark Ranger" + "name": "Elite Dark Ranger", + "id": "8317" }, { - "id": "8318", - "name": "Elite Dark Ranger" + "name": "Elite Dark Ranger", + "id": "8318" }, { - "id": "8319", - "name": "Elite Dark Ranger" + "name": "Elite Dark Ranger", + "id": "8319" }, { - "id": "8320", - "name": "Elite Dark Mage" + "name": "Elite Dark Mage", + "id": "8320" }, { - "id": "8321", - "name": "Elite Dark Mage" + "name": "Elite Dark Mage", + "id": "8321" }, { - "id": "8322", - "name": "Elite Dark Mage" + "name": "Elite Dark Mage", + "id": "8322" }, { - "id": "8323", - "name": "Elite Dark Mage" + "name": "Elite Dark Mage", + "id": "8323" }, { - "id": "8324", - "name": "Elite Black Knight" + "name": "Elite Black Knight", + "id": "8324" }, { - "id": "8325", - "name": "Elite Black Knight" + "name": "Elite Black Knight", + "id": "8325" }, { - "id": "8326", - "name": "Elite Black Knight" + "name": "Elite Black Knight", + "id": "8326" }, { - "id": "8327", - "name": "Elite Black Knight" + "name": "Elite Black Knight", + "id": "8327" }, { - "id": "8328", - "name": "Elite Black Knight" + "name": "Elite Black Knight", + "id": "8328" }, { - "id": "8329", - "name": "Elite Black Knight" + "name": "Elite Black Knight", + "id": "8329" }, { - "id": "8330", - "name": "Elite Black Knight" + "name": "Elite Black Knight", + "id": "8330" }, { - "id": "8331", - "name": "Guardian of Armadyl" + "name": "Guardian of Armadyl", + "id": "8331" }, { - "id": "8332", - "name": "Guardian of Armadyl" + "name": "Guardian of Armadyl", + "id": "8332" }, { - "id": "8333", - "name": "Guardian of Armadyl" + "name": "Guardian of Armadyl", + "id": "8333" }, { - "id": "8334", - "name": "Mercenary axeman" + "name": "Mercenary axeman", + "id": "8334" }, { - "id": "8335", - "name": "Mercenary mage" + "name": "Mercenary mage", + "id": "8335" }, { - "id": "8336", - "name": "Idria" + "name": "Idria", + "id": "8336" }, { - "id": "8337", - "name": "Idria" + "name": "Idria", + "id": "8337" }, { - "id": "8338", - "name": "Akrisae" + "name": "Akrisae", + "id": "8338" }, { - "id": "8339", - "name": "Shady stranger" + "name": "Shady stranger", + "id": "8339" }, { - "id": "8340", - "name": "Shady stranger" + "name": "Shady stranger", + "id": "8340" }, { - "id": "8341", - "name": "Shady stranger" + "name": "Shady stranger", + "id": "8341" }, { - "id": "8342", - "name": "Guardian of Armadyl" + "name": "Guardian of Armadyl", + "id": "8342" }, { - "id": "8344", - "name": "Guardian of Armadyl" + "name": "Guardian of Armadyl", + "id": "8344" }, { - "id": "8345", - "name": "Local thug" + "name": "Local thug", + "id": "8345" }, { - "id": "8347", - "name": "Local mage" + "name": "Local mage", + "id": "8347" }, { - "id": "8367", - "name": "Undead troll" + "name": "Undead troll", + "id": "8367" }, { - "id": "8378", - "name": "Undead troll" + "name": "Undead troll", + "id": "8378" }, { - "id": "8379", - "name": "Undead troll" + "name": "Undead troll", + "id": "8379" }, { - "id": "8380", - "name": "Undead troll" + "name": "Undead troll", + "id": "8380" }, { - "id": "8381", - "name": "Undead troll" + "name": "Undead troll", + "id": "8381" }, { - "id": "8382", - "name": "Undead troll" + "name": "Undead troll", + "id": "8382" }, { - "id": "8383", - "name": "Undead troll" + "name": "Undead troll", + "id": "8383" }, { - "id": "8384", - "name": "Undead troll" + "name": "Undead troll", + "id": "8384" }, { - "id": "8385", - "name": "Undead troll" + "name": "Undead troll", + "id": "8385" }, { - "id": "8386", - "name": "Undead troll" + "name": "Undead troll", + "id": "8386" }, { - "id": "8387", - "name": "Undead troll" + "name": "Undead troll", + "id": "8387" }, { - "id": "8388", - "name": "Undead troll" + "name": "Undead troll", + "id": "8388" }, { - "id": "8389", - "name": "Undead troll" + "name": "Undead troll", + "id": "8389" }, { - "id": "8390", - "name": "Undead troll" + "name": "Undead troll", + "id": "8390" }, { - "id": "8391", - "name": "Undead troll" + "name": "Undead troll", + "id": "8391" }, { - "id": "8392", - "name": "Undead troll" + "name": "Undead troll", + "id": "8392" }, { - "id": "8393", - "name": "Vasador" + "name": "Vasador", + "id": "8393" }, { - "id": "8394", - "name": "Valator" + "name": "Valator", + "id": "8394" }, { - "id": "8395", - "name": "Vaasdor" + "name": "Vaasdor", + "id": "8395" }, { - "id": "8396", - "name": "Varosad" + "name": "Varosad", + "id": "8396" }, { - "id": "8397", - "name": "Verisad" + "name": "Verisad", + "id": "8397" }, { - "id": "8398", - "name": "Vudisor" + "name": "Vudisor", + "id": "8398" }, { - "id": "8399", - "name": "Vlatoad" + "name": "Vlatoad", + "id": "8399" }, { - "id": "8400", - "name": "Vedolas" + "name": "Vedolas", + "id": "8400" }, { - "id": "8401", - "name": "Vundiar" + "name": "Vundiar", + "id": "8401" }, { - "id": "8402", - "name": "Versita" + "name": "Versita", + "id": "8402" }, { - "id": "8403", - "name": "Vislota" + "name": "Vislota", + "id": "8403" }, { - "id": "8404", - "name": "Vanjuta" + "name": "Vanjuta", + "id": "8404" }, { - "id": "8405", - "name": "Vasador" + "name": "Vasador", + "id": "8405" }, { - "id": "8406", - "name": "Valator" + "name": "Valator", + "id": "8406" }, { - "id": "8407", - "name": "Vaasdor" + "name": "Vaasdor", + "id": "8407" }, { - "id": "8408", - "name": "Varosad" + "name": "Varosad", + "id": "8408" }, { - "id": "8409", - "name": "Verisad" + "name": "Verisad", + "id": "8409" }, { - "id": "8410", - "name": "Vudisor" + "name": "Vudisor", + "id": "8410" }, { - "id": "8411", - "name": "Vlatoad" + "name": "Vlatoad", + "id": "8411" }, { - "id": "8412", - "name": "Vedolas" + "name": "Vedolas", + "id": "8412" }, { - "id": "8413", - "name": "Vundiar" + "name": "Vundiar", + "id": "8413" }, { - "id": "8414", - "name": "Versita" + "name": "Versita", + "id": "8414" }, { - "id": "8415", - "name": "Vislota" + "name": "Vislota", + "id": "8415" }, { - "id": "8416", - "name": "Vanjuta" + "name": "Vanjuta", + "id": "8416" }, { - "id": "8417", - "name": "Ivy Sophista" + "name": "Ivy Sophista", + "id": "8417" }, { - "id": "8418", - "name": "Thaerisk Cemphier" + "name": "Thaerisk Cemphier", + "id": "8418" }, { - "id": "8419", - "name": "Thaerisk Cemphier" + "name": "Thaerisk Cemphier", + "id": "8419" }, { - "id": "8420", - "name": "Assassin" + "name": "Assassin", + "id": "8420" }, { - "id": "8423", - "name": "Assassin" + "name": "Assassin", + "id": "8423" }, { - "id": "8424", - "name": "Mithril dragon" + "name": "Mithril dragon", + "id": "8424" }, { - "id": "8425", - "name": "Dragon head" + "name": "Dragon head", + "id": "8425" }, { - "id": "8426", - "name": "Dragon head" + "name": "Dragon head", + "id": "8426" }, { - "id": "8427", - "name": "Dragon head" + "name": "Dragon head", + "id": "8427" }, { - "id": "8428", - "name": "Khazard launderer" + "name": "Khazard launderer", + "id": "8428" }, { - "id": "8429", - "name": "Khazard cook" + "name": "Khazard cook", + "id": "8429" }, { - "id": "8430", - "name": "Silif" + "name": "Silif", + "id": "8430" }, { - "id": "8431", - "name": "Silif" + "name": "Silif", + "id": "8431" }, { - "id": "8432", - "name": "Silif" + "name": "Silif", + "id": "8432" }, { - "id": "8433", - "name": "Silif" + "name": "Silif", + "id": "8433" }, { - "id": "8434", - "name": "Silif" + "name": "Silif", + "id": "8434" }, { - "id": "8435", - "name": "Shady stranger" + "name": "Shady stranger", + "id": "8435" }, { - "id": "8436", - "name": "Suspicious outsider" + "name": "Suspicious outsider", + "id": "8436" }, { - "id": "8437", - "name": "Elite Khazard guard" + "name": "Elite Khazard guard", + "id": "8437" }, { - "id": "8438", - "name": "Elite Khazard guard" + "name": "Elite Khazard guard", + "id": "8438" }, { - "id": "8439", - "name": "Elite Khazard guard" + "name": "Elite Khazard guard", + "id": "8439" }, { - "id": "8440", - "name": "Elite Khazard guard" + "name": "Elite Khazard guard", + "id": "8440" }, { - "id": "8441", - "name": "Dark Squall" + "name": "Dark Squall", + "id": "8441" }, { - "id": "8442", - "name": "Surok Magis" + "name": "Surok Magis", + "id": "8442" }, { - "id": "8443", - "name": "Lucien" + "name": "Lucien", + "id": "8443" }, { - "id": "8444", - "name": "Druid" + "name": "Druid", + "id": "8444" }, { - "id": "8445", - "name": "Druid" + "name": "Druid", + "id": "8445" }, { - "id": "8446", - "name": "Druid" + "name": "Druid", + "id": "8446" }, { - "id": "8447", - "name": "Druid bodyguard" + "name": "Druid bodyguard", + "id": "8447" }, { - "id": "8448", - "name": "Druid bodyguard" + "name": "Druid bodyguard", + "id": "8448" }, { - "id": "8449", - "name": "Movario" + "name": "Movario", + "id": "8449" }, { - "id": "8450", - "name": "Darve" + "name": "Darve", + "id": "8450" }, { - "id": "8451", - "name": "Cave goblin" + "name": "Cave goblin", + "id": "8451" }, { - "id": "8452", - "name": "Movario" + "name": "Movario", + "id": "8452" }, { - "id": "8453", - "name": "Darve" + "name": "Darve", + "id": "8453" }, { - "id": "8454", - "name": "Light creature" + "name": "Light creature", + "id": "8454" }, { - "id": "8455", - "name": "Light creature" + "name": "Light creature", + "id": "8455" }, { - "id": "8456", - "name": "Druid spirit" + "name": "Druid spirit", + "id": "8456" }, { - "id": "8457", - "name": "Druid spirit" + "name": "Druid spirit", + "id": "8457" }, { - "id": "8458", - "name": "Druid spirit" + "name": "Druid spirit", + "id": "8458" }, { - "id": "8459", - "name": "Druid spirit" + "name": "Druid spirit", + "id": "8459" }, { - "id": "8460", - "name": "Turael" + "name": "Turael", + "id": "8460" }, { - "id": "8462", - "name": "Spria" + "name": "Spria", + "id": "8462" }, { - "id": "8463", - "name": "Mazchna" + "name": "Mazchna", + "id": "8463" }, { - "id": "8464", - "name": "Mazchna" + "name": "Mazchna", + "id": "8464" }, { - "id": "8465", - "name": "Achtryn" + "name": "Achtryn", + "id": "8465" }, { - "id": "8466", - "name": "Duradel" + "name": "Duradel", + "id": "8466" }, { - "id": "8467", - "name": "Lapalok" + "name": "Lapalok", + "id": "8467" }, { - "id": "8468", - "name": "Laidee Gnonock" + "name": "Laidee Gnonock", + "id": "8468" }, { - "id": "8485", - "name": "Hazelmere" + "name": "Hazelmere", + "id": "8485" }, { - "id": "8486", - "name": "Undead mage" + "name": "Undead mage", + "id": "8486" }, { - "id": "8487", - "name": "Wild broav" + "name": "Wild broav", + "id": "8487" }, { - "id": "8488", - "name": "Hazelmere" + "name": "Hazelmere", + "id": "8488" }, { - "id": "8490", - "name": "Hazelmere" + "name": "Hazelmere", + "id": "8490" }, { - "id": "8491", - "name": "Broav" + "name": "Broav", + "id": "8491" }, { - "id": "8492", - "name": "Sithaph" + "name": "Sithaph", + "id": "8492" }, { - "id": "8494", - "name": "Strisath" + "name": "Strisath", + "id": "8494" }, { - "id": "8495", - "name": "Sakirth" + "name": "Sakirth", + "id": "8495" }, { - "id": "8496", - "name": "Hazelmere's hat" + "name": "Hazelmere's hat", + "id": "8496" }, { - "id": "8497", - "name": "Lucien" + "name": "Lucien", + "id": "8497" }, { - "id": "8498", - "name": "A lazy Khazard guard" + "name": "A lazy Khazard guard", + "id": "8498" }, { - "id": "8499", - "name": "Thanksgiving Turkey" + "name": "Thanksgiving Turkey", + "id": "8499" }, { - "id": "8500", - "name": "Cook's brother" + "name": "Cook's brother", + "id": "8500" }, { - "id": "8501", - "name": "Turkey" + "name": "Turkey", + "id": "8501" }, { - "id": "8502", - "name": "Cactus" + "name": "Cactus", + "id": "8502" }, { - "id": "8503", - "name": "Cactus" + "name": "Cactus", + "id": "8503" }, { - "id": "8504", - "name": "Bush" + "name": "Bush", + "id": "8504" }, { - "id": "8505", - "name": "Toadstool" + "name": "Toadstool", + "id": "8505" }, { - "id": "8506", - "name": "Barrel" + "name": "Barrel", + "id": "8506" }, { - "id": "8507", - "name": "Bush" + "name": "Bush", + "id": "8507" }, { - "id": "8508", - "name": "Crate" + "name": "Crate", + "id": "8508" }, { - "id": "8509", - "name": "Bush" + "name": "Bush", + "id": "8509" }, { - "id": "8510", - "name": "Rock" + "name": "Rock", + "id": "8510" }, { - "id": "8511", - "name": "Bush" + "name": "Bush", + "id": "8511" }, { - "id": "8512", - "name": "Marvin" + "name": "Marvin", + "id": "8512" }, { - "id": "8513", - "name": "Marius" + "name": "Marius", + "id": "8513" }, { - "id": "8514", - "name": "Benny" + "name": "Benny", + "id": "8514" }, { - "id": "8515", - "name": "Yeti" + "name": "Yeti", + "id": "8515" }, { - "id": "8516", - "name": "Yeti" + "name": "Yeti", + "id": "8516" }, { - "id": "8517", - "name": "Jack Frost" + "name": "Jack Frost", + "id": "8517" }, { - "id": "8518", - "name": "Jack Frost" + "name": "Jack Frost", + "id": "8518" }, { - "id": "8519", - "name": "Jack Frost" + "name": "Jack Frost", + "id": "8519" }, { - "id": "8520", - "name": "Jack Frost" + "name": "Jack Frost", + "id": "8520" }, { - "id": "8521", - "name": "Snow imp" + "name": "Snow imp", + "id": "8521" }, { - "id": "8537", - "name": "Snow imp" + "name": "Snow imp", + "id": "8537" }, { - "id": "8538", - "name": "Snow imp" + "name": "Snow imp", + "id": "8538" }, { - "id": "8539", - "name": "Queen of Snow" + "name": "Queen of Snow", + "id": "8539" }, { - "id": "8540", - "name": "Santa Claus" + "name": "Santa Claus", + "id": "8540" }, { - "id": "8541", - "name": "Head snow imp" + "name": "Head snow imp", + "id": "8541" }, { - "id": "8542", - "name": "Head snow imp" + "name": "Head snow imp", + "id": "8542" }, { - "id": "8543", - "name": "Head snow imp" + "name": "Head snow imp", + "id": "8543" }, { - "id": "8544", - "name": "Isidor" + "name": "Isidor", + "id": "8544" }, { - "id": "8545", - "name": "Mystic" + "name": "Mystic", + "id": "8545" }, { - "id": "8546", - "name": "Balance Elemental" + "name": "Balance Elemental", + "id": "8546" }, { - "id": "8547", - "name": "Wounded phoenix" + "name": "Wounded phoenix", + "id": "8547" }, { - "id": "8548", - "name": "Phoenix" + "name": "Phoenix", + "id": "8548" }, { - "id": "8549", - "name": "Phoenix" + "name": "Phoenix", + "id": "8549" }, { - "id": "8550", - "name": "Phoenix eggling" + "name": "Phoenix eggling", + "id": "8550" }, { - "id": "8551", - "name": "Phoenix eggling" + "name": "Phoenix eggling", + "id": "8551" }, { - "id": "8552", - "name": "Large egg" + "name": "Large egg", + "id": "8552" }, { - "id": "8553", - "name": "Priest of Guthix" + "name": "Priest of Guthix", + "id": "8553" }, { - "id": "8556", - "name": "Brian Twitcher" + "name": "Brian Twitcher", + "id": "8556" }, { - "id": "8557", - "name": "Lesser reborn warrior" + "name": "Lesser reborn warrior", + "id": "8557" }, { - "id": "8558", - "name": "Lesser reborn warrior" + "name": "Lesser reborn warrior", + "id": "8558" }, { - "id": "8559", - "name": "Greater reborn warrior" + "name": "Greater reborn warrior", + "id": "8559" }, { - "id": "8560", - "name": "Greater reborn warrior" + "name": "Greater reborn warrior", + "id": "8560" }, { - "id": "8561", - "name": "Lesser reborn ranger" + "name": "Lesser reborn ranger", + "id": "8561" }, { - "id": "8562", - "name": "Lesser reborn ranger" + "name": "Lesser reborn ranger", + "id": "8562" }, { - "id": "8563", - "name": "Greater reborn ranger" + "name": "Greater reborn ranger", + "id": "8563" }, { - "id": "8564", - "name": "Greater reborn ranger" + "name": "Greater reborn ranger", + "id": "8564" }, { - "id": "8565", - "name": "Lesser reborn mage" + "name": "Lesser reborn mage", + "id": "8565" }, { - "id": "8566", - "name": "Lesser reborn mage" + "name": "Lesser reborn mage", + "id": "8566" }, { - "id": "8567", - "name": "Greater reborn mage" + "name": "Greater reborn mage", + "id": "8567" }, { - "id": "8568", - "name": "Greater reborn mage" + "name": "Greater reborn mage", + "id": "8568" }, { - "id": "8569", - "name": "Lesser reborn warrior" + "name": "Lesser reborn warrior", + "id": "8569" }, { - "id": "8570", - "name": "Greater reborn warrior" + "name": "Greater reborn warrior", + "id": "8570" }, { - "id": "8571", - "name": "Lesser reborn ranger" + "name": "Lesser reborn ranger", + "id": "8571" }, { - "id": "8572", - "name": "Greater reborn ranger" + "name": "Greater reborn ranger", + "id": "8572" }, { - "id": "8573", - "name": "Lesser reborn mage" + "name": "Lesser reborn mage", + "id": "8573" }, { - "id": "8574", - "name": "Greater reborn mage" + "name": "Greater reborn mage", + "id": "8574" }, { - "id": "8575", - "name": "Phoenix" + "name": "Phoenix", + "id": "8575" }, { - "id": "8576", - "name": "Phoenix" + "name": "Phoenix", + "id": "8576" }, { - "id": "8577", - "name": "Phoenix eggling" + "name": "Phoenix eggling", + "id": "8577" }, { - "id": "8578", - "name": "Phoenix eggling" + "name": "Phoenix eggling", + "id": "8578" }, { - "id": "8579", - "name": "Karma the chameleon" + "name": "Karma the chameleon", + "id": "8579" }, { - "id": "8581", - "name": "Karma the chameleon" + "name": "Karma the chameleon", + "id": "8581" }, { - "id": "8582", - "name": "Karma the chameleon" + "name": "Karma the chameleon", + "id": "8582" }, { - "id": "8583", - "name": "Karma the chameleon" + "name": "Karma the chameleon", + "id": "8583" }, { - "id": "8584", - "name": "Karma the chameleon" + "name": "Karma the chameleon", + "id": "8584" }, { - "id": "8585", - "name": "Karma the chameleon" + "name": "Karma the chameleon", + "id": "8585" }, { - "id": "8586", - "name": "Karma the chameleon" + "name": "Karma the chameleon", + "id": "8586" }, { - "id": "8587", - "name": "Karma the chameleon" + "name": "Karma the chameleon", + "id": "8587" }, { - "id": "8588", - "name": "Karma the chameleon" + "name": "Karma the chameleon", + "id": "8588" }, { - "id": "8589", - "name": "Karma the chameleon" + "name": "Karma the chameleon", + "id": "8589" }, { - "id": "8590", - "name": "Geoffrey" + "name": "Geoffrey", + "id": "8590" }, { "examine": "Ooh, shiny things!", @@ -87864,33 +87848,13 @@ "id": "3209" }, { + "examine": "She doesn't look too happy.", "name": "Elena", - "id": "335", - "examine": "She doesn't look too happy." - }, - { - "examine": "Use 1344 to change me", - "name": "Skippy Varbit", - "id": "2795" + "id": "335" }, { "examine": "He looks angry and smells drunk.", "name": "Skippy", "id": "2796" - }, - { - "name": "Skippy", - "id": "2797", - "examine": "Skippy, just looking a little 'tender'." - }, - { - "name": "Skippy", - "id": "2798", - "examine": "He seems a lot less angry and smells a great deal fresher." - }, - { - "name": "Skippy", - "id": "2799", - "examine": "He seems a bit confused." } ] \ No newline at end of file From c296e34b50fbc5621c9fcdb6c1357e3ec01a2baf Mon Sep 17 00:00:00 2001 From: Lucid Enigma Date: Sat, 12 Apr 2025 11:43:57 +0000 Subject: [PATCH 024/117] Corrected Gunslik's shop items --- Server/data/configs/shops.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/data/configs/shops.json b/Server/data/configs/shops.json index a84aedda9..2f285314c 100644 --- a/Server/data/configs/shops.json +++ b/Server/data/configs/shops.json @@ -177,7 +177,7 @@ "general_store": "true", "id": "21", "title": "Gunslik's Assorted Items", - "stock": "{1931,30,100}-{1935,30,100}-{1735,10,100}-{1925,10,100}-{1923,10,100}-{1887,10,100}-{590,10,100}-{1755,10,100}-{2347,10,100}-{550,10,100}-{9003,10,100}" + "stock": "{1935,10,100}-{1925,30,100}-{590,10,100}-{1755,10,100}-{2347,10,100}-{36,10,100}-{596,10,100}-{973,10,100}-{1059,10,100}-{229,300,100}-{233,10,100}-{954,10,100}" }, { "npcs": "1334", From 711ef542fa030c562dde078694823b448dd4e5ad Mon Sep 17 00:00:00 2001 From: Daarth Cammie Date: Sat, 24 May 2025 02:50:40 +0000 Subject: [PATCH 025/117] Fixed charging orbs to check magic level instead of crafting --- .../main/content/global/skill/magic/modern/ModernListeners.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt b/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt index 6525f3bc6..e36eaa577 100644 --- a/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt +++ b/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt @@ -313,7 +313,7 @@ class ModernListeners : SpellListener("modern"){ create { _, amount -> var crafted = 0 queueScript(player, 0) { - if (!hasLevelDyn(player, Skills.CRAFTING, spell.level)) { + if (!hasLevelDyn(player, Skills.MAGIC, spell.level)) { sendMessage(player, "You need a magic level of ${spell.level} to cast this spell.") return@queueScript stopExecuting(player) } From fc00509b9d5601bee0006923f2a7934c57546dfe Mon Sep 17 00:00:00 2001 From: Moon Date: Sat, 24 May 2025 03:01:18 +0000 Subject: [PATCH 026/117] Fixed Goblin Diplomacy quest log text --- .../goblinvillage/quest/goblindiplomacy/GoblinDiplomacy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/src/main/content/region/asgarnia/goblinvillage/quest/goblindiplomacy/GoblinDiplomacy.java b/Server/src/main/content/region/asgarnia/goblinvillage/quest/goblindiplomacy/GoblinDiplomacy.java index a85d815cb..ad3d9c910 100644 --- a/Server/src/main/content/region/asgarnia/goblinvillage/quest/goblindiplomacy/GoblinDiplomacy.java +++ b/Server/src/main/content/region/asgarnia/goblinvillage/quest/goblindiplomacy/GoblinDiplomacy.java @@ -82,7 +82,7 @@ public class GoblinDiplomacy extends Quest { line(player, BLUE + "I have some " + RED + "Blue Goblin Armour. " + BLUE + "I should show it to the", 12+ 7); line(player, BLUE + "generals.", 13+ 7); } else { - line(player, BLUE + "I should bring the goblins s+ 7+ 7);e " + RED + "Blue Goblin Armour", 12+ 7); + line(player, BLUE + "I should bring the goblins some " + RED + "Blue Goblin Armour", 12+ 7); line(player, BLUE + "Maybe the generals will know where to get some.", 13+ 7); } break; From eafa5f42aba29ee115b6a6e06ead5b06600bba2b Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Sat, 24 May 2025 03:36:22 +0000 Subject: [PATCH 027/117] Implemented Lletya Fruit Tree Patch Implemented Harmony Island Allotment Patch. Patch is still unreachable without The Great Brain Robbery --- Server/src/main/content/global/skill/farming/FarmingPatch.kt | 4 ++++ .../main/content/global/skill/farming/UseWithPatchHandler.kt | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Server/src/main/content/global/skill/farming/FarmingPatch.kt b/Server/src/main/content/global/skill/farming/FarmingPatch.kt index ed033d253..6b3a1814f 100644 --- a/Server/src/main/content/global/skill/farming/FarmingPatch.kt +++ b/Server/src/main/content/global/skill/farming/FarmingPatch.kt @@ -94,6 +94,10 @@ enum class FarmingPatch(val varbit: Int, val type: PatchType) { patchNodes.addAll(8382..8383)//spirit trees patchNodes.add(8338) //spirit tree patchNodes.add(18816) //death plateau wrapper + patchNodes.add(21950) //harmony island allotment + patchNodes.add(28919) //lletya fruit patch. Was usable in 2009: https://www.youtube.com/watch?v=7sXOW4CRZ3k + //patchNodes.add(37988) Wilderness flower patch. Can only plant Limpwurt Seeds. Intentionally left out, as it is a reward from Spirit of Summer. + for (patch in patchNodes) { val def = SceneryDefinition.forId(patch) diff --git a/Server/src/main/content/global/skill/farming/UseWithPatchHandler.kt b/Server/src/main/content/global/skill/farming/UseWithPatchHandler.kt index 6dac10dce..d24e5252d 100644 --- a/Server/src/main/content/global/skill/farming/UseWithPatchHandler.kt +++ b/Server/src/main/content/global/skill/farming/UseWithPatchHandler.kt @@ -255,6 +255,10 @@ class UseWithPatchHandler : InteractionListener { return@onUseWith true } + /*if (patch == FarmingPatch.WILDERNESS_FLOWER && plantable != Plantable.LIMPWURT_SEED){ + return@onUseWith true + }*/ + val requiredItem = when (patch.type) { PatchType.TREE_PATCH, PatchType.FRUIT_TREE_PATCH -> Items.SPADE_952 PatchType.FLOWER_PATCH -> if (plantable == Plantable.SCARECROW) null else Items.SEED_DIBBER_5343 From 4fb3ae45d8339f38fadb4a05eb4b7665c52b1610 Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Sat, 24 May 2025 03:37:34 +0000 Subject: [PATCH 028/117] Fixed dark beast dropping big bones Fixed too high health for leech --- Server/data/configs/drop_tables.json | 22 +--------------------- Server/data/configs/npc_configs.json | 2 +- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/Server/data/configs/drop_tables.json b/Server/data/configs/drop_tables.json index 00bceefe8..076cf6b80 100644 --- a/Server/data/configs/drop_tables.json +++ b/Server/data/configs/drop_tables.json @@ -31660,7 +31660,7 @@ { "minAmount": "1", "weight": "100.0", - "id": "532", + "id": "526", "maxAmount": "1" } ], @@ -31696,26 +31696,6 @@ "maxAmount": "1" } ], - "tertiary": [ - { - "minAmount": "1", - "weight": "9973.0", - "id": "0", - "maxAmount": "1" - }, - { - "minAmount": "1", - "weight": "25.0", - "id": "10976", - "maxAmount": "1" - }, - { - "minAmount": "1", - "weight": "2.0", - "id": "10977", - "maxAmount": "1" - } - ], "ids": "2783", "description": "", "main": [ diff --git a/Server/data/configs/npc_configs.json b/Server/data/configs/npc_configs.json index a3a5c43fe..f183ea23e 100644 --- a/Server/data/configs/npc_configs.json +++ b/Server/data/configs/npc_configs.json @@ -23388,7 +23388,7 @@ "name": "Abyssal leech", "defence_level": "52", "safespot": null, - "lifepoints": "74", + "lifepoints": "10", "strength_level": "1", "id": "2263", "aggressive": "true", From 5a1c7cb141b9d96523929d958cc357f38b14c629 Mon Sep 17 00:00:00 2001 From: F V Date: Fri, 23 May 2025 21:38:14 -0600 Subject: [PATCH 029/117] Fixed bug where the front door to Lord Handlemort's mansion in Ardougne remains locked even after obtaining totem --- .../region/karamja/quest/tribaltotem/TribalTotemListeners.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Server/src/main/content/region/karamja/quest/tribaltotem/TribalTotemListeners.kt b/Server/src/main/content/region/karamja/quest/tribaltotem/TribalTotemListeners.kt index bf48bc464..0fdbf616f 100644 --- a/Server/src/main/content/region/karamja/quest/tribaltotem/TribalTotemListeners.kt +++ b/Server/src/main/content/region/karamja/quest/tribaltotem/TribalTotemListeners.kt @@ -26,7 +26,9 @@ class TribalTotemListeners : InteractionListener { if(player.questRepository.getStage(Quests.TRIBAL_TOTEM) >= 35){ core.game.global.action.DoorActionHandler.handleAutowalkDoor(player,door.asScenery()) } - sendMessage(player,"The door is locked shut.") + else { + sendMessage(player,"The door is locked shut.") + } return@on true } @@ -97,6 +99,7 @@ class TribalTotemListeners : InteractionListener { if(!player.inventory.containsAtLeastOneItem(Items.TOTEM_1857)){ sendDialogue(player,"Inside the chest you find the tribal totem.") addItemOrDrop(player,Items.TOTEM_1857) + player.questRepository.getQuest(Quests.TRIBAL_TOTEM).setStage(player,35) } else{ sendDialogue(player,"Inside the chest you don't find anything because you already took the totem!") From eebbebe772d71e97375956fbffb13baf02541335 Mon Sep 17 00:00:00 2001 From: Player Name Date: Wed, 18 Jun 2025 13:28:21 +0000 Subject: [PATCH 030/117] Corrected Sumona's red dragon assignment Removed inauthentic aviansie assignments Fixed GWD beacon not being repairable Surprise exam is no longer optional Fixed level checks for All Fired Up beacon repair --- .../main/content/global/ame/RandomEventNPC.kt | 2 +- .../main/content/global/ame/RandomEvents.kt | 2 +- .../surpriseexam/MysteriousOldManDialogue.kt | 41 --- .../surpriseexam/MysteriousOldManNPC.kt | 45 ++-- .../events/surpriseexam/SurpriseExamUtils.kt | 4 - .../content/global/skill/slayer/Master.java | 242 +++++++++--------- .../allfiredup/AFURepairClimbHandler.kt | 3 +- 7 files changed, 151 insertions(+), 188 deletions(-) delete mode 100644 Server/src/main/content/global/ame/events/surpriseexam/MysteriousOldManDialogue.kt diff --git a/Server/src/main/content/global/ame/RandomEventNPC.kt b/Server/src/main/content/global/ame/RandomEventNPC.kt index 716420e25..b03a075f9 100644 --- a/Server/src/main/content/global/ame/RandomEventNPC.kt +++ b/Server/src/main/content/global/ame/RandomEventNPC.kt @@ -1,6 +1,6 @@ package content.global.ame -import content.global.ame.events.MysteriousOldManNPC +import content.global.ame.events.surpriseexam.MysteriousOldManNPC import core.api.playGlobalAudio import core.api.poofClear import core.api.sendMessage diff --git a/Server/src/main/content/global/ame/RandomEvents.kt b/Server/src/main/content/global/ame/RandomEvents.kt index 49bac5151..4acf90c99 100644 --- a/Server/src/main/content/global/ame/RandomEvents.kt +++ b/Server/src/main/content/global/ame/RandomEvents.kt @@ -1,7 +1,7 @@ package content.global.ame import org.rs09.consts.Items -import content.global.ame.events.MysteriousOldManNPC +import content.global.ame.events.surpriseexam.MysteriousOldManNPC import content.global.ame.events.certer.CerterNPC import content.global.ame.events.drilldemon.SergeantDamienNPC import content.global.ame.events.drunkendwarf.DrunkenDwarfNPC diff --git a/Server/src/main/content/global/ame/events/surpriseexam/MysteriousOldManDialogue.kt b/Server/src/main/content/global/ame/events/surpriseexam/MysteriousOldManDialogue.kt deleted file mode 100644 index 1415317a6..000000000 --- a/Server/src/main/content/global/ame/events/surpriseexam/MysteriousOldManDialogue.kt +++ /dev/null @@ -1,41 +0,0 @@ -package content.global.ame.events - -import core.game.node.entity.player.Player -import content.global.ame.events.surpriseexam.SurpriseExamUtils -import core.game.dialogue.DialogueFile -import core.game.system.timer.impl.AntiMacro - -class MysteriousOldManDialogue(val type: String) : DialogueFile() { - - val CHOICE_STAGE = 50000 - - override fun handle(componentID: Int, buttonID: Int) { - - if(type == "sexam" && stage < CHOICE_STAGE){ - npc("Would you like to come do a surprise exam?") - stage = CHOICE_STAGE - } - - else if(stage >= CHOICE_STAGE){ - when(stage) { - CHOICE_STAGE -> options("Yeah, sure!", "No, thanks.").also { stage++ } - CHOICE_STAGE.substage(1) -> when(buttonID){ - 1 -> { - end() - teleport(player!!,type) - AntiMacro.terminateEventNpc(player!!) - } - 2 -> { - end() - AntiMacro.terminateEventNpc(player!!) - } - } - } - } - } - fun teleport(player: Player,type: String){ - when(type){ - "sexam" -> SurpriseExamUtils.teleport(player) - } - } -} \ No newline at end of file diff --git a/Server/src/main/content/global/ame/events/surpriseexam/MysteriousOldManNPC.kt b/Server/src/main/content/global/ame/events/surpriseexam/MysteriousOldManNPC.kt index 3ed7289b1..04818dda8 100644 --- a/Server/src/main/content/global/ame/events/surpriseexam/MysteriousOldManNPC.kt +++ b/Server/src/main/content/global/ame/events/surpriseexam/MysteriousOldManNPC.kt @@ -1,31 +1,40 @@ -package content.global.ame.events +package content.global.ame.events.surpriseexam import content.global.ame.RandomEventNPC -import core.game.node.entity.npc.NPC -import core.tools.RandomFunction +import content.global.ame.kidnapPlayer +import core.api.* import org.rs09.consts.NPCs import core.api.utils.WeightBasedTable +import core.game.interaction.QueueStrength +import core.game.node.entity.npc.NPC +import core.game.node.entity.player.link.TeleportManager +import core.game.world.map.Location +import core.game.world.update.flag.context.Graphics +import org.rs09.consts.Sounds class MysteriousOldManNPC(var type: String = "", override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.MYSTERIOUS_OLD_MAN_410) { override fun init() { super.init() - sayLine() - } - - override fun tick() { - super.tick() - if(RandomFunction.random(1,10) == 5) sayLine() - } - - fun sayLine() { - when(type){ - "sexam" -> sendChat("Surprise exam, ${player.username.capitalize()}!") + sendChat("Surprise exam, ${player.username}!") + queueScript(player, 4, QueueStrength.SOFT) { stage: Int -> + when (stage) { + 0 -> { + lock(player, 6) + sendGraphics(Graphics(1576, 0, 0), player.location) + animate(player,8939) + playAudio(player, Sounds.TELEPORT_ALL_200) + return@queueScript delayScript(player, 3) + } + 1 -> { + kidnapPlayer(player, Location(1886, 5025, 0), TeleportManager.TeleportType.INSTANT) + return@queueScript stopExecuting(player) + } + else -> return@queueScript stopExecuting(player) + } } } override fun talkTo(npc: NPC) { - when(type){ - "sexam" -> player.dialogueInterpreter.open(MysteriousOldManDialogue("sexam"),this.asNpc()) - } + sendMessage(player, "He isn't interested in talking to you.") } -} \ No newline at end of file +} diff --git a/Server/src/main/content/global/ame/events/surpriseexam/SurpriseExamUtils.kt b/Server/src/main/content/global/ame/events/surpriseexam/SurpriseExamUtils.kt index 01a722343..4f1530eeb 100644 --- a/Server/src/main/content/global/ame/events/surpriseexam/SurpriseExamUtils.kt +++ b/Server/src/main/content/global/ame/events/surpriseexam/SurpriseExamUtils.kt @@ -26,10 +26,6 @@ object SurpriseExamUtils { intArrayOf(Items.FLY_FISHING_ROD_309,Items.BARBARIAN_ROD_11323,Items.SMALL_FISHING_NET_303,Items.HARPOON_311) ) - fun teleport(player: Player) { - kidnapPlayer(player, Location.create(1886, 5025, 0), TeleportManager.TeleportType.INSTANT) - } - fun cleanup(player: Player){ returnPlayer(player) removeAttributes(player, SE_KEY_INDEX, SE_KEY_CORRECT) diff --git a/Server/src/main/content/global/skill/slayer/Master.java b/Server/src/main/content/global/skill/slayer/Master.java index aad0fa5e4..0ce93eb54 100644 --- a/Server/src/main/content/global/skill/slayer/Master.java +++ b/Server/src/main/content/global/skill/slayer/Master.java @@ -24,7 +24,7 @@ public enum Master { new Task(Tasks.CAVE_BUG, 8), new Task(Tasks.CAVE_CRAWLERS, 8), new Task(Tasks.CAVE_SLIMES, 8), - new Task(Tasks.COWS, 8), + new Task(Tasks.COWS, 8), new Task(Tasks.CRAWLING_HAND, 8), new Task(Tasks.DESERT_LIZARDS, 8), new Task(Tasks.DOG, 7), @@ -37,144 +37,143 @@ public enum Master { new Task(Tasks.MONKEYS, 6), new Task(Tasks.SCORPIONS, 7), new Task(Tasks.SKELETONS, 7), - new Task(Tasks.SPIDERS, 6), + new Task(Tasks.SPIDERS, 6), new Task(Tasks.WOLVES, 7), new Task(Tasks.ZOMBIES, 7)), MAZCHNA(8274, 20, 0, new int[]{40, 70}, new int[]{2, 5, 15}, new Task(Tasks.BANSHEE, 8), - new Task(Tasks.BATS,7), - new Task(Tasks.BEARS,6), - new Task(Tasks.CATABLEPONS,8), - new Task(Tasks.CAVE_BUG,8), + new Task(Tasks.BATS, 7), + new Task(Tasks.BEARS, 6), + new Task(Tasks.CATABLEPONS, 8), + new Task(Tasks.CAVE_BUG, 8), new Task(Tasks.CAVE_CRAWLERS, 8), new Task(Tasks.CAVE_SLIMES, 8), new Task(Tasks.COCKATRICES, 8), new Task(Tasks.CRAWLING_HAND, 8), new Task(Tasks.DESERT_LIZARDS, 8), - new Task(Tasks.DOG,7), - new Task(Tasks.EARTH_WARRIORS,6), - new Task(Tasks.FLESH_CRAWLERS,7), - new Task(Tasks.GHOSTS,7), + new Task(Tasks.DOG, 7), + new Task(Tasks.EARTH_WARRIORS, 6), + new Task(Tasks.FLESH_CRAWLERS, 7), + new Task(Tasks.GHOSTS, 7), new Task(Tasks.GHOULS, 7), - new Task(Tasks.HILL_GIANTS,7), + new Task(Tasks.HILL_GIANTS, 7), new Task(Tasks.HOBGOBLINS, 7), new Task(Tasks.ICE_WARRIOR, 7), - new Task(Tasks.KALPHITES,6), + new Task(Tasks.KALPHITES, 6), new Task(Tasks.MOGRES, 8), new Task(Tasks.PYREFIENDS, 8), - new Task(Tasks.ROCK_SLUGS,8), - new Task(Tasks.SHADE,8), - new Task(Tasks.SKELETONS, 7), + new Task(Tasks.ROCK_SLUGS, 8), + new Task(Tasks.SHADE, 8), + new Task(Tasks.SKELETONS, 7), new Task(Tasks.VAMPIRES, 6), - // new Task(Tasks.WALL_BEASTS,7), + // new Task(Tasks.WALL_BEASTS, 7), new Task(Tasks.WOLVES, 7), - new Task(Tasks.ZOMBIES,7)), + new Task(Tasks.ZOMBIES, 7)), VANNAKA(1597, 40, 0, new int[]{60, 120}, new int[]{4, 20, 60}, new Task(Tasks.ABERRANT_SPECTRES, 8), - new Task(Tasks.ANKOU,7), - new Task(Tasks.BANSHEE,6), - new Task(Tasks.BASILISKS,8), - new Task(Tasks.BLUE_DRAGONS,7), - new Task(Tasks.BLOODVELDS,8), - new Task(Tasks.BRINE_RATS,7), - new Task(Tasks.CAVE_BUG,7), - new Task(Tasks.CAVE_CRAWLERS,7), - new Task(Tasks.CAVE_SLIMES,7), - new Task(Tasks.COCKATRICES,8), - new Task(Tasks.CRAWLING_HAND,6), - new Task(Tasks.CROCODILES,6, new Integer[]{30, 60}), + new Task(Tasks.ANKOU, 7), + new Task(Tasks.BANSHEE, 6), + new Task(Tasks.BASILISKS, 8), + new Task(Tasks.BLUE_DRAGONS, 7), + new Task(Tasks.BLOODVELDS, 8), + new Task(Tasks.BRINE_RATS, 7), + new Task(Tasks.CAVE_BUG, 7), + new Task(Tasks.CAVE_CRAWLERS, 7), + new Task(Tasks.CAVE_SLIMES, 7), + new Task(Tasks.COCKATRICES, 8), + new Task(Tasks.CRAWLING_HAND, 6), + new Task(Tasks.CROCODILES, 6, new Integer[]{30, 60}), new Task(Tasks.DAGANNOTHS, 7), - new Task(Tasks.DESERT_LIZARDS,7, new Integer[]{30, 60}), - new Task(Tasks.DUST_DEVILS,8), - new Task(Tasks.EARTH_WARRIORS,6, new Integer[]{30, 60}), + new Task(Tasks.DESERT_LIZARDS, 7, new Integer[]{30, 60}), + new Task(Tasks.DUST_DEVILS, 8), + new Task(Tasks.EARTH_WARRIORS, 6, new Integer[]{30, 60}), new Task(Tasks.ELVES, 7, new Integer[]{30, 60}), - //new Task(Tasks.FEVER_SPIDERS,7), - new Task(Tasks.FIRE_GIANTS,7), - new Task(Tasks.GHOULS,7), - new Task(Tasks.GREEN_DRAGONS,6, new Integer[]{30, 60}), - new Task(Tasks.HARPIE_BUG_SWARMS,8), - new Task(Tasks.HELLHOUNDS,7), - new Task(Tasks.HILL_GIANTS,7), - new Task(Tasks.ICE_GIANTS,7, new Integer[]{30, 60}), - new Task(Tasks.ICE_WARRIOR,7), - new Task(Tasks.INFERNAL_MAGES,8), - new Task(Tasks.JELLIES,8), + //new Task(Tasks.FEVER_SPIDERS, 7), + new Task(Tasks.FIRE_GIANTS, 7), + new Task(Tasks.GHOULS, 7), + new Task(Tasks.GREEN_DRAGONS, 6, new Integer[]{30, 60}), + new Task(Tasks.HARPIE_BUG_SWARMS, 8), + new Task(Tasks.HELLHOUNDS, 7), + new Task(Tasks.HILL_GIANTS, 7), + new Task(Tasks.ICE_GIANTS, 7, new Integer[]{30, 60}), + new Task(Tasks.ICE_WARRIOR, 7), + new Task(Tasks.INFERNAL_MAGES, 8), + new Task(Tasks.JELLIES, 8), new Task(Tasks.JUNGLE_HORRORS, 8), - new Task(Tasks.KALPHITES,7), - // new Task(Tasks.KILLERWATTS,6), - new Task(Tasks.KURASKS,7), - new Task(Tasks.LESSER_DEMONS,7), - new Task(Tasks.MOGRES,7), - // new Task(Tasks.MOLANISKS,7), - new Task(Tasks.MOSS_GIANTS,7), - new Task(Tasks.OGRES,7), - new Task(Tasks.OTHERWORDLY_BEING,8), - new Task(Tasks.PYREFIENDS,8), - new Task(Tasks.ROCK_SLUGS,7), - // new Task(Tasks.SEA_SNAKES,6), - new Task(Tasks.SHADE,8), + new Task(Tasks.KALPHITES, 7), + // new Task(Tasks.KILLERWATTS, 6), + new Task(Tasks.KURASKS, 7), + new Task(Tasks.LESSER_DEMONS, 7), + new Task(Tasks.MOGRES, 7), + // new Task(Tasks.MOLANISKS, 7), + new Task(Tasks.MOSS_GIANTS, 7), + new Task(Tasks.OGRES, 7), + new Task(Tasks.OTHERWORDLY_BEING, 8), + new Task(Tasks.PYREFIENDS, 8), + new Task(Tasks.ROCK_SLUGS, 7), + // new Task(Tasks.SEA_SNAKES, 6), + new Task(Tasks.SHADE, 8), // new Task(Tasks.SHADOW_WARRIORS, 8), - new Task(Tasks.TROLLS,7), + new Task(Tasks.TROLLS, 7), new Task(Tasks.TUROTHS, 8), - new Task(Tasks.VAMPIRES,7), - // new Task(Tasks.WALL_BEASTS,6), - new Task(Tasks.WEREWOLVES,7)), + new Task(Tasks.VAMPIRES, 7), + // new Task(Tasks.WALL_BEASTS, 6, , new Integer[]{10, 20}), + new Task(Tasks.WEREWOLVES, 7)), CHAELDAR(1598, 70, 0, new int[]{110, 170}, new int[]{10, 50, 150}, - new Task(Tasks.ABERRANT_SPECTRES,8), - new Task(Tasks.ABYSSAL_DEMONS,12), + new Task(Tasks.ABERRANT_SPECTRES, 8), + new Task(Tasks.ABYSSAL_DEMONS, 12), new Task(Tasks.BANSHEE, 5), - new Task(Tasks.BASILISKS,7), - new Task(Tasks.BLUE_DRAGONS,8), - new Task(Tasks.BLOODVELDS,8), - new Task(Tasks.BRINE_RATS,7), - new Task(Tasks.BRONZE_DRAGONS,11, new Integer[]{30, 60}), + new Task(Tasks.BASILISKS, 7), + new Task(Tasks.BLUE_DRAGONS, 8), + new Task(Tasks.BLOODVELDS, 8), + new Task(Tasks.BRINE_RATS, 7), + new Task(Tasks.BRONZE_DRAGONS, 11, new Integer[]{30, 60}), new Task(Tasks.CAVE_BUG, 5), - new Task(Tasks.CAVE_CRAWLERS, 5), - new Task(Tasks.CAVE_HORRORS,10), - new Task(Tasks.CAVE_SLIMES,6), - new Task(Tasks.COCKATRICES,6), + new Task(Tasks.CAVE_CRAWLERS, 5), + new Task(Tasks.CAVE_HORRORS, 10), + new Task(Tasks.CAVE_SLIMES, 6), + new Task(Tasks.COCKATRICES, 6), new Task(Tasks.CRAWLING_HAND, 5), new Task(Tasks.CROCODILES, 5, new Integer[]{30, 60}), - new Task(Tasks.DAGANNOTHS,11), + new Task(Tasks.DAGANNOTHS, 11), new Task(Tasks.DESERT_LIZARDS, 5, new Integer[]{30, 60}), - new Task(Tasks.DUST_DEVILS,9), - new Task(Tasks.ELVES,8, new Integer[]{60, 90}), - //new Task(Tasks.FEVER_SPIDERS,7), + new Task(Tasks.DUST_DEVILS, 9), + new Task(Tasks.ELVES, 8, new Integer[]{60, 90}), + //new Task(Tasks.FEVER_SPIDERS, 7), new Task(Tasks.FIRE_GIANTS, 12), - new Task(Tasks.GARGOYLES,11), - new Task(Tasks.GREATER_DEMONS,9), - new Task(Tasks.HARPIE_BUG_SWARMS,6), - new Task(Tasks.HELLHOUNDS,9), - new Task(Tasks.IRON_DRAGONS,12, new Integer[]{30, 60}), - new Task(Tasks.INFERNAL_MAGES,7), + new Task(Tasks.GARGOYLES, 11), + new Task(Tasks.GREATER_DEMONS, 9), + new Task(Tasks.HARPIE_BUG_SWARMS, 6), + new Task(Tasks.HELLHOUNDS, 9), + new Task(Tasks.IRON_DRAGONS, 12, new Integer[]{30, 60}), + new Task(Tasks.INFERNAL_MAGES, 7), new Task(Tasks.JELLIES, 10), - new Task(Tasks.JUNGLE_HORRORS,10), - new Task(Tasks.KALPHITES,11), + new Task(Tasks.JUNGLE_HORRORS, 10), + new Task(Tasks.KALPHITES, 11), new Task(Tasks.KURASKS, 12), - new Task(Tasks.LESSER_DEMONS,9), - new Task(Tasks.MOGRES,6), - // new Task(Tasks.MOLANISKS,6), - //new Task(Tasks.MUTATED_ZYGOMITES,7, new Integer[]{30, 60}), + new Task(Tasks.LESSER_DEMONS, 9), + new Task(Tasks.MOGRES, 6), + // new Task(Tasks.MOLANISKS, 6), + //new Task(Tasks.MUTATED_ZYGOMITES, 7, new Integer[]{30, 60}), new Task(Tasks.NECHRYAELS, 12), - new Task(Tasks.PYREFIENDS,6), + new Task(Tasks.PYREFIENDS, 6), new Task(Tasks.ROCK_SLUGS, 5), - // new Task(Tasks.SHADOW_WARRIORS,8), - new Task(Tasks.SPIRTUAL_WARRIORS,4), - new Task(Tasks.SPIRTUAL_RANGERS,4), - new Task(Tasks.SPIRTUAL_MAGES,4), - new Task(Tasks.TROLLS,11), + // new Task(Tasks.SHADOW_WARRIORS, 8), + new Task(Tasks.SPIRTUAL_WARRIORS, 4), + new Task(Tasks.SPIRTUAL_RANGERS, 4), + new Task(Tasks.SPIRTUAL_MAGES, 4), + new Task(Tasks.TROLLS, 11), new Task(Tasks.TUROTHS, 10)), - // new Task(Tasks.WALL_BEASTS,6, new Integer[]{10, 20}), + // new Task(Tasks.WALL_BEASTS, 6, new Integer[]{10, 20}), // new Task(Tasks.WARPED_TERROR_BIRD, 5), // new Task(Tasks.WARPED_TORTOISE, 5) SUMONA(7780, 85, 35, new int[]{120, 185}, new int[]{12, 60, 180}, new Task(Tasks.ABERRANT_SPECTRES, 15), new Task(Tasks.ABYSSAL_DEMONS, 10), - new Task(Tasks.AVIANSIES, 10), new Task(Tasks.BANSHEE, 15), new Task(Tasks.BASILISKS, 15), new Task(Tasks.BLACK_DEMONS, 10), @@ -193,7 +192,7 @@ public enum Master { new Task(Tasks.KALPHITES, 10), new Task(Tasks.KURASKS, 15), new Task(Tasks.NECHRYAELS, 10), - new Task(Tasks.RED_DRAGONS, 5), + new Task(Tasks.RED_DRAGONS, 5, new Integer[]{30, 60}), // new Task(Tasks.SCABARITES, 9, new Integer[]{30, 60}), new Task(Tasks.SPIRTUAL_MAGES, 10), new Task(Tasks.SPIRTUAL_WARRIORS, 10), @@ -203,41 +202,40 @@ public enum Master { // new Task(Tasks.WARPED_TORTOISE, 15)), DURADEL(8275, 100, 50, new int[]{130, 200}, new int[]{15, 75, 225}, - new Task(Tasks.ABERRANT_SPECTRES,7), - new Task(Tasks.ABYSSAL_DEMONS,12), - new Task(Tasks.AVIANSIES, 10), - new Task(Tasks.BLACK_DEMONS,8), - new Task(Tasks.BLACK_DRAGONS,9, new Integer[]{40, 80}), - new Task(Tasks.BLOODVELDS,8), - new Task(Tasks.DAGANNOTHS,9), - new Task(Tasks.DARK_BEASTS,11), - new Task(Tasks.DUST_DEVILS,5), - new Task(Tasks.FIRE_GIANTS,7), - new Task(Tasks.GARGOYLES,8), - new Task(Tasks.GORAKS,9), - new Task(Tasks.GREATER_DEMONS,9), + new Task(Tasks.ABERRANT_SPECTRES, 7), + new Task(Tasks.ABYSSAL_DEMONS, 12), + new Task(Tasks.BLACK_DEMONS, 8), + new Task(Tasks.BLACK_DRAGONS, 9, new Integer[]{40, 80}), + new Task(Tasks.BLOODVELDS, 8), + new Task(Tasks.DAGANNOTHS, 9), + new Task(Tasks.DARK_BEASTS, 11), + new Task(Tasks.DUST_DEVILS, 5), + new Task(Tasks.FIRE_GIANTS, 7), + new Task(Tasks.GARGOYLES, 8), + new Task(Tasks.GORAKS, 9, new Integer[]{40, 80}), + new Task(Tasks.GREATER_DEMONS, 9), new Task(Tasks.HELLHOUNDS, 10), - new Task(Tasks.IRON_DRAGONS,5, new Integer[]{40, 80}), - new Task(Tasks.KALPHITES,9), - new Task(Tasks.MITHRIL_DRAGONS,9, new Integer[]{4, 8}), - new Task(Tasks.NECHRYAELS,9), + new Task(Tasks.IRON_DRAGONS, 5, new Integer[]{40, 80}), + new Task(Tasks.KALPHITES, 9), + new Task(Tasks.MITHRIL_DRAGONS, 9, new Integer[]{4, 8}), + new Task(Tasks.NECHRYAELS, 9), // new Task(Tasks.SCABARITES, 9, new Integer[]{40, 80}), - new Task(Tasks.SKELETAL_WYVERN,7, new Integer[]{40, 80}), - new Task(Tasks.SPIRTUAL_MAGES,2), - new Task(Tasks.STEEL_DRAGONS,7, new Integer[]{40, 80}), - new Task(Tasks.SUQAHS,8, new Integer[]{40, 80}), - // new Task(Tasks.WARPED_TERROR_BIRD,8), - new Task(Tasks.WATERFIENDS,2)); + new Task(Tasks.SKELETAL_WYVERN, 7, new Integer[]{40, 80}), + new Task(Tasks.SPIRTUAL_MAGES, 2), + new Task(Tasks.STEEL_DRAGONS, 7, new Integer[]{40, 80}), + new Task(Tasks.SUQAHS, 8, new Integer[]{40, 80}), + // new Task(Tasks.WARPED_TERROR_BIRD, 8), + new Task(Tasks.WATERFIENDS, 2)); - private static final HashMap idMap = new HashMap<>(); + private static final HashMap idMap = new HashMap<>(); static{ Arrays.stream(Master.values()).forEach(m -> idMap.putIfAbsent(m.npc_id, m)); } final int npc_id; - final int required_combat; - final int required_slayer; + final int required_combat; + final int required_slayer; public final int[] default_assignment_range; final int[] streakPoints; public final List tasks; diff --git a/Server/src/main/content/minigame/allfiredup/AFURepairClimbHandler.kt b/Server/src/main/content/minigame/allfiredup/AFURepairClimbHandler.kt index f95dfb1d9..03e5c45f1 100644 --- a/Server/src/main/content/minigame/allfiredup/AFURepairClimbHandler.kt +++ b/Server/src/main/content/minigame/allfiredup/AFURepairClimbHandler.kt @@ -49,7 +49,7 @@ class AFURepairClimbHandler : InteractionListener { ent.destinationUp?.withinDistance(player.location,2) == true){ return ent } - return null + return RepairClimbObject.GWD //the only one that does not have down/up destinations } private fun repair(player: Player,rco: RepairClimbObject){ @@ -95,6 +95,7 @@ class AFURepairClimbHandler : InteractionListener { val level = rco.levelRequirement?.second ?: 0 if(player.skills.getLevel(skill) < level){ player.dialogueInterpreter.sendDialogue("You need level $level ${Skills.SKILL_NAME[skill]} for this.") + return } var requiresNeedle = false From 91f3a70f75f2311fe21ea297b70b724c2e320fc5 Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Wed, 18 Jun 2025 13:29:12 +0000 Subject: [PATCH 031/117] The Ancient Cavern Canoe can now be used without the screen permanently fading to black until logging out --- .../main/content/global/skill/slayer/dungeon/AncientCavern.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/src/main/content/global/skill/slayer/dungeon/AncientCavern.java b/Server/src/main/content/global/skill/slayer/dungeon/AncientCavern.java index 7e8e55cd4..f86de623f 100644 --- a/Server/src/main/content/global/skill/slayer/dungeon/AncientCavern.java +++ b/Server/src/main/content/global/skill/slayer/dungeon/AncientCavern.java @@ -152,7 +152,7 @@ public final class AncientCavern extends MapZone implements Plugin { } }); - player.getInterfaceManager().open(c); + player.getInterfaceManager().openOverlay(c); break; case 3: PacketRepository.send(MinimapState.class, new MinimapStateContext(player, 2)); From e8c3f3178690873af5c5797ea8f36d651db36c1b Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Wed, 18 Jun 2025 13:35:50 +0000 Subject: [PATCH 032/117] Farming improvements White Berry Bushes can now be protected Giant Ent properly increases Belladonna yield Mushrooms now disease properly Fixed various bush bugs Fruit Trees can now be chopped Fixed Scarecrow needing to grow to work Mushrooms should now visually update as each mushroom is picked from the patch Poison Ivy Bushes are now disease immune Poison Ivy Berries picked in the Champion's Guild Patch now finishes a Varrock Diary task --- .../global/skill/farming/CropHarvester.kt | 17 ++++-- .../skill/farming/DigUpPatchDialogue.kt | 8 +++ .../skill/farming/FruitAndBerryPicker.kt | 5 ++ .../global/skill/farming/FruitTreeChopper.kt | 57 ++++++++++++++++++ .../content/global/skill/farming/Patch.kt | 59 +++++++++++++++---- .../content/global/skill/farming/Plantable.kt | 8 +-- .../skill/farming/UseWithPatchHandler.kt | 2 +- .../global/skill/farming/timers/CropGrowth.kt | 4 +- 8 files changed, 137 insertions(+), 23 deletions(-) create mode 100644 Server/src/main/content/global/skill/farming/FruitTreeChopper.kt diff --git a/Server/src/main/content/global/skill/farming/CropHarvester.kt b/Server/src/main/content/global/skill/farming/CropHarvester.kt index 1ba3dd426..e47820bb3 100644 --- a/Server/src/main/content/global/skill/farming/CropHarvester.kt +++ b/Server/src/main/content/global/skill/farming/CropHarvester.kt @@ -15,7 +15,7 @@ import core.plugin.Plugin import org.rs09.consts.Items import org.rs09.consts.Sounds -val livesBased = arrayOf(PatchType.HERB_PATCH, PatchType.CACTUS_PATCH, PatchType.BELLADONNA_PATCH, PatchType.HOPS_PATCH, PatchType.ALLOTMENT, PatchType.EVIL_TURNIP_PATCH) +val livesBased = arrayOf(PatchType.HERB_PATCH, PatchType.CACTUS_PATCH, PatchType.HOPS_PATCH, PatchType.ALLOTMENT) @Initializable class CropHarvester : OptionHandler() { @@ -43,14 +43,16 @@ class CropHarvester : OptionHandler() { override fun pulse(): Boolean { var reward = Item(crop) - val familiar = player.familiarManager.familiar - if (familiar != null && familiar is GiantEntNPC) { - familiar.modifyFarmingReward(fPatch, reward) - } if (!hasSpaceFor(player, reward)) { sendMessage(player, "You have run out of inventory space.") return true } + + val familiar = player.familiarManager.familiar + if (familiar != null && familiar is GiantEntNPC) { + familiar.modifyFarmingReward(fPatch, reward) + } + var requiredItem = when (fPatch.type) { PatchType.TREE_PATCH -> Items.SECATEURS_5329 else -> Items.SPADE_952 @@ -92,7 +94,7 @@ class CropHarvester : OptionHandler() { // TODO: If a flower patch is being harvested, delay the clearing of the // patch until after the animation has played - https://youtu.be/lg4GktlVNUY?t=75 delay = 2 - addItem(player, reward.id) + addItemOrDrop(player, reward.id,reward.amount) rewardXP(player, Skills.FARMING, plantable.harvestXP) if (patch.patch.type in livesBased) { patch.rollLivesDecrement( @@ -104,6 +106,9 @@ class CropHarvester : OptionHandler() { if (patch.harvestAmt <= 0 && crop == plantable.harvestItem) { patch.clear() } + else if (fPatch.type == PatchType.MUSHROOM_PATCH){ + patch.setCurrentState(patch.getCurrentState() + 1) + } } if (sendHarvestMessages && (patch.cropLives <= 0 || patch.harvestAmt <= 0)) { sendMessage(player, "The $patchName is now empty.") diff --git a/Server/src/main/content/global/skill/farming/DigUpPatchDialogue.kt b/Server/src/main/content/global/skill/farming/DigUpPatchDialogue.kt index a0d70bb07..ab170aa74 100644 --- a/Server/src/main/content/global/skill/farming/DigUpPatchDialogue.kt +++ b/Server/src/main/content/global/skill/farming/DigUpPatchDialogue.kt @@ -30,6 +30,14 @@ class DigUpPatchDialogue(player: Player? = null) : DialoguePlugin(player) { return true } } + if (patch?.patch?.type == PatchType.FRUIT_TREE_PATCH) { + val isTreeStump = patch?.getCurrentState() == patch?.plantable!!.value + 25 + if (patch!!.isGrown() && !isTreeStump) { + sendMessage(player, "You need to chop this tree down first.") // this message is not authentic + stage = 1000 + return true + } + } sendDialogueOptions(player, "Are you sure you want to dig up this patch?", "Yes, I want to clear it for new crops.", "No, I want to leave it as it is.") stage = 0 return true diff --git a/Server/src/main/content/global/skill/farming/FruitAndBerryPicker.kt b/Server/src/main/content/global/skill/farming/FruitAndBerryPicker.kt index f07689362..50bba4a24 100644 --- a/Server/src/main/content/global/skill/farming/FruitAndBerryPicker.kt +++ b/Server/src/main/content/global/skill/farming/FruitAndBerryPicker.kt @@ -6,6 +6,7 @@ import core.game.interaction.OptionHandler import core.game.node.Node import content.global.skill.summoning.familiar.GiantEntNPC import core.game.node.entity.player.Player +import core.game.node.entity.player.link.diary.DiaryType import core.game.node.entity.skill.Skills import core.game.node.item.Item import core.game.system.task.Pulse @@ -86,6 +87,10 @@ class FruitAndBerryPicker : OptionHandler() { sendMessage(player, "You pick $determiner ${reward.name.lowercase()}.") } + if (plantable == Plantable.POISON_IVY_SEED && patch.patch == FarmingPatch.CHAMPIONS_GUILD_BUSH){ + player.achievementDiaryManager.finishTask(player, DiaryType.VARROCK, 2, 0) + } + return patch.getFruitOrBerryCount() == 0 } }) diff --git a/Server/src/main/content/global/skill/farming/FruitTreeChopper.kt b/Server/src/main/content/global/skill/farming/FruitTreeChopper.kt new file mode 100644 index 000000000..c335007f6 --- /dev/null +++ b/Server/src/main/content/global/skill/farming/FruitTreeChopper.kt @@ -0,0 +1,57 @@ +package content.global.skill.farming + +import content.data.skill.SkillingTool +import core.api.* +import core.cache.def.impl.SceneryDefinition +import core.game.interaction.OptionHandler +import core.game.node.Node +import core.game.node.entity.player.Player +import core.game.system.task.Pulse +import core.plugin.Initializable +import core.plugin.Plugin +import core.tools.RandomFunction +import org.rs09.consts.Sounds + +@Initializable +class FruitTreeChopper : OptionHandler() { + override fun newInstance(arg: Any?): Plugin { + SceneryDefinition.setOptionHandler("chop-down",this) + SceneryDefinition.setOptionHandler("chop down",this) + return this + } + + override fun handle(player: Player?, node: Node?, option: String?): Boolean { + player ?: return false + node ?: return false + + val fPatch = FarmingPatch.forObject(node.asScenery()) + fPatch ?: return false + + val patch = fPatch.getPatchFor(player) + + val plantable = patch.plantable + plantable ?: return false + + val animation = SkillingTool.getHatchet(player).animation + + submitIndividualPulse(player, object : Pulse(animation.duration) { + override fun pulse(): Boolean { + animate(player, animation) + val soundIndex = RandomFunction.random(0, woodcuttingSounds.size) + playAudio(player, woodcuttingSounds[soundIndex]) + patch.setCurrentState(patch.getCurrentState() + 19) + sendMessage(player, "You chop down the ${plantable.displayName.lowercase().removeSuffix(" sapling")}.") + return true + } + }) + return true + } + + private val woodcuttingSounds = intArrayOf( + Sounds.WOODCUTTING_HIT_3038, + Sounds.WOODCUTTING_HIT_3039, + Sounds.WOODCUTTING_HIT_3040, + Sounds.WOODCUTTING_HIT_3041, + Sounds.WOODCUTTING_HIT_3042 + ) +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/farming/Patch.kt b/Server/src/main/content/global/skill/farming/Patch.kt index 8965167db..f8ce4dfce 100644 --- a/Server/src/main/content/global/skill/farming/Patch.kt +++ b/Server/src/main/content/global/skill/farming/Patch.kt @@ -29,7 +29,11 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl Plantable.WILLOW_SAPLING -> 0 else -> 1 } - if(plantable != null && plantable?.applicablePatch != PatchType.FLOWER_PATCH) { + if(plantable != null + && plantable?.applicablePatch != PatchType.FLOWER_PATCH + && plantable?.applicablePatch != PatchType.BELLADONNA_PATCH + && plantable?.applicablePatch != PatchType.MUSHROOM_PATCH + && plantable?.applicablePatch != PatchType.EVIL_TURNIP_PATCH) { harvestAmt += compostMod } cropLives = 3 + compostMod @@ -70,8 +74,6 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl var chance = when(patch.type){ PatchType.ALLOTMENT -> 8 //average of 8 per life times 3 lives = average 24 PatchType.HOPS_PATCH -> 6 //average of 6 per life times 3 lives = 18 - PatchType.BELLADONNA_PATCH -> 2 //average of 2 per life times 3 lives = 6 - PatchType.EVIL_TURNIP_PATCH -> 2 //average 2 per, same as BELLADONNA PatchType.CACTUS_PATCH -> 3 //average of 3 per life times 3 lives = 9 else -> 0 // nothing should go here, but if it does, do not give extra crops amd decrement cropLives } @@ -88,6 +90,11 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl return getCurrentState() in 0..2 } + fun isChoppedFruitTree(): Boolean { + return (patch.type == PatchType.FRUIT_TREE_PATCH) + && getCurrentState() == (plantable?.value ?: 0) + 25 + } + fun isEmptyAndWeeded(): Boolean { return getCurrentState() == 3 } @@ -198,6 +205,10 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl else if(isDiseased && !isDead) setVisualState(getHerbDiseaseValue()) else setVisualState((plantable?.value ?: 0) + currentGrowthStage) } + PatchType.MUSHROOM_PATCH -> { + if(isDead) setVisualState(getMushroomDeathValue()) + else if(isDiseased && !isDead) setVisualState(getMushroomDiseaseValue()) + } else -> {} } } @@ -221,16 +232,27 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl private fun getBushDiseaseValue(): Int{ if(plantable == Plantable.POISON_IVY_SEED){ return (plantable?.value ?: 0) + currentGrowthStage + 12 - } else { + } + else if (plantable == Plantable.REDBERRY_SEED + || plantable == Plantable.CADAVABERRY_SEED){ + return (plantable?.value ?: 0) + currentGrowthStage + 65 + } + else { return (plantable?.value ?: 0) + currentGrowthStage + 64 } } private fun getBushDeathValue(): Int{ if(plantable == Plantable.POISON_IVY_SEED){ - return (plantable?.value ?: 0) + currentGrowthStage + 22 - } else { - return (plantable?.value ?: 0) + currentGrowthStage + 126 + return (plantable?.value ?: 0) + currentGrowthStage + 20 + } + else if (plantable == Plantable.REDBERRY_SEED + || plantable == Plantable.CADAVABERRY_SEED + || plantable == Plantable.WHITEBERRY_SEED){ + return (plantable?.value ?: 0) + currentGrowthStage + 129 + } + else { + return (plantable?.value ?: 0) + currentGrowthStage + 128 } } @@ -258,6 +280,14 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl return (plantable?.value ?: 0) + currentGrowthStage + 16 } + private fun getMushroomDiseaseValue(): Int { + return (plantable?.value ?: 0) + currentGrowthStage + 11 + } + + private fun getMushroomDeathValue(): Int { + return (plantable?.value ?: 0) + currentGrowthStage + 16 + } + private fun getHerbDiseaseValue(): Int { return if (plantable?.value ?: -1 <= 103) { 128 + (((plantable?.ordinal ?: 0) - Plantable.GUAM_SEED.ordinal) * 3) + currentGrowthStage - 1 @@ -275,7 +305,7 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl } private fun grow(){ - if((isWeedy() || isEmptyAndWeeded()) && getCurrentState() > 0) { + if((isWeedy() || isEmptyAndWeeded() || (plantable == Plantable.SCARECROW && !isGrown())) && getCurrentState() > 0) { nextGrowth = System.currentTimeMillis() + 60000 setCurrentState(getCurrentState() - 1) currentGrowthStage-- @@ -294,7 +324,14 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl CompostType.SUPERCOMPOST -> 13 } - if(patch != FarmingPatch.TROLL_STRONGHOLD_HERB && RandomFunction.random(128) <= (17 - diseaseMod) && !isWatered && !isGrown() && !protectionPaid && !isFlowerProtected() && patch.type != PatchType.EVIL_TURNIP_PATCH && currentGrowthStage != 0){ + if(patch != FarmingPatch.TROLL_STRONGHOLD_HERB + && RandomFunction.random(128) <= (17 - diseaseMod) + && !isWatered && !isGrown() + && !protectionPaid + && !isFlowerProtected() + && patch.type != PatchType.EVIL_TURNIP_PATCH + && plantable != Plantable.POISON_IVY_SEED + && currentGrowthStage != 0){ isDiseased = true // If we manually set disease mod reset it back to 0 so that crops can naturally grow after being treated/accidentally attempted to disease when they cannot be if (diseaseMod < 0) diseaseMod = 0 @@ -389,7 +426,9 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl else -> return false }.getPatchFor(player, false) - return (fpatch.plantable != null && + if (fpatch.plantable == Plantable.SCARECROW && fpatch.plantable == plantable?.protectionFlower){ + return true + } else return (fpatch.plantable != null && (fpatch.plantable == plantable?.protectionFlower || fpatch.plantable == Plantable.forItemID(Items.WHITE_LILY_SEED_14589)) && fpatch.isGrown()) } diff --git a/Server/src/main/content/global/skill/farming/Plantable.kt b/Server/src/main/content/global/skill/farming/Plantable.kt index 83c016c64..ddb8069dd 100644 --- a/Server/src/main/content/global/skill/farming/Plantable.kt +++ b/Server/src/main/content/global/skill/farming/Plantable.kt @@ -14,7 +14,7 @@ enum class Plantable(val itemID: Int, val displayName: String, val value: Int, v WHITE_LILY_SEED(Items.WHITE_LILY_SEED_14589,"white lily seed",37,4,42.0,250.0,0.0,52,PatchType.FLOWER_PATCH,Items.WHITE_LILY_14583), // Flower (technically) - SCARECROW(Items.SCARECROW_6059,"scarecrow",33,3,0.0,0.0,0.0,23,PatchType.FLOWER_PATCH,Items.SCARECROW_6059), + SCARECROW(Items.SCARECROW_6059,"scarecrow",36,-3,0.0,0.0,0.0,23,PatchType.FLOWER_PATCH,Items.SCARECROW_6059), // Allotments POTATO_SEED(Items.POTATO_SEED_5318, "potato seed", 6, 4, 8.0, 9.0, 0.0, 1, PatchType.ALLOTMENT, Items.POTATO_1942,Item(Items.COMPOST_6032,2),MARIGOLD_SEED), @@ -53,9 +53,9 @@ enum class Plantable(val itemID: Int, val displayName: String, val value: Int, v // Bushes REDBERRY_SEED(Items.REDBERRY_SEED_5101,"redberry bush seed",5,5,11.5,4.5,64.0,10,PatchType.BUSH_PATCH,Items.REDBERRIES_1951,Item(Items.CABBAGES10_5478,4)), CADAVABERRY_SEED(Items.CADAVABERRY_SEED_5102,"cadavaberry bush seed",15,6,18.0,7.0,102.5,22,PatchType.BUSH_PATCH,Items.CADAVA_BERRIES_753,Item(Items.TOMATOES5_5968,3)), - DWELLBERRY_SEED(Items.DWELLBERRY_SEED_5103,"dwellberry bush seed",26,27,31.5,12.0,177.5,36,PatchType.BUSH_PATCH,Items.DWELLBERRIES_2126,Item(Items.STRAWBERRIES5_5406,3)), + DWELLBERRY_SEED(Items.DWELLBERRY_SEED_5103,"dwellberry bush seed",26,7,31.5,12.0,177.5,36,PatchType.BUSH_PATCH,Items.DWELLBERRIES_2126,Item(Items.STRAWBERRIES5_5406,3)), JANGERBERRY_SEED(Items.JANGERBERRY_SEED_5104,"jangerberry bush seed",38,8,50.5,19.0,284.5,48,PatchType.BUSH_PATCH,Items.JANGERBERRIES_247,Item(Items.WATERMELON_5982,6)), - WHITEBERRY_SEED(Items.WHITEBERRY_SEED_5105,"whiteberry bush seed",51,8,78.0,29.0,437.5,59,PatchType.BUSH_PATCH,Items.WHITE_BERRIES_239,null), + WHITEBERRY_SEED(Items.WHITEBERRY_SEED_5105,"whiteberry bush seed",51,8,78.0,29.0,437.5,59,PatchType.BUSH_PATCH,Items.WHITE_BERRIES_239,Item(Items.MUSHROOM_6004,8)), POISON_IVY_SEED(Items.POISON_IVY_SEED_5106,"poison ivy bush seed",197,8,120.0,45.0,675.0,70,PatchType.BUSH_PATCH,Items.POISON_IVY_BERRIES_6018,null), // Herbs @@ -78,7 +78,7 @@ enum class Plantable(val itemID: Int, val displayName: String, val value: Int, v // Special BELLADONNA_SEED(Items.BELLADONNA_SEED_5281, "belladonna seed", 4, 4, 91.0, 128.0, 0.0, 63, PatchType.BELLADONNA_PATCH, Items.CAVE_NIGHTSHADE_2398), - MUSHROOM_SPORE(Items.MUSHROOM_SPORE_5282, "mushroom spore", 6, 7, 61.5, 57.7, 0.0, 53, PatchType.MUSHROOM_PATCH, Items.MUSHROOM_6004), + MUSHROOM_SPORE(Items.MUSHROOM_SPORE_5282, "mushroom spore", 4, 6, 61.5, 57.7, 0.0, 53, PatchType.MUSHROOM_PATCH, Items.MUSHROOM_6004), CACTUS_SEED(Items.CACTUS_SEED_5280, "cactus seed", 8, 7, 66.5, 25.0, 374.0, 55, PatchType.CACTUS_PATCH, Items.CACTUS_SPINE_6016), EVIL_TURNIP_SEED(Items.EVIL_TURNIP_SEED_12148, "evil turnip seed", 4, 1, 41.0, 46.0, 0.0, 42, PatchType.EVIL_TURNIP_PATCH, Items.EVIL_TURNIP_12134) ; diff --git a/Server/src/main/content/global/skill/farming/UseWithPatchHandler.kt b/Server/src/main/content/global/skill/farming/UseWithPatchHandler.kt index d24e5252d..538357dd0 100644 --- a/Server/src/main/content/global/skill/farming/UseWithPatchHandler.kt +++ b/Server/src/main/content/global/skill/farming/UseWithPatchHandler.kt @@ -235,7 +235,7 @@ class UseWithPatchHandler : InteractionListener { } val p = patch.getPatchFor(player) - if (p.getCurrentState() < 3 && p.isWeedy() && plantable != Plantable.SCARECROW) { + if (p.getCurrentState() < 3 && p.isWeedy()) { sendMessage(player, "This patch needs weeding first.") return@onUseWith true } else if (p.getCurrentState() > 3) { diff --git a/Server/src/main/content/global/skill/farming/timers/CropGrowth.kt b/Server/src/main/content/global/skill/farming/timers/CropGrowth.kt index 074e5ae43..46dbfc61f 100644 --- a/Server/src/main/content/global/skill/farming/timers/CropGrowth.kt +++ b/Server/src/main/content/global/skill/farming/timers/CropGrowth.kt @@ -41,7 +41,7 @@ class CropGrowth : PersistTimer (500, "farming:crops", isSoft = true) { //Another more extreme example is if you planted something at 10:31 that takes 5 minutes to grow. 10:35 comes around, it hasn't been 5 minutes, so it doesn't grow, meaning //it actually grows at 10:40, an extra 4 minutes. //this code makes it so crops planted both at 10:31 and 10:34 grow at 10:35 if they are supposed to take 5 minutes for each stage. - if(patch.nextGrowth < (System.currentTimeMillis() + 240_000L) && !patch.isDead){ + if(patch.nextGrowth < (System.currentTimeMillis() + 240_000L) && !patch.isDead && !patch.isChoppedFruitTree()){ patch.nextGrowth = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(patch.getStageGrowthMinutes().toLong()) patch.update() } @@ -58,7 +58,7 @@ class CropGrowth : PersistTimer (500, "farming:crops", isSoft = true) { for ((_, patch) in patchMap) { val type = patch.patch.type val shouldPlayCatchup = !patch.isGrown() || (type == PatchType.BUSH_PATCH && patch.getFruitOrBerryCount() < 4) || (type == PatchType.FRUIT_TREE_PATCH && patch.getFruitOrBerryCount() < 6) - if (shouldPlayCatchup && !patch.isDead) { + if (shouldPlayCatchup && !patch.isDead && !patch.isChoppedFruitTree()) { var stagesToSimulate = if (!patch.isGrown()) { if (patch.isWeedy() || patch.isEmptyAndWeeded()) patch.currentGrowthStage % 4 else patch.plantable!!.stages - patch.currentGrowthStage From 0c425033f347ee25b000741fa48ea96f49ea2b23 Mon Sep 17 00:00:00 2001 From: Player Name Date: Wed, 18 Jun 2025 13:38:46 +0000 Subject: [PATCH 033/117] Shooting stars now authentically award the discovery bonus XP in a single chunk (players with unclaimed XP prior to this change will still be able to claim it by mining the star) --- .../global/activity/shootingstar/ShootingStarMiningPulse.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Server/src/main/content/global/activity/shootingstar/ShootingStarMiningPulse.kt b/Server/src/main/content/global/activity/shootingstar/ShootingStarMiningPulse.kt index 5f5ee3649..da17fe79b 100644 --- a/Server/src/main/content/global/activity/shootingstar/ShootingStarMiningPulse.kt +++ b/Server/src/main/content/global/activity/shootingstar/ShootingStarMiningPulse.kt @@ -46,9 +46,8 @@ class ShootingStarMiningPulse(player: Player?, node: Scenery?, val star: Shootin //checks if the star has been discovered and if not, awards the bonus xp. Xp can be awarded regardless of mining level as per the wiki. if (!star.isDiscovered && !player.isArtificial) { val bonusXp = 75 * player.skills.getStaticLevel(Skills.MINING) - player.incrementAttribute("/save:shooting-star:bonus-xp", bonusXp) + rewardXP(player, Skills.MINING, bonusXp.toDouble()) Repository.sendNews(player.username + " is the discoverer of the crashed star near " + star.location + "!") - player.sendMessage("You have ${player.skills.experienceMultiplier * player.getAttribute("shooting-star:bonus-xp", 0).toDouble()} bonus xp towards mining stardust.") ShootingStarPlugin.submitScoreBoard(player) star.isDiscovered = true ShootingStarPlugin.getStoreFile()["isDiscovered"] = star.isDiscovered @@ -88,6 +87,7 @@ class ShootingStarMiningPulse(player: Player?, node: Scenery?, val star: Shootin val bonusXp = player.getAttribute("shooting-star:bonus-xp", 0).toDouble() var xp = star.level.exp.toDouble() if(bonusXp > 0) { + // legacy: shooting star bonus xp used to be handed out in an inauthentic way; see GL !2092 val delta = Math.min(bonusXp, xp) player.incrementAttribute("/save:shooting-star:bonus-xp", (-delta).toInt()) xp += delta From c1d932a6f1e7049d81d1d39ff081cf9e478daf6b Mon Sep 17 00:00:00 2001 From: Player Name Date: Wed, 18 Jun 2025 13:43:53 +0000 Subject: [PATCH 034/117] Mithril dragons now only use their range attack when outside melee range --- .../global/skill/slayer/MithrilDragonNPC.java | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/Server/src/main/content/global/skill/slayer/MithrilDragonNPC.java b/Server/src/main/content/global/skill/slayer/MithrilDragonNPC.java index aa9e96601..0f95eb9cb 100644 --- a/Server/src/main/content/global/skill/slayer/MithrilDragonNPC.java +++ b/Server/src/main/content/global/skill/slayer/MithrilDragonNPC.java @@ -1,8 +1,7 @@ package content.global.skill.slayer; import core.game.node.entity.Entity; -import core.game.node.entity.combat.BattleState; -import core.game.node.entity.combat.CombatStyle; +import core.game.node.entity.combat.*; import core.game.node.entity.combat.equipment.SwitchAttack; import content.global.handlers.item.equipment.special.DragonfireSwingHandler; import core.game.node.entity.impl.Animator.Priority; @@ -13,8 +12,6 @@ import core.game.world.update.flag.context.Animation; import core.game.world.update.flag.context.Graphics; import core.plugin.Initializable; import core.tools.RandomFunction; -import core.game.node.entity.combat.CombatSwingHandler; -import core.game.node.entity.combat.MultiSwingHandler; /** * Handles a mithril dragon npc. @@ -23,15 +20,23 @@ import core.game.node.entity.combat.MultiSwingHandler; @Initializable public final class MithrilDragonNPC extends AbstractNPC { - /** - * The dragonfire attack. - */ - private static final SwitchAttack DRAGONFIRE = DragonfireSwingHandler.get(false, 52, new Animation(81, Priority.HIGH), Graphics.create(1), null, null); - /** * Handles the combat. */ - private final CombatSwingHandler combatAction = new MultiSwingHandler(true, new SwitchAttack(CombatStyle.MELEE.getSwingHandler(), new Animation(80, Priority.HIGH)), new SwitchAttack(CombatStyle.MELEE.getSwingHandler(), new Animation(80, Priority.HIGH)), new SwitchAttack(CombatStyle.MAGIC.getSwingHandler(), new Animation(81, Priority.HIGH), null, null, Projectile.create((Entity) null, null, 500, 20, 20, 41, 40, 18, 255)), DRAGONFIRE, new SwitchAttack(CombatStyle.RANGE.getSwingHandler(), new Animation(81, Priority.HIGH), null, null, Projectile.create((Entity) null, null, 16, 20, 20, 41, 40, 18, 255))); + private static final SwitchAttack DRAGONFIRE = DragonfireSwingHandler.get(false, 52, new Animation(81, Priority.HIGH), Graphics.create(1), null, null); + private static final SwitchAttack MELEE = new SwitchAttack(CombatStyle.MELEE.getSwingHandler(), new Animation(80, Priority.HIGH)); + private static final SwitchAttack MAGIC = new SwitchAttack(CombatStyle.MAGIC.getSwingHandler(), new Animation(81, Priority.HIGH), null, null, Projectile.create((Entity) null, null, 500, 20, 20, 41, 40, 18, 255)); + private static class InRangeSwitchAttack extends SwitchAttack { + public InRangeSwitchAttack(CombatSwingHandler swingHandler, Animation animation, Graphics startGraphic, Graphics endGraphic, Projectile projectile) { + super(swingHandler, animation, startGraphic, endGraphic, projectile); + } + @Override + public boolean canSelect(Entity entity, Entity victim, BattleState state) { + return CombatStyle.MELEE.getSwingHandler().canSwing(entity, victim) == InteractionType.NO_INTERACT; //only select range if not in melee distance + } + }; + private static final SwitchAttack RANGE = new InRangeSwitchAttack(CombatStyle.RANGE.getSwingHandler(), new Animation(81, Priority.HIGH), null, null, Projectile.create((Entity) null, null, 16, 20, 20, 41, 40, 18, 255)); + private final CombatSwingHandler combatAction = new MultiSwingHandler(true, MELEE, MAGIC, DRAGONFIRE, RANGE); /** * Constructs a new {@code MithrilDragonNPC} {@code Object}. From 7cf50496879ec9c2add31b68be5ec714619cac33 Mon Sep 17 00:00:00 2001 From: Tom Vanlaer Date: Wed, 18 Jun 2025 13:47:04 +0000 Subject: [PATCH 035/117] Corrected wizard projectiles and cast height --- Server/data/configs/npc_configs.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Server/data/configs/npc_configs.json b/Server/data/configs/npc_configs.json index f183ea23e..123422daf 100644 --- a/Server/data/configs/npc_configs.json +++ b/Server/data/configs/npc_configs.json @@ -26647,6 +26647,8 @@ { "examine": "Hydro-power!", "combat_style": "2", + "start_gfx": "93", + "start_height": "80", "melee_animation": "414", "range_animation": "0", "magic_level": "14", @@ -26662,6 +26664,7 @@ "strength_level": "1", "id": "2710", "range_level": "1", + "projectile": "94", "attack_level": "1" }, { @@ -40355,6 +40358,7 @@ "examine": "He works evil magic.", "start_gfx": "93", "combat_style": "2", + "start_height": "80", "melee_animation": "810", "range_animation": "0", "combat_audio": "511,513,512", @@ -40379,6 +40383,7 @@ "examine": "He works evil magic.", "start_gfx": "96", "combat_style": "2", + "start_height": "80", "melee_animation": "810", "range_animation": "0", "combat_audio": "511,513,512", From 264c2aa550657b2264c37ae15bad4527971741ab Mon Sep 17 00:00:00 2001 From: Player Name Date: Tue, 15 Jul 2025 11:56:19 +0000 Subject: [PATCH 036/117] Rewrote or refactored many interfaces, fixing many small bugs --- .../handlers/iface/ClanInterfacePlugin.java | 159 ------------ .../handlers/iface/CrystalKeyChestPlugin.java | 50 ---- .../global/handlers/iface/DeathInterface.kt | 17 ++ .../handlers/iface/DeathInterfacePlugin.java | 32 --- .../handlers/iface/EmoteTabInterface.java | 30 --- .../handlers/iface/EquipmentInterface.java | 231 ------------------ .../global/handlers/iface/GameInterface.java | 185 -------------- .../handlers/iface/HairDresserInterface.kt | 8 +- .../global/handlers/iface/LoginInterface.kt | 34 +++ .../handlers/iface/LoginInterfacePlugin.java | 51 ---- .../handlers/iface/LogoutInterface.java | 42 ---- .../handlers/iface/MagicBookInterface.java | 50 ---- .../handlers/iface/MainGameInterface.kt | 34 +-- .../handlers/iface/MusicTabInterface.java | 57 ----- .../iface/MysticStaffEnchantInterface.kt | 58 +++++ .../iface/MysticStaffEnchantingPlugin.java | 95 ------- .../handlers/iface/PrayerTabInterface.java | 39 --- .../handlers/iface/QuestTabInterface.java | 71 ------ .../handlers/iface/SettingTabInterface.java | 89 ------- .../global/handlers/iface/SkillInterface.java | 32 --- .../handlers/iface/SkillTabInterface.java | 162 ------------ .../handlers/iface/tabs/ClanTabInterface.kt | 109 +++++++++ .../iface/{ => tabs}/CombatTabInterface.java | 2 +- .../handlers/iface/tabs/EmoteTabInterface.kt | 14 ++ .../iface/tabs/EquipmentTabInterface.kt | 136 +++++++++++ .../handlers/iface/tabs/LogoutTabInterface.kt | 34 +++ .../handlers/iface/tabs/MagicTabInterface.kt | 23 ++ .../handlers/iface/tabs/MusicTabInterface.kt | 38 +++ .../handlers/iface/tabs/PrayerTabInterface.kt | 14 ++ .../handlers/iface/tabs/QuestTabInterface.kt | 36 +++ .../iface/{ => tabs}/QuestTabUtils.kt | 4 +- .../iface/tabs/SettingsTabInterface.kt | 35 +++ .../handlers/iface/tabs/StatsTabInterface.kt | 87 +++++++ .../global/skill/construction/HouseZone.java | 6 +- .../minigame/barbassault/BarbAssaultArea.kt | 18 ++ .../castlewars/areas/CastleWarsGameArea.kt | 2 + .../troublebrewing/TroubleBrewingArea.kt | 18 ++ .../diary/SeersVillageAchievementDiary.kt | 12 + Server/src/main/core/api/ContentAPI.kt | 2 +- .../core/cache/def/impl/ItemDefinition.java | 21 -- .../container/impl/EquipmentContainer.java | 13 +- .../player/info/login/LoginConfiguration.java | 83 +++---- .../entity/player/link/PacketDispatch.java | 8 +- .../entity/player/link/SpellBookManager.java | 19 +- .../node/entity/player/link/emote/Emotes.java | 2 +- .../entity/player/link/prayer/PrayerType.java | 25 +- .../src/main/core/game/world/GameSettings.kt | 11 +- .../core/net/packet/PacketRepository.java | 6 +- .../main/core/net/packet/out/LastLoginInfo.kt | 15 ++ 49 files changed, 796 insertions(+), 1523 deletions(-) delete mode 100644 Server/src/main/content/global/handlers/iface/ClanInterfacePlugin.java delete mode 100644 Server/src/main/content/global/handlers/iface/CrystalKeyChestPlugin.java create mode 100644 Server/src/main/content/global/handlers/iface/DeathInterface.kt delete mode 100644 Server/src/main/content/global/handlers/iface/DeathInterfacePlugin.java delete mode 100644 Server/src/main/content/global/handlers/iface/EmoteTabInterface.java delete mode 100644 Server/src/main/content/global/handlers/iface/EquipmentInterface.java delete mode 100644 Server/src/main/content/global/handlers/iface/GameInterface.java create mode 100644 Server/src/main/content/global/handlers/iface/LoginInterface.kt delete mode 100644 Server/src/main/content/global/handlers/iface/LoginInterfacePlugin.java delete mode 100644 Server/src/main/content/global/handlers/iface/LogoutInterface.java delete mode 100644 Server/src/main/content/global/handlers/iface/MagicBookInterface.java delete mode 100644 Server/src/main/content/global/handlers/iface/MusicTabInterface.java create mode 100644 Server/src/main/content/global/handlers/iface/MysticStaffEnchantInterface.kt delete mode 100644 Server/src/main/content/global/handlers/iface/MysticStaffEnchantingPlugin.java delete mode 100644 Server/src/main/content/global/handlers/iface/PrayerTabInterface.java delete mode 100644 Server/src/main/content/global/handlers/iface/QuestTabInterface.java delete mode 100644 Server/src/main/content/global/handlers/iface/SettingTabInterface.java delete mode 100644 Server/src/main/content/global/handlers/iface/SkillInterface.java delete mode 100644 Server/src/main/content/global/handlers/iface/SkillTabInterface.java create mode 100644 Server/src/main/content/global/handlers/iface/tabs/ClanTabInterface.kt rename Server/src/main/content/global/handlers/iface/{ => tabs}/CombatTabInterface.java (98%) create mode 100644 Server/src/main/content/global/handlers/iface/tabs/EmoteTabInterface.kt create mode 100644 Server/src/main/content/global/handlers/iface/tabs/EquipmentTabInterface.kt create mode 100644 Server/src/main/content/global/handlers/iface/tabs/LogoutTabInterface.kt create mode 100644 Server/src/main/content/global/handlers/iface/tabs/MagicTabInterface.kt create mode 100644 Server/src/main/content/global/handlers/iface/tabs/MusicTabInterface.kt create mode 100644 Server/src/main/content/global/handlers/iface/tabs/PrayerTabInterface.kt create mode 100644 Server/src/main/content/global/handlers/iface/tabs/QuestTabInterface.kt rename Server/src/main/content/global/handlers/iface/{ => tabs}/QuestTabUtils.kt (99%) create mode 100644 Server/src/main/content/global/handlers/iface/tabs/SettingsTabInterface.kt create mode 100644 Server/src/main/content/global/handlers/iface/tabs/StatsTabInterface.kt create mode 100644 Server/src/main/content/minigame/barbassault/BarbAssaultArea.kt create mode 100644 Server/src/main/content/minigame/troublebrewing/TroubleBrewingArea.kt create mode 100644 Server/src/main/core/net/packet/out/LastLoginInfo.kt diff --git a/Server/src/main/content/global/handlers/iface/ClanInterfacePlugin.java b/Server/src/main/content/global/handlers/iface/ClanInterfacePlugin.java deleted file mode 100644 index 5003507c2..000000000 --- a/Server/src/main/content/global/handlers/iface/ClanInterfacePlugin.java +++ /dev/null @@ -1,159 +0,0 @@ -package content.global.handlers.iface; - -import core.game.component.Component; -import core.game.component.ComponentDefinition; -import core.game.component.ComponentPlugin; -import core.game.node.entity.player.Player; -import core.game.node.entity.player.link.RunScript; -import core.game.system.communication.ClanRank; -import core.game.system.communication.ClanRepository; -import core.net.amsc.MSPacketRepository; -import core.net.amsc.WorldCommunicator; -import core.plugin.Initializable; -import core.plugin.Plugin; -import core.tools.StringUtils; -import kotlin.Unit; - -import static core.api.ContentAPIKt.sendInputDialogue; -import static core.api.ContentAPIKt.setInterfaceText; - -/** - * Represents the plugin used to handle the clan interfaces. - * @author Vexia - */ -@Initializable -public final class ClanInterfacePlugin extends ComponentPlugin { - - @Override - public Plugin newInstance(Object arg) throws Throwable { - ComponentDefinition.put(590, this); - ComponentDefinition.put(589, this); - return this; - } - - @Override - public boolean handle(Player player, Component component, int opcode, int button, int slot, int itemId) { - switch (component.getId()) { - case 589: - switch (button) { - case 9: - if (player.getInterfaceManager().getComponent(590) != null) { - player.getPacketDispatch().sendMessage("Please close the interface you have open before using 'Clan Setup'"); - return true; - } - ClanRepository.openSettings(player); - return true; - case 14: - if (player.getIronmanManager().checkRestriction()) { - return false; - } - player.getDetails().getCommunication().toggleLootshare(player); - return true; - } - - break; - case 590: - final ClanRepository clan = ClanRepository.get(player.getName(), true); - switch (button) { - case 23: - if (opcode == 155) { - clan.setJoinRequirement(ClanRank.NONE); - } else { - clan.setJoinRequirement(getRank(opcode)); - } - player.getDetails().getCommunication().setJoinRequirement(clan.getJoinRequirement()); - MSPacketRepository.setClanSetting(player, 0, clan.getJoinRequirement()); - player.getPacketDispatch().sendString(clan.getJoinRequirement().getInfo(), 590, 23); - break; - case 24: - clan.setMessageRequirement(getRank(opcode)); - player.getDetails().getCommunication().setMessageRequirement(clan.getMessageRequirement()); - MSPacketRepository.setClanSetting(player, 1, clan.getMessageRequirement()); - player.getPacketDispatch().sendString(clan.getMessageRequirement().getInfo(), 590, 24); - break; - case 25: - clan.setKickRequirement(getRank(opcode)); - player.getDetails().getCommunication().setKickRequirement(clan.getKickRequirement()); - MSPacketRepository.setClanSetting(player, 2, clan.getKickRequirement()); - player.getPacketDispatch().sendString(clan.getKickRequirement().getInfo(), 590, 25); - clan.update(); - break; - case 26: - if (opcode == 230) { - clan.setLootRequirement(ClanRank.ADMINISTRATOR); - } else { - clan.setLootRequirement(getRank(opcode)); - } - player.getDetails().getCommunication().setLootRequirement(clan.getLootRequirement()); - MSPacketRepository.setClanSetting(player, 3, clan.getLootRequirement()); - player.getPacketDispatch().sendString(clan.getLootRequirement().getInfo(), 590, 26); - break; - case 22: - switch (opcode) { - case 196: - clan.setName("Chat disabled"); - player.getCommunication().setClanName(""); - player.getPacketDispatch().sendString(clan.getName(), 590, 22); - if (WorldCommunicator.isEnabled()) { - MSPacketRepository.sendClanRename(player, ""); - break; - } - clan.clean(true); - break; - default: - sendInputDialogue(player, false, "Enter clan prefix:", (value) -> { - String name = StringUtils.formatDisplayName((String) value); - setInterfaceText(player, name, 590, 22); - if(WorldCommunicator.isEnabled()){ - MSPacketRepository.sendClanRename(player, name); - clan.setName(name); - return Unit.INSTANCE; - } - if (clan.getName().equals("Chat disabled")) { - player.getPacketDispatch().sendMessage("Your clan channel has now been enabled!"); - player.getPacketDispatch().sendMessage("Join your channel by clicking 'Join Chat' and typing: " + player.getUsername()); - } - clan.setName(name); - player.getCommunication().setClanName(name); - clan.update(); - return Unit.INSTANCE; - }); - break; - } - break; - } - break; - } - return true; - } - - /** - * Gets the value to set. - * @param opcode the opcode. - * @return the value. - */ - public static ClanRank getRank(int opcode) { - switch (opcode) { - case 155: - return ClanRank.NONE; - case 196: - return ClanRank.FRIEND; - case 124: - return ClanRank.RECRUIT; - case 199: - return ClanRank.CORPORAL; - case 234: - return ClanRank.SERGEANT; - case 168: - return ClanRank.LIEUTENANT; - case 166: - return ClanRank.CAPTAIN; - case 64: - return ClanRank.GENERAL; - case 53: - return ClanRank.OWNER; - } - return ClanRank.NONE; - } - -} diff --git a/Server/src/main/content/global/handlers/iface/CrystalKeyChestPlugin.java b/Server/src/main/content/global/handlers/iface/CrystalKeyChestPlugin.java deleted file mode 100644 index 852eb4997..000000000 --- a/Server/src/main/content/global/handlers/iface/CrystalKeyChestPlugin.java +++ /dev/null @@ -1,50 +0,0 @@ -package content.global.handlers.iface; - -import core.game.component.Component; -import core.game.component.ComponentDefinition; -import core.game.component.ComponentPlugin; -import core.game.node.entity.player.Player; -import core.plugin.Initializable; -import core.plugin.Plugin; - -@Initializable -public class CrystalKeyChestPlugin extends ComponentPlugin { - - private final Integer CHEST_INTERFACE = 501; - - private Player player; - - public CrystalKeyChestPlugin() { - /* - * Empty - */ - } - - public CrystalKeyChestPlugin(Player player) { - this.player = player; - } - - - @Override - public Plugin newInstance(Object arg) throws Throwable { - ComponentDefinition.put(CHEST_INTERFACE, this); - return this; - } - - @Override - public boolean handle(Player player, Component component, int opcode, int button, int slot, int itemId) { - return false; - } - - public void constructInterface(Player player) { - Integer[] hiddenChildren = new Integer[] { 3, 4, 5, 6, 7}; - Component component = new Component(CHEST_INTERFACE); - for (int i = 3; i < hiddenChildren.length; i++) { - player.getPacketDispatch().sendInterfaceConfig(CHEST_INTERFACE, i, true); - } - player.getPacketDispatch().sendItemOnInterface(989, 1, CHEST_INTERFACE, hiddenChildren[2]); - player.getInterfaceManager().open(component); - this.player = player; - } - -} diff --git a/Server/src/main/content/global/handlers/iface/DeathInterface.kt b/Server/src/main/content/global/handlers/iface/DeathInterface.kt new file mode 100644 index 000000000..3b0dae31f --- /dev/null +++ b/Server/src/main/content/global/handlers/iface/DeathInterface.kt @@ -0,0 +1,17 @@ +package content.global.handlers.iface + +import core.api.closeInterface +import core.game.interaction.InterfaceListener +import org.rs09.consts.Components + +class DeathInterface : InterfaceListener { + override fun defineInterfaceListeners() { + on(Components.AIDE_DEATH_153) { player, _, _, buttonID, _, _ -> + if (buttonID == 1) { + player.savedData.globalData.setDisableDeathScreen(true) + closeInterface(player) + } + return@on true + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/handlers/iface/DeathInterfacePlugin.java b/Server/src/main/content/global/handlers/iface/DeathInterfacePlugin.java deleted file mode 100644 index 5b57c3078..000000000 --- a/Server/src/main/content/global/handlers/iface/DeathInterfacePlugin.java +++ /dev/null @@ -1,32 +0,0 @@ -package content.global.handlers.iface; - -import core.game.component.Component; -import core.game.component.ComponentDefinition; -import core.game.component.ComponentPlugin; -import core.game.node.entity.player.Player; -import core.plugin.Initializable; -import core.plugin.Plugin; - -/** - * Handles the death interface. - * @author Vexia - */ -@Initializable -public final class DeathInterfacePlugin extends ComponentPlugin { - - @Override - public Plugin newInstance(Object arg) throws Throwable { - ComponentDefinition.forId(153).setPlugin(this); - return this; - } - - @Override - public boolean handle(Player player, Component component, int opcode, int button, int slot, int itemId) { - if (button == 1) { - player.getSavedData().getGlobalData().setDisableDeathScreen(true); - player.getInterfaceManager().close(); - } - return true; - } - -} diff --git a/Server/src/main/content/global/handlers/iface/EmoteTabInterface.java b/Server/src/main/content/global/handlers/iface/EmoteTabInterface.java deleted file mode 100644 index 7506f5fcb..000000000 --- a/Server/src/main/content/global/handlers/iface/EmoteTabInterface.java +++ /dev/null @@ -1,30 +0,0 @@ -package content.global.handlers.iface; - -import core.game.component.Component; -import core.game.component.ComponentDefinition; -import core.game.component.ComponentPlugin; -import core.game.node.entity.player.Player; -import core.game.node.entity.player.link.emote.Emotes; -import core.plugin.Initializable; -import core.plugin.Plugin; - -/** - * Handles the emote tab interface. - * @author Vexia - * - */ -@Initializable -public final class EmoteTabInterface extends ComponentPlugin { - - @Override - public Plugin newInstance(Object arg) throws Throwable { - ComponentDefinition.put(464, this); - return this; - } - - @Override - public boolean handle(Player player, Component component, int opcode, int button, int slot, int itemId) { - Emotes.handle(player, button); - return true; - } -} diff --git a/Server/src/main/content/global/handlers/iface/EquipmentInterface.java b/Server/src/main/content/global/handlers/iface/EquipmentInterface.java deleted file mode 100644 index f8cfa2660..000000000 --- a/Server/src/main/content/global/handlers/iface/EquipmentInterface.java +++ /dev/null @@ -1,231 +0,0 @@ -package content.global.handlers.iface; - -import content.global.skill.summoning.familiar.BurdenBeast; -import core.api.ContentAPIKt; -import core.cache.def.impl.ItemDefinition; -import core.game.component.Component; -import core.game.component.ComponentDefinition; -import core.game.component.ComponentPlugin; -import core.game.container.Container; -import core.game.container.ContainerEvent; -import core.game.container.ContainerListener; -import core.game.container.access.InterfaceContainer; -import core.game.container.impl.EquipmentContainer; -import core.game.interaction.OptionHandler; -import core.game.node.entity.combat.DeathTask; -import core.game.node.entity.player.Player; -import core.game.node.entity.player.link.prayer.PrayerType; -import core.game.node.item.Item; -import core.game.system.task.Pulse; -import core.net.packet.PacketRepository; -import core.net.packet.context.ContainerContext; -import core.net.packet.out.ContainerPacket; -import core.plugin.Initializable; -import core.plugin.Plugin; -import core.game.global.action.EquipHandler; -import core.game.interaction.IntType; -import core.game.interaction.InteractionListeners; -import core.game.world.GameWorld; -import core.tools.Log; - -/** - * Represents the equipment interface. - * @author Emperor - * - */ -@Initializable -public final class EquipmentInterface extends ComponentPlugin { - - @Override - public Plugin newInstance(Object arg) throws Throwable { - ComponentDefinition.put(102, this); - ComponentDefinition.put(387, this); - ComponentDefinition.put(667, this); - ComponentDefinition.put(670, this); - return this; - } - - @Override - public boolean handle(final Player p, Component component, int opcode, int button, final int slot, final int itemId) { - if (component.getId() == 667) { - if (button != 14) { - return false; - } - switch (opcode) { - case 155: - p.getPulseManager().clear(); - GameWorld.getPulser().submit(new Pulse(1, p) { - @Override - public boolean pulse() { - EquipHandler.unequip(p, slot, itemId); - return true; - } - }); - return true; - case 9: - p.sendMessage(p.getEquipment().get(slot).getDefinition().getExamine()); - return true; - case 196: - GameWorld.getPulser().submit(new Pulse(1, p) { - @Override - public boolean pulse() { - operate(p, slot, itemId); - return true; - } - }); - return true; - } - return false; - } - else if (component.getId() == 670) { - switch (opcode) { - case 155: - p.getPulseManager().clear(); - final Item item = p.getInventory().get(slot); - GameWorld.getPulser().submit(new Pulse(1, p) { - @Override - public boolean pulse() { - if (item == null) return true; - InteractionListeners.run(item.getId(), IntType.ITEM,"equip",p, item); - return true; - } - }); - return true; - case 9: - p.sendMessage(p.getInventory().get(slot).getDefinition().getExamine()); - return true; - } - } - switch (opcode) { - case 206: - if (button != 28) { - return false; - } - GameWorld.getPulser().submit(new Pulse(1, p) { - @Override - public boolean pulse() { - operate(p, slot, itemId); - return true; - } - }); - return true; - default: - switch (button) { - case 52: - if (p.getInterfaceManager().isOpened() && p.getInterfaceManager().getOpened().getId() == 102) { - return true; - } - - // (Highlight white items are auto destroyed on death Enum#616 (Items kept on death interface) TODO: Parse server sided - // SCRIPT 118 - Items kept on death interface CS - // ARG 0: Safe location check Takes: 0 Safe Area/2 in POH/3 in Castle Wars/4 in Trouble Brewing/5 in Barbass - int zoneType = p.getZoneMonitor().getType(); - // ARG 1: Amount of items kept on death Takes: 0/1/3/4 - Container[] itemArray = DeathTask.getContainers(p); - Container kept = itemArray[0]; - int amtKeptOnDeath = kept.itemCount(); - if (amtKeptOnDeath > 4 && zoneType == 0) { - ContentAPIKt.log(this.getClass(), Log.ERR, "Items kept on death interface should not contain more than 4 items when not in a safe zone!"); - } - // ARG 2: Item kept on death slot 0 - int slot0 = kept.getId(0); - // ARG 3: Item kept on death slot 1 - int slot1 = kept.getId(1); - // ARG 4: Item kept on death slot 2 - int slot2 = kept.getId(2); - // ARG 5: Item kept on death slot 3 - int slot3 = kept.getId(3); - // ARG 6: Player skulled Takes: 0 not skulled/1 skulled - int skulled = p.getSkullManager().isSkulled() ? 1 : 0; - // ARG 7: Player has summoning creature out Takes: 0 not out/1 Creature summoned - int hasBoB; - if (p.getFamiliarManager().hasFamiliar()) { - if (p.getFamiliarManager().getFamiliar().isBurdenBeast()) { - hasBoB = ((BurdenBeast) p.getFamiliarManager().getFamiliar()).getContainer().isEmpty() ? 0 : 1; - } else { - hasBoB = 0; - } - } else { - hasBoB = 0; - } - // ARG 8: String for effect: - // if (arg1 == 0) arg8 + " This reduces the items you keep from three to zero!" - // if (arg1 == 1) arg8 + " This reduces the items you keep from three to zero!" + "
" + "
" + "However, you also have the " + "" + "Protect Items" + "" + " prayer active, which saves you one extra item!"); - Object[] params = new Object[] { hasBoB, skulled, slot3, slot2, slot1, slot0, amtKeptOnDeath, zoneType, "You are skulled." }; - p.getPacketDispatch().sendRunScript(118, "siiooooii", params); - - p.getInterfaceManager().openComponent(102); - break; - case 28: - if (opcode == 81) { - p.getPulseManager().clear(); - GameWorld.getPulser().submit(new Pulse(1, p) { - @Override - public boolean pulse() { - EquipHandler.unequip(p, slot, itemId); - return true; - } - }); - return true; - } - break; - case 55: - if (p.getInterfaceManager().isOpened() && p.getInterfaceManager().getOpened().getId() == 667) { - return true; - } - final ContainerListener listener = new ContainerListener() { - @Override - public void update(Container c, ContainerEvent e) { - PacketRepository.send(ContainerPacket.class, new ContainerContext(p, -1, -1, 98, e.getItems(), false, e.getSlots())); - } - - @Override - public void refresh(Container c) { - PacketRepository.send(ContainerPacket.class, new ContainerContext(p, -1, -1, 98, c, false)); - } - }; - p.getInterfaceManager().openComponent(667).setCloseEvent((player, c) -> { - player.removeAttribute("equip_stats_open"); - player.getInterfaceManager().closeSingleTab(); - player.getInventory().getListeners().remove(listener); - return true; - }); - p.setAttribute("equip_stats_open", true); - EquipmentContainer.update(p); - p.getInterfaceManager().openSingleTab(new Component(670)); - InterfaceContainer.generateItems(p, p.getInventory().toArray(), new String[] { "Equip" }, 670, 0, 7, 4, 93); - p.getInventory().getListeners().add(listener); - p.getInventory().refresh(); - ItemDefinition.statsUpdate(p); - p.getPacketDispatch().sendIfaceSettings(1278, 14, 667, 0, 13); - break; - } - } - return true; - } - - /** - * Operates an item. - * @param player The player. - * @param slot The container slot. - * @param itemId The item id. - */ - public void operate(Player player, int slot, int itemId) { - if (slot < 0 || slot > 13) { - return; - } - Item item = player.getEquipment().get(slot); - if (item == null) { - return; - } - if(InteractionListeners.run(item.getId(),IntType.ITEM,"operate",player,item)){ - return; - } - OptionHandler handler = item.getOperateHandler(); - if (handler != null && handler.handle(player, item, "operate")) { - return; - } - player.getPacketDispatch().sendMessage("You can't operate that."); - } - -} diff --git a/Server/src/main/content/global/handlers/iface/GameInterface.java b/Server/src/main/content/global/handlers/iface/GameInterface.java deleted file mode 100644 index 0c8754f29..000000000 --- a/Server/src/main/content/global/handlers/iface/GameInterface.java +++ /dev/null @@ -1,185 +0,0 @@ -package content.global.handlers.iface; - -import core.game.component.Component; -import core.game.component.ComponentDefinition; -import core.game.component.ComponentPlugin; -import core.plugin.Initializable; -import core.game.node.entity.combat.equipment.WeaponInterface; -import core.game.node.entity.combat.equipment.WeaponInterface.WeaponInterfaces; -import core.game.node.entity.player.Player; -import core.game.node.entity.player.info.Rights; -import core.game.world.GameWorld; -import core.plugin.Plugin; - -/** - * Represents the component plugin used for the game interface. - * @author Vexia - * - */ -@Initializable -public final class GameInterface extends ComponentPlugin { - - @Override - public Plugin newInstance(Object arg) throws Throwable { - ComponentDefinition.put(740, this); - return this; - } - - @Override - public boolean handle(final Player player, Component component, int opcode, int button, int slot, int itemId) { - switch (component.getId()) { - case 740: - switch (button){ - case 3: - player.getInterfaceManager().closeChatbox(); - break; - } - return true; - case 746: - switch (button){ - case 12: - player.getPacketDispatch().sendString("When you have finished playing " + GameWorld.getSettings().getName() + ", always use the button below to logout safely. ", 182, 0); - break; - case 49: - player.getPacketDispatch().sendString("Friends List - " + GameWorld.getSettings().getName() + " " + GameWorld.getSettings().getWorldId(), 550, 3); - break; - case 110: - configureWorldMap(player); - break; - } - return true; - case 548: - if (button >= 38 && button<= 44 || button >= 20 && button <= 26) { - player.getInterfaceManager().setCurrentTabIndex(getTabIndex(button)); - } - //Interface buttons that advance the Tutorial Island stages - switch (button) { - case 21://Friends Tab - player.getPacketDispatch().sendString("Friends List -" + GameWorld.getSettings().getName() + " " + GameWorld.getSettings().getWorldId(), 550, 3); - break; - case 22://Ignore Tab - break; - case 24://Settings Tab - break; - case 25://Emotes Tab - break; - case 26://Music Tab - break; - case 38://Attack Tab - if (player.getExtension(WeaponInterface.class) == WeaponInterfaces.STAFF) { - final Component c = new Component(WeaponInterfaces.STAFF.getInterfaceId()); - player.getInterfaceManager().openTab(0, c); - final WeaponInterface inter = player.getExtension(WeaponInterface.class); - inter.updateInterface(); - } - break; - case 39://Skill Tab - break; - case 40://Quest Tab - /*if (GameWorld.isEconomyWorld()) { - player.getQuestRepository().syncronizeTab(player); - } else { - player.getSavedData().getSpawnData().drawStatsTab(player); - }*/ - - player.getQuestRepository().syncronizeTab(player); - break; - case 41://Inventory Tab - player.getInventory().refresh(); - break; - case 42://Worn Equipment Tab - break; - case 43://Prayer Tab - break; - case 44://Magic Tab - break; - case 66://World map - case 110: - configureWorldMap(player); - break; - case 69://Logout - player.getPacketDispatch().sendString("When you have finished playing " + GameWorld.getSettings().getName() + ", always use the button below to logout safely. ", 182, 0); - break; - } - return true; - case 750: - switch (opcode) { - case 155: - switch (button) { - case 1: - player.getSettings().toggleRun(); - break; - } - break; - } - return true; - case 751: - switch (opcode) { - case 155: - switch (button) { - case 27: - openReport(player); - break; - } - break; - } - return true; - } - return true; - } - - /** - * World map - * Thanks, Snickerize! - */ - private void configureWorldMap(Player player) { - if (player.inCombat()) { - player.getPacketDispatch().sendMessage("It wouldn't be very wise opening the world map during combat."); - return; - } - if(player.getLocks().isInteractionLocked() || player.getLocks().isMovementLocked()){ - player.getPacketDispatch().sendMessage("You can't do this right now."); - return; - } - player.getInterfaceManager().openWindowsPane(new Component(755)); - int posHash = (player.getLocation().getZ() << 28) | (player.getLocation().getX() << 14) | player.getLocation().getY(); - player.getPacketDispatch().sendScriptConfigs(622, posHash, "", 0); - player.getPacketDispatch().sendScriptConfigs(674, posHash, "", 0); - } - - /** - * Method used to open the report interface. - * @param player the player. - */ - public static void openReport(final Player player) { - player.getInterfaceManager().open(new Component(553)).setCloseEvent((player1, c) -> { - player1.getPacketDispatch().sendRunScript(80, ""); - player1.getPacketDispatch().sendRunScript(137, ""); - return true; - }); - player.getPacketDispatch().sendRunScript(508, ""); - if (player.getDetails().getRights() != Rights.REGULAR_PLAYER) { - for (int i = 0; i < 18; i++) { - player.getPacketDispatch().sendInterfaceConfig(553, i, false); - } - } - } - - /** - * Gets the tab index. - * @param button The button id. - * @return The tab index. - */ - private static int getTabIndex(int button) { - int tabIndex = button - 38; - if (button < 27) { - tabIndex = (button - 20) + 7; - } - return tabIndex; - } - - /** - * Configures the world map for a player. - * @param player The player. - */ -} diff --git a/Server/src/main/content/global/handlers/iface/HairDresserInterface.kt b/Server/src/main/content/global/handlers/iface/HairDresserInterface.kt index b1750ae63..b831471f2 100644 --- a/Server/src/main/content/global/handlers/iface/HairDresserInterface.kt +++ b/Server/src/main/content/global/handlers/iface/HairDresserInterface.kt @@ -152,11 +152,11 @@ class HairDresserInterface : ComponentPlugin(){ player.setAttribute("beard-setting",false) if(!pl.getAttribute("hairdresser-paid",false)){ val original_hair = player.getAttribute("original-hair",0) - val original_beard = player.getAttribute("original_beard",-1) - val original_color = player.getAttribute("original_color",0) + val original_beard = player.getAttribute("original-beard",-1) + val original_color = player.getAttribute("original-color",0) pl.appearance.hair.changeLook(original_hair) pl.appearance.hair.changeColor(original_color) - if(original_beard != -1) { + if (original_beard != -1) { pl.appearance.beard.changeLook(original_beard) } pl.appearance.sync() @@ -174,7 +174,7 @@ class HairDresserInterface : ComponentPlugin(){ when(button){ 199 -> player.setAttribute("beard-setting",false) 200 -> player.setAttribute("beard-setting",true) - 68,196,274 -> pay(player) + 196,274 -> pay(player) else -> when(component?.id){ 592 -> { //Female if(femaleColorButtonRange.contains(button)){ diff --git a/Server/src/main/content/global/handlers/iface/LoginInterface.kt b/Server/src/main/content/global/handlers/iface/LoginInterface.kt new file mode 100644 index 000000000..e2464c176 --- /dev/null +++ b/Server/src/main/content/global/handlers/iface/LoginInterface.kt @@ -0,0 +1,34 @@ +package content.global.handlers.iface + +import core.api.closeInterface +import core.api.runTask +import core.game.interaction.InterfaceListener +import core.game.node.entity.player.info.login.LoginConfiguration +import org.rs09.consts.Components + +class LoginInterface : InterfaceListener { + override fun defineInterfaceListeners() { + on(Components.WELCOME_SCREEN_378) { player, _, _, buttonID, _, _ -> + val playButton = 140 + val creditButton = 145 + val discordButton = 204 + + when (buttonID) { + playButton -> { + player.locks.lock("login", 2) + closeInterface(player) + runTask(player, 1, 0) { + LoginConfiguration.configureGameWorld(player) + } + } + creditButton -> return@on true + discordButton -> return@on true + } + return@on true + } + + onClose(Components.WELCOME_SCREEN_378) { player, _ -> + return@onClose player.locks.isLocked("login") + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/handlers/iface/LoginInterfacePlugin.java b/Server/src/main/content/global/handlers/iface/LoginInterfacePlugin.java deleted file mode 100644 index f91a3000e..000000000 --- a/Server/src/main/content/global/handlers/iface/LoginInterfacePlugin.java +++ /dev/null @@ -1,51 +0,0 @@ -package content.global.handlers.iface; - -import core.game.component.Component; -import core.game.component.ComponentDefinition; -import core.game.component.ComponentPlugin; -import core.game.node.entity.player.Player; -import core.game.node.entity.player.info.login.LoginConfiguration; -import core.game.system.task.Pulse; -import core.plugin.Initializable; -import core.plugin.Plugin; - -/** - * Represents the plugin used for the login interface. - * @author 'Vexia - * @version 1.0 - */ -@Initializable -public final class LoginInterfacePlugin extends ComponentPlugin { - - @Override - public Plugin newInstance(Object arg) throws Throwable { - ComponentDefinition.put(378, this); - return null; - } - - @Override - public boolean handle(final Player player, Component component, int opcode, int button, int slot, int itemId) { - switch (button) { - case 140: - if (player.getLocks().isLocked("login")) { - return true; - } - player.getLocks().lock("login", 2); - player.getInterfaceManager().close(); - player.getPulseManager().run(new Pulse(1) { - @Override - public boolean pulse() { - LoginConfiguration.configureGameWorld(player); - return true; - } - }); - break; - case 145://credits - break; - case 204://message centre - break; - } - return true; - } - -} diff --git a/Server/src/main/content/global/handlers/iface/LogoutInterface.java b/Server/src/main/content/global/handlers/iface/LogoutInterface.java deleted file mode 100644 index d3a7c9c1a..000000000 --- a/Server/src/main/content/global/handlers/iface/LogoutInterface.java +++ /dev/null @@ -1,42 +0,0 @@ -package content.global.handlers.iface; - -import core.game.component.Component; -import core.game.component.ComponentDefinition; -import core.game.component.ComponentPlugin; -import core.game.node.entity.player.Player; -import core.game.world.GameWorld; -import core.plugin.Initializable; -import core.plugin.Plugin; -import core.game.world.repository.Repository; - -/** - * Represents the interface used to logout of the game. - * @author 'Vexia - * @version 1.0 - */ -@Initializable -public final class LogoutInterface extends ComponentPlugin { - - @Override - public Plugin newInstance(Object arg) throws Throwable { - ComponentDefinition.put(182, this); - return this; - } - - @Override - public boolean handle(Player player, Component component, int opcode, int button, int slot, int itemId) { - if (!player.getZoneMonitor().canLogout()) { - return true; - } - if (player.inCombat()) { - player.getPacketDispatch().sendMessage("You can't log out until 10 seconds after the end of combat."); - return true; - } - if (player.isTeleporting()) { - player.sendMessage("Please finish your teleport before logging out."); - return true; - } - Repository.getDisconnectionQueue().add(player); - return true; - } -} diff --git a/Server/src/main/content/global/handlers/iface/MagicBookInterface.java b/Server/src/main/content/global/handlers/iface/MagicBookInterface.java deleted file mode 100644 index af5c87baa..000000000 --- a/Server/src/main/content/global/handlers/iface/MagicBookInterface.java +++ /dev/null @@ -1,50 +0,0 @@ -package content.global.handlers.iface; - -import content.global.skill.magic.SpellListener; -import content.global.skill.magic.SpellListeners; -import content.global.skill.magic.SpellUtils; -import core.game.event.SpellCastEvent; -import core.game.component.Component; -import core.game.component.ComponentDefinition; -import core.game.component.ComponentPlugin; -import core.plugin.Initializable; -import core.game.node.entity.combat.spell.MagicSpell; -import core.game.node.entity.player.Player; -import core.game.node.entity.player.link.SpellBookManager.SpellBook; -import core.game.world.GameWorld; -import core.plugin.Plugin; - -/** - * Represents the magic book interface handling of non-combat spells. - * @author 'Vexia - * @version 1.0 - */ -@Initializable -public final class MagicBookInterface extends ComponentPlugin { - - @Override - public Plugin newInstance(Object arg) throws Throwable { - ComponentDefinition.put(192, this); - ComponentDefinition.put(193, this); - ComponentDefinition.put(430, this); - return this; - } - - @Override - public boolean handle(final Player player, Component component, int opcode, int button, int slot, int itemId) { - if (GameWorld.getTicks() < player.getAttribute("magic:delay", -1)) { - return true; - } - - SpellBook spellBook = component.getId() == 192 - ? SpellBook.MODERN - : component.getId() == 193 - ? SpellBook.ANCIENT - : SpellBook.LUNAR; - - SpellListeners.run(button, SpellListener.NONE, SpellUtils.getBookFromInterface(component.getId()),player,null); - boolean result = MagicSpell.castSpell(player, spellBook, button, player); - - return result; - } -} diff --git a/Server/src/main/content/global/handlers/iface/MainGameInterface.kt b/Server/src/main/content/global/handlers/iface/MainGameInterface.kt index 243337f4e..e7f4edb05 100644 --- a/Server/src/main/content/global/handlers/iface/MainGameInterface.kt +++ b/Server/src/main/content/global/handlers/iface/MainGameInterface.kt @@ -31,32 +31,13 @@ class MainGameInterface : InterfaceListener { } on(TOPLEVEL_FS){player, _, _, buttonID, _, _ -> - when (buttonID) { - 12 -> setInterfaceText(player, - "When you have finished playing " + settings!!.name + ", always use the button below to logout safely. ", - 182, - 0 - ) - 49 -> setInterfaceText(player, - "Friends List - " + settings!!.name + " " + settings!!.worldId, - 550, - 3 - ) - 110 -> configureWorldMap(player) - } + if (buttonID == 110) + configureWorldMap(player) return@on true } - on(TOPLEVEL){player, _, _, buttonID, _, _ -> when (buttonID) { - 21 -> { - player.packetDispatch.sendString( - "Friends List -" + settings!!.name + " " + settings!!.worldId, - 550, - 3 - ) - } 38 -> { if (player.getExtension(WeaponInterface::class.java) === WeaponInterfaces.STAFF) { val c = Component(WeaponInterfaces.STAFF.interfaceId) @@ -67,12 +48,7 @@ class MainGameInterface : InterfaceListener { } 40 -> player.questRepository.syncronizeTab(player) 41 -> player.inventory.refresh() - 66, 110 -> configureWorldMap(player) - 69 -> player.packetDispatch.sendString( - "When you have finished playing " + settings!!.name + ", always use the button below to logout safely. ", - 182, - 0 - ) + 66 -> configureWorldMap(player) } return@on true } @@ -95,11 +71,11 @@ class MainGameInterface : InterfaceListener { private fun configureWorldMap(player: Player) { if (player.inCombat()) { - player.packetDispatch.sendMessage("It wouldn't be very wise opening the world map during combat.") + sendMessage(player, "It wouldn't be very wise opening the world map during combat.") return } if (player.locks.isInteractionLocked || player.locks.isMovementLocked) { - player.packetDispatch.sendMessage("You can't do this right now.") + sendMessage(player, "You can't do this right now.") return } player.interfaceManager.close() diff --git a/Server/src/main/content/global/handlers/iface/MusicTabInterface.java b/Server/src/main/content/global/handlers/iface/MusicTabInterface.java deleted file mode 100644 index 579607fe5..000000000 --- a/Server/src/main/content/global/handlers/iface/MusicTabInterface.java +++ /dev/null @@ -1,57 +0,0 @@ -package content.global.handlers.iface; - -import core.game.component.Component; -import core.game.component.ComponentDefinition; -import core.game.component.ComponentPlugin; -import core.game.node.entity.player.Player; -import core.game.node.entity.player.info.Rights; -import core.game.node.entity.player.link.music.MusicEntry; -import core.plugin.Initializable; -import core.plugin.Plugin; - -/** - * Handles the interface tab buttons. - * @author Emperor - */ -@Initializable -public final class MusicTabInterface extends ComponentPlugin { - - @Override - public Plugin newInstance(Object arg) throws Throwable { - ComponentDefinition.put(187, this); - return this; - } - - @Override - public boolean handle(Player player, Component component, int opcode, int button, int slot, int itemId) { - switch (opcode) { - case 155: - switch (button) { - case 11: - player.getMusicPlayer().toggleLooping(); - return true; - case 1: - MusicEntry entry = player.getMusicPlayer().getUnlocked().get(slot); - if (entry == null) { - if(player.getRights().equals(Rights.ADMINISTRATOR)){ - for(MusicEntry ent : MusicEntry.getSongs().values()){ - if(ent.getIndex() == slot){ - player.getMusicPlayer().unlock(ent.getId()); - break; - } - } - } else { - player.getPacketDispatch().sendMessage("You have not unlocked this piece of music yet!"); - } - return true; - } - player.getMusicPlayer().setPlaying(false); - player.getMusicPlayer().play(entry); - return true; - } - break; - } - return false; - } - -} diff --git a/Server/src/main/content/global/handlers/iface/MysticStaffEnchantInterface.kt b/Server/src/main/content/global/handlers/iface/MysticStaffEnchantInterface.kt new file mode 100644 index 000000000..46a3bde24 --- /dev/null +++ b/Server/src/main/content/global/handlers/iface/MysticStaffEnchantInterface.kt @@ -0,0 +1,58 @@ +package content.global.handlers.iface + +import core.api.* +import core.game.dialogue.FacialExpression +import core.game.interaction.InterfaceListener +import core.game.node.item.Item +import core.tools.StringUtils +import org.rs09.consts.Items +import org.rs09.consts.NPCs + +class MysticStaffEnchantInterface : InterfaceListener { + override fun defineInterfaceListeners() { + on(INTERFACE_332) { player, _, _, buttonID, _, _ -> + val staff = buttonMap[buttonID] ?: return@on true + val price = if (inEquipment(player, Items.SEERS_HEADBAND_14631)) 27000 else 40000 + + if (!inInventory(player, staff.basicID)) { + sendMessage(player, "You don't have a${if (StringUtils.isPlusN(getItemName(staff.basicID))) "n" else ""} ${getItemName(staff.basicID)} to enchant.") + return@on true + } + + closeInterface(player) + + if (!inInventory(player, Items.COINS_995, price)) { + sendNPCDialogue(player, NPCs.THORMAC_389, "I need ${String.format("%,d", price)} coins for materials. Come back when you have the money!", FacialExpression.NEUTRAL) + return@on true + } + + if (removeItem(player, Item(staff.basicID, 1)) && removeItem(player, Item(Items.COINS_995, price))) { + sendNPCDialogue(player, NPCs.THORMAC_389, "Just a moment... hang on... hocus pocus abra-cadabra... there you go! Enjoy your enchanted staff!", FacialExpression.NEUTRAL) + addItem(player, staff.enchantedID, 1) + } + return@on true + } + } + + enum class EnchantedStaff(val enchantedID: Int, val basicID: Int, val buttonID: Int) { + AIR(Items.MYSTIC_AIR_STAFF_1405, Items.AIR_BATTLESTAFF_1397, 21), + WATER(Items.MYSTIC_WATER_STAFF_1403, Items.WATER_BATTLESTAFF_1395, 22), + EARTH(Items.MYSTIC_EARTH_STAFF_1407, Items.EARTH_BATTLESTAFF_1399, 23), + FIRE(Items.MYSTIC_FIRE_STAFF_1401, Items.FIRE_BATTLESTAFF_1393, 24), + LAVA(Items.MYSTIC_LAVA_STAFF_3054, Items.LAVA_BATTLESTAFF_3053, 25), + MUD(Items.MYSTIC_MUD_STAFF_6563, Items.MUD_BATTLESTAFF_6562, 26), + STEAM(Items.MYSTIC_STEAM_STAFF_11738, Items.STEAM_BATTLESTAFF_11736, 27), + } + + companion object { + private const val INTERFACE_332 = 332 + + val buttonMap = HashMap() + + init { + for (staff in EnchantedStaff.values()) { + buttonMap[staff.buttonID] = staff + } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/handlers/iface/MysticStaffEnchantingPlugin.java b/Server/src/main/content/global/handlers/iface/MysticStaffEnchantingPlugin.java deleted file mode 100644 index c8d752141..000000000 --- a/Server/src/main/content/global/handlers/iface/MysticStaffEnchantingPlugin.java +++ /dev/null @@ -1,95 +0,0 @@ -package content.global.handlers.iface; - -import core.game.component.Component; -import core.game.component.ComponentDefinition; -import core.game.component.ComponentPlugin; -import org.rs09.consts.Items; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; -import core.plugin.Plugin; -import core.plugin.Initializable; -import core.tools.StringUtils; - -import java.util.HashMap; - -/** - * Represents the plugin used to handle the agility ticket interface. - * @author afaroutdude - */ -@Initializable -public final class MysticStaffEnchantingPlugin extends ComponentPlugin { - - private final Component COMPONENT = new Component(332); - - protected enum EnchantedStaff { - AIR(Items.MYSTIC_AIR_STAFF_1405, Items.AIR_BATTLESTAFF_1397, 21), - WATER(Items.MYSTIC_WATER_STAFF_1403, Items.WATER_BATTLESTAFF_1395, 22), - EARTH(Items.MYSTIC_EARTH_STAFF_1407, Items.EARTH_BATTLESTAFF_1399, 23), - FIRE(Items.MYSTIC_FIRE_STAFF_1401, Items.FIRE_BATTLESTAFF_1393, 24), - LAVA(Items.MYSTIC_LAVA_STAFF_3054, Items.LAVA_BATTLESTAFF_3053, 25), - MUD(Items.MYSTIC_MUD_STAFF_6563, Items.MUD_BATTLESTAFF_6562, 26), - STEAM(Items.MYSTIC_STEAM_STAFF_11738, Items.STEAM_BATTLESTAFF_11736, 27); - - public final int enchanted; - public final int basic; - public final int child; - - private static final HashMap basicToEnchanted = new HashMap<>(); - private static final HashMap childToBasic = new HashMap<>(); - - static { - for (EnchantedStaff staff : EnchantedStaff.values()) { - basicToEnchanted.put(staff.basic, staff.enchanted); - childToBasic.put(staff.child, staff.basic); - } - } - - EnchantedStaff(int enchantedId, int basicId, int childId) { - this.enchanted = enchantedId; - this.basic = basicId; - this.child = childId; - } - } - - @Override - public Plugin newInstance(Object arg) throws Throwable { - ComponentDefinition.forId(332).setPlugin(this); - return this; - } - - @Override - public void open(Player player, Component component) { - super.open(player, component); - // zoom doesn't work, but based on https://youtu.be/qxxhhCdxBsQ?t=75 seems correct anyway - for (EnchantedStaff staff : EnchantedStaff.values()) { - player.getPacketDispatch().sendItemZoomOnInterface(staff.basic, 240, COMPONENT.getId(), staff.child); - } - } - - @Override - public boolean handle(Player player, Component component, int opcode, int buttonId, int slot, int itemId) { - - if (EnchantedStaff.childToBasic.containsKey(buttonId)) { - Item basicStaff = new Item(EnchantedStaff.childToBasic.get(buttonId)); - Item enchantedStaff = new Item(EnchantedStaff.basicToEnchanted.get(basicStaff.getId())); - - if (!player.getInventory().containsItem(basicStaff)) { - player.getPacketDispatch().sendMessage("You don't have a" + (StringUtils.isPlusN(basicStaff.getName()) ? "n " : " ") + basicStaff.getName() + " to enchant."); - return true; - } - int cost = player.getEquipment().contains(Items.SEERS_HEADBAND_14631, 1)? 27000 : 40000; - if (!player.getInventory().contains(995, cost)) { - player.getInterfaceManager().close(); - player.getDialogueInterpreter().sendDialogues(389, null, "I need " + String.format("%,d", cost) + " coins for materials. Come", "back when you have the money!"); - return true; - } - if (player.getInventory().remove(basicStaff, new Item(995, cost))) { - player.getInterfaceManager().close(); - player.getDialogueInterpreter().sendDialogues(389, null, "Just a moment... hang on... hocus pocus abra-", "cadabra... there you go! Enjoy your enchanted staff!"); - player.getInventory().add(enchantedStaff); - } - } - - return true; - } -} diff --git a/Server/src/main/content/global/handlers/iface/PrayerTabInterface.java b/Server/src/main/content/global/handlers/iface/PrayerTabInterface.java deleted file mode 100644 index 2c31b11ce..000000000 --- a/Server/src/main/content/global/handlers/iface/PrayerTabInterface.java +++ /dev/null @@ -1,39 +0,0 @@ -package content.global.handlers.iface; - -import core.game.component.Component; -import core.game.component.ComponentDefinition; -import core.game.component.ComponentPlugin; -import core.game.node.entity.player.Player; -import core.game.node.entity.player.link.prayer.PrayerType; -import core.plugin.Initializable; -import core.plugin.Plugin; - -import static core.api.ContentAPIKt.hasRequirement; -import content.data.Quests; - -/** - * Represents the prayer interface. - * @author 'Vexia - */ -@Initializable -public final class PrayerTabInterface extends ComponentPlugin { - - @Override - public Plugin newInstance(Object arg) throws Throwable { - ComponentDefinition.put(271, this); - return this; - } - - @Override - public boolean handle(Player player, Component component, int opcode, int button, int slot, int itemId) { - final PrayerType type = PrayerType.get(button); - if (type == PrayerType.CHIVALRY || type == PrayerType.PIETY) - if (!hasRequirement(player, Quests.KINGS_RANSOM)) - return true; - if (type == null) { - return true; - } - player.getPrayer().toggle(type); - return true; - } -} diff --git a/Server/src/main/content/global/handlers/iface/QuestTabInterface.java b/Server/src/main/content/global/handlers/iface/QuestTabInterface.java deleted file mode 100644 index 90d580af2..000000000 --- a/Server/src/main/content/global/handlers/iface/QuestTabInterface.java +++ /dev/null @@ -1,71 +0,0 @@ -package content.global.handlers.iface; - -import core.game.component.Component; -import core.game.component.ComponentDefinition; -import core.game.component.ComponentPlugin; -import core.game.node.entity.player.Player; -import core.game.node.entity.player.link.diary.AchievementDiary; -import core.game.node.entity.player.link.diary.DiaryType; -import core.game.node.entity.player.link.quest.Quest; -import core.plugin.Initializable; -import core.plugin.Plugin; - -/** - * Handles the quest tab reward buttons. - * @author Emperor - * @author Vexia - */ -@Initializable -public class QuestTabInterface extends ComponentPlugin { - - @Override - public Plugin newInstance(Object arg) throws Throwable { - ComponentDefinition.put(274, this); // Quests - ComponentDefinition.put(259, this); // Achievement diary - return this; - } - - @Override - public boolean handle(Player p, Component component, int opcode, int button, int slot, int itemId) { - p.getPulseManager().clear(); - switch (component.getId()) { - case 274: -// if (!GameWorld.isEconomyWorld()) { -// p.getSavedData().getSpawnData().handleButton(p, button); -// } - switch (button) { - case 3: - p.getAchievementDiaryManager().openTab(); - return true; - case 10: - break; - default: -// if (GameWorld.isEconomyWorld()) { - - Quest quest = p.getQuestRepository().forButtonId(button); - if (quest != null) { - p.getInterfaceManager().open(new Component(275)); - quest.drawJournal(p, quest.getStage(p)); - return true; - } else QuestTabUtils.showRequirementsInterface(p, button); -// } - return false; - } - break; - case 259: - switch (button) { - case 8: - p.getInterfaceManager().openTab(2, new Component(274)); - return true; - default: - AchievementDiary diary = p.getAchievementDiaryManager().getDiary(DiaryType.forChild(button)); - if (diary != null) { - diary.open(p); - } - return true; - } - } - return true; - } - -} diff --git a/Server/src/main/content/global/handlers/iface/SettingTabInterface.java b/Server/src/main/content/global/handlers/iface/SettingTabInterface.java deleted file mode 100644 index c8bac8cb7..000000000 --- a/Server/src/main/content/global/handlers/iface/SettingTabInterface.java +++ /dev/null @@ -1,89 +0,0 @@ -package content.global.handlers.iface; - -import static core.api.ContentAPIKt.*; - -import core.game.component.Component; -import core.game.component.ComponentDefinition; -import core.game.component.ComponentPlugin; -import core.plugin.Initializable; -import core.game.node.entity.player.Player; -import core.plugin.Plugin; - -/** - * @author 'Vexia - */ -@Initializable -public class SettingTabInterface extends ComponentPlugin { - - @Override - public Plugin newInstance(Object arg) throws Throwable { - ComponentDefinition.put(261, this); - return this; - } - - @Override - public boolean handle(Player p, Component component, int opcode, int button, int slot, int itemId) { - switch (button) { - case 10:// brightness - int brightness = button - 7; - p.getSettings().setBrightness(brightness); - break; - case 11: - case 12: - case 13: - case 14: - case 15:// music - int volume = 15 - button; - p.getSettings().setMusicVolume(volume); - break; - case 16://530 settings - /* if (TutorialSession.getExtension(p).getStage() != TutorialSession.MAX_STAGE) { - p.sendMessage("You must finish the tutorial before opening the graphic settings."); - break; - }*/ - p.getInterfaceManager().open(new Component(742)); - break; - case 18://530 settings - p.getInterfaceManager().open(new Component(743)); - break; - case 17: - case 19: - case 20:// sonund - int volume1 = 20 - button; - p.getSettings().setSoundEffectVolume(volume1); - break; - case 29: - case 30: - case 31: - case 32: - case 33:// all sound - int volume11 = 33 - button; - p.getSettings().setAreaSoundVolume(volume11); - break; - case 6:// mouse - p.getSettings().toggleMouseButton(); - break; - case 4:// effects - p.getSettings().toggleChatEffects(); - break; - case 5:// private chat - p.getSettings().toggleSplitPrivateChat(); - break; - case 7:// aid - if (p.getIronmanManager().checkRestriction()) { - return true; - } - p.getSettings().toggleAcceptAid(); - break; - case 3:// run - p.getSettings().toggleRun(); - break; - case 8:// house - p.getInterfaceManager().close(); - setVarp(p, 261, getVarp(p, 261) & 0x1); - p.getInterfaceManager().openSingleTab(new Component(398)); - break; - } - return true; - } -} diff --git a/Server/src/main/content/global/handlers/iface/SkillInterface.java b/Server/src/main/content/global/handlers/iface/SkillInterface.java deleted file mode 100644 index a0ac47d21..000000000 --- a/Server/src/main/content/global/handlers/iface/SkillInterface.java +++ /dev/null @@ -1,32 +0,0 @@ -package content.global.handlers.iface; - -import core.game.component.Component; -import core.game.component.ComponentDefinition; -import core.game.component.ComponentPlugin; -import core.game.node.entity.player.Player; -import core.plugin.Initializable; -import core.plugin.Plugin; - -import static core.api.ContentAPIKt.*; - -/** - * Represents the plugin used for the skilling interface. - * @author 'Vexia - * @version 1.0 - */ -@Initializable -public final class SkillInterface extends ComponentPlugin { - - @Override - public Plugin newInstance(Object arg) throws Throwable { - ComponentDefinition.put(499, this); - return this; - } - - @Override - public boolean handle(Player player, Component component, int opcode, int button, int slot, int itemId) { - setVarbit(player, 3288, player.getAttribute("skillMenu", -1)); - setVarbit(player, 3289, button - 10); - return true; - } -} diff --git a/Server/src/main/content/global/handlers/iface/SkillTabInterface.java b/Server/src/main/content/global/handlers/iface/SkillTabInterface.java deleted file mode 100644 index 9dba5877b..000000000 --- a/Server/src/main/content/global/handlers/iface/SkillTabInterface.java +++ /dev/null @@ -1,162 +0,0 @@ -package content.global.handlers.iface; - -import core.game.component.Component; -import core.game.component.ComponentDefinition; -import core.game.component.ComponentPlugin; -import core.game.node.entity.skill.LevelUp; -import core.game.node.entity.skill.Skills; -import core.game.node.entity.player.Player; -import core.game.world.GameWorld; -import core.plugin.Initializable; -import core.plugin.Plugin; - -import static core.api.ContentAPIKt.*; - -/** - * Represents the plugin used to handle the skilling tab. - * @author Vexia - * @author Splinter - * @version 1.1 - */ -@Initializable -public final class SkillTabInterface extends ComponentPlugin { - - @Override - public Plugin newInstance(Object arg) throws Throwable { - ComponentDefinition.put(320, this); - return this; - } - - @Override - public boolean handle(Player p, Component component, int opcode, int button, int slot, int itemId) { - final SkillConfig config = SkillConfig.forId(button); - if (config == null) { - return true; - } - if (!GameWorld.getSettings().isPvp()) { - if (p.getAttribute("levelup:" + config.getSkillId(), false)) { - p.removeAttribute("levelup:" + config.getSkillId()); - LevelUp.sendFlashingIcons(p, -1); - setVarp(p, 1230, ADVANCE_CONFIGS[config.getSkillId()]); - p.getInterfaceManager().open(new Component(741)); - } else { - p.getPulseManager().clear(); - p.getInterfaceManager().open(new Component(499)); - setVarp(p, 965, config.getConfig()); - p.getAttributes().put("skillMenu", config.getConfig()); - } - } else { - if (config.getSkillId() > 6) { - p.getPacketDispatch().sendMessage("You cannot set a target level for this skill."); - return false; - } - if (p.canSpawn()) { - p.sendMessage("You must be inside Edgeville bank to set levels."); - return false; - } - } - return true; - } - - /** - * Holds all the config values of the skill advances. - */ - public static final int[] ADVANCE_CONFIGS = { - 9, 40, 17, 49, - 25, 57, 33, 641, - 659, 664, 121, 649, - 89, 114, 107, 72, - 64, 80, 673, 680, - 99, 698, 689, 705, - }; - - public enum SkillConfig { - ATTACK(125, 1, Skills.ATTACK), - STRENGTH(126, 2, Skills.STRENGTH), - DEFENCE(127, 5, Skills.DEFENCE), - RANGE(128, 3, Skills.RANGE), - PRAYER(129, 7, Skills.PRAYER), - MAGIC(130, 4, Skills.MAGIC), - RUNECRAFTING(131, 12, Skills.RUNECRAFTING), - HITPOINTS(133, 6, Skills.HITPOINTS), - AGILITY(134, 8, Skills.AGILITY), - HERBLORE(135, 9, Skills.HERBLORE), - THIEVING(136, 10, Skills.THIEVING), - CRAFTING(137, 11, Skills.CRAFTING), - FLETCHING(138, 19, Skills.FLETCHING), - SLAYER(139, 20, Skills.SLAYER), - MINING(141, 13, Skills.MINING), - SMITHING(142, 14, Skills.SMITHING), - FISHING(143, 15, Skills.FISHING), - COOKING(144, 16, Skills.COOKING), - FIREMAKING(145, 17, Skills.FIREMAKING), - WOODCUTTING(146, 18, Skills.WOODCUTTING), - FARMING(147, 21, Skills.FARMING), - CONSTRUCTION(132, 22, Skills.CONSTRUCTION), - HUNTER(140, 23, Skills.HUNTER), - SUMMONING(148, 24, Skills.SUMMONING); - - /** - * Constructs a new {@code SkillConfig} {@code Object}. - * @param button the button. - * @param config the config. - */ - SkillConfig(int button, int config, int skillId) { - this.button = button; - this.config = config; - this.skillId = skillId; - } - - /** - * Represents the button. - */ - private int button; - - /** - * Represents the config. - */ - private int config; - - /** - * The skill id. - */ - private int skillId; - - /** - * Gets the skill config. - * @param id the id. - * @return the skill config. - */ - public static SkillConfig forId(int id) { - for (SkillConfig config : SkillConfig.values()) { - if (config.button == id) - return config; - } - return null; - } - - /** - * Gets the button. - * @return the button. - */ - public int getButton() { - return button; - } - - /** - * Gets the config. - * @return the config. - */ - public int getConfig() { - return config; - } - - /** - * Gets the skill id. - * @return The skill id. - */ - public int getSkillId() { - return skillId; - } - } -} diff --git a/Server/src/main/content/global/handlers/iface/tabs/ClanTabInterface.kt b/Server/src/main/content/global/handlers/iface/tabs/ClanTabInterface.kt new file mode 100644 index 000000000..c46293f59 --- /dev/null +++ b/Server/src/main/content/global/handlers/iface/tabs/ClanTabInterface.kt @@ -0,0 +1,109 @@ +package content.global.handlers.iface.tabs + +import core.api.sendInputDialogue +import core.api.sendMessage +import core.game.interaction.InterfaceListener +import core.game.system.communication.ClanRank +import core.game.system.communication.ClanRepository +import core.net.amsc.MSPacketRepository +import core.net.amsc.WorldCommunicator +import core.tools.StringUtils +import org.rs09.consts.Components + +class ClanTabInterface : InterfaceListener { + override fun defineInterfaceListeners() { + on(Components.CLANJOIN_589) { player, _, _, buttonID, _, _ -> + if (buttonID == 9) { + if (player.interfaceManager.opened != null) { + sendMessage(player, "Please close the interface you have open before using 'Clan Setup'") + } else { + ClanRepository.openSettings(player) + } + } + + if (buttonID == 14) { + player.communication.toggleLootshare(player) + } + return@on true + } + + on(Components.CLANSETUP_590) { player, _, opcode, buttonID, _, _ -> + val clan = ClanRepository.get(player.name, true) + + when (buttonID) { + 22 -> { + if (opcode == 155) { + sendInputDialogue(player, false, "Enter clan prefix:", ) { value -> + val clanName = StringUtils.formatDisplayName(value.toString()) + + if (WorldCommunicator.isEnabled()) { MSPacketRepository.sendClanRename(player, clanName) } + + if (clan.name == "Chat disabled") { + sendMessage(player, "Your clan channel has now been enabled!") + sendMessage(player, "Join your channel by clicking 'Join Chat' and typing: ${player.username}") + } + + clan.name = clanName + player.communication.clanName = clanName + clan.updateSettings(player) + clan.update() + } + } + + if (opcode == 196) { + clan.name = "Chat disabled" + player.communication.clanName = "" + if (WorldCommunicator.isEnabled()) { MSPacketRepository.sendClanRename(player, player.communication.clanName) } + clan.updateSettings(player) + clan.delete() + } + } + + 23 -> { + clan.joinRequirement = getRank(opcode) + player.communication.joinRequirement = clan.joinRequirement + MSPacketRepository.setClanSetting(player, 0, clan.joinRequirement) + } + + 24 -> { + clan.messageRequirement = getRank(opcode) + player.communication.messageRequirement = clan.messageRequirement + MSPacketRepository.setClanSetting(player, 1, clan.messageRequirement) + } + + 25 -> { + clan.kickRequirement = getRank(opcode) + player.communication.kickRequirement = clan.kickRequirement + MSPacketRepository.setClanSetting(player, 2, clan.kickRequirement) + } + + 26 -> { + clan.lootRequirement = if (opcode == 155) ClanRank.ADMINISTRATOR else getRank(opcode) + player.communication.lootRequirement = clan.lootRequirement + MSPacketRepository.setClanSetting(player, 3, clan.lootRequirement) + } + + 33 -> sendMessage(player, "CoinShare is not available.") + } + + clan.updateSettings(player) + clan.update() + return@on true + } + } + + fun getRank(opcode: Int): ClanRank { + return when (opcode) { + 155 -> ClanRank.NONE + 196 -> ClanRank.FRIEND + 124 -> ClanRank.RECRUIT + 199 -> ClanRank.CORPORAL + 234 -> ClanRank.SERGEANT + 168 -> ClanRank.LIEUTENANT + 166 -> ClanRank.CAPTAIN + 64 -> ClanRank.GENERAL + 53 -> ClanRank.OWNER + else -> ClanRank.NONE + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/handlers/iface/CombatTabInterface.java b/Server/src/main/content/global/handlers/iface/tabs/CombatTabInterface.java similarity index 98% rename from Server/src/main/content/global/handlers/iface/CombatTabInterface.java rename to Server/src/main/content/global/handlers/iface/tabs/CombatTabInterface.java index bd3938d5c..0ca513102 100644 --- a/Server/src/main/content/global/handlers/iface/CombatTabInterface.java +++ b/Server/src/main/content/global/handlers/iface/tabs/CombatTabInterface.java @@ -1,4 +1,4 @@ -package content.global.handlers.iface; +package content.global.handlers.iface.tabs; import core.game.component.Component; import core.game.component.ComponentDefinition; diff --git a/Server/src/main/content/global/handlers/iface/tabs/EmoteTabInterface.kt b/Server/src/main/content/global/handlers/iface/tabs/EmoteTabInterface.kt new file mode 100644 index 000000000..db5efdd84 --- /dev/null +++ b/Server/src/main/content/global/handlers/iface/tabs/EmoteTabInterface.kt @@ -0,0 +1,14 @@ +package content.global.handlers.iface.tabs + +import core.game.interaction.InterfaceListener +import core.game.node.entity.player.link.emote.Emotes +import org.rs09.consts.Components + +class EmoteTabInterface : InterfaceListener { + override fun defineInterfaceListeners() { + on(Components.EMOTES_464) { player, _, _, buttonID, _, _ -> + Emotes.handle(player, buttonID) + return@on true + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/handlers/iface/tabs/EquipmentTabInterface.kt b/Server/src/main/content/global/handlers/iface/tabs/EquipmentTabInterface.kt new file mode 100644 index 000000000..e3dd69b0e --- /dev/null +++ b/Server/src/main/content/global/handlers/iface/tabs/EquipmentTabInterface.kt @@ -0,0 +1,136 @@ +package content.global.handlers.iface.tabs + +import content.global.skill.summoning.familiar.BurdenBeast +import core.api.* + +import core.game.container.access.InterfaceContainer +import core.game.container.impl.EquipmentContainer +import core.game.global.action.EquipHandler +import core.game.interaction.IntType +import core.game.interaction.InteractionListeners +import core.game.interaction.InterfaceListener +import core.game.node.entity.combat.DeathTask +import core.game.node.entity.player.Player +import core.game.node.entity.player.link.prayer.PrayerType +import core.tools.Log +import org.rs09.consts.Components +import org.rs09.consts.Items + +class EquipmentTabInterface : InterfaceListener { + override fun defineInterfaceListeners() { + onOpen(ITEMS_KEPT_ON_DEATH_102) { player, component -> + /** + * (Highlight white items are auto destroyed on death Enum#616 (Items kept on death interface) TODO: Parse server sided + * CS2 Script 118 - Items kept on death interface + * Credit Woahscam for figuring this all out. + * @arg_0 (Int): Zone type - 0 Default/1 Safe/2 POH/3 Castle Wars/4 Trouble Brewing/5 Barbarian Assault + * @arg_1 (Int): Amount of items kept on death - 0/1/3/4 + * @arg_2 (Object): Item kept on death - slot 0 item id + * @arg_3 (Object): Item kept on death - slot 1 item id + * @arg_4 (Object): Item kept on death - slot 2 item id + * @arg_5 (Object): Item kept on death - slot 3 item id + * @arg_6 (Int): Player is skulled - 0 Not Skulled/1 Skulled + * @arg_7 (Int): Player has BoB summoned with items - 0 BoB not summoned or has no items /1 BoB summoned with items + * @arg_8 (String): String to append based on amount of items kept on death. + */ + val zoneType = player.zoneMonitor.type + val itemsKeptOnDeath = DeathTask.getContainers(player)[0] + val amountKeptOnDeath = if (!player.skullManager.isSkulled && itemsKeptOnDeath.itemCount() < 3) { + if (player.prayer[PrayerType.PROTECT_ITEMS]) 4 else 3 + } else { + itemsKeptOnDeath.itemCount() + } + val slot0 = itemsKeptOnDeath.getId(0) + val slot1 = itemsKeptOnDeath.getId(1) + val slot2 = itemsKeptOnDeath.getId(2) + val slot3 = itemsKeptOnDeath.getId(3) + val hasSkull = if (player.skullManager.isSkulled) 1 else 0 + val beast: BurdenBeast? = if (player.familiarManager.hasFamiliar() && player.familiarManager.familiar.isBurdenBeast) player.familiarManager.familiar as BurdenBeast else null + val hasBob = if (beast != null && !beast.container.isEmpty) 1 else 0 + val message = "You are skulled." + val cs2Args = arrayOf(hasBob, hasSkull, slot3, slot2, slot1, slot0, amountKeptOnDeath, zoneType, message) + + if (amountKeptOnDeath > 4 && zoneType == 0) { + log(this::class.java, Log.ERR, "Items kept on death interface should not contain more than 4 items when not in a safe zone!") + } + + player.packetDispatch.sendRunScript(118, "siiooooii", *cs2Args) + + val settings = IfaceSettingsBuilder().enableAllOptions().build() + player.packetDispatch.sendIfaceSettings(settings, 18, component.id, 0, itemsKeptOnDeath.itemCount()) + player.packetDispatch.sendIfaceSettings(settings, 21, component.id, 0, DeathTask.getContainers(player)[1].itemCount()) + return@onOpen true + } + + on(Components.WORNITEMS_387) { player, component, opcode, buttonID, slot, itemID -> + when (buttonID) { + 28 -> { + if (opcode == 81) EquipHandler.unequip(player, slot, itemID) + if (opcode == 206) operateItem(player, slot) + } + 52 -> openInterface(player, ITEMS_KEPT_ON_DEATH_102) + 55 -> openInterface(player, Components.EQUIP_SCREEN2_667) + } + return@on true + } + + onOpen(Components.EQUIP_SCREEN2_667) { player, component -> + val settings = IfaceSettingsBuilder().enableAllOptions().build() + player.packetDispatch.sendIfaceSettings(settings, 14, component.id, 0, 13) + EquipmentContainer.update(player) + openSingleTab(player, Components.INVENTORY_WEAR2_670) + return@onOpen true + } + + on(Components.EQUIP_SCREEN2_667) { player, _, opcode, buttonID, slot, itemID -> + if (buttonID == 14) { + when (opcode) { + 9 -> sendMessage(player, player.equipment.get(slot).definition.examine) + 155 -> EquipHandler.unequip(player, slot, itemID) + 196 -> operateItem(player, slot) + } + } + return@on true + } + + onClose(Components.EQUIP_SCREEN2_667) { player, _ -> + closeTabInterface(player) + return@onClose true + } + + onOpen(Components.INVENTORY_WEAR2_670) { player, component -> + InterfaceContainer.generateItems(player, player.inventory.toArray(), arrayOf("Equip"), component.id, 0, 7, 4, 93) + return@onOpen true + } + + on(Components.INVENTORY_WEAR2_670) { player, _, opcode, _, slot, _ -> + if (opcode == 9) sendMessage(player, player.inventory.get(slot).definition.examine) + if (opcode == 155) equipItem(player, slot) + return@on true + } + } + + private fun equipItem(player: Player, slot: Int) { + val item = player.inventory.get(slot) ?: return + + if (item.definition.options.any { it in arrayOf("Equip", "Wield", "Wear") } || item.id == Items.BEER_1917) { + InteractionListeners.run(item.id, IntType.ITEM, "equip", player, item) + } else { + sendMessage(player, "You can't wear that.") + } + } + + private fun operateItem(player: Player, slot: Int) { + val item = player.equipment.get(slot) ?: return + + when { + InteractionListeners.run(item.id, IntType.ITEM, "operate", player, item) -> return + item.operateHandler?.handle(player, item, "operate") == true -> return + else -> sendMessage(player, "You can't operate that.") + } + } + + companion object { + private const val ITEMS_KEPT_ON_DEATH_102 = 102 + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/handlers/iface/tabs/LogoutTabInterface.kt b/Server/src/main/content/global/handlers/iface/tabs/LogoutTabInterface.kt new file mode 100644 index 000000000..f40ccd4a6 --- /dev/null +++ b/Server/src/main/content/global/handlers/iface/tabs/LogoutTabInterface.kt @@ -0,0 +1,34 @@ +package content.global.handlers.iface.tabs + +import core.api.sendMessage +import core.game.interaction.InterfaceListener +import core.game.world.repository.Repository +import org.rs09.consts.Components + +class LogoutTabInterface : InterfaceListener { + override fun defineInterfaceListeners() { + on(Components.LOGOUT_182) { player, _, _, buttonID, _, _ -> + if (buttonID == 6) { + return@on when { + !player.zoneMonitor.canLogout() -> true + + player.inCombat() -> { + sendMessage(player, "You can't log out until 10 seconds after the end of combat.") + true + } + + player.isTeleporting -> { + sendMessage(player, "Please finish your teleport before logging out.") + true + } + + else -> { + Repository.disconnectionQueue.add(player) + true + } + } + } + return@on true + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/handlers/iface/tabs/MagicTabInterface.kt b/Server/src/main/content/global/handlers/iface/tabs/MagicTabInterface.kt new file mode 100644 index 000000000..5cdd88867 --- /dev/null +++ b/Server/src/main/content/global/handlers/iface/tabs/MagicTabInterface.kt @@ -0,0 +1,23 @@ +package content.global.handlers.iface.tabs + +import content.global.skill.magic.SpellListener +import content.global.skill.magic.SpellListeners +import core.api.getAttribute +import core.game.interaction.InterfaceListener +import core.game.node.entity.combat.spell.MagicSpell +import core.game.node.entity.player.link.SpellBookManager.SpellBook +import core.game.world.GameWorld + +class MagicTabInterface : InterfaceListener { + override fun defineInterfaceListeners() { + SpellBook.values().forEach { + on(it.interfaceId) { player, _, _, buttonID, _, _ -> + if (GameWorld.ticks < getAttribute(player, "magic:delay", -1)) return@on true + + SpellListeners.run(buttonID, SpellListener.NONE, it.name.lowercase(), player) + + return@on MagicSpell.castSpell(player, it, buttonID, player) + } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/handlers/iface/tabs/MusicTabInterface.kt b/Server/src/main/content/global/handlers/iface/tabs/MusicTabInterface.kt new file mode 100644 index 000000000..76643299c --- /dev/null +++ b/Server/src/main/content/global/handlers/iface/tabs/MusicTabInterface.kt @@ -0,0 +1,38 @@ +package content.global.handlers.iface.tabs + +import core.api.sendMessage +import core.game.interaction.InterfaceListener +import core.game.node.entity.player.link.music.MusicEntry +import org.rs09.consts.Components + +class MusicTabInterface : InterfaceListener { + override fun defineInterfaceListeners() { + on(Components.MUSIC_V3_187) { player, _, opcode, buttonID, slot, _ -> + if (opcode == 155) { + if (buttonID == 11) { + player.musicPlayer.toggleLooping() + return@on true + } + + if (buttonID == 1) { + if (player.musicPlayer.unlocked[slot] != null) { + player.musicPlayer.play(player.musicPlayer.unlocked[slot]) + return@on true + } + + if (player.isAdmin) { + for (entry in MusicEntry.getSongs().values) { + if (entry.index == slot) { + player.musicPlayer.unlock(entry.id) + } + } + } else { + sendMessage(player, "You have not unlocked this piece of music yet!") + } + return@on true + } + } + return@on true + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/handlers/iface/tabs/PrayerTabInterface.kt b/Server/src/main/content/global/handlers/iface/tabs/PrayerTabInterface.kt new file mode 100644 index 000000000..684001eaa --- /dev/null +++ b/Server/src/main/content/global/handlers/iface/tabs/PrayerTabInterface.kt @@ -0,0 +1,14 @@ +package content.global.handlers.iface.tabs + +import core.game.interaction.InterfaceListener +import core.game.node.entity.player.link.prayer.PrayerType +import org.rs09.consts.Components + +class PrayerTabInterface : InterfaceListener { + override fun defineInterfaceListeners() { + on(Components.PRAYER_271) { player, _, _, buttonID, _, _ -> + val prayer = PrayerType.get(buttonID) ?: return@on true + return@on player.prayer.toggle(prayer) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/handlers/iface/tabs/QuestTabInterface.kt b/Server/src/main/content/global/handlers/iface/tabs/QuestTabInterface.kt new file mode 100644 index 000000000..2a9703946 --- /dev/null +++ b/Server/src/main/content/global/handlers/iface/tabs/QuestTabInterface.kt @@ -0,0 +1,36 @@ +package content.global.handlers.iface.tabs + +import content.global.handlers.iface.tabs.QuestTabUtils.showRequirementsInterface +import core.api.openInterface +import core.game.component.Component +import core.game.interaction.InterfaceListener +import core.game.node.entity.player.link.diary.DiaryType +import org.rs09.consts.Components + +class QuestTabInterface : InterfaceListener { + override fun defineInterfaceListeners() { + on(Components.QUESTJOURNAL_V2_274) { player, _, _, buttonID, _, _ -> + if (buttonID == 3) { + player.achievementDiaryManager.openTab() + } else { + val quest = player.questRepository.forButtonId(buttonID) + if (quest != null) { + openInterface(player, Components.QUESTJOURNAL_SCROLL_275) + quest.drawJournal(player, quest.getStage(player)) + } else { + showRequirementsInterface(player, buttonID) + } + } + return@on true + } + + on(Components.AREA_TASK_259) { player, _, _, buttonID, _, _ -> + if (buttonID == 8) { + player.interfaceManager.openTab(2, Component(Components.QUESTJOURNAL_V2_274)) + } else { + player.achievementDiaryManager.getDiary(DiaryType.forChild(buttonID))?.open(player) + } + return@on true + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/handlers/iface/QuestTabUtils.kt b/Server/src/main/content/global/handlers/iface/tabs/QuestTabUtils.kt similarity index 99% rename from Server/src/main/content/global/handlers/iface/QuestTabUtils.kt rename to Server/src/main/content/global/handlers/iface/tabs/QuestTabUtils.kt index 3af184404..40e410348 100644 --- a/Server/src/main/content/global/handlers/iface/QuestTabUtils.kt +++ b/Server/src/main/content/global/handlers/iface/tabs/QuestTabUtils.kt @@ -1,4 +1,4 @@ -package content.global.handlers.iface +package content.global.handlers.iface.tabs import core.game.requirement.* import core.api.* @@ -242,4 +242,4 @@ object QuestTabUtils { } return quest as String } -} +} \ No newline at end of file diff --git a/Server/src/main/content/global/handlers/iface/tabs/SettingsTabInterface.kt b/Server/src/main/content/global/handlers/iface/tabs/SettingsTabInterface.kt new file mode 100644 index 000000000..29a1669e9 --- /dev/null +++ b/Server/src/main/content/global/handlers/iface/tabs/SettingsTabInterface.kt @@ -0,0 +1,35 @@ +package content.global.handlers.iface.tabs + +import core.api.* +import core.game.interaction.InterfaceListener +import core.game.node.entity.player.link.IronmanMode +import org.rs09.consts.Components + +class SettingsTabInterface : InterfaceListener { + override fun defineInterfaceListeners() { + on(Components.OPTIONS_261) { player, _, _, buttonID, _, _ -> + when (buttonID) { + RUN -> player.settings.toggleRun() + CHAT_EFFECTS -> player.settings.toggleChatEffects() + SPLIT_PM -> player.settings.toggleSplitPrivateChat() + MOUSE -> player.settings.toggleMouseButton() + AID -> restrictForIronman(player, IronmanMode.STANDARD) { player.settings.toggleAcceptAid() } + HOUSE -> openSingleTab(player, Components.POH_HOUSE_OPTIONS_398) + GRAPHICS -> openInterface(player, Components.GRAPHICS_OPTIONS_742) + AUDIO -> openInterface(player, Components.SOUND_OPTIONS_743) + } + return@on true + } + } + + companion object { + const val RUN = 3 + const val CHAT_EFFECTS = 4 + const val SPLIT_PM = 5 + const val MOUSE = 6 + const val AID = 7 + const val HOUSE = 8 + const val GRAPHICS = 16 + const val AUDIO = 18 + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/handlers/iface/tabs/StatsTabInterface.kt b/Server/src/main/content/global/handlers/iface/tabs/StatsTabInterface.kt new file mode 100644 index 000000000..fbe1effc4 --- /dev/null +++ b/Server/src/main/content/global/handlers/iface/tabs/StatsTabInterface.kt @@ -0,0 +1,87 @@ +package content.global.handlers.iface.tabs + +import core.api.* +import core.game.interaction.InterfaceListener +import core.game.node.entity.skill.LevelUp +import core.game.node.entity.skill.Skills +import org.rs09.consts.Components + + +class StatsTabInterface : InterfaceListener { + override fun defineInterfaceListeners() { + on(Components.STATS_320) { player, _, _, buttonID, _, _ -> + val config = skillMap[buttonID] ?: return@on true + + if (getAttribute(player, "levelup:${config.skillID}", false)) { + removeAttributes(player, "levelup:${config.skillID}") + LevelUp.sendFlashingIcons(player, -1) + setVarp(player, 1230, ADVANCE_CONFIGS[config.skillID]) + openInterface(player, 741) + } else { + openInterface(player, Components.SKILL_GUIDE_V2_499) + setVarp(player, 965, config.configID) + setAttribute(player, "skillMenu", config.configID) + } + return@on true + } + + on(Components.SKILL_GUIDE_V2_499) { player, _, _, buttonID, _, _ -> + setVarbit(player, 3288, getAttribute(player, "skillMenu", -1)) + setVarbit(player, 3289, buttonID - 10) + return@on true + } + + on(LEVEL_UP_INTERFACE_740) { player, component, opcode, buttonID, slot, itemID -> + if (buttonID == 3) { + closeInterface(player) + } + return@on true + } + } + + enum class SkillConfig(val buttonID: Int, val configID: Int, val skillID: Int) { + ATTACK(125, 1, Skills.ATTACK), + STRENGTH(126, 2, Skills.STRENGTH), + DEFENCE(127, 5, Skills.DEFENCE), + RANGE(128, 3, Skills.RANGE), + PRAYER(129, 7, Skills.PRAYER), + MAGIC(130, 4, Skills.MAGIC), + RUNECRAFT(131, 12, Skills.RUNECRAFTING), + HITPOINTS(133, 6, Skills.HITPOINTS), + AGILITY(134, 8, Skills.AGILITY), + HERBLORE(135, 9, Skills.HERBLORE), + THIEVING(136, 10, Skills.THIEVING), + CRAFTING(137, 11, Skills.CRAFTING), + FLETCHING(138, 19, Skills.FLETCHING), + SLAYER(139, 20, Skills.SLAYER), + MINING(141, 13, Skills.MINING), + SMITHING(142, 14, Skills.SMITHING), + FISHING(143, 15, Skills.FISHING), + COOKING(144, 16, Skills.COOKING), + FIREMAKING(145, 17, Skills.FIREMAKING), + WOODCUTTING(146, 18, Skills.WOODCUTTING), + FARMING(147, 21, Skills.FARMING), + CONSTRUCTION(132, 22, Skills.CONSTRUCTION), + HUNTER(140, 23, Skills.HUNTER), + SUMMONING(148, 24, Skills.SUMMONING), + } + + companion object { + private const val LEVEL_UP_INTERFACE_740 = 740 + val skillMap= HashMap() + val ADVANCE_CONFIGS = intArrayOf( + 9, 40, 17, 49, + 25, 57, 33, 641, + 659, 664, 121, 649, + 89, 114, 107, 72, + 64, 80, 673, 680, + 99, 698, 689, 705 + ) + + init { + for (skill in SkillConfig.values()) { + skillMap[skill.buttonID] = skill + } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/construction/HouseZone.java b/Server/src/main/content/global/skill/construction/HouseZone.java index ccd426c19..83bbd1da2 100644 --- a/Server/src/main/content/global/skill/construction/HouseZone.java +++ b/Server/src/main/content/global/skill/construction/HouseZone.java @@ -3,12 +3,11 @@ package content.global.skill.construction; import core.game.node.entity.Entity; import core.game.node.entity.player.Player; -import core.game.world.map.build.DynamicRegion; import core.game.world.map.zone.MapZone; import core.game.world.map.RegionManager; import core.game.world.map.Region; import core.game.system.task.Pulse; -import core.game.world.map.zone.ZoneRestriction; +import core.game.world.map.zone.ZoneType; import static core.api.ContentAPIKt.*; @@ -44,6 +43,7 @@ public final class HouseZone extends MapZone { @Override public void configure() { + setZoneType(ZoneType.P_O_H.getId()); unregisterOldRegions(); registerRegion(house.getHouseRegion().getId()); if (house.getDungeonRegion() != null) { @@ -82,7 +82,7 @@ public final class HouseZone extends MapZone { if (e instanceof Player) { Player p = (Player) e; HouseManager.leave(p); - return true; + return true; } return super.death(e, killer); } diff --git a/Server/src/main/content/minigame/barbassault/BarbAssaultArea.kt b/Server/src/main/content/minigame/barbassault/BarbAssaultArea.kt new file mode 100644 index 000000000..7c95758ba --- /dev/null +++ b/Server/src/main/content/minigame/barbassault/BarbAssaultArea.kt @@ -0,0 +1,18 @@ +package content.minigame.barbassault + +import core.api.MapArea +import core.api.getRegionBorders +import core.game.node.entity.Entity +import core.game.world.map.zone.ZoneBorders +import core.game.world.map.zone.ZoneType + +class BarbAssaultArea : MapArea { + override fun defineAreaBorders(): Array { + return arrayOf(getRegionBorders(7509)) + } + + override fun areaEnter(entity: Entity) { + zone.zoneType = ZoneType.BARBARIAN_ASSAULT.id + super.areaEnter(entity) + } +} \ No newline at end of file diff --git a/Server/src/main/content/minigame/castlewars/areas/CastleWarsGameArea.kt b/Server/src/main/content/minigame/castlewars/areas/CastleWarsGameArea.kt index 2b32486aa..fb44ea7d9 100644 --- a/Server/src/main/content/minigame/castlewars/areas/CastleWarsGameArea.kt +++ b/Server/src/main/content/minigame/castlewars/areas/CastleWarsGameArea.kt @@ -7,6 +7,7 @@ import core.game.node.entity.player.Player import core.game.node.item.Item import core.game.world.map.Location import core.game.world.map.zone.ZoneBorders +import core.game.world.map.zone.ZoneType import core.tools.Log import core.tools.ticksPerMinute import org.rs09.consts.Components @@ -70,6 +71,7 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener { override fun areaEnter(entity: Entity) { val player = entity as? Player ?: return super.areaEnter(player) + zone.zoneType = ZoneType.CASTLE_WARS.id registerTimer (player, spawnTimer("teleblock", (CastleWars.gameTimeMinutes)*60*2)) if (saradominPlayers.contains(player)) { diff --git a/Server/src/main/content/minigame/troublebrewing/TroubleBrewingArea.kt b/Server/src/main/content/minigame/troublebrewing/TroubleBrewingArea.kt new file mode 100644 index 000000000..55a299b4e --- /dev/null +++ b/Server/src/main/content/minigame/troublebrewing/TroubleBrewingArea.kt @@ -0,0 +1,18 @@ +package content.minigame.troublebrewing + +import core.api.MapArea +import core.api.getRegionBorders +import core.game.node.entity.Entity +import core.game.world.map.zone.ZoneBorders +import core.game.world.map.zone.ZoneType + +class TroubleBrewingArea : MapArea { + override fun defineAreaBorders(): Array { + return arrayOf(getRegionBorders(15150)) + } + + override fun areaEnter(entity: Entity) { + zone.zoneType = ZoneType.TROUBLE_BREWING.id + super.areaEnter(entity) + } +} diff --git a/Server/src/main/content/region/kandarin/seers/diary/SeersVillageAchievementDiary.kt b/Server/src/main/content/region/kandarin/seers/diary/SeersVillageAchievementDiary.kt index b42c7643a..bd9e90188 100644 --- a/Server/src/main/content/region/kandarin/seers/diary/SeersVillageAchievementDiary.kt +++ b/Server/src/main/content/region/kandarin/seers/diary/SeersVillageAchievementDiary.kt @@ -9,6 +9,7 @@ import core.game.diary.DiaryLevel import core.game.event.* import core.game.node.entity.player.Player import core.game.node.entity.player.link.diary.DiaryType +import core.game.node.entity.player.link.prayer.PrayerType import core.game.node.item.Item import core.game.world.map.Location import core.game.world.map.zone.ZoneBorders @@ -30,6 +31,7 @@ class SeersVillageAchievementDiary : DiaryEventHookBase(DiaryType.SEERS_VILLAGE) private val SEERS_VILLAGE_AREA = ZoneBorders(2687, 3455, 2742, 3507) private val SEERS_BANK_AREA = ZoneBorders(2721, 3490, 2730, 3493) private val SEERS_COAL_TRUCKS_AREA = ZoneBorders(2690, 3502, 2699, 3508) + private val SEERS_COURTHOUSE_AREA = ZoneBorders(2732, 3467, 2739, 3471) private val RANGING_GUILD_LOCATION = Location(2657, 3439) @@ -352,4 +354,14 @@ class SeersVillageAchievementDiary : DiaryEventHookBase(DiaryType.SEERS_VILLAGE) } } } + + override fun onAreaVisited(player: Player) { + if (inBorders(player, SEERS_COURTHOUSE_AREA) && player.prayer.equals(PrayerType.PIETY)) { + finishTask( + player, + DiaryLevel.HARD, + HardTasks.ENTER_SEERS_COURTHOUSE_WITH_PIETY + ) + } + } } \ No newline at end of file diff --git a/Server/src/main/core/api/ContentAPI.kt b/Server/src/main/core/api/ContentAPI.kt index ae01af745..ef1cfbad7 100644 --- a/Server/src/main/core/api/ContentAPI.kt +++ b/Server/src/main/core/api/ContentAPI.kt @@ -1918,7 +1918,7 @@ fun runTask(entity: Entity, delay: Int = 0, repeatTimes: Int = 1, task: () -> Un entity.pulseManager.run(object : Pulse(delay) { override fun pulse(): Boolean { task.invoke() - return --cycles == 0 + return --cycles <= 0 } }) } diff --git a/Server/src/main/core/cache/def/impl/ItemDefinition.java b/Server/src/main/core/cache/def/impl/ItemDefinition.java index 0474e11f8..13eb0940e 100644 --- a/Server/src/main/core/cache/def/impl/ItemDefinition.java +++ b/Server/src/main/core/cache/def/impl/ItemDefinition.java @@ -1544,27 +1544,6 @@ public class ItemDefinition extends Definition { */ private static final String[] BONUS_NAMES = { "Stab: ", "Slash: ", "Crush: ", "Magic: ", "Ranged: ", "Stab: ", "Slash: ", "Crush: ", "Magic: ", "Ranged: ", "Summoning: ", "Strength: ", "Prayer: " }; - /** - * Updates the equipment stats interface. - * @param player The player to update for. - */ - public static void statsUpdate(Player player) { - if (!player.getAttribute("equip_stats_open", false)) { - return; - } - int index = 0; - int[] bonuses = player.getProperties().getBonuses(); - for (int i = 36; i < 50; i++) { - if (i == 47) { - continue; - } - int bonus = bonuses[index]; - String bonusValue = bonus > -1 ? ("+" + bonus) : Integer.toString(bonus); - player.getPacketDispatch().sendString(BONUS_NAMES[index++] + bonusValue, 667, i); - } - player.getPacketDispatch().sendString("Attack bonus", 667, 34); - } - /** * Checks if it has a plugin. * @return {@code True} if so. diff --git a/Server/src/main/core/game/container/impl/EquipmentContainer.java b/Server/src/main/core/game/container/impl/EquipmentContainer.java index 1e5eba34b..d2eb70398 100644 --- a/Server/src/main/core/game/container/impl/EquipmentContainer.java +++ b/Server/src/main/core/game/container/impl/EquipmentContainer.java @@ -16,6 +16,7 @@ import core.plugin.Plugin; import org.jetbrains.annotations.Nullable; import core.game.interaction.InteractionListeners; import core.game.system.config.ItemConfigParser; +import org.rs09.consts.Components; import java.util.ArrayList; @@ -303,11 +304,11 @@ public final class EquipmentContainer extends Container { player.removeAttribute("dfs_spec"); player.getProperties().getCombatPulse().setHandler(null); if (!player.getSettings().isSpecialToggled()) { - setVarp(player, 301, 0); + setVarp(player, 301, 0); } } player.getAppearance().setAnimations(); - player.updateAppearance(); + player.updateAppearance(); player.getSettings().updateWeight(); updateBonuses(player); } @@ -349,7 +350,9 @@ public final class EquipmentContainer extends Container { bonuses[10] += increase; } player.getProperties().setBonuses(bonuses); - update(player); + if (player.getInterfaceManager().hasMainComponent(Components.EQUIP_SCREEN2_667)) { + update(player); + } } /** @@ -357,9 +360,6 @@ public final class EquipmentContainer extends Container { * @param player The player to update for. */ public static void update(Player player) { - if (!player.getInterfaceManager().hasMainComponent(667)) { - return; - } int index = 0; int[] bonuses = player.getProperties().getBonuses(); for (int i = 36; i < 50; i++) { @@ -370,6 +370,7 @@ public final class EquipmentContainer extends Container { String bonusValue = bonus > -1 ? ("+" + bonus) : Integer.toString(bonus); player.getPacketDispatch().sendString(BONUS_NAMES[index++] + bonusValue, 667, i); } + player.getSettings().updateWeight(); player.getPacketDispatch().sendString("Attack bonus", 667, 34); } } diff --git a/Server/src/main/core/game/node/entity/player/info/login/LoginConfiguration.java b/Server/src/main/core/game/node/entity/player/info/login/LoginConfiguration.java index 4fcc49823..fb50ee051 100644 --- a/Server/src/main/core/game/node/entity/player/info/login/LoginConfiguration.java +++ b/Server/src/main/core/game/node/entity/player/info/login/LoginConfiguration.java @@ -1,6 +1,5 @@ package core.game.node.entity.player.info.login; -import core.game.component.CloseEvent; import core.game.component.Component; import core.game.node.entity.player.Player; import core.game.node.entity.player.link.emote.Emotes; @@ -48,22 +47,17 @@ public final class LoginConfiguration { */ private static final Component LOBBY_PANE = new Component(549); + /** + * The lobby interface component. + */ + private static final Component LOBBY_INTERFACE = new Component(378); + /** * The lobby message of the week models & constant to be set for auto selecting the models */ private static final int[] MESSAGE_MODEL = {15, 16, 17, 18, 19, 20, 21, 22, 23, 405, 447, 622, 623, 679, 715, 800}; private static int messModel; - /** - * The lobby interface close event. - */ - private static final Component LOBBY_INTERFACE = new Component(378).setCloseEvent(new CloseEvent() { - @Override - public boolean close(Player player, Component c) { - return player.getLocks().isLocked("login"); - } - }); - /** * Constructs a new {@Code LoginConfiguration} {@Code Object} */ @@ -102,25 +96,25 @@ public final class LoginConfiguration { } } Repository.getLobbyPlayers().add(player); - player.getPacketDispatch().sendString(getLastLogin(player), 378, 116); - player.getPacketDispatch().sendString("Welcome to " + GameWorld.getSettings().getName(), 378, 115); - player.getPacketDispatch().sendString(" ", 378, 37); - player.getPacketDispatch().sendString("Want to stay up to date with the latest news and updates? Join our
discord by using the link below!", 378, 38); - player.getPacketDispatch().sendString(" ", 378, 39); - player.getPacketDispatch().sendString("Discord Invite", 378, 14); - player.getPacketDispatch().sendString("Discord Invite", 378, 129); - player.getPacketDispatch().sendString("Credits", 378, 94); - player.getPacketDispatch().sendString(player.getDetails().getCredits() + "", 378, 96); - player.getPacketDispatch().sendString(" ", 378, 229); - player.getPacketDispatch().sendString("Want to contribute to " + ServerConstants.SERVER_NAME + "?
Visit the GitLab using the link below!", 378, 230); - player.getPacketDispatch().sendString(" ", 378, 231); - player.getPacketDispatch().sendString("Github", 378, 240); - player.getPacketDispatch().sendString(GameWorld.getSettings().getMessage_string(), messModel, getMessageChild(messModel)); - player.getPacketDispatch().sendString("You can gain more credits by voting, reporting bugs and various other methods of contribution.", 378, 93); + setInterfaceText(player, "Welcome to " + ServerConstants.SERVER_NAME, 378, 115); + setInterfaceText(player, getLastLogin(player), 378, 116); + setInterfaceText(player, "", 378, 37); + setInterfaceText(player, "Want to stay up to date with the latest news and updates? Join our discord by using the link on our website!", 378, 38); + setInterfaceText(player, "", 378, 39); + setInterfaceText(player, "Discord Invite", 378, 14); + setInterfaceText(player, "Discord Invite", 378, 129); + setInterfaceText(player, "You can gain more credits by reporting bugs and various other methods of contribution.", 378, 93); + setInterfaceText(player, player.getDetails().getCredits() + "", 378, 96); + setInterfaceText(player, "Credits", 378, 94); + setInterfaceText(player, "", 378, 229); + setInterfaceText(player, "Want to contribute to " + ServerConstants.SERVER_NAME + "? Visit the GitLab using the link on our website!", 378, 230); + setInterfaceText(player, "", 378, 231); + setInterfaceText(player, "Github", 378, 240); + setInterfaceText(player, GameWorld.getSettings().getMessage_string(), messModel, getMessageChild(messModel)); player.getInterfaceManager().openWindowsPane(LOBBY_PANE); player.getInterfaceManager().setOpened(LOBBY_INTERFACE); - PacketRepository.send(Interface.class, new InterfaceContext(player, 549, 2, 378, true)); - PacketRepository.send(Interface.class, new InterfaceContext(player, 549, 3, messModel, true));//UPDATE `configs` SET `value`=FLOOR(RAND()*(25-10)+10) WHERE key_="messageInterface" + PacketRepository.send(Interface.class, new InterfaceContext(player, LOBBY_PANE.getId(), 2, 378, true)); + PacketRepository.send(Interface.class, new InterfaceContext(player, LOBBY_PANE.getId(), 3, messModel, true));//UPDATE `configs` SET `value`=FLOOR(RAND()*(25-10)+10) WHERE key_="messageInterface" player.getDetails().setLastLogin(System.currentTimeMillis()); } @@ -168,12 +162,11 @@ public final class LoginConfiguration { // 1050 is checked client-side for making piety/chivalry disallowed sfx, likely due to the minigame requirement. // Set it here unconditionally until the minigame is implemented. - setVarbit(player, 3909, 8, false); + if (hasRequirement(player, Quests.KINGS_RANSOM, false)) { + setVarbit(player, 3909, 8, false); + } if(ServerConstants.RULES_AND_INFO_ENABLED) RulesAndInfo.openFor(player); - /*if (GameWorld.getSettings().isPvp()) { - player.getPacketDispatch().sendString("", 226, 1); - }*/ /*if (TutorialSession.getExtension(player).getStage() != 73) { TutorialStage.load(player, TutorialSession.getExtension(player).getStage(), true); }*/ @@ -206,21 +199,14 @@ public final class LoginConfiguration { * @param player the player. Fullscreen mode Object id: */ public static final void welcome(final Player player) { - if (GameWorld.getSettings().isPvp()) { - player.getPacketDispatch().sendString("", 226, 0); - } if (player.isArtificial()) { - return; } - player.getPacketDispatch().sendMessage("Welcome to " + GameWorld.getSettings().getName() + "."); - //player.getPacketDispatch().sendMessage("You are currently playing in beta version 1.2"); + player.getPacketDispatch().sendMessage("Welcome to " + ServerConstants.SERVER_NAME + "."); if (player.getDetails().isMuted()) { player.getPacketDispatch().sendMessage("You are muted."); player.getPacketDispatch().sendMessage("To prevent further mutes please read the rules."); } -// ResourceAIPManager.get().load(player); -// ResourceAIPManager.get().save(player); } /** @@ -240,7 +226,8 @@ public final class LoginConfiguration { player.getPacketDispatch().sendRunEnergy(); player.getFamiliarManager().login(); player.getInterfaceManager().openDefaultTabs(); - player.getPacketDispatch().sendString("Friends List - World " + GameWorld.getSettings().getWorldId(), 550, 3); + setInterfaceText(player, "Friends List - " + ServerConstants.SERVER_NAME + " " + GameWorld.getSettings().getWorldId(), 550, 3); + setInterfaceText(player, "When you have finished playing " + ServerConstants.SERVER_NAME + ", always use the button below to logout safely.", 182, 0); player.getQuestRepository().syncronizeTab(player); player.getInterfaceManager().close(); player.getEmoteManager().refresh(); @@ -260,11 +247,11 @@ public final class LoginConfiguration { return 8; } else if (interfaceId == 16) { return 6; - } else if (interfaceId == 17 || interfaceId == 15 || interfaceId == 18 || interfaceId == 19 || interfaceId == 21 || interfaceId == 22 || interfaceId == 447 || interfaceId == 405) { - return 4; } else if (interfaceId == 20 || interfaceId == 623) { return 5; - } else if (interfaceId == 23 || interfaceId == 800) { + } else if (interfaceId == 15 || interfaceId == 18 || interfaceId == 19 || interfaceId == 21 || interfaceId == 22 || interfaceId == 447 || interfaceId == 405) { + return 4; + } else if (interfaceId == 17 || interfaceId == 23 || interfaceId == 800) { return 3; } else if (interfaceId == 715) { return 2; @@ -289,12 +276,8 @@ public final class LoginConfiguration { * @return the last login. */ public static String getLastLogin(Player player) { - String lastIp = player.getDetails().accountInfo.getLastUsedIp(); - if (lastIp.equals("")) { - lastIp = player.getDetails().getIpAddress(); - } - player.getDetails().accountInfo.setLastUsedIp(player.getDetails().getIpAddress()); - String string = "You last logged in @timeAgo from: " + lastIp; + player.getPacketDispatch().sendLastLoginInfo(); + String string = "You last logged in @timeAgo from:"; long time = player.getDetails().getLastLogin(); Date lastLogin = new Date(time); Date current = new Date(); diff --git a/Server/src/main/core/game/node/entity/player/link/PacketDispatch.java b/Server/src/main/core/game/node/entity/player/link/PacketDispatch.java index 302b19400..de6bb8233 100644 --- a/Server/src/main/core/game/node/entity/player/link/PacketDispatch.java +++ b/Server/src/main/core/game/node/entity/player/link/PacketDispatch.java @@ -3,7 +3,6 @@ package core.game.node.entity.player.link; import core.game.node.entity.player.Player; import core.game.node.scenery.Scenery; import core.tools.Log; -import core.tools.SystemLogger; import core.game.system.task.Pulse; import core.game.world.GameWorld; import core.game.world.map.Location; @@ -431,8 +430,11 @@ public final class PacketDispatch { PacketRepository.send(CSConfigPacket.class, new CSConfigContext(player, id, value, type, params)); } - public void resetInterface(int id) - { + public void resetInterface(int id) { PacketRepository.send(ResetInterface.class, new InterfaceContext(player, 0, 0, id, false)); } + + public void sendLastLoginInfo() { + PacketRepository.send(LastLoginInfo.class, new PlayerContext(player)); + } } diff --git a/Server/src/main/core/game/node/entity/player/link/SpellBookManager.java b/Server/src/main/core/game/node/entity/player/link/SpellBookManager.java index f5dbc2de4..7e0e456c4 100644 --- a/Server/src/main/core/game/node/entity/player/link/SpellBookManager.java +++ b/Server/src/main/core/game/node/entity/player/link/SpellBookManager.java @@ -4,9 +4,11 @@ import core.game.component.Component; import core.game.node.entity.combat.spell.MagicSpell; import core.game.node.entity.player.Player; - import java.util.HashMap; import java.util.Map; +import java.util.Objects; + +import static core.api.ContentAPIKt.setVarbit; /** * Represents a managing class of a players spell book. @@ -15,7 +17,7 @@ import java.util.Map; public final class SpellBookManager { /** - * Represents the current interface if of the spellbook. + * Represents the current interface if of the spell book. */ private int spellBook = SpellBook.MODERN.getInterfaceId(); @@ -23,25 +25,26 @@ public final class SpellBookManager { * Constructs a new {@code SpellBookManager} {@code Object}. */ public SpellBookManager() { - /** + /* * empty. */ } /** * Sets the spell book. - * @param book + * @param book the spell book. */ public void setSpellBook(SpellBook book) { this.spellBook = book.getInterfaceId(); } /** - * Updates the - * @param player + * Updates the spell book. + * @param player the player. */ public void update(Player player) { - player.getInterfaceManager().openTab(new Component(SpellBook.forInterface(this.spellBook).getInterfaceId())); + player.getInterfaceManager().openTab(new Component(spellBook)); + setVarbit(player, 357, Objects.requireNonNull(SpellBook.forInterface(spellBook)).ordinal()); } /** @@ -53,7 +56,7 @@ public final class SpellBookManager { } /** - * Represents a characters spellbook. + * Represents a characters spell book. * @author 'Vexia * @author Emperor */ diff --git a/Server/src/main/core/game/node/entity/player/link/emote/Emotes.java b/Server/src/main/core/game/node/entity/player/link/emote/Emotes.java index 00f281939..2bda2ca57 100644 --- a/Server/src/main/core/game/node/entity/player/link/emote/Emotes.java +++ b/Server/src/main/core/game/node/entity/player/link/emote/Emotes.java @@ -298,7 +298,7 @@ public enum Emotes { } /** - * Handles the reward button for an emote. + * Handles the button for an emote. * * @param player the player. * @param buttonId the button id. diff --git a/Server/src/main/core/game/node/entity/player/link/prayer/PrayerType.java b/Server/src/main/core/game/node/entity/player/link/prayer/PrayerType.java index e8fba525e..41f7dfbf2 100644 --- a/Server/src/main/core/game/node/entity/player/link/prayer/PrayerType.java +++ b/Server/src/main/core/game/node/entity/player/link/prayer/PrayerType.java @@ -1,5 +1,6 @@ package core.game.node.entity.player.link.prayer; +import content.data.Quests; import core.game.node.entity.player.link.diary.DiaryType; import core.game.node.entity.skill.SkillBonus; import core.game.node.entity.skill.Skills; @@ -221,11 +222,20 @@ public enum PrayerType { * @return True if it is permitted. */ public boolean permitted(final Player player) { - if (player.getSkills().getStaticLevel(Skills.PRAYER) < getLevel() || player.getSkills().getStaticLevel(Skills.DEFENCE) < defenceReq) { - playAudio(player, Sounds.PRAYER_OFF_2673); - player.getDialogueInterpreter().sendDialogue("You need a Prayer level of " + getLevel() + (player.getSkills().getStaticLevel(Skills.DEFENCE) < defenceReq ? (" and a Defence level of " + defenceReq) : "") + " to use " + StringUtils.formatDisplayName(name().toLowerCase().replace("_", " ")) + "."); + if (!hasLevelStat(player, Skills.PRAYER, level) || !hasLevelStat(player, Skills.DEFENCE, defenceReq)) { + sendDialogue(player, "You need a " + (!hasLevelStat(player, Skills.PRAYER, level) ? "Prayer level of " + level + (!hasLevelStat(player, Skills.DEFENCE, defenceReq) ? " and a " : "") : "") + (!hasLevelStat(player, Skills.DEFENCE, defenceReq) ? "Defence level of " + defenceReq : "") + " to use " + StringUtils.formatDisplayName(name().toLowerCase().replace("_", " ")) + "."); return false; } + + if (defenceReq == 65 || defenceReq == 70) { + if (!hasRequirement(player, Quests.KINGS_RANSOM)) { + return false; + } else { + if (getVarbit(player, 3909) != 8) { + setVarbit(player, 3909, 8, false); + } + } + } return true; } @@ -241,17 +251,12 @@ public enum PrayerType { player.getPrayer().getActive().add(this); iconify(player, getIcon(player, this)); playAudio(player, sound.id); - - if (this == PrayerType.PIETY - && new ZoneBorders(2732, 3467, 2739, 3471, 0).insideBorder(player)) { - player.getAchievementDiaryManager().finishTask(player, DiaryType.SEERS_VILLAGE, 2, 3); - } - player.dispatch (new PrayerActivatedEvent(this)); + player.dispatch (new PrayerActivatedEvent(this)); } else { player.getPrayer().getActive().remove(this); playAudio(player, Sounds.CANCEL_PRAYER_2663); findNextIcon(player); - player.dispatch (new PrayerDeactivatedEvent(this)); + player.dispatch (new PrayerDeactivatedEvent(this)); } return true; } diff --git a/Server/src/main/core/game/world/GameSettings.kt b/Server/src/main/core/game/world/GameSettings.kt index c75a55efd..7c8c6e9d5 100644 --- a/Server/src/main/core/game/world/GameSettings.kt +++ b/Server/src/main/core/game/world/GameSettings.kt @@ -88,17 +88,16 @@ class GameSettings /**"Lobby" interface * The message of the week models to display - * 15 & 22 = keys & lock || 16 = fly swat || 17 = person with question marks || 18 & 447 = wise old man - * 19 = man & woman with mouth closed || 20 = man & lock & key || 21 = closed chests - * 23 = snowmen || 405 = Construction houses || 622 = Two sets of 3 people range, mage, melee - * 623 = Woodcutting || 679 = Summoning || 715 = Easter || 800 = Halloween - * Any value that isn't one listed above = random selection + * 15 & 22 = Lock w/key & keys || 16 = Fly swatters || 17 = Man & woman with question marks + * 18 & 447 = Wise old man & woman || 19 = Man with mouth zipped & woman talking || 20 = Man & lock w/key + * 21 = Chests that open & close || 23 = Christmas || 405 = Construction houses + * 622 = Two sets 3 people range, mage, melee 623 = Woodcutting || 679 = Summoning || 715 = Easter || 800 = Halloween */ var message_model: Int, /**"Lobby" interface * The message of the week text - * The "child" for writing text to these interfaces is located inside of LoginConfiguration.java + * The "child" for writing text to these interfaces is located inside LoginConfiguration.java * method: getMessageChild */ var message_string: String diff --git a/Server/src/main/core/net/packet/PacketRepository.java b/Server/src/main/core/net/packet/PacketRepository.java index 60c6fddb0..e2656b02a 100644 --- a/Server/src/main/core/net/packet/PacketRepository.java +++ b/Server/src/main/core/net/packet/PacketRepository.java @@ -75,9 +75,10 @@ public final class PacketRepository { OUTGOING_PACKETS.put(InstancedLocationUpdate.class, new InstancedLocationUpdate()); // OUTGOING_PACKETS.put(CSConfigPacket.class, new CSConfigPacket()); // OUTGOING_PACKETS.put(Varbit.class, new Varbit()); - OUTGOING_PACKETS.put(ResetInterface.class, new ResetInterface()); + OUTGOING_PACKETS.put(ResetInterface.class, new ResetInterface()); OUTGOING_PACKETS.put(VarcUpdate.class, new VarcUpdate()); OUTGOING_PACKETS.put(InterfaceSetAngle.class, new InterfaceSetAngle()); + OUTGOING_PACKETS.put(LastLoginInfo.class, new LastLoginInfo()); } /** @@ -105,5 +106,4 @@ public final class PacketRepository { } } } - -} +} \ No newline at end of file diff --git a/Server/src/main/core/net/packet/out/LastLoginInfo.kt b/Server/src/main/core/net/packet/out/LastLoginInfo.kt new file mode 100644 index 000000000..1892cf487 --- /dev/null +++ b/Server/src/main/core/net/packet/out/LastLoginInfo.kt @@ -0,0 +1,15 @@ +package core.net.packet.out + +import core.net.packet.IoBuffer +import core.net.packet.OutgoingPacket +import core.net.packet.context.PlayerContext +import java.net.Inet4Address + +class LastLoginInfo : OutgoingPacket { + override fun send(context: PlayerContext) { + val buffer = IoBuffer(164) + buffer.imp4(Inet4Address.getByName(context.player.details.ipAddress).hashCode()) + buffer.cypherOpcode(context.player.session.isaacPair.output) + context.player.session.write(buffer) + } +} \ No newline at end of file From 2a438ce349fc958c3f2f314d743a56ee93ec7187 Mon Sep 17 00:00:00 2001 From: Cole Reilly Date: Tue, 15 Jul 2025 11:58:20 +0000 Subject: [PATCH 037/117] Fixed exception running ::completediaries --- .../game/node/entity/player/link/diary/AchievementDiary.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/src/main/core/game/node/entity/player/link/diary/AchievementDiary.java b/Server/src/main/core/game/node/entity/player/link/diary/AchievementDiary.java index ddcdddeaf..6027731bf 100644 --- a/Server/src/main/core/game/node/entity/player/link/diary/AchievementDiary.java +++ b/Server/src/main/core/game/node/entity/player/link/diary/AchievementDiary.java @@ -206,7 +206,7 @@ public class AchievementDiary { } if(complete){ completedLevels.add(level); - } else if(completedLevels.contains(level)) completedLevels.remove(level); + } else if(completedLevels.contains(level)) completedLevels.remove((Object) level); } } From 5c13610776fc42b6d012027982b1dcf16a41fdb7 Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Tue, 15 Jul 2025 12:13:02 +0000 Subject: [PATCH 038/117] Implemented farming spirit trees --- .../skill/farming/FarmerPayOptionDialogue.kt | 40 ++++++++++++++++- .../content/global/skill/farming/Farmers.kt | 6 ++- .../global/skill/farming/HealthChecker.kt | 6 ++- .../content/global/skill/farming/Patch.kt | 20 ++++++++- .../content/global/skill/farming/Plantable.kt | 5 ++- .../skill/farming/UseWithPatchHandler.kt | 20 ++++++--- .../travel/trees/GnomeSpiritTreeListener.kt | 45 ++++++++++++++++--- 7 files changed, 122 insertions(+), 20 deletions(-) diff --git a/Server/src/main/content/global/skill/farming/FarmerPayOptionDialogue.kt b/Server/src/main/content/global/skill/farming/FarmerPayOptionDialogue.kt index 7b29a5568..24c4dd90a 100644 --- a/Server/src/main/content/global/skill/farming/FarmerPayOptionDialogue.kt +++ b/Server/src/main/content/global/skill/farming/FarmerPayOptionDialogue.kt @@ -11,6 +11,14 @@ import org.rs09.consts.Items class FarmerPayOptionDialogue(val patch: Patch, val quickPay: Boolean = false): DialogueFile() { var item: Item? = null + var itemSecondary: Item? = null + var itemTertiary: Item? = null + + fun hasAllItems(): Boolean { + return (inInventory(player!!, item!!.id, item!!.amount) || inInventory(player!!, note(item!!).id, note(item!!).amount)) + && (inInventory(player!!, itemSecondary!!.id, itemSecondary!!.amount) || inInventory(player!!, note(itemSecondary!!).id, note(itemSecondary!!).amount)) + && (inInventory(player!!, itemTertiary!!.id, itemTertiary!!.amount) || inInventory(player!!, note(itemTertiary!!).id, note(itemTertiary!!).amount)) + } override fun handle(componentID: Int, buttonID: Int) { when (stage) { START_DIALOGUE -> { @@ -33,6 +41,9 @@ class FarmerPayOptionDialogue(val patch: Patch, val quickPay: Boolean = false): npc("That patch is already fully grown!", "I don't know what you want me to do with it!").also { stage = END_DIALOGUE } } else { item = patch.plantable?.protectionItem + itemSecondary = patch.plantable?.protectionItemSecondary + itemTertiary = patch.plantable?.protectionItemTertiary + val protectionText = when (item?.id) { Items.COMPOST_6032 -> if (item?.amount == 1) "bucket of compost" else "buckets of compost" Items.POTATOES10_5438 -> if (item?.amount == 1) "sack of potatoes" else "sacks of potatoes" @@ -51,10 +62,22 @@ class FarmerPayOptionDialogue(val patch: Patch, val quickPay: Boolean = false): } if (item == null) { npc("Sorry, I won't protect that.").also { stage = END_DIALOGUE } + } else if (patch.patch.type == PatchType.SPIRIT_TREE_PATCH && quickPay + && !hasAllItems()) { + val amount = if (item?.amount == 1) "one" else item?.amount + npc(FacialExpression.HAPPY, "I want $amount $protectionText, one monkey bar,", "and one ground tooth for that.") + stage = 200 } else if (quickPay && !(inInventory(player!!, item!!.id, item!!.amount) || inInventory(player!!, note(item!!).id, note(item!!).amount))) { val amount = if (item?.amount == 1) "one" else item?.amount npc(FacialExpression.HAPPY, "I want $amount $protectionText for that.") stage = 200 + } else if (patch.patch.type == PatchType.SPIRIT_TREE_PATCH && quickPay) { + showTopics( + //Found a 2011 source for quick-pay, but no earlier dialogue sources (https://www.youtube.com/watch?v=RdIcNH50v7I) + Topic("Yes", 20, true), + Topic("No", END_DIALOGUE, true), + title = "Pay the gnome?" + ) } else if (quickPay) { val amount = if (item?.amount == 1) "one" else item?.amount showTopics( @@ -62,6 +85,10 @@ class FarmerPayOptionDialogue(val patch: Patch, val quickPay: Boolean = false): Topic("No", END_DIALOGUE, true), title = "Pay $amount $protectionText?" ) + } else if (patch.patch.type == PatchType.SPIRIT_TREE_PATCH) { + val amount = if (item?.amount == 1) "one" else item?.amount + npc("If you like, but I want $amount $protectionText,", "one monkey bar, and one ground tooth for that.") + stage++ } else { val amount = if (item?.amount == 1) "one" else item?.amount npc("If you like, but I want $amount $protectionText for that.") @@ -71,7 +98,9 @@ class FarmerPayOptionDialogue(val patch: Patch, val quickPay: Boolean = false): } 1 -> { - if (!(inInventory(player!!, item!!.id, item!!.amount) || inInventory(player!!, note(item!!).id, note(item!!).amount))) { + if (patch.patch.type == PatchType.SPIRIT_TREE_PATCH && !hasAllItems()) { + player("I'm afraid I don't have any of those at the moment.").also { stage = 10 } + } else if (!(inInventory(player!!, item!!.id, item!!.amount) || inInventory(player!!, note(item!!).id, note(item!!).amount))) { player("I'm afraid I don't have any of those at the moment.").also { stage = 10 } } else { showTopics( @@ -84,7 +113,14 @@ class FarmerPayOptionDialogue(val patch: Patch, val quickPay: Boolean = false): 10 -> npc("Well, I'm not wasting my time for free.").also { stage = END_DIALOGUE } 20 -> { - if (removeItem(player!!, item) || removeItem(player!!, note(item!!))) { + if (patch.patch.type == PatchType.SPIRIT_TREE_PATCH + && (removeItem(player!!, item) || removeItem(player!!, note(item!!))) + && (removeItem(player!!, itemSecondary) || removeItem(player!!, note(itemSecondary!!))) + && (removeItem(player!!, itemTertiary) || removeItem(player!!, note(itemTertiary!!)))) { + patch.protectionPaid = true + npc("That'll do nicely, ${if (player!!.isMale) "sir" else "madam"}. Leave it with me - I'll make sure", "that tree grows up good and strong.").also { stage = END_DIALOGUE } + } else if (patch.patch.type != PatchType.SPIRIT_TREE_PATCH + && removeItem(player!!, item) || removeItem(player!!, note(item!!))) { patch.protectionPaid = true // Note: A slight change in this dialogue was seen in a December 2009 video - https://youtu.be/7gVh42ylQ48?t=138 npc("That'll do nicely, ${if (player!!.isMale) "sir" else "madam"}. Leave it with me - I'll make sure", "those crops grow for you.").also { stage = END_DIALOGUE } diff --git a/Server/src/main/content/global/skill/farming/Farmers.kt b/Server/src/main/content/global/skill/farming/Farmers.kt index 4a57feeef..206eb254b 100644 --- a/Server/src/main/content/global/skill/farming/Farmers.kt +++ b/Server/src/main/content/global/skill/farming/Farmers.kt @@ -21,7 +21,11 @@ enum class Farmers(val id: Int, val patches: Array) { FRANCIS(2327, arrayOf(FarmingPatch.ENTRANA_HOPS)), DREVEN(2335, arrayOf(FarmingPatch.CHAMPIONS_GUILD_BUSH)), TARIA(2336, arrayOf(FarmingPatch.RIMMINGTON_BUSH)), - TORRELL(2338, arrayOf(FarmingPatch.ARDOUGNE_BUSH)); + TORRELL(2338, arrayOf(FarmingPatch.ARDOUGNE_BUSH)), + FRIZZY(4560, arrayOf(FarmingPatch.PORT_SARIM_SPIRIT_TREE)), + YULF(4561, arrayOf(FarmingPatch.ETCETERIA_SPIRIT_TREE)), + PRAISTAN(4562, arrayOf(FarmingPatch.KARAMJA_SPIRIT_TREE)) + ; companion object{ @JvmField diff --git a/Server/src/main/content/global/skill/farming/HealthChecker.kt b/Server/src/main/content/global/skill/farming/HealthChecker.kt index 5c819f1a3..23b53864b 100644 --- a/Server/src/main/content/global/skill/farming/HealthChecker.kt +++ b/Server/src/main/content/global/skill/farming/HealthChecker.kt @@ -26,7 +26,7 @@ class HealthChecker : OptionHandler() { val patch = fPatch.getPatchFor(player) val type = patch.patch.type - if (type != PatchType.BUSH_PATCH && type != PatchType.FRUIT_TREE_PATCH && type != PatchType.TREE_PATCH && type != PatchType.CACTUS_PATCH) { + if (!patch.isCheckable()) { sendMessage(player, "This shouldn't be happening. Please report this.") return true } @@ -44,6 +44,10 @@ class HealthChecker : OptionHandler() { patch.setCurrentState(patch.getCurrentState() - 14) sendMessage(player, "You examine the tree for signs of disease and find that it is in perfect health.") } + PatchType.SPIRIT_TREE_PATCH -> { + patch.setCurrentState(patch.getCurrentState() - 24) + sendMessage(player, "You examine the tree for signs of disease and find that it is in perfect health.") + } PatchType.BUSH_PATCH -> { patch.setCurrentState(patch.plantable!!.value + patch.plantable!!.stages + 4) sendMessage(player, "You examine the bush for signs of disease and find that it's in perfect health.") diff --git a/Server/src/main/content/global/skill/farming/Patch.kt b/Server/src/main/content/global/skill/farming/Patch.kt index f8ce4dfce..b150b0590 100644 --- a/Server/src/main/content/global/skill/farming/Patch.kt +++ b/Server/src/main/content/global/skill/farming/Patch.kt @@ -16,6 +16,7 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl var compost = CompostType.NONE var protectionPaid = false var cropLives = 3 + val checkablePatches = arrayOf(PatchType.TREE_PATCH, PatchType.BUSH_PATCH, PatchType.FRUIT_TREE_PATCH, PatchType.SPIRIT_TREE_PATCH, PatchType.CACTUS_PATCH) fun setNewHarvestAmount() { val compostMod = when(compost) { @@ -162,6 +163,7 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl PatchType.BUSH_PATCH -> setVarbit(player, patch.varbit, 250 + (plantable!!.ordinal - Plantable.REDBERRY_SEED.ordinal)) PatchType.CACTUS_PATCH -> setVarbit(player, patch.varbit, 31) PatchType.TREE_PATCH -> setVarbit(player, patch.varbit, plantable!!.value + plantable!!.stages) + PatchType.SPIRIT_TREE_PATCH -> setVarbit(player, patch.varbit, plantable!!.value + plantable!!.stages + 24) else -> log(this::class.java, Log.WARN, "Invalid setting of isCheckHealth for patch type: " + patch.type.name + "at" + patch.name) } } else { @@ -187,6 +189,10 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl if (state != getVarbit(player, patch.varbit)) setVisualState(state) } + PatchType.SPIRIT_TREE_PATCH -> { + if(isDead) setVisualState(getSpiritTreeDeathValue()) + else if(isDiseased && !isDead) setVisualState(getSpiritTreeDiseaseValue()) + } PatchType.FRUIT_TREE_PATCH -> { if(isDead) setVisualState(getFruitTreeDeathValue()) else if(isDiseased && !isDead) setVisualState(getFruitTreeDiseaseValue()) @@ -256,6 +262,14 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl } } + private fun getSpiritTreeDiseaseValue(): Int { + return (plantable?.value ?: 0) + currentGrowthStage + 12 + } + + private fun getSpiritTreeDeathValue(): Int { + return (plantable?.value ?: 0) + currentGrowthStage + 24 + } + private fun getFruitTreeDiseaseValue(): Int { return (plantable?.value ?: 0) + currentGrowthStage + 12 } @@ -338,7 +352,7 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl return } - if((patch.type == PatchType.FRUIT_TREE_PATCH || patch.type == PatchType.TREE_PATCH || patch.type == PatchType.BUSH_PATCH || patch.type == PatchType.CACTUS_PATCH) && plantable != null && plantable?.stages == currentGrowthStage + 1){ + if(isCheckable() && plantable != null && plantable?.stages == currentGrowthStage + 1){ isCheckHealth = true } @@ -432,4 +446,8 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl (fpatch.plantable == plantable?.protectionFlower || fpatch.plantable == Plantable.forItemID(Items.WHITE_LILY_SEED_14589)) && fpatch.isGrown()) } + + fun isCheckable(): Boolean{ + return (patch.type in checkablePatches) + } } diff --git a/Server/src/main/content/global/skill/farming/Plantable.kt b/Server/src/main/content/global/skill/farming/Plantable.kt index ddb8069dd..1cf2ecdbd 100644 --- a/Server/src/main/content/global/skill/farming/Plantable.kt +++ b/Server/src/main/content/global/skill/farming/Plantable.kt @@ -3,7 +3,7 @@ package content.global.skill.farming import core.game.node.item.Item import org.rs09.consts.Items -enum class Plantable(val itemID: Int, val displayName: String, val value: Int, val stages: Int, val plantingXP: Double, val harvestXP: Double, val checkHealthXP: Double, val requiredLevel: Int, val applicablePatch: PatchType, val harvestItem: Int, val protectionItem: Item? = null, val protectionFlower: Plantable? = null) { +enum class Plantable(val itemID: Int, val displayName: String, val value: Int, val stages: Int, val plantingXP: Double, val harvestXP: Double, val checkHealthXP: Double, val requiredLevel: Int, val applicablePatch: PatchType, val harvestItem: Int, val protectionItem: Item? = null, val protectionFlower: Plantable? = null, val protectionItemSecondary: Item? = null, val protectionItemTertiary: Item? = null) { // Flowers MARIGOLD_SEED(Items.MARIGOLD_SEED_5096,"marigold seed",8,4,8.5,47.0,0.0,2,PatchType.FLOWER_PATCH,Items.MARIGOLDS_6010), @@ -80,7 +80,8 @@ enum class Plantable(val itemID: Int, val displayName: String, val value: Int, v BELLADONNA_SEED(Items.BELLADONNA_SEED_5281, "belladonna seed", 4, 4, 91.0, 128.0, 0.0, 63, PatchType.BELLADONNA_PATCH, Items.CAVE_NIGHTSHADE_2398), MUSHROOM_SPORE(Items.MUSHROOM_SPORE_5282, "mushroom spore", 4, 6, 61.5, 57.7, 0.0, 53, PatchType.MUSHROOM_PATCH, Items.MUSHROOM_6004), CACTUS_SEED(Items.CACTUS_SEED_5280, "cactus seed", 8, 7, 66.5, 25.0, 374.0, 55, PatchType.CACTUS_PATCH, Items.CACTUS_SPINE_6016), - EVIL_TURNIP_SEED(Items.EVIL_TURNIP_SEED_12148, "evil turnip seed", 4, 1, 41.0, 46.0, 0.0, 42, PatchType.EVIL_TURNIP_PATCH, Items.EVIL_TURNIP_12134) + EVIL_TURNIP_SEED(Items.EVIL_TURNIP_SEED_12148, "evil turnip seed", 4, 1, 41.0, 46.0, 0.0, 42, PatchType.EVIL_TURNIP_PATCH, Items.EVIL_TURNIP_12134), + SPIRIT_SAPLING(Items.SPIRIT_SAPLING_5375, "spirit tree sapling", 8, 12, 199.5, 0.0, 19301.0, 83, PatchType.SPIRIT_TREE_PATCH, 0, Item(Items.MONKEY_NUTS_4012, 5), null, Item(Items.MONKEY_BAR_4014, 1), Item(Items.GROUND_TOOTH_9082, 1) ) ; constructor(itemID: Int, displayName: String, value: Int, stages: Int, plantingXP: Double, harvestXP: Double, checkHealthXP: Double, requiredLevel: Int, applicablePatch: PatchType, harvestItem: Int, protectionFlower: Plantable) diff --git a/Server/src/main/content/global/skill/farming/UseWithPatchHandler.kt b/Server/src/main/content/global/skill/farming/UseWithPatchHandler.kt index 538357dd0..eb0c5de69 100644 --- a/Server/src/main/content/global/skill/farming/UseWithPatchHandler.kt +++ b/Server/src/main/content/global/skill/farming/UseWithPatchHandler.kt @@ -38,6 +38,7 @@ class UseWithPatchHandler : InteractionListener { onUseWith(IntType.SCENERY, allowedNodes.toIntArray(), *FarmingPatch.patchNodes.toIntArray()) { player, used, with -> val patch = FarmingPatch.forObject(with.asScenery()) ?: return@onUseWith true val usedItem = used.asItem() + val trimmablePatches = arrayOf(PatchType.TREE_PATCH, PatchType.BUSH_PATCH, PatchType.FRUIT_TREE_PATCH, PatchType.SPIRIT_TREE_PATCH) if (patch == FarmingPatch.TROLL_STRONGHOLD_HERB) { if (!hasRequirement(player, Quests.MY_ARMS_BIG_ADVENTURE)) @@ -79,7 +80,7 @@ class UseWithPatchHandler : InteractionListener { } SECATEURS, MAGIC_SECATEURS -> { val p = patch.getPatchFor(player) - if (patch.type == PatchType.TREE_PATCH) { + if (patch.type in trimmablePatches) { if (p.isDiseased && !p.isDead) { submitIndividualPulse(player, object: Pulse() { override fun pulse(): Boolean { @@ -131,7 +132,7 @@ class UseWithPatchHandler : InteractionListener { Items.PLANT_CURE_6036 -> { val p = patch.getPatchFor(player) val patchName = p.patch.type.displayName() - if (p.isDiseased && !p.isDead) { + if (p.isDiseased && !p.isDead && patch.type !in trimmablePatches) { sendMessage(player, "You treat the $patchName with the plant cure.") queueScript(player, 0, QueueStrength.WEAK) { stage: Int -> when (stage) { @@ -260,12 +261,17 @@ class UseWithPatchHandler : InteractionListener { }*/ val requiredItem = when (patch.type) { - PatchType.TREE_PATCH, PatchType.FRUIT_TREE_PATCH -> Items.SPADE_952 + PatchType.TREE_PATCH, PatchType.FRUIT_TREE_PATCH, PatchType.SPIRIT_TREE_PATCH -> Items.SPADE_952 PatchType.FLOWER_PATCH -> if (plantable == Plantable.SCARECROW) null else Items.SEED_DIBBER_5343 else -> Items.SEED_DIBBER_5343 } if (requiredItem != null && !inInventory(player, requiredItem)) { - sendMessage(player, "You need ${prependArticle(requiredItem.asItem().name.lowercase())} to plant that.") + sendDialogue(player, "You need ${prependArticle(requiredItem.asItem().name.lowercase())} to plant that.") + return@onUseWith true + } + val spiritTreePlanted = (getVarbit(player, 720) > 3 || getVarbit(player, 722) > 3 || getVarbit(player, 724) > 3) + if (plantable == Plantable.SPIRIT_SAPLING && spiritTreePlanted){ + sendDialogue(player, "You already have a spirit tree. You can't plant another one.") return@onUseWith true } @@ -278,7 +284,7 @@ class UseWithPatchHandler : InteractionListener { } animate(player, anim) playAudio(player, sound) - val delay = if (patch.type == PatchType.TREE_PATCH || patch.type == PatchType.FRUIT_TREE_PATCH || plantable == Plantable.SCARECROW) 3 else 0 + val delay = if (patch.type == PatchType.TREE_PATCH || patch.type == PatchType.FRUIT_TREE_PATCH || patch.type == PatchType.SPIRIT_TREE_PATCH || plantable == Plantable.SCARECROW) 3 else 0 return@queueScript delayScript(player,anim.duration + delay) } 1 -> { @@ -289,12 +295,12 @@ class UseWithPatchHandler : InteractionListener { p.plant(plantable) rewardXP(player, Skills.FARMING, plantable.plantingXP) p.setNewHarvestAmount() - if (p.patch.type == PatchType.TREE_PATCH || p.patch.type == PatchType.FRUIT_TREE_PATCH) { + if (p.patch.type == PatchType.TREE_PATCH || p.patch.type == PatchType.FRUIT_TREE_PATCH || p.patch.type == PatchType.SPIRIT_TREE_PATCH) { addItem(player, Items.PLANT_POT_5350) } val itemAmount = - if (p.patch.type == PatchType.TREE_PATCH || p.patch.type == PatchType.FRUIT_TREE_PATCH) "the" + if (p.patch.type == PatchType.TREE_PATCH || p.patch.type == PatchType.FRUIT_TREE_PATCH || p.patch.type == PatchType.SPIRIT_TREE_PATCH) "the" else if (plantItem.amount == 1) "a" else plantItem.amount val itemName = diff --git a/Server/src/main/content/global/travel/trees/GnomeSpiritTreeListener.kt b/Server/src/main/content/global/travel/trees/GnomeSpiritTreeListener.kt index 838473c20..2f6e9a7ed 100644 --- a/Server/src/main/content/global/travel/trees/GnomeSpiritTreeListener.kt +++ b/Server/src/main/content/global/travel/trees/GnomeSpiritTreeListener.kt @@ -1,9 +1,5 @@ package content.global.travel.trees -import core.api.isQuestComplete -import core.api.openDialogue -import core.api.sendDialogue -import core.api.teleport import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.node.entity.player.link.diary.DiaryType @@ -18,9 +14,10 @@ import core.game.interaction.IntType import core.game.world.GameWorld.Pulser import core.tools.END_DIALOGUE import content.data.Quests +import core.api.* class GnomeSpiritTreeListener: InteractionListener { - val spiritTrees = intArrayOf(1317,1293,1294) + val spiritTrees = intArrayOf(1317,1293,1294,8355) override fun defineListeners() { on(spiritTrees, IntType.SCENERY, "talk-to"){ player, _ -> @@ -39,7 +36,11 @@ class GnomeSpiritTreeTeleportDialogue: DialogueFile() { Location(2542, 3170, 0), Location(2461, 3444, 0), Location(2556, 3259, 0), - Location(3184, 3508, 0) + Location(3184, 3508, 0), + Location(3060, 3256, 0), //Port Sarim + Location(2613, 3856, 0), //Etceteria + Location(2800, 3203, 0) //Brimhaven + ) private val ANIMATIONS = arrayOf(Animation(7082), Animation(7084)) @@ -86,6 +87,15 @@ class GnomeSpiritTreeTeleportDialogue: DialogueFile() { stage = END_DIALOGUE return } + + var plantedSpiritTreeLocation = when { + getVarbit(player!!, 720) == 20 -> "Port Sarim" + getVarbit(player!!, 722) == 20 -> "Etceteria" + getVarbit(player!!, 724) == 20 -> "Brimhaven" + else -> null + //There should never be a case where more than one spirit tree is planted. + } + if(plantedSpiritTreeLocation == null) { when (stage) { 0 -> interpreter!!.sendOptions( "Where would you like to go?", @@ -100,6 +110,29 @@ class GnomeSpiritTreeTeleportDialogue: DialogueFile() { 3 -> sendTeleport(player!!, LOCATIONS[2]) 4 -> sendTeleport(player!!, LOCATIONS[3]) } + } + } + else when (stage) { + 0 -> interpreter!!.sendOptions( + "Where would you like to go?", + "Tree Gnome Village", + "Tree Gnome Stronghold", + "Battlefield of Khazard", + "Grand Exchange", + plantedSpiritTreeLocation + ).also { stage++ } + 1 -> when (buttonID) { + 1 -> sendTeleport(player!!, LOCATIONS[0]) + 2 -> sendTeleport(player!!, LOCATIONS[1]) + 3 -> sendTeleport(player!!, LOCATIONS[2]) + 4 -> sendTeleport(player!!, LOCATIONS[3]) + 5 -> when (plantedSpiritTreeLocation) { + "Port Sarim" -> sendTeleport(player!!,LOCATIONS[4]) + "Etceteria" -> sendTeleport(player!!,LOCATIONS[5]) + "Brimhaven" -> sendTeleport(player!!,LOCATIONS[6]) + else -> stage = END_DIALOGUE + } + } } } } From cb17786a8614460380aa0ba33f42a19e27a2f17d Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Tue, 15 Jul 2025 12:14:25 +0000 Subject: [PATCH 039/117] Relicym's Balm is now created as a 3-dose rather than 4-dose potion All Barbarian Mixes can now be consumed with appropriate effects Corrected healing for caviar Barbarian Mixes --- .../main/content/data/consumables/Consumables.java | 12 ++++++++---- .../global/handlers/item/EmptyOptionListener.kt | 4 ++-- .../content/global/skill/herblore/BarbarianPotion.kt | 2 +- .../ZogrePotionAndFletchingListeners.kt | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Server/src/main/content/data/consumables/Consumables.java b/Server/src/main/content/data/consumables/Consumables.java index 82c6650a1..58143afc0 100644 --- a/Server/src/main/content/data/consumables/Consumables.java +++ b/Server/src/main/content/data/consumables/Consumables.java @@ -342,7 +342,7 @@ public enum Consumables { ZAMMY_BREW(new Potion(new int[] {2450, 189, 191, 193}, new MultiEffect(new DamageEffect(10, true), new SkillEffect(Skills.ATTACK, 0, 0.25), new SkillEffect(Skills.STRENGTH, 0, 0.15), new SkillEffect(Skills.DEFENCE, 0, -0.1)))), ANTIFIRE(new Potion(new int[] {2452, 2454, 2456, 2458}, new AddTimerEffect("dragonfire:immunity", 600, true))), GUTH_REST(new Potion(new int[] {4417, 4419, 4421, 4423}, new MultiEffect(new RemoveTimerEffect("poison"), new EnergyEffect(5), new HealingEffect(5)))), - MAGIC_ESS(new Potion(new int[] {11491, 11489}, new SkillEffect(Skills.MAGIC,3,0))), + MAGIC_ESS(new Potion(new int[] {9021, 9022, 9023, 9024}, new SkillEffect(Skills.MAGIC,3,0))), SANFEW(new Potion(new int[] {10925, 10927, 10929, 10931}, new MultiEffect(new RestoreEffect(8,0.25, true), new AddTimerEffect("poison:immunity", secondsToTicks(90)), new RemoveTimerEffect("disease")))), SUPER_ENERGY(new Potion(new int[] {3016, 3018, 3020, 3022}, new EnergyEffect(20))), BLAMISH_OIL(new FakeConsumable(1582, new String[] {"You know... I'd really rather not."})), @@ -365,9 +365,13 @@ public enum Consumables { SUPER_ENERGY_MIX(new BarbarianMix(new int[] {11481, 11483}, new MultiEffect(new EnergyEffect(20), new HealingEffect(6)))), HUNTING_MIX(new BarbarianMix(new int[] {11517, 11519}, new MultiEffect(new SkillEffect(Skills.HUNTER, 3, 0), new HealingEffect(6)))), SUPER_STR_MIX(new BarbarianMix(new int[] {11485, 11487}, new MultiEffect(new SkillEffect(Skills.STRENGTH, 5, 0.15), new HealingEffect(6)))), - ANTIDOTE_PLUS_MIX(new BarbarianMix(new int[] {11501, 11503}, new MultiEffect(new AddTimerEffect("poison:immunity", minutesToTicks(9)), new RandomHealthEffect(3, 7)))), - ANTIP_SUPERMIX(new BarbarianMix(new int[] {11473, 11475}, new MultiEffect(new AddTimerEffect("poison:immunity", minutesToTicks(6)), new RandomHealthEffect(3, 7)))), - ANTIFIRE_MIX(new BarbarianMix(new int[] {11505, 11507}, new MultiEffect(new AddTimerEffect("dragonfire:immunity", 600, true), new RandomHealthEffect(3, 7)))), + ANTIDOTE_PLUS_MIX(new BarbarianMix(new int[] {11501, 11503}, new MultiEffect(new AddTimerEffect("poison:immunity", minutesToTicks(9)), new HealingEffect(6)))), + ANTIP_SUPERMIX(new BarbarianMix(new int[] {11473, 11475}, new MultiEffect(new AddTimerEffect("poison:immunity", minutesToTicks(6)), new HealingEffect(6)))), + ANTIFIRE_MIX(new BarbarianMix(new int[] {11505, 11507}, new MultiEffect(new AddTimerEffect("dragonfire:immunity", 600, true), new HealingEffect(6)))), + MAGIC_ESS_MIX(new BarbarianMix(new int[] {11489, 11491}, new MultiEffect(new SkillEffect(Skills.MAGIC, 3, 0), new HealingEffect(6)))), + SUPER_DEF_MIX(new BarbarianMix(new int[] {11497, 11499}, new MultiEffect(new SkillEffect(Skills.DEFENCE, 5, 0.15), new HealingEffect(6)))), + RANGING_MIX(new BarbarianMix(new int[] {11509, 11511}, new MultiEffect(new SkillEffect(Skills.RANGE, 4, 0.1), new HealingEffect(6)))), + MAGIC_MIX(new BarbarianMix(new int[] {11513, 11515}, new MultiEffect(new SkillEffect(Skills.MAGIC, 4, 0), new HealingEffect(6)))), /** Stealing creation potions */ SC_PRAYER(new Potion(new int[] {14207, 14209, 14211, 14213, 14215}, new PrayerEffect(7, 0.25))), diff --git a/Server/src/main/content/global/handlers/item/EmptyOptionListener.kt b/Server/src/main/content/global/handlers/item/EmptyOptionListener.kt index 12771ec0b..b4ae28212 100644 --- a/Server/src/main/content/global/handlers/item/EmptyOptionListener.kt +++ b/Server/src/main/content/global/handlers/item/EmptyOptionListener.kt @@ -17,7 +17,7 @@ class EmptyOptionListener : InteractionListener { override fun defineListeners() { on(EmptyItem.emptyItemList.toIntArray(), IntType.ITEM, "empty", "empty bowl", "empty dish") { player, node -> - if (node.name.contains("brew") || node.name.contains("potion") || node.name.lowercase(Locale.getDefault()).contains("poison") || node.name.lowercase(Locale.getDefault()).contains("serum") || node.name.contains("cure") || node.name.contains("mix") || node.name.contains("balm")) { + if (node.name.contains("brew") || node.name.contains("potion") || node.name.lowercase(Locale.getDefault()).contains("poison") || node.name.lowercase(Locale.getDefault()).contains("serum") || node.name.contains("cure") || node.name.contains("mix") || node.name.contains("balm") || node.name.contains("Super ")) { replaceSlot(player, node.asItem().slot, Item(EmptyItem.getEmpty(Items.POTION_195)!!), node.asItem()) playAudio(player, EmptyItem.getEmptyAudio(Items.POTION_195)!!) return@on true @@ -70,7 +70,7 @@ class EmptyOptionListener : InteractionListener { emptyItemList.add(item.fullId) } for (item in ItemDefinition.getDefinitions().values) - if (item.name.contains("potion") || item.name.contains("brew") || item.name.contains("poison") || item.name.lowercase(Locale.getDefault()).contains("serum") || item.name.contains("cure") || item.name.contains("mix") || item.name.contains("balm")) { + if (item.name.contains("potion") || item.name.contains("brew") || item.name.contains("poison") || item.name.lowercase(Locale.getDefault()).contains("serum") || item.name.contains("cure") || item.name.contains("mix") || item.name.contains("balm") || item.name.contains("Super ")) { emptyItemList.add(item.id) } } diff --git a/Server/src/main/content/global/skill/herblore/BarbarianPotion.kt b/Server/src/main/content/global/skill/herblore/BarbarianPotion.kt index 5876c4018..ce8e7202e 100644 --- a/Server/src/main/content/global/skill/herblore/BarbarianPotion.kt +++ b/Server/src/main/content/global/skill/herblore/BarbarianPotion.kt @@ -6,7 +6,7 @@ package content.global.skill.herblore; * @author treevar */ public enum class BarbarianPotion { - ATTACK_POTION(123, 4, 11429, 8.0, true), ANTI_POISION_POTION(177, 6, 11433, 12.0, true), RELIC(4846, 9, 11437, 14.0, true), STRENGTH_POTION(117, 14, 11443, 17.0, true), RESTORE_POTION(129, 24, 11449, 21.0, true), ENERGY_POTION(3012, 29, 11453, 23.0, false), DEFENCE_POTION(135, 33, 11457, 25.0, false), AGILITY_POTION(3036, 37, 11461, 27.0, false), COMBAT_POTION(9743, 40, 11445, 28.0, false), PRAYER_POTION(141, 42, 11465, 29.0, false), SUPER_ATTACK_POTION(147, 47, 11469, 33.0, false), SUPER_ANTIPOISION_POTION(183, 51, 11473, 35.0, false), FISHING_POTION(153, 53, 11477, 38.0, false), SUPER_ENERGY_POTION(3020, 56, 11481, 42.0, false), HUNTER_POTION(10002, 58, 11517, 40.0, false), SUPER_STRENGTH_POTION(159, 59, 11485, 42.0, false), SUPER_RESTORE(3028, 67, 11493, 48.0, false), SUPER_DEFENCE_POTION(165, 71, 11497, 50.0, false), ANTIDOTE_PLUS(5947, 74, 11501, 52.0, false), ANTIFIRE_POTION(2456, 75, 11505, 53.0, false), RANGING_POTION(171, 80, 11509, 54.0, false), MAGIC_POTION(3044, 83, 11513, 57.0, false), ZAMORAK_BREW(191, 85, 11521, 58.0, false); + ATTACK_POTION(123, 4, 11429, 8.0, true), ANTI_POISION_POTION(177, 6, 11433, 12.0, true), RELIC(4846, 9, 11437, 14.0, true), STRENGTH_POTION(117, 14, 11443, 17.0, true), RESTORE_POTION(129, 24, 11449, 21.0, true), ENERGY_POTION(3012, 29, 11453, 23.0, false), DEFENCE_POTION(135, 33, 11457, 25.0, false), AGILITY_POTION(3036, 37, 11461, 27.0, false), COMBAT_POTION(9743, 40, 11445, 28.0, false), PRAYER_POTION(141, 42, 11465, 29.0, false), SUPER_ATTACK_POTION(147, 47, 11469, 33.0, false), SUPER_ANTIPOISION_POTION(183, 51, 11473, 35.0, false), FISHING_POTION(153, 53, 11477, 38.0, false), SUPER_ENERGY_POTION(3020, 56, 11481, 42.0, false), HUNTER_POTION(10002, 58, 11517, 40.0, false), SUPER_STRENGTH_POTION(159, 59, 11485, 42.0, false), MAGIC_ESSENCE_POTION(9023, 61, 11489, 43.0, false), SUPER_RESTORE(3028, 67, 11493, 48.0, false), SUPER_DEFENCE_POTION(165, 71, 11497, 50.0, false), ANTIDOTE_PLUS(5947, 74, 11501, 52.0, false), ANTIFIRE_POTION(2456, 75, 11505, 53.0, false), RANGING_POTION(171, 80, 11509, 54.0, false), MAGIC_POTION(3044, 83, 11513, 57.0, false), ZAMORAK_BREW(191, 85, 11521, 58.0, false); /** * Constructs a new {@code BarbarianPotion} {@Code Object}. diff --git a/Server/src/main/content/region/kandarin/feldip/quest/zogreflesheaters/ZogrePotionAndFletchingListeners.kt b/Server/src/main/content/region/kandarin/feldip/quest/zogreflesheaters/ZogrePotionAndFletchingListeners.kt index ca14c0f1c..1db9325ff 100644 --- a/Server/src/main/content/region/kandarin/feldip/quest/zogreflesheaters/ZogrePotionAndFletchingListeners.kt +++ b/Server/src/main/content/region/kandarin/feldip/quest/zogreflesheaters/ZogrePotionAndFletchingListeners.kt @@ -46,7 +46,7 @@ class ZogrePotionAndFletchingListeners : InteractionListener { if(removeItem(player, used) && removeItem(player, with)) { sendMessage(player, "You add the snake weed to the rogues purse solution and make Relicyms Balm.") - addItem(player, Items.RELICYMS_BALM4_4842) + addItem(player, Items.RELICYMS_BALM3_4844) rewardXP(player, Skills.HERBLORE, 40.0) } return@onUseWith true From 98f662d0c61799b625c88d911aa0cf008eea57c2 Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Tue, 15 Jul 2025 12:23:58 +0000 Subject: [PATCH 040/117] Cats will no longer eat buckets Created a new command, ::petrate, for testing pet growth. ::petrate 0 is for pausing growth, 1 for 1x rate, and 2 uses the dev rate of growth (100x normal) Adult and overgrown cats will not grow hungry Removed several pets inauthentic to 2009 Dogs no longer can turn into minotaurs Wolpertingers now double yield and experience from bushes --- .../skill/farming/FruitAndBerryPicker.kt | 12 ++++- .../summoning/familiar/FamiliarManager.java | 4 ++ .../summoning/pet/KittenInteractDialogue.java | 2 +- .../global/skill/summoning/pet/Pet.java | 13 +++-- .../global/skill/summoning/pet/Pets.java | 47 +++++++++---------- .../game/system/command/sets/FunCommandSet.kt | 22 +++++++++ 6 files changed, 67 insertions(+), 33 deletions(-) diff --git a/Server/src/main/content/global/skill/farming/FruitAndBerryPicker.kt b/Server/src/main/content/global/skill/farming/FruitAndBerryPicker.kt index 50bba4a24..380334445 100644 --- a/Server/src/main/content/global/skill/farming/FruitAndBerryPicker.kt +++ b/Server/src/main/content/global/skill/farming/FruitAndBerryPicker.kt @@ -5,6 +5,7 @@ import core.cache.def.impl.SceneryDefinition import core.game.interaction.OptionHandler import core.game.node.Node import content.global.skill.summoning.familiar.GiantEntNPC +import content.global.skill.summoning.familiar.WolpertingerNPC import core.game.node.entity.player.Player import core.game.node.entity.player.link.diary.DiaryType import core.game.node.entity.skill.Skills @@ -76,8 +77,15 @@ class FruitAndBerryPicker : OptionHandler() { animate(player, animation) playAudio(player, Sounds.FARMING_PICK_2437) - addItemOrDrop(player, reward.id, reward.amount) - rewardXP(player, Skills.FARMING, plantable.harvestXP) + + if (familiar != null && familiar is WolpertingerNPC && patch.patch.type == PatchType.BUSH_PATCH) { + addItemOrDrop(player, reward.id, reward.amount * 2) + rewardXP(player, Skills.FARMING, plantable.harvestXP * 2) + } + else { + addItemOrDrop(player, reward.id, reward.amount) + rewardXP(player, Skills.FARMING, plantable.harvestXP) + } patch.setCurrentState(patch.getCurrentState() - 1) if (patch.patch.type == PatchType.CACTUS_PATCH) { diff --git a/Server/src/main/content/global/skill/summoning/familiar/FamiliarManager.java b/Server/src/main/content/global/skill/summoning/familiar/FamiliarManager.java index 3f210dd95..3e8e3b7e8 100644 --- a/Server/src/main/content/global/skill/summoning/familiar/FamiliarManager.java +++ b/Server/src/main/content/global/skill/summoning/familiar/FamiliarManager.java @@ -16,6 +16,7 @@ import core.game.node.item.Item; import core.game.world.map.Location; import core.game.world.map.zone.ZoneRestriction; import core.game.world.update.flag.context.Animation; +import org.rs09.consts.Items; import java.util.ArrayList; import java.util.HashMap; @@ -341,6 +342,9 @@ public final class FamiliarManager { for (int food : pets.getFood()) { if (food == foodId) { player.getInventory().remove(new Item(foodId)); + if (foodId == Items.BUCKET_OF_MILK_1927) { + player.getInventory().add(new Item(Items.BUCKET_1925)); + } player.getPacketDispatch().sendMessage("Your pet happily eats the " + ItemDefinition.forId(food).getName() + "."); player.animate(new Animation(827)); npc.getDetails().updateHunger(-15.0); diff --git a/Server/src/main/content/global/skill/summoning/pet/KittenInteractDialogue.java b/Server/src/main/content/global/skill/summoning/pet/KittenInteractDialogue.java index 8925c4c42..24a387d18 100644 --- a/Server/src/main/content/global/skill/summoning/pet/KittenInteractDialogue.java +++ b/Server/src/main/content/global/skill/summoning/pet/KittenInteractDialogue.java @@ -67,7 +67,7 @@ public final class KittenInteractDialogue extends DialoguePlugin { interpreter.sendDialogues(player, null, "That cat sure loves to be stroked."); stage = 99; break; - case 2:// chase-vermine + case 2:// chase-vermin end(); player.sendChat("Go on puss...kill that rat!"); boolean cant = true; diff --git a/Server/src/main/content/global/skill/summoning/pet/Pet.java b/Server/src/main/content/global/skill/summoning/pet/Pet.java index 99048c140..a115040d1 100644 --- a/Server/src/main/content/global/skill/summoning/pet/Pet.java +++ b/Server/src/main/content/global/skill/summoning/pet/Pet.java @@ -5,7 +5,6 @@ import content.global.skill.summoning.familiar.Familiar; import content.global.skill.summoning.familiar.FamiliarSpecial; import core.game.node.entity.player.Player; import core.game.node.item.Item; -import core.game.world.GameWorld; import static core.api.ContentAPIKt.*; @@ -67,10 +66,13 @@ public final class Pet extends Familiar { @Override public void handleTickActions() { final PetDetails petDetails = details; - if (getPet().getFood().length > 0) { + if (getPet().getFood().length > 0 && !pet.isGrownCat(itemId)) { if(!SkillcapePerks.isActive(SkillcapePerks.PET_MASTERY, owner)) { double amount = itemId == pet.getBabyItemId() ? 0.025 : 0.018; - if (GameWorld.getSettings().isDevMode()) { + if (owner.getAttribute("petrate",1) == 0) { + amount = 0 ; + } + else if (owner.getAttribute("petrate",1) == 2) { amount *= 100; } petDetails.updateHunger(amount); @@ -96,7 +98,10 @@ public final class Pet extends Familiar { double growth = petDetails.getGrowth(); double growthrate = pet.getGrowthRate(); if (growthrate > 0.000) { - if (GameWorld.getSettings().isDevMode()) { + if (owner.getAttribute("petrate",1) == 0) { + growthrate = 0; + } + else if (owner.getAttribute("petrate",1) == 2) { growthrate *= 100; } petDetails.updateGrowth(growthrate); diff --git a/Server/src/main/content/global/skill/summoning/pet/Pets.java b/Server/src/main/content/global/skill/summoning/pet/Pets.java index 263d04aef..7f0e3d92a 100644 --- a/Server/src/main/content/global/skill/summoning/pet/Pets.java +++ b/Server/src/main/content/global/skill/summoning/pet/Pets.java @@ -25,16 +25,6 @@ public enum Pets { */ CLOCKWORK_CAT(7771, 7772, -1, 3598, -1, -1, 0.0, 0), - /** - * The firemaker's curse pets. - */ - SEARING_FLAME(22994, -1, -1, 14769, -1, -1, 0.0, 0), GLOWING_EMBER(22993, -1, -1, 14768, -1, -1, 0.0, 0), TWISTED_FIRESTARTER(22995, -1, -1, 14770, -1, -1, 0.0, 0), WARMING_FLAME(22992, -1, -1, 14767, -1, -1, 0.0, 0), - - /** - * Troll baby pet. - */ - TROLL_BABY(23030, 23030, -1, 14846, -1, -1, 0.0, 0), - /** * A bulldog pet. */ @@ -63,7 +53,7 @@ public enum Pets { /** * A terrier pet. */ - TERRIER(12512, 12513, -1, 6958, 6859, -1, 0.0033333333333333, 4, 2132, 2134, 2136, 2138, 10816, 9986, 9978, 526), TERRIER_1(12700, 12701, -1, 7237, 7238, -1, 0.0033333333333333, 4, 2132, 2134, 2136, 2138, 10816, 9986, 9978, 526), TERRIER_2(12702, 12703, -1, 7239, 7240, -1, 0.0033333333333333, 4, 2132, 2134, 2136, 2138, 10816, 9986, 9978, 526), + TERRIER(12512, 12513, -1, 6958, 6959, -1, 0.0033333333333333, 4, 2132, 2134, 2136, 2138, 10816, 9986, 9978, 526), TERRIER_1(12700, 12701, -1, 7237, 7238, -1, 0.0033333333333333, 4, 2132, 2134, 2136, 2138, 10816, 9986, 9978, 526), TERRIER_2(12702, 12703, -1, 7239, 7240, -1, 0.0033333333333333, 4, 2132, 2134, 2136, 2138, 10816, 9986, 9978, 526), /** * A creeping hand pet. @@ -90,11 +80,6 @@ public enum Pets { */ //ABYSSAL_MINION(14651, -1, -1, 8624, -1, -1, 0.0033333333333333, 4, 592), - /** - * Rune guardian pets. - */ - RUNE_GUARDIAN(15626, -1, -1, 9656, -1, -1, 0.0033333333333333, 4), RUNE_GUARDIAN_1(15627, -1, -1, 9657, -1, -1, 0.0033333333333333, 4), RUNE_GUARDIAN_2(15628, -1, -1, 9658, -1, -1, 0.0033333333333333, 4), RUNE_GUARDIAN_3(15629, -1, -1, 9659, -1, -1, 0.0033333333333333, 4), RUNE_GUARDIAN_4(15630, -1, -1, 9660, -1, -1, 0.0033333333333333, 4), RUNE_GUARDIAN_5(15631, -1, -1, 9661, -1, -1, 0.0033333333333333, 4), RUNE_GUARDIAN_6(15632, -1, -1, 9662, -1, -1, 0.0033333333333333, 4), RUNE_GUARDIAN_7(15633, -1, -1, 9663, -1, -1, 0.0033333333333333, 4), RUNE_GUARDIAN_8(15634, -1, -1, 9664, -1, -1, 0.0033333333333333, 4), RUNE_GUARDIAN_9(15635, -1, -1, 9665, -1, -1, 0.0033333333333333, 4), RUNE_GUARDIAN_10(15636, -1, -1, 9666, -1, -1, 0.0033333333333333, 4), RUNE_GUARDIAN_11(15637, -1, -1, 9667, -1, -1, 0.0033333333333333, 4), RUNE_GUARDIAN_12(15638, -1, -1, 9668, -1, -1, 0.0033333333333333, 4), RUNE_GUARDIAN_13(15639, -1, -1, 9669, -1, -1, 0.0033333333333333, 4), - /** * Gecko pet. */ @@ -115,11 +100,6 @@ public enum Pets { */ PENGUIN(12481, 12482, -1, 6908, 6909, -1, 0.0046296296296296, 30, 321, 363, 341, 15264, 345, 377, 353, 389, 7944, 349, 331, 327, 395, 383, 317, 371, 335, 359, 15264, 15270), PENGUIN_1(12763, 12762, -1, 7313, 7314, -1, 0.0046296296296296, 30, 321, 363, 341, 15264, 345, 377, 353, 389, 7944, 349, 331, 327, 395, 383, 317, 371, 335, 359, 15264, 15270), PENGUIN_2(12765, 12764, -1, 7316, 7317, -1, 0.0046296296296296, 30, 321, 363, 341, 15264, 345, 377, 353, 389, 7944, 349, 331, 327, 395, 383, 317, 371, 335, 359, 15264, 15270), - /** - * A tooth creature pet. - */ - TOOTH_CREATURE(18671, 18669, -1, 11411, 11413, -1, 0.075757575757576, 37, 1927, 1977), - /** * A giant crab pet. */ @@ -155,11 +135,6 @@ public enum Pets { */ RACCOON(12486, 12487, -1, 6913, 6914, -1, 0.0029444444444444, 80, 321, 363, 341, 15264, 345, 377, 353, 389, 7944, 349, 331, 327, 395, 383, 317, 371, 335, 359, 15264, 15270, 2132, 2134, 2136, 2138, 10816, 9986, 9978), RACCOON_1(12734, 12735, -1, 7271, 7272, -1, 0.0029444444444444, 80, 321, 363, 341, 15264, 345, 377, 353, 389, 7944, 349, 331, 327, 395, 383, 317, 371, 335, 359, 15264, 15270, 2132, 2134, 2136, 2138, 10816, 9986, 9978), RACCOON_2(12736, 12737, -1, 7273, 7274, -1, 0.0029444444444444, 80, 321, 363, 341, 15264, 345, 377, 353, 389, 7944, 349, 331, 327, 395, 383, 317, 371, 335, 359, 15264, 15270, 2132, 2134, 2136, 2138, 10816, 9986, 9978), - /** - * A sneaker peeper pet. - */ - SNEAKER_PEEPER(19894, 19895, -1, 13089, 13090, -1, 0.05, 80, 221), - /** * A vulture pet. */ @@ -446,4 +421,24 @@ public enum Pets { return false; } } + + /** + * Checks if this pet is a grown cat + * @return a boolean, true if the pet is a grown cat + */ + public boolean isGrownCat(int id) { + switch (this) { + case CAT: + case CAT_1: + case CAT_2: + case CAT_3: + case CAT_4: + case CAT_5: + case CAT_6: + case HELLCAT: + return id == grownItemId || id == overgrownItemId; + default: + return false; + } + } } diff --git a/Server/src/main/core/game/system/command/sets/FunCommandSet.kt b/Server/src/main/core/game/system/command/sets/FunCommandSet.kt index ec69cdd72..498fe6ce5 100644 --- a/Server/src/main/core/game/system/command/sets/FunCommandSet.kt +++ b/Server/src/main/core/game/system/command/sets/FunCommandSet.kt @@ -217,6 +217,28 @@ class FunCommandSet : CommandSet(Privilege.ADMIN) { p.graphics(Graphics(369, 0)) } } + + /** + * Toggles pet hunger to off, normal, or dev + * 0 = No hunger/growth mode (hunger/growth does not progress) + * 1 = Normal hunger/growth mode (1x speed) + * 2 = Dev hunger/growth mode (100x speed) + */ + define("petrate", Privilege.ADMIN, "petrate 0-2", "Sets pet hunger and growth to off, normal, or dev."){ player, args -> + if(args.size < 2) { + notify(player, "Pet mode is currently ${player.getAttribute("petrate", 1)}") + return@define + } + val mode = args[1].toIntOrNull() ?: (-1).also { reject(player, "Please enter a valid number") } + if (mode in 0 .. 2) { + player.setAttribute("petrate", mode) + notify(player, "Setting pet mode to $mode") + } + else { + reject(player, "Only modes 0-2 are valid") + } + } + } fun bury(player: Player){ From bef103e25934ab2ba465e157fe776d947aa4a49b Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Tue, 15 Jul 2025 12:29:09 +0000 Subject: [PATCH 041/117] Implemented semi-authentic Pious Pete random event --- Server/data/configs/npc_spawns.json | 4 + .../main/content/global/ame/RandomEvents.kt | 6 + .../candlelight/CandlelightInterface.kt | 164 ++++++++++++++++++ .../events/candlelight/PiousPeteDialogue.kt | 94 ++++++++++ .../ame/events/candlelight/PiousPeteNPC.kt | 49 ++++++ Server/src/main/core/ServerConstants.kt | 3 + .../game/system/config/ServerConfigParser.kt | 1 + Server/worldprops/default.conf | 2 + 8 files changed, 323 insertions(+) create mode 100644 Server/src/main/content/global/ame/events/candlelight/CandlelightInterface.kt create mode 100644 Server/src/main/content/global/ame/events/candlelight/PiousPeteDialogue.kt create mode 100644 Server/src/main/content/global/ame/events/candlelight/PiousPeteNPC.kt diff --git a/Server/data/configs/npc_spawns.json b/Server/data/configs/npc_spawns.json index 8899eea83..e152ca446 100644 --- a/Server/data/configs/npc_spawns.json +++ b/Server/data/configs/npc_spawns.json @@ -6727,6 +6727,10 @@ "npc_id": "3205", "loc_data": "{3139,3448,0,1,3}-" }, + { + "npc_id": "3207", + "loc_data": "{1970,5002,0,1,4}-" + }, { "npc_id": "3213", "loc_data": "{2568,3334,0,1,0}-" diff --git a/Server/src/main/content/global/ame/RandomEvents.kt b/Server/src/main/content/global/ame/RandomEvents.kt index 4acf90c99..88522b4a0 100644 --- a/Server/src/main/content/global/ame/RandomEvents.kt +++ b/Server/src/main/content/global/ame/RandomEvents.kt @@ -10,6 +10,7 @@ import content.global.ame.events.evilchicken.EvilChickenNPC import content.global.ame.events.freakyforester.FreakyForesterNPC import content.global.ame.events.maze.MazeNPC import content.global.ame.events.genie.GenieNPC +import content.global.ame.events.candlelight.PiousPeteNPC import content.global.ame.events.pillory.PilloryNPC import content.global.ame.events.rickturpentine.RickTurpentineNPC import content.global.ame.events.rivertroll.RiverTrollRENPC @@ -21,6 +22,7 @@ import content.global.ame.events.strangeplant.StrangePlantNPC import content.global.ame.events.swarm.SwarmNPC import content.global.ame.events.treespirit.TreeSpiritRENPC import content.global.ame.events.zombie.ZombieRENPC +import core.ServerConstants import core.api.utils.WeightBasedTable import core.api.utils.WeightedItem @@ -29,6 +31,7 @@ import core.game.node.entity.skill.Skills enum class RandomEvents(val npc: RandomEventNPC, val loot: WeightBasedTable? = null, val skillIds: IntArray = intArrayOf(), val type: String = "") { SANDWICH_LADY(npc = SandwichLadyRENPC()), GENIE(npc = GenieNPC()), + CANDLELIGHT(npc = PiousPeteNPC(), skillIds = intArrayOf(Skills.PRAYER)), CERTER(npc = CerterNPC(), loot = WeightBasedTable.create( WeightedItem(Items.UNCUT_SAPPHIRE_1623,1,1,3.4), WeightedItem(Items.KEBAB_1971,1,1,1.7), @@ -82,6 +85,9 @@ enum class RandomEvents(val npc: RandomEventNPC, val loot: WeightBasedTable? = n private fun populateMappings() { for (event in values()) { + if (!ServerConstants.INAUTHENTIC_CANDLELIGHT_RANDOM && event == CANDLELIGHT) { + continue + } for (id in event.skillIds) { val list = skillMap[id] ?: ArrayList().also { skillMap[id] = it } list.add (event) diff --git a/Server/src/main/content/global/ame/events/candlelight/CandlelightInterface.kt b/Server/src/main/content/global/ame/events/candlelight/CandlelightInterface.kt new file mode 100644 index 000000000..249f9f194 --- /dev/null +++ b/Server/src/main/content/global/ame/events/candlelight/CandlelightInterface.kt @@ -0,0 +1,164 @@ +package content.global.ame.events.candlelight + +import core.api.* +import core.api.utils.PlayerCamera +import core.game.interaction.InteractionListener +import core.game.interaction.InterfaceListener +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import core.game.world.map.Direction +import core.game.world.map.Location +import core.game.world.map.zone.ZoneBorders +import core.game.world.map.zone.ZoneRestriction +import org.rs09.consts.Scenery + +/** + * Candlelight Interface CANDLELIGHT_178 + * + */ +class CandlelightInterface : InterfaceListener, InteractionListener, MapArea { + companion object { + const val CANDLELIGHT_INTERFACE = 178 + const val CANDLELIGHT_RETURN_LOC = "/save:original-loc" + const val CANDLELIGHT_CANDLE_ARRAY = "/save:candlelight:candle-array" + const val CANDLELIGHT_CAMERA_AT = "candlelight:camera-at" + + val CANDLE_LOC_ARRAY = arrayOf( + Location(1967, 4997), + Location(1968, 4998), + Location(1967, 4999), + Location(1968, 5000), + Location(1967, 5001), + Location(1968, 5002), + Location(1967, 5003), + Location(1968, 5004), + Location(1967, 5005), + Location(1968, 5006), + Location(1967, 5007), + ) + + fun initCandlelight(player: Player) { + val candleArray = intArrayOf(0,0,0,0,0,0,2,2,2,2,2) + candleArray.shuffle() + setAttribute(player, CANDLELIGHT_CANDLE_ARRAY, candleArray) + for (candleIndex in 0..10) { + setVarbit(player, 1771 + candleIndex, candleArray[candleIndex]) + } + } + + fun areCandlesLit(player: Player): Boolean { + val candleArray = getAttribute(player, CANDLELIGHT_CANDLE_ARRAY, intArrayOf(0,0,0,0,0,0,0,0,0,0,0)) + for (candle in candleArray) { + if (candle == 0) { + return false + } + } + return true + } + + fun lightCandle(player: Player) { + var currentCamLoc = getAttribute(player, CANDLELIGHT_CAMERA_AT, Location(1968, 5002)) + val candleIndex = CANDLE_LOC_ARRAY.indexOf(currentCamLoc) + val varbit = candleIndex + 1771 // Essentially all varbits are 1771 .. 1881 + + if (candleIndex != -1) { + val candleArray = getAttribute(player, CANDLELIGHT_CANDLE_ARRAY, intArrayOf(0,0,0,0,0,0,0,0,0,0,0)) + if (candleArray[candleIndex] == 0) { + candleArray[candleIndex] = 1 + setAttribute(player, CANDLELIGHT_CANDLE_ARRAY, candleArray) + setVarbit(player, varbit, 1) + sendMessage(player, "You light the candle.") + } else if (candleArray[candleIndex] == 1) { + sendMessage(player, "This candle is already lit.") + } else { + sendMessage(player, "This candle is too short to light.") + } + } else { + sendMessage(player, "There is nothing to light here.") + } + } + + fun moveCamera(player: Player, direction: Direction, firstTime: Boolean = false) { + var currentCamLoc = getAttribute(player, CANDLELIGHT_CAMERA_AT, Location(1968, 5002)) + when(direction) { + Direction.NORTH -> currentCamLoc = currentCamLoc.transform(Direction.NORTH) + Direction.SOUTH -> currentCamLoc = currentCamLoc.transform(Direction.SOUTH) + Direction.EAST -> currentCamLoc = currentCamLoc.transform(Direction.EAST) + Direction.WEST -> currentCamLoc = currentCamLoc.transform(Direction.WEST) + else -> {} + } + if (currentCamLoc.x < 1967) { currentCamLoc.x = 1967 } + if (currentCamLoc.x > 1968) { currentCamLoc.x = 1968 } + if (currentCamLoc.y < 4997) { currentCamLoc.y = 4997 } + if (currentCamLoc.y > 5007) { currentCamLoc.y = 5007 } + setAttribute(player, CANDLELIGHT_CAMERA_AT, currentCamLoc) + PlayerCamera(player).rotateTo(currentCamLoc.x - 30, currentCamLoc.y,0,200) // height is kind of a relative value? + PlayerCamera(player).panTo(currentCamLoc.x + 2, currentCamLoc.y,350, if(firstTime) 400 else 10) + } + } + + override fun defineInterfaceListeners() { + on(CANDLELIGHT_INTERFACE){ player, component, opcode, buttonID, slot, itemID -> + when (buttonID) { + 1 -> moveCamera(player, Direction.WEST) + 2 -> moveCamera(player, Direction.EAST) + 3 -> lightCandle(player)/* Light */ + 4 -> moveCamera(player, Direction.SOUTH) + 5 -> moveCamera(player, Direction.NORTH) + 9 -> closeInterface(player) + } + return@on true + } + + onOpen(CANDLELIGHT_INTERFACE){ player, component -> + // Move camera + return@onOpen true + } + + onClose(CANDLELIGHT_INTERFACE){ player, component -> + PlayerCamera(player).reset() + // Reset camera + return@onClose true + } + } + + + override fun defineListeners() { + on((11364 .. 11394).toIntArray(), SCENERY, "light") { player, node -> + setAttribute(player, CANDLELIGHT_CAMERA_AT, Location(node.location.x, node.location.y)) + moveCamera(player, Direction.NORTH_WEST, true) + openInterface(player, CANDLELIGHT_INTERFACE) + return@on true + } + } + + override fun defineDestinationOverrides() { + setDest(SCENERY, (11364 .. 11394).toIntArray(),"light"){ player, node -> + return@setDest Location(1970, node.location.y) + } + } + + + override fun defineAreaBorders(): Array { + return arrayOf(ZoneBorders.forRegion(7758)) + } + + override fun getRestrictions(): Array { + return arrayOf(ZoneRestriction.RANDOM_EVENTS, ZoneRestriction.CANNON, ZoneRestriction.FOLLOWERS, ZoneRestriction.TELEPORT, ZoneRestriction.OFF_MAP) + } + + override fun areaEnter(entity: Entity) { + if (entity is Player) { + initCandlelight(entity) + entity.interfaceManager.removeTabs(0, 1, 2, 3, 4, 5, 6, 12) + } + } + + override fun areaLeave(entity: Entity, logout: Boolean) { + if (entity is Player) { + entity.interfaceManager.restoreTabs() + } + } + + +} \ No newline at end of file diff --git a/Server/src/main/content/global/ame/events/candlelight/PiousPeteDialogue.kt b/Server/src/main/content/global/ame/events/candlelight/PiousPeteDialogue.kt new file mode 100644 index 000000000..9eb9b7ede --- /dev/null +++ b/Server/src/main/content/global/ame/events/candlelight/PiousPeteDialogue.kt @@ -0,0 +1,94 @@ +package content.global.ame.events.candlelight + +import content.global.ame.RandomEvents +import content.global.ame.events.pillory.PilloryInterface +import content.global.ame.returnPlayer +import core.api.* +import core.game.dialogue.* +import core.game.interaction.QueueStrength +import core.game.node.entity.npc.NPC +import core.game.node.entity.player.Player +import core.game.world.map.Location +import core.game.world.update.flag.context.Graphics +import core.plugin.Initializable +import core.tools.END_DIALOGUE +import org.rs09.consts.Items +import org.rs09.consts.NPCs +import org.rs09.consts.Sounds + +// iface 178 + +@Initializable +class PiousPeteDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun newInstance(player: Player): DialoguePlugin { + return PiousPeteDialogue(player) + } + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, PiousPeteDialogueFile(), npc) + return false + } + override fun getIds(): IntArray { + return intArrayOf(NPCs.PIOUS_PETE_3207, NPCs._6564) + } +} + +class PiousPeteDialogueFile : DialogueLabeller() { + + override fun addConversation() { + npc(ChatAnim.THINKING, "Have you lit all the tall candles?") + exec { player, npc -> + if (CandlelightInterface.areCandlesLit(player)) { + loadLabel(player, "yeslit") + } else { + loadLabel(player, "nolit") + } + } + + label("nolit") + player(ChatAnim.HALF_GUILTY, "Sorry, not yet.") + npc(ChatAnim.SAD, "Please help me in lighting the candles. I just need you to light all the tall candles, but not the short ones.") + line("Click on the pillars to open an interface", "to move around and light the candles.") + + label("yeslit") + player(ChatAnim.FRIENDLY, "Yes, the tall ones are all lit.") + npc(ChatAnim.HAPPY, "Thank you my brother! I will now return you where you came from with a parting gift.") + npc(ChatAnim.FRIENDLY, "Take care brother!") + exec { player, npc -> + queueScript(player, 0, QueueStrength.SOFT) { stage: Int -> + when (stage) { + 0 -> { + lock(player, 6) + sendGraphics(Graphics(1576, 0, 0), player.location) + animate(player,8939) + playAudio(player, Sounds.TELEPORT_ALL_200) + return@queueScript delayScript(player, 3) + } + 1 -> { + returnPlayer(player) + sendGraphics(Graphics(1577, 0, 0), player.location) + animate(player,8941) + closeInterface(player) + return@queueScript delayScript(player, 3) + } + 2 -> { + val loot = RandomEvents.CERTER.loot!!.roll(player)[0] + addItemOrDrop(player, loot.id, loot.amount) + return@queueScript stopExecuting(player) + } + else -> return@queueScript stopExecuting(player) + } + } + } + } +} +class PiousPeteStartingDialogueFile : DialogueLabeller() { + + override fun addConversation() { + npc("I'm sorry to drag you away from your tasks, but I need a little help with something.") + player(ChatAnim.THINKING,"How can I help?") + npc("This is a chapel dedicated to our lord, Saradomin, and I'm tasked with maintaining this chapel.") + npc(ChatAnim.SAD,"My task is to light the chapel candles, but I couldn't reach them myself and I kept getting dazzled by the light whenever I tried.") + npc("So I need your help in lighting the candles. I need you to light all the tall candles, but not the short ones.") + npc(ChatAnim.FRIENDLY, "Once all the tall candles are all lit, come back and see me, and I will reward you for your work.") + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/ame/events/candlelight/PiousPeteNPC.kt b/Server/src/main/content/global/ame/events/candlelight/PiousPeteNPC.kt new file mode 100644 index 000000000..70cf1e11c --- /dev/null +++ b/Server/src/main/content/global/ame/events/candlelight/PiousPeteNPC.kt @@ -0,0 +1,49 @@ +package content.global.ame.events.candlelight + +import content.global.ame.RandomEventNPC +import content.global.ame.kidnapPlayer +import core.api.* +import core.api.utils.WeightBasedTable +import core.game.interaction.QueueStrength +import core.game.node.entity.npc.NPC +import core.game.node.entity.player.link.TeleportManager +import core.game.world.map.Location +import core.game.world.update.flag.context.Graphics +import org.rs09.consts.NPCs +import org.rs09.consts.Sounds + +/** "::revent -p player_name -e candlelight" **/ +class PiousPeteNPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.PRIEST_3206) { + + override fun init() { + super.init() + // Supposed to be "I'm sorry to drag you away from your tasks, but I need a little help with something." but it's too goddamn long. + sendChat("${player.username.capitalize()}! I need a little help with something.") + face(player) + queueScript(player, 4, QueueStrength.SOFT) { stage: Int -> + when (stage) { + 0 -> { + lock(player, 6) + sendGraphics(Graphics(1576, 0, 0), player.location) + animate(player,8939) + playAudio(player, Sounds.TELEPORT_ALL_200) + return@queueScript delayScript(player, 3) + } + 1 -> { + CandlelightInterface.initCandlelight(player) + kidnapPlayer(player, Location(1972, 5002, 0), TeleportManager.TeleportType.INSTANT) + // AntiMacro.terminateEventNpc(player) + sendGraphics(Graphics(1577, 0, 0), player.location) + animate(player,8941) + openDialogue(player, PiousPeteStartingDialogueFile(), NPC(NPCs.PIOUS_PETE_3207)) + return@queueScript stopExecuting(player) + } + else -> return@queueScript stopExecuting(player) + } + } + } + + override fun talkTo(npc: NPC) { + player.dialogueInterpreter.open(PiousPeteDialogueFile(),npc) + } +} \ No newline at end of file diff --git a/Server/src/main/core/ServerConstants.kt b/Server/src/main/core/ServerConstants.kt index 30a6ab377..d67997daf 100644 --- a/Server/src/main/core/ServerConstants.kt +++ b/Server/src/main/core/ServerConstants.kt @@ -318,6 +318,9 @@ class ServerConstants { @JvmField var NEW_PLAYER_ANNOUNCEMENT = true + @JvmField + var INAUTHENTIC_CANDLELIGHT_RANDOM = false + @JvmField var HOLIDAY_EVENT_RANDOMS = true diff --git a/Server/src/main/core/game/system/config/ServerConfigParser.kt b/Server/src/main/core/game/system/config/ServerConfigParser.kt index 69c660f44..72df173ff 100644 --- a/Server/src/main/core/game/system/config/ServerConfigParser.kt +++ b/Server/src/main/core/game/system/config/ServerConfigParser.kt @@ -160,6 +160,7 @@ object ServerConfigParser { ServerConstants.GRAFANA_TTL_DAYS = data.getLong("integrations.grafana_log_ttl_days", 7L).toInt() ServerConstants.BETTER_DFS = data.getBoolean("world.better_dfs", true) ServerConstants.NEW_PLAYER_ANNOUNCEMENT = data.getBoolean("world.new_player_announcement", true) + ServerConstants.INAUTHENTIC_CANDLELIGHT_RANDOM = data.getBoolean("world.inauthentic_candlelight_random", false) ServerConstants.HOLIDAY_EVENT_RANDOMS = data.getBoolean("world.holiday_event_randoms", true) ServerConstants.FORCE_HALLOWEEN_EVENTS = data.getBoolean("world.force_halloween_randoms", false) ServerConstants.FORCE_CHRISTMAS_EVENTS = data.getBoolean("world.force_christmas_randoms", false) diff --git a/Server/worldprops/default.conf b/Server/worldprops/default.conf index 6024160fe..511b7e9ce 100644 --- a/Server/worldprops/default.conf +++ b/Server/worldprops/default.conf @@ -99,6 +99,8 @@ better_agility_pyramid_gp = true better_dfs = true #new player announcement new_player_announcement = true +#enables inauthentic candlelight random event (adds an additional normal random event) +inauthentic_candlelight_random = true #enables holiday random events (no effect on normal random events) holiday_event_randoms = true #force holiday randoms (can only force one at a time) From 363461647532cc38a1cf8cf645707061d9ea3ee7 Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Tue, 15 Jul 2025 12:41:03 +0000 Subject: [PATCH 042/117] Willow branches now properly regrow every 5 minutes --- Server/src/main/content/global/skill/farming/Patch.kt | 4 ++++ .../main/content/global/skill/farming/timers/CropGrowth.kt | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Server/src/main/content/global/skill/farming/Patch.kt b/Server/src/main/content/global/skill/farming/Patch.kt index b150b0590..4fde65b5e 100644 --- a/Server/src/main/content/global/skill/farming/Patch.kt +++ b/Server/src/main/content/global/skill/farming/Patch.kt @@ -426,6 +426,10 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl // restocking their fruit should take 40 minutes per fruit minutes = 40 } + else if(plantable == Plantable.WILLOW_SAPLING && isGrown()) { + // Willow Branches grow back in only 5 minutes + minutes = 5 + } return minutes } diff --git a/Server/src/main/content/global/skill/farming/timers/CropGrowth.kt b/Server/src/main/content/global/skill/farming/timers/CropGrowth.kt index 46dbfc61f..3cc14cd55 100644 --- a/Server/src/main/content/global/skill/farming/timers/CropGrowth.kt +++ b/Server/src/main/content/global/skill/farming/timers/CropGrowth.kt @@ -57,7 +57,7 @@ class CropGrowth : PersistTimer (500, "farming:crops", isSoft = true) { private fun runOfflineCatchupLogic() { for ((_, patch) in patchMap) { val type = patch.patch.type - val shouldPlayCatchup = !patch.isGrown() || (type == PatchType.BUSH_PATCH && patch.getFruitOrBerryCount() < 4) || (type == PatchType.FRUIT_TREE_PATCH && patch.getFruitOrBerryCount() < 6) + val shouldPlayCatchup = !patch.isGrown() || (type == PatchType.BUSH_PATCH && patch.getFruitOrBerryCount() < 4) || (type == PatchType.FRUIT_TREE_PATCH && patch.getFruitOrBerryCount() < 6) || (patch.plantable == Plantable.WILLOW_SAPLING && patch.harvestAmt < 6) if (shouldPlayCatchup && !patch.isDead && !patch.isChoppedFruitTree()) { var stagesToSimulate = if (!patch.isGrown()) { if (patch.isWeedy() || patch.isEmptyAndWeeded()) patch.currentGrowthStage % 4 @@ -69,6 +69,8 @@ class CropGrowth : PersistTimer (500, "farming:crops", isSoft = true) { stagesToSimulate += Math.min(4, 4 - patch.getFruitOrBerryCount()) if (type == PatchType.FRUIT_TREE_PATCH) stagesToSimulate += Math.min(6, 6 - patch.getFruitOrBerryCount()) + if (patch.plantable == Plantable.WILLOW_SAPLING) + stagesToSimulate += Math.min(6, 6 - patch.harvestAmt) } val nowTime = System.currentTimeMillis() From db05c190fe9d5a91f50666e03856aa4add259621 Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Tue, 15 Jul 2025 12:42:37 +0000 Subject: [PATCH 043/117] Added the "You do not have an axe to use." message for trying to chop a fruit tree without an axe --- .../main/content/global/skill/farming/FruitTreeChopper.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Server/src/main/content/global/skill/farming/FruitTreeChopper.kt b/Server/src/main/content/global/skill/farming/FruitTreeChopper.kt index c335007f6..288bf1e01 100644 --- a/Server/src/main/content/global/skill/farming/FruitTreeChopper.kt +++ b/Server/src/main/content/global/skill/farming/FruitTreeChopper.kt @@ -32,6 +32,11 @@ class FruitTreeChopper : OptionHandler() { val plantable = patch.plantable plantable ?: return false + if (SkillingTool.getHatchet(player) == null) { + sendMessage(player, "You do not have an axe to use.") + return true + } + val animation = SkillingTool.getHatchet(player).animation submitIndividualPulse(player, object : Pulse(animation.duration) { From ef0c208bfbee31770d6c15d4bfb967366232cf3c Mon Sep 17 00:00:00 2001 From: Cole Reilly Date: Tue, 15 Jul 2025 12:45:48 +0000 Subject: [PATCH 044/117] Seers' flax claim migrated to daily-seers-flax.json file Geoffrey in Seers' Village should give the correct amount of noted flax every day --- .../seers/dialogue/GeoffreyDialogue.kt | 58 +++++++++++-------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/Server/src/main/content/region/kandarin/seers/dialogue/GeoffreyDialogue.kt b/Server/src/main/content/region/kandarin/seers/dialogue/GeoffreyDialogue.kt index 87eac97a9..323e82d85 100644 --- a/Server/src/main/content/region/kandarin/seers/dialogue/GeoffreyDialogue.kt +++ b/Server/src/main/content/region/kandarin/seers/dialogue/GeoffreyDialogue.kt @@ -1,37 +1,41 @@ package content.region.kandarin.seers.dialogue -import core.Util import core.game.dialogue.DialoguePlugin import core.game.node.entity.player.Player import core.game.node.entity.player.link.diary.DiaryType +import core.game.node.entity.player.link.diary.AchievementDiary import core.game.node.item.Item import core.plugin.Initializable import org.rs09.consts.Items +import core.ServerStore +import core.ServerStore.Companion.getBoolean +import org.json.simple.JSONObject @Initializable class GeoffreyDialogue(player: Player? = null) : DialoguePlugin(player) { override fun open(vararg args: Any?): Boolean { - val diary = player.achievementDiaryManager.getDiary(DiaryType.SEERS_VILLAGE) - if (diary.levelRewarded.any()) { + //determine reward level that has been claimed + var gotoStage = 0 + //want the highest value, so this is checked hardest->easiest + if (AchievementDiary.hasClaimedLevelRewards(player,DiaryType.SEERS_VILLAGE,2)) { + gotoStage = 102 + } + else if (AchievementDiary.hasClaimedLevelRewards(player,DiaryType.SEERS_VILLAGE,1)) { + gotoStage = 101 + } + else if (AchievementDiary.hasClaimedLevelRewards(player,DiaryType.SEERS_VILLAGE,0)) { + gotoStage = 100 + } + //give reward, or proceed to normal dialogue + if (gotoStage != 0) { player("Hello there. Are you Geoff-erm-Flax? I've been told that", "you'll give me some flax.") - // If 1 day has not passed since last flax reward - if (player.getAttribute("diary:seers:flax-timer", 0) > System.currentTimeMillis()) { - stage = 98 - return true - } - // If player cannot receive flax reward - if (!player.inventory.hasSpaceFor(Item(Items.FLAX_1780, 1))) { - stage = 99 - return true - } - // Determine flax reward by seers diary reward status - when (diary.reward) { - -1 -> stage = 999 - 0 -> stage = 100 - 1 -> stage = 101 - 2 -> stage = 102 - } - } else { + //Already claimed flax else no room else give correct reward + stage = if (getStoreFile().getBoolean(player.username.lowercase())) { 98 } + else if (!player.inventory.hasSpaceFor(Item(Items.FLAX_1780, 1))) { 99 } + else { gotoStage } + } + //If the diary has not been completed + else { player("Hello there. You look busy.") stage = 0 } @@ -46,7 +50,7 @@ class GeoffreyDialogue(player: Player? = null) : DialoguePlugin(player) { when (stage) { 999 -> end() 0 -> npc("Yes, I am very busy. Picking GLORIOUS flax.", "The GLORIOUS flax won't pick itself. So I pick it.", "I pick it all day long.").also { stage++ } - 1 -> player("Wow, all that flax must really mount up. What do you do with it all?").also { stage++ } + 1 -> player("Wow, all that flax must really mount up.", "What do you do with it all?").also { stage++ } 2 -> npc("I give it away! I love picking the GLORIOUS flax,", "but, if I let it all mount up, I wouldn't have any", "room for more GLORIOUS flax.").also { stage++ } 3 -> player("So, you're just picking the flax for fun? You must", "really like flax.").also { stage++ } 4 -> npc("'Like' the flax? I don't just 'like' flax. The", "GLORIOUS flax is my calling, my reason to live.", "I just love the feeling of it in my hands!").also { stage++ } @@ -55,7 +59,7 @@ class GeoffreyDialogue(player: Player? = null) : DialoguePlugin(player) { 7 -> player("I know this area! It's, erm, Seers' Village. There's", "a pub and, er, a bank.").also { stage++ } 8 -> npc("Pah! You call that local knowledge? Perhaps if you", "were wearing some kind of item from one of the", "seers, I might trust you.").also { stage = 999 } - 98 -> npc("I've already given you your GLORIOUS flax", "for the day. Come back tomorrow.").also { stage = 999 } // TODO find accurate dialogue + 98 -> npc("Don't be greedy. Other people want GLORIOUS flax too.", "You can have some more tomorrow.").also { stage = 999 } // TODO find accurate dialogue, no source found yet, so I'm using the modern RS3 dialogue from wiki source (https://runescape.wiki/w/Transcript:Geoffrey) 99 -> npc("Yes, but your inventory is full. Come back", "when you have some space for GLORIOUS flax.").also { stage = 999 } // TODO find accurate dialogue 100 -> {rewardFlax(30, "Yes. The seers have instructed me to give you an", "allowance of 30 GLORIOUS flax a day. I'm not going", "to argue with them, so here you go.")} // TODO find accurate dialogue 101 -> {rewardFlax(60, "Yes. Stankers has instructed me to give you an", "allowance of 60 GLORIOUS flax a day. I'm not going", "to argue with a dwarf, so here you go.")} // TODO find accurate dialogue @@ -67,7 +71,7 @@ class GeoffreyDialogue(player: Player? = null) : DialoguePlugin(player) { fun rewardFlax(n: Int, vararg messages: String): Unit { npc(*messages) player.inventory.add(Item(Items.FLAX_1780, n)) - player.setAttribute("/save:diary:seers:flax-timer", Util.nextMidnight(System.currentTimeMillis())) + getStoreFile()[player.username.toLowerCase()] = true stage = 999 } @@ -75,4 +79,8 @@ class GeoffreyDialogue(player: Player? = null) : DialoguePlugin(player) { return intArrayOf(8590) } -} + fun getStoreFile(): JSONObject { + return ServerStore.getArchive("daily-seers-flax") + } + +} \ No newline at end of file From 9cff5cc314b551159c71b1a4a195a8c4e341b4b5 Mon Sep 17 00:00:00 2001 From: Michael Veit Date: Tue, 15 Jul 2025 12:45:58 +0000 Subject: [PATCH 045/117] Added dialogue for gnome woman NPCs --- .../dialogue/GnomeWomanDialogue.kt | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 Server/src/main/content/region/kandarin/gnomestronghold/dialogue/GnomeWomanDialogue.kt diff --git a/Server/src/main/content/region/kandarin/gnomestronghold/dialogue/GnomeWomanDialogue.kt b/Server/src/main/content/region/kandarin/gnomestronghold/dialogue/GnomeWomanDialogue.kt new file mode 100644 index 000000000..c6f7fd2fd --- /dev/null +++ b/Server/src/main/content/region/kandarin/gnomestronghold/dialogue/GnomeWomanDialogue.kt @@ -0,0 +1,104 @@ +package content.region.kandarin.gnomestronghold.dialogue + +import core.api.addItemOrDrop +import core.api.openDialogue +import core.game.dialogue.ChatAnim +import core.game.dialogue.DialogueLabeller +import core.game.dialogue.DialoguePlugin +import core.game.node.entity.player.Player +import core.game.node.item.Item +import core.plugin.Initializable +import org.rs09.consts.Items +import org.rs09.consts.NPCs + +/** + * Handled the dialogue for the Gnome Woman NPCs in the Tree Gnome Stronghold. + * @author Broseki + */ +@Initializable +class GnomeWomanDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun newInstance(player: Player?): DialoguePlugin { + return GnomeWomanDialogue(player) + } + + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + // Pick a random number between 0 - 5 to determine the dialogue tree to use + val randomDialogue = (0..5).random() + + when (randomDialogue) { + 0 -> openDialogue(player, GnomeWomanDialogueFile0(), npc) + 1 -> openDialogue(player, GnomeWomanDialogueFile1(), npc) + 2 -> openDialogue(player, GnomeWomanDialogueFile2(), npc) + 3 -> openDialogue(player, GnomeWomanDialogueFile3(), npc) + 4 -> openDialogue(player, GnomeWomanDialogueFile4(), npc) + else -> openDialogue(player, GnomeWomanDialogueFile5(), npc) + } + return false + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.GNOME_WOMAN_168, NPCs.GNOME_WOMAN_169) + } +} + +class GnomeWomanDialogueFile0 : DialogueLabeller() { + override fun addConversation() { + player(ChatAnim.FRIENDLY, "Hello.") + npc(ChatAnim.OLD_HAPPY, "Hello adventurer. Here are some wise words:") + player(ChatAnim.FRIENDLY, "OK.") + npc(ChatAnim.OLD_CALM_TALK1, "Happiness is inward and not outward. So it does not depend on what we have but on what we are!") + } +} + +class GnomeWomanDialogueFile1 : DialogueLabeller() { + override fun addConversation() { + player(ChatAnim.FRIENDLY, "Hello.") + npc(ChatAnim.OLD_HAPPY, "Hi. I've never seen so many humans in my life.") + player(ChatAnim.LAUGH, "I've never seen so many gnomes!") + npc(ChatAnim.OLD_LAUGH1, "So we're both learning.") + } +} + +class GnomeWomanDialogueFile2 : DialogueLabeller() { + override fun addConversation() { + player(ChatAnim.FRIENDLY, "Hello.") + npc(ChatAnim.OLD_HAPPY, "Hello traveller. Are you eating properly? You look tired.") + player(ChatAnim.FRIENDLY, "I think so.") + npc(ChatAnim.OLD_CALM_TALK1, "Here, get this worm down you. It'll do you the world of good.") + item(Item(Items.KING_WORM_2162), "The gnome gives you a worm.") + exec { player, npc -> + addItemOrDrop(player, Items.KING_WORM_2162) + } + player(ChatAnim.HAPPY, "Thanks!") + } +} + +class GnomeWomanDialogueFile3 : DialogueLabeller() { + override fun addConversation() { + player(ChatAnim.FRIENDLY, "Hello.") + npc(ChatAnim.OLD_HAPPY, "Well good day to you kind sir. Are you new to these parts?") + player(ChatAnim.FRIENDLY + , "Kind of.") + npc(ChatAnim.OLD_HAPPY, "Well if you're looking for a good night out: Blurberry's cocktail bar is great!") + } +} + +class GnomeWomanDialogueFile4 : DialogueLabeller() { + override fun addConversation() { + player(ChatAnim.FRIENDLY, "Hello.") + npc(ChatAnim.OLD_CALM_TALK1, "Some people grumble because roses have thorns. I'm thankful that thorns have roses!") + player(ChatAnim.HAPPY, "Good attitude!") + } +} + +class GnomeWomanDialogueFile5 : DialogueLabeller() { + override fun addConversation() { + player(ChatAnim.FRIENDLY, "Hello.") + player(ChatAnim.FRIENDLY, "How are you?") + npc(ChatAnim.OLD_HAPPY, "Not bad, a little worn out.") + player(ChatAnim.FRIENDLY, "Maybe you should have a lie down.") + npc(ChatAnim.OLD_HAPPY, "With three kids to feed I've no time for naps!") + player(ChatAnim.HAPPY, "Sounds like hard work!") + npc(ChatAnim.OLD_HAPPY, "It is but they're worth it.") + } +} From af6d260a1e289526594c69d5985dac8e73988ecf Mon Sep 17 00:00:00 2001 From: Freidan Date: Tue, 15 Jul 2025 12:47:39 +0000 Subject: [PATCH 046/117] Changed Rogue's Purse herblore level requirement for cleaning from 8 to 3 and corrected XP awarded --- Server/src/main/content/global/skill/herblore/Herbs.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/src/main/content/global/skill/herblore/Herbs.java b/Server/src/main/content/global/skill/herblore/Herbs.java index 1973162f1..4226fb436 100644 --- a/Server/src/main/content/global/skill/herblore/Herbs.java +++ b/Server/src/main/content/global/skill/herblore/Herbs.java @@ -7,7 +7,7 @@ import core.game.node.item.Item; * @author 'Vexia */ public enum Herbs { - GUAM(new Item(199), 2.5, 3, new Item(249)), MARRENTILL(new Item(201), 3.8, 5, new Item(251)), TARROMIN(new Item(203), 5, 11, new Item(253)), HARRALANDER(new Item(205), 6.3, 20, new Item(255)), RANARR(new Item(207), 7.5, 25, new Item(257)), TOADFLAX(new Item(3049), 8, 30, new Item(2998)), SPIRIT_WEED(new Item(12174), 7.8, 35, new Item(12172)), IRIT(new Item(209), 8.8, 40, new Item(259)), AVANTOE(new Item(211), 10, 48, new Item(261)), KWUARM(new Item(213), 11.3, 54, new Item(263)), SNAPDRAGON(new Item(3051), 11.8, 59, new Item(3000)), CADANTINE(new Item(215), 12.5, 65, new Item(265)), LANTADYME(new Item(2485), 13.1, 67, new Item(2481)), DWARF_WEED(new Item(217), 13.8, 70, new Item(267)), TORSTOL(new Item(219), 15, 75, new Item(269)), SNAKE_WEED(new Item(1525), 2.5, 3, new Item(1526)), ARDRIGAL(new Item(1527), 2.5, 3, new Item(1528)), SITO_FOIL(new Item(1529), 2.5, 3, new Item(1530)), VOLENCIA_MOSS(new Item(1531), 2.5, 3, new Item(1532)), ROGUES_PURSE(new Item(1533), 0, 8, new Item(1534)); + GUAM(new Item(199), 2.5, 3, new Item(249)), MARRENTILL(new Item(201), 3.8, 5, new Item(251)), TARROMIN(new Item(203), 5, 11, new Item(253)), HARRALANDER(new Item(205), 6.3, 20, new Item(255)), RANARR(new Item(207), 7.5, 25, new Item(257)), TOADFLAX(new Item(3049), 8, 30, new Item(2998)), SPIRIT_WEED(new Item(12174), 7.8, 35, new Item(12172)), IRIT(new Item(209), 8.8, 40, new Item(259)), AVANTOE(new Item(211), 10, 48, new Item(261)), KWUARM(new Item(213), 11.3, 54, new Item(263)), SNAPDRAGON(new Item(3051), 11.8, 59, new Item(3000)), CADANTINE(new Item(215), 12.5, 65, new Item(265)), LANTADYME(new Item(2485), 13.1, 67, new Item(2481)), DWARF_WEED(new Item(217), 13.8, 70, new Item(267)), TORSTOL(new Item(219), 15, 75, new Item(269)), SNAKE_WEED(new Item(1525), 2.5, 3, new Item(1526)), ARDRIGAL(new Item(1527), 2.5, 3, new Item(1528)), SITO_FOIL(new Item(1529), 2.5, 3, new Item(1530)), VOLENCIA_MOSS(new Item(1531), 2.5, 3, new Item(1532)), ROGUES_PURSE(new Item(1533), 2.5, 3, new Item(1534)); /** * Represents the herb item. From 1ebbed5453a7b6d71e56e9e2b5afd01cea1af257 Mon Sep 17 00:00:00 2001 From: damighty <27978131-real_damighty@users.noreply.gitlab.com> Date: Mon, 18 Aug 2025 13:32:52 +0300 Subject: [PATCH 047/117] Restricted random events inside KBD lair --- .../handlers/KingBlackDragonArea.kt | 14 +++++++++++ .../test/kotlin/content/RandomEventTests.kt | 25 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 Server/src/main/content/region/wilderness/handlers/KingBlackDragonArea.kt diff --git a/Server/src/main/content/region/wilderness/handlers/KingBlackDragonArea.kt b/Server/src/main/content/region/wilderness/handlers/KingBlackDragonArea.kt new file mode 100644 index 000000000..7940a2468 --- /dev/null +++ b/Server/src/main/content/region/wilderness/handlers/KingBlackDragonArea.kt @@ -0,0 +1,14 @@ +package content.region.wilderness.handlers + +import core.api.* +import core.game.world.map.zone.ZoneBorders +import core.game.world.map.zone.ZoneRestriction + +class KingBlackDragonArea : MapArea { + override fun defineAreaBorders() : Array { + return arrayOf(ZoneBorders(2256, 4680, 2287, 4711, 0, true)) + } + override fun getRestrictions() : Array { + return arrayOf(ZoneRestriction.RANDOM_EVENTS) + } +} \ No newline at end of file diff --git a/Server/src/test/kotlin/content/RandomEventTests.kt b/Server/src/test/kotlin/content/RandomEventTests.kt index 9ab1aa02f..e28f1045b 100644 --- a/Server/src/test/kotlin/content/RandomEventTests.kt +++ b/Server/src/test/kotlin/content/RandomEventTests.kt @@ -7,6 +7,9 @@ import core.api.* import core.game.system.timer.impl.AntiMacro import core.game.world.map.Location import core.game.world.map.zone.impl.WildernessZone +import content.region.wilderness.handlers.KingBlackDragonArea +import core.game.world.map.zone.MapZone +import core.game.world.map.zone.ZoneBuilder import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import org.rs09.consts.Items @@ -132,6 +135,28 @@ class RandomEventTests { } } + @Test fun randomEventShouldNotSpawnInKingBlackDragonLair() { + TestUtils.getMockPlayer("antimacronospawninkbdlair").use { p -> + val timer = getTimer(p) ?: Assertions.fail("AntiMacro timer was null!") + TestUtils.advanceTicks(5, false) + + // Manually configure KBD lair zone like ClassScanner does for MapArea instances + val kbdArea = KingBlackDragonArea() + val zone = object : MapZone(kbdArea.javaClass.simpleName + "MapArea", true, *kbdArea.getRestrictions()) {} + for(border in kbdArea.defineAreaBorders()) zone.register(border) + ZoneBuilder.configure(zone) + + val kbdLocation = Location.create(2273, 4698, 0) // KBD spawn location + p.location = kbdLocation + + Assertions.assertEquals(kbdLocation, p.location) + + timer.nextExecution = getWorldTicks() + 5 + TestUtils.advanceTicks(10, false) + Assertions.assertNull(AntiMacro.getEventNpc(p), "Random event should not spawn in KBD lair but one was found!") + } + } + //FIXME //@Test fun randomEventShouldNotSpawnIfOneAlreadyActive() { // TestUtils.getMockPlayer("antimacronospawnifalreadyhas").use { p -> From d99e6b2541331409246422c92d3ab23defde1ade Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Mon, 18 Aug 2025 10:46:07 +0000 Subject: [PATCH 048/117] Improved battlestaff crafting --- .../global/skill/crafting/BattlestaffListener.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Server/src/main/content/global/skill/crafting/BattlestaffListener.kt b/Server/src/main/content/global/skill/crafting/BattlestaffListener.kt index 54a82b557..5c65d0850 100644 --- a/Server/src/main/content/global/skill/crafting/BattlestaffListener.kt +++ b/Server/src/main/content/global/skill/crafting/BattlestaffListener.kt @@ -17,6 +17,10 @@ class BattlestaffListener : InteractionListener { onUseWith(IntType.ITEM, orbs, battlestaff) { player, used, with -> val product = BattlestaffProduct.productMap[used.id] ?: return@onUseWith true + fun getMaxAmount(_unused: Int = 0): Int { + return min(amountInInventory(player, with.id), amountInInventory(player, used.id)) + } + if (!hasLevelDyn(player, Skills.CRAFTING, product.minimumLevel)) { sendMessage(player, "You need a Crafting level of ${product.minimumLevel} to make this.") return@onUseWith true @@ -41,7 +45,7 @@ class BattlestaffListener : InteractionListener { withItems(product.producedItemId) create { _, amount -> - runTask(player, 2, amount) { + runTask(player, 2, min(amount, getMaxAmount())) { if (amount < 1) return@runTask if (removeItem(player, product.requiredOrbItemId) && removeItem(player, Items.BATTLESTAFF_1391)) { @@ -55,9 +59,7 @@ class BattlestaffListener : InteractionListener { } } - calculateMaxAmount { _ -> - min(amountInInventory(player, with.id), amountInInventory(player, used.id)) - } + calculateMaxAmount(::getMaxAmount) } return@onUseWith true From dc0a0b1cffa819278b8c9392eb4db41f1adfca1b Mon Sep 17 00:00:00 2001 From: damighty <27978131-real_damighty@users.noreply.gitlab.com> Date: Mon, 18 Aug 2025 13:49:14 +0300 Subject: [PATCH 049/117] Removed usage of predetermined GE prices --- Server/src/main/core/game/ge/GrandExchange.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Server/src/main/core/game/ge/GrandExchange.kt b/Server/src/main/core/game/ge/GrandExchange.kt index 5b763bf71..9d5546721 100644 --- a/Server/src/main/core/game/ge/GrandExchange.kt +++ b/Server/src/main/core/game/ge/GrandExchange.kt @@ -131,15 +131,11 @@ class GrandExchange : StartupListener, Commands { @JvmStatic fun getRecommendedPrice(itemID: Int, from_bot: Boolean = false): Int { - var base = max(PriceIndex.getValue(itemID), getItemDefPrice(itemID)) + var base = PriceIndex.getValue(itemID) if (from_bot) base = (max(BotPrices.getPrice(itemID), base) * 1.10).toInt() return base } - private fun getItemDefPrice(itemID: Int): Int { - return max(itemDefinition(itemID).getConfiguration(ItemConfigParser.GE_PRICE) ?: 0, itemDefinition(itemID).value) - } - @JvmStatic fun getOfferStats(itemID: Int, sale: Boolean) : String { From 93d2c8f4aa64a075cf9713def6f9b5da238c678e Mon Sep 17 00:00:00 2001 From: damighty <27978131-real_damighty@users.noreply.gitlab.com> Date: Mon, 18 Aug 2025 13:49:54 +0300 Subject: [PATCH 050/117] Poison immune enemies are now immune to poison --- Server/src/main/core/api/ContentAPI.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Server/src/main/core/api/ContentAPI.kt b/Server/src/main/core/api/ContentAPI.kt index ef1cfbad7..09e0840d5 100644 --- a/Server/src/main/core/api/ContentAPI.kt +++ b/Server/src/main/core/api/ContentAPI.kt @@ -3034,6 +3034,9 @@ fun applyPoison (entity: Entity, source: Entity, severity: Int) { if(hasTimerActive(entity)) { return } + if(entity.isPoisonImmune()) { + return + } val existingTimer = getTimer(entity) if (existingTimer != null) { From ee9a704ad04180dd2d55b1127988ad6c73c61457 Mon Sep 17 00:00:00 2001 From: damighty <27978131-real_damighty@users.noreply.gitlab.com> Date: Mon, 18 Aug 2025 13:50:53 +0300 Subject: [PATCH 051/117] Fixed spam casting Humidify consuming additional runes upon next cast --- .../src/main/content/global/skill/magic/lunar/LunarListeners.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt b/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt index 1a89b9f85..bb81bff18 100644 --- a/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt +++ b/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt @@ -437,7 +437,7 @@ class LunarListeners : SpellListener("lunar"), Commands { if(playerEmpties.isEmpty()) { sendMessage(player, "You have nothing in your inventory that this spell can humidify.") - return + throw IllegalStateException() } removeRunes(player) From c7afeab2ab3b8e71a06c7149a80854873c704004 Mon Sep 17 00:00:00 2001 From: damighty <27978131-real_damighty@users.noreply.gitlab.com> Date: Mon, 18 Aug 2025 13:55:05 +0300 Subject: [PATCH 052/117] Fixed first stage of Kalphite Queen not counting as a Kalphite slayer task --- .../game/node/entity/combat/MeleeSwingHandler.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Server/src/main/core/game/node/entity/combat/MeleeSwingHandler.kt b/Server/src/main/core/game/node/entity/combat/MeleeSwingHandler.kt index b58fe0c0c..b00afddb0 100644 --- a/Server/src/main/core/game/node/entity/combat/MeleeSwingHandler.kt +++ b/Server/src/main/core/game/node/entity/combat/MeleeSwingHandler.kt @@ -3,6 +3,7 @@ package core.game.node.entity.combat import content.global.skill.skillcapeperks.SkillcapePerks import content.global.skill.slayer.SlayerEquipmentFlags import content.global.skill.slayer.SlayerManager +import content.global.skill.slayer.Tasks import core.api.* import core.api.EquipmentSlot import core.game.container.impl.EquipmentContainer @@ -156,7 +157,10 @@ open class MeleeSwingHandler (vararg flags: SwingHandlerFlag) val amuletId = getItemFromEquipment(entity, EquipmentSlot.NECK)?.id ?: 0 if ((amuletId == Items.SALVE_AMULET_4081 || amuletId == Items.SALVE_AMULETE_10588) && checkUndead(victimName)) { effectiveAttackLevel *= if (amuletId == Items.SALVE_AMULET_4081) 1.15 else 1.2 - } else if (getSlayerTask(entity)?.ids?.contains((entity.properties.combatPulse?.getVictim()?.id ?: 0)) == true) { + } else if (getSlayerTask(entity)?.let { task -> + val victimId = entity.properties.combatPulse?.getVictim()?.id ?: 0 + task.ids.contains(victimId) || (task == Tasks.KALPHITES && (victimId == 1158)) // Kalphite Queen phase 1 + } == true) { effectiveAttackLevel *= SlayerEquipmentFlags.getDamAccBonus(entity) //Slayer Helm/ Black Mask/ Slayer cape if (getSlayerTask(entity)?.dragon == true && inEquipment(entity, Items.DRAGON_SLAYER_GLOVES_12862)) effectiveAttackLevel *= 1.1 @@ -192,8 +196,12 @@ open class MeleeSwingHandler (vararg flags: SwingHandlerFlag) if (!flags.contains(SwingHandlerFlag.IGNORE_STAT_BOOSTS_DAMAGE)) effectiveStrengthLevel *= styleStrengthBonus else effectiveStrengthLevel *= 64 - if (getSlayerTask(entity)?.ids?.contains((entity.properties.combatPulse?.getVictim()?.id ?: 0)) == true) + if (getSlayerTask(entity)?.let { task -> + val victimId = entity.properties.combatPulse?.getVictim()?.id ?: 0 + task.ids.contains(victimId) || (task == Tasks.KALPHITES && (victimId == 1158)) // Kalphite Queen phase 1 + } == true) { effectiveStrengthLevel *= SlayerEquipmentFlags.getDamAccBonus(entity) //Slayer Helm/ Black Mask/ Slayer cape + } return (floor((0.5 + (effectiveStrengthLevel / 640.0))) * modifier).toInt() } From b68f914440e3d13d79123f08bb7eb883c5f82fa3 Mon Sep 17 00:00:00 2001 From: damighty <27978131-real_damighty@users.noreply.gitlab.com> Date: Mon, 18 Aug 2025 13:59:26 +0300 Subject: [PATCH 053/117] Fixed skull visually persisting on death --- Server/src/main/core/game/system/timer/impl/Skulled.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Server/src/main/core/game/system/timer/impl/Skulled.kt b/Server/src/main/core/game/system/timer/impl/Skulled.kt index fee4474ab..f8e8bcd76 100644 --- a/Server/src/main/core/game/system/timer/impl/Skulled.kt +++ b/Server/src/main/core/game/system/timer/impl/Skulled.kt @@ -19,6 +19,11 @@ class Skulled : PersistTimer (1, "skulled", flags = arrayOf(TimerFlag.ClearOnDea return false } + override fun onRemoval (entity: Entity) { + if (entity !is Player) return + entity.skullManager.reset() + } + override fun getTimer (vararg args: Any) : RSTimer { val t = Skulled() t.runInterval = args.getOrNull(0) as? Int ?: 500 From faf2222f4c577e37f7b70339c8258a42304b7fc7 Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Mon, 18 Aug 2025 11:03:31 +0000 Subject: [PATCH 054/117] Refactored Peer the Seer to better handle edge cases --- .../thefremenniktrials/PeerTheSeerDialogue.kt | 215 +++++++++--------- Server/src/main/core/api/ContentAPI.kt | 4 + 2 files changed, 111 insertions(+), 108 deletions(-) diff --git a/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/PeerTheSeerDialogue.kt b/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/PeerTheSeerDialogue.kt index 58714b206..c30737ab4 100644 --- a/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/PeerTheSeerDialogue.kt +++ b/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/PeerTheSeerDialogue.kt @@ -1,19 +1,20 @@ package content.region.fremennik.rellekka.quest.thefremenniktrials -import core.api.addItem -import core.api.dumpContainer -import core.api.getQuestStage -import core.api.removeItem import core.game.node.entity.player.Player -import core.game.node.entity.player.link.diary.DiaryType import core.plugin.Initializable import core.tools.RandomFunction import core.tools.END_DIALOGUE import kotlin.random.Random import content.data.Quests +import core.api.* +import core.game.dialogue.DialoguePlugin +import core.game.dialogue.FacialExpression +import core.game.node.entity.player.link.IronmanMode +import org.rs09.consts.Items +import org.rs09.consts.NPCs @Initializable -class PeerTheSeerDialogue(player: Player? = null) : core.game.dialogue.DialoguePlugin(player) { +class PeerTheSeerDialogue(player: Player? = null) : DialoguePlugin(player) { val predictionOne = arrayOf("one","two","three","four","five","six","seven","eight","ten") val predictionTwo = arrayOf("black","blue","brown","cyan","green","pink","purple","red","yellow") val predictionThree = arrayOf("fire giant","ghosts","giant","goblin","green dragon","hobgoblin","lesser demon","moss giant","ogre","zombie") @@ -31,232 +32,230 @@ class PeerTheSeerDialogue(player: Player? = null) : core.game.dialogue.DialogueP override fun open(vararg args: Any?): Boolean { if(player.inventory.contains(3710,1)){ - playerl(core.game.dialogue.FacialExpression.HAPPY,"Can I have a weather forecast now please?") + playerl(FacialExpression.HAPPY,"Can I have a weather forecast now please?") stage = 15 return true } else if(player.inventory.contains(3705,1)){ - playerl(core.game.dialogue.FacialExpression.ASKING,"So, about this forecast...") + playerl(FacialExpression.ASKING,"So, about this forecast...") stage = 20 return true } else if(player.getAttribute("sigmundreturning",false) == true){ - playerl(core.game.dialogue.FacialExpression.ASKING,"I've got an item to trade but I don't know if it's for you.") + playerl(FacialExpression.ASKING,"I've got an item to trade but I don't know if it's for you.") stage = 26 return true } else if(player.getAttribute("sigmund-steps", 0) == 10){ - playerl(core.game.dialogue.FacialExpression.ASKING,"I don't suppose you have any idea where I could find a brave and powerful warrior to act as a bodyguard?") + playerl(FacialExpression.ASKING,"I don't suppose you have any idea where I could find a brave and powerful warrior to act as a bodyguard?") stage = 8 return true } else if(player.getAttribute("sigmund-steps", 0) == 9){ - playerl(core.game.dialogue.FacialExpression.ASKING,"I don't suppose you have any idea where I could find a weather forecast from the Fremennik Seer do you?") + playerl(FacialExpression.ASKING,"I don't suppose you have any idea where I could find a weather forecast from the Fremennik Seer do you?") stage = 1 return true } - else if(player.getAttribute("PeerStarted",false) && !player.inventory.isEmpty || !player.equipment.isEmpty){ - npcl(core.game.dialogue.FacialExpression.SAD,"Uuuh... What was that dark presence I felt?") + else if(player.getAttribute("PeerStarted",false) && !(player.inventory.isEmpty && player.equipment.isEmpty)){ + npcl(FacialExpression.SAD,"Uuuh... What was that dark presence I felt?") stage = 100 return true } else if(player.getAttribute("PeerStarted",false) && player.inventory.isEmpty && player.equipment.isEmpty){ - npcl(core.game.dialogue.FacialExpression.SAD,"Uuuh... What was that dark presence I felt?") + npcl(FacialExpression.SAD,"Uuuh... What was that dark presence I felt?") stage = 110 return true } else if (getQuestStage(player, Quests.THE_FREMENNIK_TRIALS) > 0 && player.getAttribute("fremtrials:peer-vote",false)) { - npcl(core.game.dialogue.FacialExpression.SAD,"Uuuh... What was that dark presence I felt?") + npcl(FacialExpression.SAD,"Uuuh... What was that dark presence I felt?") stage = 120 return true } else if(player.questRepository.isComplete(Quests.THE_FREMENNIK_TRIALS)){ - npcl(core.game.dialogue.FacialExpression.SAD,"Uuuh... What was that dark presence I felt?") + npcl(FacialExpression.SAD,"Uuuh... What was that dark presence I felt?") stage = 150 return true } else if(getQuestStage(player, Quests.THE_FREMENNIK_TRIALS) > 0){ - npcl(core.game.dialogue.FacialExpression.SAD,"Uuuh... What was that dark presence I felt?") + npcl(FacialExpression.SAD,"Uuuh... What was that dark presence I felt?") stage = 50 return true } if (getQuestStage(player, Quests.THE_FREMENNIK_TRIALS) == 0) { - npc(core.game.dialogue.FacialExpression.SAD,"Uuuh... What was that dark presence I felt?").also { stage = 300 } + npc(FacialExpression.SAD,"Uuuh... What was that dark presence I felt?").also { stage = 300 } } return true } override fun handle(interfaceId: Int, buttonId: Int): Boolean { when(stage){ - 1 -> npcl(core.game.dialogue.FacialExpression.ANNOYED,"Er.... Yes, because I AM the Fremennik Seer.").also { stage++ } - 2 -> playerl(core.game.dialogue.FacialExpression.ASKING,"Can I have a weather forecast then please?").also { stage++ } - 3 -> npcl(core.game.dialogue.FacialExpression.THINKING,"You require a divination of the weather? This is a simple matter for me, but I will require something in return from you for this small service.").also { stage++ } - 4 -> playerl(core.game.dialogue.FacialExpression.ASKING,"I knew you were going to say that...").also { stage++ } - 5 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"Do not fret, outerlander; it is a fairly simple matter. I require a bodyguard for protection. Find someone willing to offer me this service.").also { stage++ } - 6 -> playerl(core.game.dialogue.FacialExpression.ASKING,"That's all?").also { stage++ } - 7 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"That is all.").also { + 1 -> npcl(FacialExpression.ANNOYED,"Er.... Yes, because I AM the Fremennik Seer.").also { stage++ } + 2 -> playerl(FacialExpression.ASKING,"Can I have a weather forecast then please?").also { stage++ } + 3 -> npcl(FacialExpression.THINKING,"You require a divination of the weather? This is a simple matter for me, but I will require something in return from you for this small service.").also { stage++ } + 4 -> playerl(FacialExpression.ASKING,"I knew you were going to say that...").also { stage++ } + 5 -> npcl(FacialExpression.HAPPY,"Do not fret, outerlander; it is a fairly simple matter. I require a bodyguard for protection. Find someone willing to offer me this service.").also { stage++ } + 6 -> playerl(FacialExpression.ASKING,"That's all?").also { stage++ } + 7 -> npcl(FacialExpression.HAPPY,"That is all.").also { player?.incrementAttribute("sigmund-steps",1) stage = 1000 } - 10 -> npcl(core.game.dialogue.FacialExpression.ANNOYED,"If I did, then I would simply have asked them myself now, wouldn't I, outerlander?").also { stage = 1000 } + 10 -> npcl(FacialExpression.ANNOYED,"If I did, then I would simply have asked them myself now, wouldn't I, outerlander?").also { stage = 1000 } - 15 -> npcl(core.game.dialogue.FacialExpression.ANNOYED,"I have already told you outerlander; You may have a reading from me when I have a signed contract from a warrior guaranteeing my protection.").also { stage++ } - 16 -> playerl(core.game.dialogue.FacialExpression.HAPPY,"Yeah, I know; I have one right here from Thorvald.").also { + 15 -> npcl(FacialExpression.ANNOYED,"I have already told you outerlander; You may have a reading from me when I have a signed contract from a warrior guaranteeing my protection.").also { stage++ } + 16 -> playerl(FacialExpression.HAPPY,"Yeah, I know; I have one right here from Thorvald.").also { removeItem(player,3710) addItem(player,3705) stage++ } - 17 -> npcl(core.game.dialogue.FacialExpression.AMAZED,"You have not only persuaded one of the Fremennik to act as a servant to me, but you have enlisted the aid of mighty Thorvald himself???").also { stage++ } - 18 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"You may take this forecast with my blessing outerlander. You have offered me the greatest security I can imagine.").also { stage = 1000 } + 17 -> npcl(FacialExpression.AMAZED,"You have not only persuaded one of the Fremennik to act as a servant to me, but you have enlisted the aid of mighty Thorvald himself???").also { stage++ } + 18 -> npcl(FacialExpression.HAPPY,"You may take this forecast with my blessing outerlander. You have offered me the greatest security I can imagine.").also { stage = 1000 } - 20 -> npcl(core.game.dialogue.FacialExpression.THINKING,"Yes, outerlander?").also { stage++ } - 21 -> playerl(core.game.dialogue.FacialExpression.ASKING,"I still don't know why you didn't just let me have one anyway in the first place. Surely it means nothing to you?").also { stage++ } - 22 -> npcl(core.game.dialogue.FacialExpression.THINKING,"That is not true, outerlander. Although I see glimpses of the future all of the time, using my powers brings the attention of the gods to me.").also { stage++ } - 23 -> npcl(core.game.dialogue.FacialExpression.THINKING,"Some of the gods are spiteful and cruel, and I fear if I use my powers too much then I will meet with unpredictable accidents.").also { stage++ } - 24 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"This is why I needed protection.").also { stage++ } - 25 -> playerl(core.game.dialogue.FacialExpression.THINKING,"Okay... I... think I understand...").also { stage = 1000 } + 20 -> npcl(FacialExpression.THINKING,"Yes, outerlander?").also { stage++ } + 21 -> playerl(FacialExpression.ASKING,"I still don't know why you didn't just let me have one anyway in the first place. Surely it means nothing to you?").also { stage++ } + 22 -> npcl(FacialExpression.THINKING,"That is not true, outerlander. Although I see glimpses of the future all of the time, using my powers brings the attention of the gods to me.").also { stage++ } + 23 -> npcl(FacialExpression.THINKING,"Some of the gods are spiteful and cruel, and I fear if I use my powers too much then I will meet with unpredictable accidents.").also { stage++ } + 24 -> npcl(FacialExpression.HAPPY,"This is why I needed protection.").also { stage++ } + 25 -> playerl(FacialExpression.THINKING,"Okay... I... think I understand...").also { stage = 1000 } - 26 -> npcl(core.game.dialogue.FacialExpression.ANNOYED,"Not me, I'm afraid.").also { stage++ } + 26 -> npcl(FacialExpression.ANNOYED,"Not me, I'm afraid.").also { stage++ } //The Seer's Trial - 50 -> npcl(core.game.dialogue.FacialExpression.AMAZED,"!").also { stage++ } - 51 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"Ahem, sorry about that. Hello outerlander. What do you want?").also { stage++ } - 52 -> playerl(core.game.dialogue.FacialExpression.ASKING,"Hello. I'm looking for members of the council of elders to vote for me to become a Fremennik.").also { stage++ } - 53 -> npcl(core.game.dialogue.FacialExpression.THINKING,"Are you now? Well that is interesting. Usually outerlanders do not concern themselves with our ways like that.").also { stage++ } - 54 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"I am one of the members of the council of elders, and should you be able to prove to me that you have something to offer my clan I will vote in your favour at the next meeting.").also { stage++ } - 55 -> playerl(core.game.dialogue.FacialExpression.ASKING,"How can I prove that to you?").also { stage++ } - 56 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"Well, I have but a simple test. This building behind me is my house. Inside I have constructed a puzzle.").also { stage++ } - 57 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"As a Seer to the clan, I value intelligence very highly, so you may think of it as an intelligence test of sorts.").also { stage++ } - 58 -> playerl(core.game.dialogue.FacialExpression.THINKING,"An intelligence test? I thought barbarians were stupid!").also { stage++ } - 59 -> npcl(core.game.dialogue.FacialExpression.ANNOYED,"That is the opinion that outerlanders usually hold of my people, it is true. But that is because people often confuse knowledge with wisdom.").also { stage++ } - 60 -> npcl(core.game.dialogue.FacialExpression.ANNOYED,"My puzzle tests not what you know, but what you can work out. All members of our clan have been tested when they took their trials.").also { stage++ } - 61 -> playerl(core.game.dialogue.FacialExpression.ASKING,"So what exactly does this puzzle consist of, then?").also { stage++ } - 62 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"Well, firstly you must enter my house with no items, weapons or armour. Then it is a simple matter of entering through one door and leaving by the other.").also { stage++ } - 63 -> playerl(core.game.dialogue.FacialExpression.ASKING,"I can't take anything in there with me?").also { stage++ } - 64 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"That is correct outerlander. Everything you need to complete the puzzle you will find inside the building. Nothing more.").also { stage++ } - 65 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"So what say you outerlander? You think you have the wit to earn yourself my vote?").also { stage++ } + 50 -> npcl(FacialExpression.AMAZED,"!").also { stage++ } + 51 -> npcl(FacialExpression.HAPPY,"Ahem, sorry about that. Hello outerlander. What do you want?").also { stage++ } + 52 -> playerl(FacialExpression.ASKING,"Hello. I'm looking for members of the council of elders to vote for me to become a Fremennik.").also { stage++ } + 53 -> npcl(FacialExpression.THINKING,"Are you now? Well that is interesting. Usually outerlanders do not concern themselves with our ways like that.").also { stage++ } + 54 -> npcl(FacialExpression.HAPPY,"I am one of the members of the council of elders, and should you be able to prove to me that you have something to offer my clan I will vote in your favour at the next meeting.").also { stage++ } + 55 -> playerl(FacialExpression.ASKING,"How can I prove that to you?").also { stage++ } + 56 -> npcl(FacialExpression.HAPPY,"Well, I have but a simple test. This building behind me is my house. Inside I have constructed a puzzle.").also { stage++ } + 57 -> npcl(FacialExpression.HAPPY,"As a Seer to the clan, I value intelligence very highly, so you may think of it as an intelligence test of sorts.").also { stage++ } + 58 -> playerl(FacialExpression.THINKING,"An intelligence test? I thought barbarians were stupid!").also { stage++ } + 59 -> npcl(FacialExpression.ANNOYED,"That is the opinion that outerlanders usually hold of my people, it is true. But that is because people often confuse knowledge with wisdom.").also { stage++ } + 60 -> npcl(FacialExpression.ANNOYED,"My puzzle tests not what you know, but what you can work out. All members of our clan have been tested when they took their trials.").also { stage++ } + 61 -> playerl(FacialExpression.ASKING,"So what exactly does this puzzle consist of, then?").also { stage++ } + 62 -> npcl(FacialExpression.HAPPY,"Well, firstly you must enter my house with no items, weapons or armour. Then it is a simple matter of entering through one door and leaving by the other.").also { stage++ } + 63 -> playerl(FacialExpression.ASKING,"I can't take anything in there with me?").also { stage++ } + 64 -> npcl(FacialExpression.HAPPY,"That is correct outerlander. Everything you need to complete the puzzle you will find inside the building. Nothing more.").also { stage++ } + 65 -> npcl(FacialExpression.HAPPY,"So what say you outerlander? You think you have the wit to earn yourself my vote?").also { stage++ } 66 -> options("Yes","No").also { stage++ } 67 -> when(buttonId){ - 1 ->{ playerl(core.game.dialogue.FacialExpression.HAPPY,"Yes, I accept your challenge, I have one small question, however...") + 1 ->{ playerl(FacialExpression.HAPPY,"Yes, I accept your challenge, I have one small question, however...") player?.setAttribute("/save:PeerStarted",true) player?.setAttribute("/save:PeerRiddle", Random.nextInt(0,3)) stage = 70 } - 2 ->{ playerl(core.game.dialogue.FacialExpression.HAPPY,"No, thinking about stuff isn't really my 'thing'. I'd rather go kill something. I'll find someone else to vote for me") + 2 ->{ playerl(FacialExpression.HAPPY,"No, thinking about stuff isn't really my 'thing'. I'd rather go kill something. I'll find someone else to vote for me") stage++ } } //No to challenge - 68 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"As you wish, outerlander.").also { stage = 1000 } + 68 -> npcl(FacialExpression.HAPPY,"As you wish, outerlander.").also { stage = 1000 } //Yes to challenge - 70 -> npcl(core.game.dialogue.FacialExpression.ASKING,"Yes outerlander?").also { stage++ } - 71 -> playerl(core.game.dialogue.FacialExpression.THINKING,"Well... you say I can bring nothing with me when I enter your house...").also { stage++ } - 72 -> npcl(core.game.dialogue.FacialExpression.ANNOYED,"Yes outerlander??").also { stage++ } - 73 -> playerl(core.game.dialogue.FacialExpression.THINKING,"Well...").also { stage++ } - 74 -> npcl(core.game.dialogue.FacialExpression.ANGRY,"Yes, outerlander???").also { stage++ } - 75 -> playerl(core.game.dialogue.FacialExpression.ASKING,"Where is the nearest bank?").also { stage++ } - 76 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"Ah, I see your problem outerlander. The nearest bank to here is the place known to outerlanders as the Seers Village.").also { stage++ } - 77 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"It is some way South. I do however have an alternative, should you wish to take it.").also { stage++ } - 78 -> playerl(core.game.dialogue.FacialExpression.ASKING,"And what is that?").also { stage++ } - 79 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"I can store all the weapons, armour and items that you have upon you directly into your bank account.").also { stage++ } - 80 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"This will tax what little magic I possess however, so you will have to travel to the bank to withdraw them again.").also { stage++ } - 81 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"What say you outerlander? Do you wish me to do this for you?").also { stage++ } + 70 -> npcl(FacialExpression.ASKING,"Yes outerlander?").also { stage++ } + 71 -> playerl(FacialExpression.THINKING,"Well... you say I can bring nothing with me when I enter your house...").also { stage++ } + 72 -> npcl(FacialExpression.ANNOYED,"Yes outerlander??").also { stage++ } + 73 -> playerl(FacialExpression.THINKING,"Well...").also { stage++ } + 74 -> npcl(FacialExpression.ANGRY,"Yes, outerlander???").also { stage++ } + 75 -> playerl(FacialExpression.ASKING,"Where is the nearest bank?").also { stage++ } + 76 -> npcl(FacialExpression.HAPPY,"Ah, I see your problem outerlander. The nearest bank to here is the place known to outerlanders as the Seers Village.").also { stage++ } + 77 -> npcl(FacialExpression.HAPPY,"It is some way South. I do however have an alternative, should you wish to take it.").also { stage++ } + 78 -> playerl(FacialExpression.ASKING,"And what is that?").also { stage++ } + 79 -> npcl(FacialExpression.HAPPY,"I can store all the weapons, armour and items that you have upon you directly into your bank account.").also { stage++ } + 80 -> npcl(FacialExpression.HAPPY,"This will tax what little magic I possess however, so you will have to travel to the bank to withdraw them again.").also { stage++ } + 81 -> npcl(FacialExpression.HAPPY,"What say you outerlander? Do you wish me to do this for you?").also { stage++ } 82 -> options("Yes","No").also { stage++ } 83 -> when(buttonId){ 1 -> { val slotAmount = player.inventory.itemCount() + player.equipment.itemCount() - if (slotAmount < player.bank.freeSlots()){ - npcl(core.game.dialogue.FacialExpression.HAPPY,"The task is done. I wish you luck with your test, outerlander.") - dumpContainer(player,player.inventory) - dumpContainer(player,player.equipment) + if (slotAmount < player.bank.freeSlots() && slotAmount == dumpContainer(player,player.inventory) + dumpContainer(player,player.equipment)){ + npcl(FacialExpression.HAPPY,"The task is done. I wish you luck with your test, outerlander.") stage = 1000 } else { - npcl(core.game.dialogue.FacialExpression.SAD,"I am sorry outerlander, the spell is not working. I believe you may have some objects that you cannot bank with you") + npcl(FacialExpression.SAD,"I am sorry outerlander, the spell is not working. I believe you may have some objects that you cannot bank with you.") stage = 1000 } } - 2 -> playerl(core.game.dialogue.FacialExpression.HAPPY,"No thanks. Nobody touches my stuff but me!").also { stage++ } + 2 -> playerl(FacialExpression.HAPPY,"No thanks. Nobody touches my stuff but me!").also { stage++ } } //No to banking - 84 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"As you wish, outerlander. You may attempt my little task when you have deposited your equipment in the bank").also { + 84 -> npcl(FacialExpression.HAPPY,"As you wish, outerlander. You may attempt my little task when you have deposited your equipment in the bank.").also { stage = 1000 } //Yes to banking but cannot bank - 90 -> npcl(core.game.dialogue.FacialExpression.SAD,"I am sorry outerlander, the spell is not working. I believe you may have some objects that you cannot bank with you").also { + 90 -> npcl(FacialExpression.SAD,"I am sorry outerlander, the spell is not working. I believe you may have some objects that you cannot bank with you.").also { stage = 1000 } //Returning after accepting with items. - 100 -> npcl(core.game.dialogue.FacialExpression.AMAZED,"!").also { stage++ } - 101 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"Ahem, sorry about that. Hello outerlander. What do you want?").also { stage++ } - 102 -> playerl(core.game.dialogue.FacialExpression.ASKING,"So I can bring nothing with me when I enter your house?").also { stage++ } - 103 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"That is correct outerlander, but as I say, I can use my small skill in magic to send your items directly into your bank account from here.").also { stage++ } - 104 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"You will need to manually go to the bank to withdraw them again however.").also { stage++ } - 105 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"Would you like me to perform this small spell upon you, outerlander?").also { stage = 82 } + 100 -> npcl(FacialExpression.AMAZED,"!").also { stage++ } + 101 -> npcl(FacialExpression.HAPPY,"Ahem, sorry about that. Hello outerlander. What do you want?").also { stage++ } + 102 -> playerl(FacialExpression.ASKING,"So I can bring nothing with me when I enter your house?").also { stage++ } + 103 -> npcl(FacialExpression.HAPPY,"That is correct outerlander, but as I say, I can use my small skill in magic to send your items directly into your bank account from here.").also { stage++ } + 104 -> npcl(FacialExpression.HAPPY,"You will need to manually go to the bank to withdraw them again however.").also { stage++ } + 105 -> npcl(FacialExpression.HAPPY,"Would you like me to perform this small spell upon you, outerlander?").also { stage = 82 } //Returning after accepting without items. - 110 -> npcl(core.game.dialogue.FacialExpression.AMAZED,"!").also { stage++ } - 111 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"Ahem, sorry about that. Hello outerlander. What do you want?").also { stage++ } - 112 -> playerl(core.game.dialogue.FacialExpression.ASKING,"So I just have to enter by one door of your house, and leave by the other?").also { stage++ } - 113 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"That is correct outerlander. Be warned it is not as easy as it may at first sound...").also { stage = 1000 } + 110 -> npcl(FacialExpression.AMAZED,"!").also { stage++ } + 111 -> npcl(FacialExpression.HAPPY,"Ahem, sorry about that. Hello outerlander. What do you want?").also { stage++ } + 112 -> playerl(FacialExpression.ASKING,"So I just have to enter by one door of your house, and leave by the other?").also { stage++ } + 113 -> npcl(FacialExpression.HAPPY,"That is correct outerlander. Be warned it is not as easy as it may at first sound...").also { stage = 1000 } //After completing the Seer's Trial. - 120 -> npcl(core.game.dialogue.FacialExpression.AMAZED,"!").also { stage++ } - 121 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"Ahem, sorry about that.").also { stage++ } - 122 -> playerl(core.game.dialogue.FacialExpression.HAPPY,"So you will vote for me at the council?").also { stage++ } - 123 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"Absolutely, outerlander. Your wisdom in passing my test marks you as worthy in my eyes.").also { stage = 1000 } + 120 -> npcl(FacialExpression.AMAZED,"!").also { stage++ } + 121 -> npcl(FacialExpression.HAPPY,"Ahem, sorry about that.").also { stage++ } + 122 -> playerl(FacialExpression.HAPPY,"So you will vote for me at the council?").also { stage++ } + 123 -> npcl(FacialExpression.HAPPY,"Absolutely, outerlander. Your wisdom in passing my test marks you as worthy in my eyes.").also { stage = 1000 } //After The Fremennik Trials - 150 -> npcl(core.game.dialogue.FacialExpression.AMAZED,"!").also { stage++ } - 151 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"Ahem, sorry about that.").also { - stage = if(player.achievementDiaryManager.getDiary(DiaryType.FREMENNIK).isComplete(0)){ + 150 -> npcl(FacialExpression.AMAZED,"!").also { stage++ } + 151 -> npcl(FacialExpression.HAPPY,"Ahem, sorry about that.").also { + stage = if(anyInEquipment(player, Items.FREMENNIK_SEA_BOOTS_1_14571, Items.FREMENNIK_SEA_BOOTS_2_14572, Items.FREMENNIK_SEA_BOOTS_3_14573) && !hasIronmanRestriction(player, IronmanMode.ULTIMATE)){ 200 }else 152 } - 152 -> playerl(core.game.dialogue.FacialExpression.HAPPY,"Hello Peer.").also { stage++ } - 153 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"Greetings to you, brother ${player.getAttribute("fremennikname","dingle")}! What brings you to see me again?").also { stage++ } + 152 -> playerl(FacialExpression.HAPPY,"Hello Peer.").also { stage++ } + 153 -> npcl(FacialExpression.HAPPY,"Greetings to you, brother ${player.getAttribute("fremennikname","dingle")}! What brings you to see me again?").also { stage++ } 154 -> options("Can you tell my future?","Nothing really.").also{stage++} 155 -> when(buttonId){ - 1 -> playerl(core.game.dialogue.FacialExpression.ASKING,"I was wondering if you could give me a reading on my future...?").also { stage++ } - 2 -> playerl(core.game.dialogue.FacialExpression.HAPPY,"Nothing really, I just stopped by to say hello").also { stage = 160 } + 1 -> playerl(FacialExpression.ASKING,"I was wondering if you could give me a reading on my future...?").also { stage++ } + 2 -> playerl(FacialExpression.HAPPY,"Nothing really, I just stopped by to say hello").also { stage = 160 } } - 156 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"Ah, you would like a prediction? I do not see that that would be so difficult... Wait a moment...").also { + 156 -> npcl(FacialExpression.HAPPY,"Ah, you would like a prediction? I do not see that that would be so difficult... Wait a moment...").also { stage++ } - 157 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"Here is your prediction: ${PREDICTIONS[prediction]}").also { stage = 1000 } + 157 -> npcl(FacialExpression.HAPPY,"Here is your prediction: ${PREDICTIONS[prediction]}").also { stage = 1000 } - 160 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"Well, hello to you too!").also { stage = 1000 } + 160 -> npcl(FacialExpression.HAPPY,"Well, hello to you too!").also { stage = 1000 } 200 -> options("Deposit service","Can you tell my future?","Nothing really.").also{ stage++ } 201 -> when(buttonId){ - 1 -> playerl(core.game.dialogue.FacialExpression.HAPPY,"Could you deposit some things for me, please?").also { stage++ } - 2 -> playerl(core.game.dialogue.FacialExpression.ASKING,"I was wondering if you could give me a reading on my future...?").also { stage = 156 } - 3 -> playerl(core.game.dialogue.FacialExpression.HAPPY,"Nothing really, I just stopped by to say hello").also { stage = 160 } + 1 -> playerl(FacialExpression.HAPPY,"Could you deposit some things for me, please?").also { stage++ } + 2 -> playerl(FacialExpression.ASKING,"I was wondering if you could give me a reading on my future...?").also { stage = 156 } + 3 -> playerl(FacialExpression.HAPPY,"Nothing really, I just stopped by to say hello").also { stage = 160 } } - 202 -> npcl(core.game.dialogue.FacialExpression.HAPPY,"Of course, ${player.getAttribute("fremennikname","dingle")}. I am always happy to aid those who have earned the right to wear Fremennik sea boots.").also { + 202 -> npcl(FacialExpression.HAPPY,"Of course, ${player.getAttribute("fremennikname","dingle")}. I am always happy to aid those who have earned the right to wear Fremennik sea boots.").also { player.bank.openDepositBox() stage = 1000 } - 300 -> npc(core.game.dialogue.FacialExpression.NEUTRAL,"!").also { stage++ } - 301 -> npcl(core.game.dialogue.FacialExpression.NEUTRAL,"Ahem, sorry about that. I have no interest in talking to you just now outerlander.").also { stage = END_DIALOGUE } + 300 -> npc(FacialExpression.NEUTRAL,"!").also { stage++ } + 301 -> npcl(FacialExpression.NEUTRAL,"Ahem, sorry about that. I have no interest in talking to you just now outerlander.").also { stage = END_DIALOGUE } 1000 -> end() } return true } - override fun newInstance(player: Player?): core.game.dialogue.DialoguePlugin { + override fun newInstance(player: Player?): DialoguePlugin { return PeerTheSeerDialogue(player) } override fun getIds(): IntArray { - return intArrayOf(1288) + return intArrayOf(NPCs.PEER_THE_SEER_1288) } } \ No newline at end of file diff --git a/Server/src/main/core/api/ContentAPI.kt b/Server/src/main/core/api/ContentAPI.kt index 09e0840d5..0767a9c91 100644 --- a/Server/src/main/core/api/ContentAPI.kt +++ b/Server/src/main/core/api/ContentAPI.kt @@ -2181,6 +2181,10 @@ fun dumpContainer(player: Player, container: core.game.container.Container): Int var dumpedCount = 0 run beginDepositing@{ + if (hasIronmanRestriction(player, IronmanMode.ULTIMATE)) { + return@beginDepositing + } + container.toArray().filterNotNull().forEach { item -> if (!bank.hasSpaceFor(item)) { sendMessage(player, "You have no more space in your bank.") From 89edf78e547ce795e95d3f5828ce18e1b0545260 Mon Sep 17 00:00:00 2001 From: Michael Veit Date: Mon, 18 Aug 2025 11:08:56 +0000 Subject: [PATCH 055/117] Changed most of the doors in Draynor Manor to not autowalk Implemented Draynor Manor chair NPCs (The ones that follow you around the house) --- Server/data/configs/door_configs.json | 6 + Server/data/configs/npc_spawns.json | 4 + .../draynor/handlers/DraynorManorChairNPC.kt | 141 ++++++++++++++++++ .../draynor/handlers/DraynorManorHouseZone.kt | 57 +++++++ 4 files changed, 208 insertions(+) create mode 100644 Server/src/main/content/region/misthalin/draynor/handlers/DraynorManorChairNPC.kt create mode 100644 Server/src/main/content/region/misthalin/draynor/handlers/DraynorManorHouseZone.kt diff --git a/Server/data/configs/door_configs.json b/Server/data/configs/door_configs.json index ec17288a2..4dd902862 100644 --- a/Server/data/configs/door_configs.json +++ b/Server/data/configs/door_configs.json @@ -1439,6 +1439,12 @@ "fence": "false", "metal": "false" }, + { + "id": "11470", + "replaceId": "11471", + "fence": "false", + "metal": "false" + }, { "id": "11483", "replaceId": "11708", diff --git a/Server/data/configs/npc_spawns.json b/Server/data/configs/npc_spawns.json index e152ca446..4c566b7dd 100644 --- a/Server/data/configs/npc_spawns.json +++ b/Server/data/configs/npc_spawns.json @@ -6991,6 +6991,10 @@ "npc_id": "3288", "loc_data": "{2987,3446,0,1,7}-{3034,3437,0,1,0}-" }, + { + "npc_id": "3293", + "loc_data": "{3113,3369,1,0,0}-{3122,3353,0,0,3}-{3124,3360,0,0,2}-{3115,3362,0,0,1}-" + }, { "npc_id": "3294", "loc_data": "{3014,3339,0,1,2}-" diff --git a/Server/src/main/content/region/misthalin/draynor/handlers/DraynorManorChairNPC.kt b/Server/src/main/content/region/misthalin/draynor/handlers/DraynorManorChairNPC.kt new file mode 100644 index 000000000..423e57dec --- /dev/null +++ b/Server/src/main/content/region/misthalin/draynor/handlers/DraynorManorChairNPC.kt @@ -0,0 +1,141 @@ +package content.region.misthalin.draynor.handlers + +import core.api.hasLineOfSight +import core.game.interaction.MovementPulse +import core.game.node.entity.Entity +import core.game.node.entity.impl.PulseType +import core.game.node.entity.npc.AbstractNPC +import core.game.node.entity.player.Player +import core.game.world.map.Location +import core.game.world.map.RegionManager +import core.game.world.map.path.Pathfinder +import core.plugin.Initializable + +/** + * Represents the Draynor Manor Chair NPC, these are the chairs that follow the player + * around the manor. + * + * @author Broseki + */ + +private const val DRAYNOR_MANOR_CHAIR_NPC_ID = 3293 +private const val FOLLOWING_DISTANCE = 5 +private const val STOP_FOLLOWING_DISTANCE = 30 + +@Initializable +class DraynorManorChairNPC(id: Int = DRAYNOR_MANOR_CHAIR_NPC_ID, location: Location? = null) : + AbstractNPC(id, location) { + + // The player this NPC is currently following, null if nobody is being followed + private var following: Player? = null + + override fun construct(id: Int, location: Location, vararg objects: Any): AbstractNPC { + return DraynorManorChairNPC(id, location) + } + + override fun handleTickActions() { + super.handleTickActions() + val closestPlayer = findClosestPlayer() + // If there is a player nearby, follow them + if (closestPlayer != null && following != closestPlayer) { + stopFollowing() + following = closestPlayer + follow(closestPlayer) + following?.let { face(it) } + } else { + // If we didn't find a player within `FOLLOWING_DISTANCE` + // but we are following a player, check that they are still + // within `STOP_FOLLOWING_DISTANCE`, if they are gone, stop + // trying to follow them. + following?.let { player -> + if (findDistanceToPlayer(player) > STOP_FOLLOWING_DISTANCE + || !player.isActive + || player.isInvisible) { + stopFollowing() + } else { + if (!pulseManager.hasPulseRunning()) { + follow(player) + } + face(player) + } + } + } + } + + /** + * Stops following `following` + */ + fun stopFollowing() { + following = null + resetWalk() + pulseManager.clear(PulseType.STANDARD) + } + + /** + * Finds the closest player to the current entity within `FOLLOWING_DISTANCE` + * that is currently in the chair's line of sight. + * + * @return The Player object representing the closest player, or null if there are no players nearby. + */ + fun findClosestPlayer(): Player? { + val players = RegionManager.getLocalPlayers(this, FOLLOWING_DISTANCE) + if (players.isEmpty()) { + return null + } + + var closestPlayer: Player? = null + var closestDistance = Double.MAX_VALUE + + for (player in players) { + // Make sure the chair does not try to start + // following a player in another room + if (!hasLineOfSight(this, player)) { + continue + } + + // If the player is invisible, don't follow them + if (player.isInvisible) { + continue + } + + val distance = findDistanceToPlayer(player) + if (distance < closestDistance) { + closestDistance = distance + closestPlayer = player + } + } + + return closestPlayer + } + + /** + * Calculates the distance between the current entity and the specified player. + * + * @param player The player whose distance from the current entity is to be calculated. + * @return The distance between the current entity and the specified player as a Double value. + */ + fun findDistanceToPlayer(player: Player): Double { + return this.location.getDistance(player.location) + } + + /** + * Triggers the current entity to follow the specified player using a movement pulse. + * + * @param player The player that the entity should follow. + */ + fun follow(player: Player) { + pulseManager.run((object : MovementPulse(this, player, Pathfinder.DUMB) { + override fun pulse(): Boolean { + return false + } + }), PulseType.STANDARD) + } + + override fun getIds(): IntArray { + return intArrayOf(DRAYNOR_MANOR_CHAIR_NPC_ID) + } + + override fun shouldPreventStacking(mover: Entity?): Boolean { + return mover is DraynorManorChairNPC + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/misthalin/draynor/handlers/DraynorManorHouseZone.kt b/Server/src/main/content/region/misthalin/draynor/handlers/DraynorManorHouseZone.kt new file mode 100644 index 000000000..9478c6656 --- /dev/null +++ b/Server/src/main/content/region/misthalin/draynor/handlers/DraynorManorHouseZone.kt @@ -0,0 +1,57 @@ +package content.region.misthalin.draynor.handlers + +import core.game.node.entity.Entity +import core.game.world.map.Location +import core.game.world.map.RegionManager.getLocalNpcs +import core.game.world.map.path.Pathfinder +import core.game.world.map.zone.MapZone +import core.game.world.map.zone.ZoneBorders +import core.game.world.map.zone.ZoneBuilder +import core.plugin.Initializable +import core.plugin.Plugin + +/** + * Represents the interior of Draynor Manor. + * + * @author Broseki + */ +@Initializable +class DraynorManorHouseZone : MapZone("Draynor Manor House", true), Plugin { + + override fun newInstance(arg: Any?): Plugin { + ZoneBuilder.configure(this) + return this + } + + override fun move(e: Entity, loc: Location?, destination: Location): Boolean { + for (n in getLocalNpcs(e, 5)) { + if (n.isInvisible() || n === e) { + continue + } + if (n.shouldPreventStacking(e)) { + val s1 = e.size() + val s2 = n.size() + val x = destination.getX() + val y = destination.getY() + val l = n.getLocation() + if (Pathfinder.isStandingIn(x, y, s1, s1, l.getX(), l.getY(), s2, s2)) { + return false + } + } + } + return true + } + + override fun enter(entity: Entity?): Boolean { + return super.enter(entity) + } + + override fun fireEvent(identifier: String?, vararg args: Any?): Any? { + return null + } + + override fun configure() { + register(ZoneBorders(3097, 3373, 3119, 3364)) + register(ZoneBorders(3090, 3363, 3126, 3354)) + } +} \ No newline at end of file From 1ff19880ef26cc6826d5d3d0d093e840c7ea03a8 Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Mon, 18 Aug 2025 11:13:31 +0000 Subject: [PATCH 056/117] Fixed seers diary inferno adze bonus Fixed Thormac staff enchantment --- .../global/handlers/iface/MysticStaffEnchantInterface.kt | 7 +++---- .../global/skill/gather/woodcutting/WoodcuttingListener.kt | 4 +++- .../content/region/kandarin/dialogue/ThormacDialogue.kt | 4 +--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Server/src/main/content/global/handlers/iface/MysticStaffEnchantInterface.kt b/Server/src/main/content/global/handlers/iface/MysticStaffEnchantInterface.kt index 46a3bde24..6d9748d09 100644 --- a/Server/src/main/content/global/handlers/iface/MysticStaffEnchantInterface.kt +++ b/Server/src/main/content/global/handlers/iface/MysticStaffEnchantInterface.kt @@ -12,7 +12,6 @@ class MysticStaffEnchantInterface : InterfaceListener { override fun defineInterfaceListeners() { on(INTERFACE_332) { player, _, _, buttonID, _, _ -> val staff = buttonMap[buttonID] ?: return@on true - val price = if (inEquipment(player, Items.SEERS_HEADBAND_14631)) 27000 else 40000 if (!inInventory(player, staff.basicID)) { sendMessage(player, "You don't have a${if (StringUtils.isPlusN(getItemName(staff.basicID))) "n" else ""} ${getItemName(staff.basicID)} to enchant.") @@ -21,12 +20,12 @@ class MysticStaffEnchantInterface : InterfaceListener { closeInterface(player) - if (!inInventory(player, Items.COINS_995, price)) { - sendNPCDialogue(player, NPCs.THORMAC_389, "I need ${String.format("%,d", price)} coins for materials. Come back when you have the money!", FacialExpression.NEUTRAL) + if (!inInventory(player, Items.COINS_995, 40000)) { + sendNPCDialogue(player, NPCs.THORMAC_389, "I need ${String.format("%,d", 40000)} coins for materials. Come back when you have the money!", FacialExpression.NEUTRAL) return@on true } - if (removeItem(player, Item(staff.basicID, 1)) && removeItem(player, Item(Items.COINS_995, price))) { + if (removeItem(player, Item(staff.basicID, 1)) && removeItem(player, Item(Items.COINS_995, 40000))) { sendNPCDialogue(player, NPCs.THORMAC_389, "Just a moment... hang on... hocus pocus abra-cadabra... there you go! Enjoy your enchanted staff!", FacialExpression.NEUTRAL) addItem(player, staff.enchantedID, 1) } diff --git a/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt b/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt index c73b135b6..6914f02fb 100644 --- a/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt +++ b/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt @@ -93,7 +93,9 @@ class WoodcuttingListener : InteractionListener { ).send() //add woodcutting experience - player.getSkills().addExperience(Skills.WOODCUTTING, resource.getExperience()) + rewardAmount = calculateRewardAmount(player, reward) // calculate amount + val experience: Double = calculateExperience(player, resource, rewardAmount) + player.getSkills().addExperience(Skills.WOODCUTTING, experience, true) //nullcheck the fire, and only if it exists award the firemaking XP val fire = Log.forId(reward) diff --git a/Server/src/main/content/region/kandarin/dialogue/ThormacDialogue.kt b/Server/src/main/content/region/kandarin/dialogue/ThormacDialogue.kt index f21493f59..ed2bc611e 100644 --- a/Server/src/main/content/region/kandarin/dialogue/ThormacDialogue.kt +++ b/Server/src/main/content/region/kandarin/dialogue/ThormacDialogue.kt @@ -9,7 +9,6 @@ import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.plugin.Initializable import core.tools.END_DIALOGUE -import org.rs09.consts.Items import org.rs09.consts.NPCs import content.data.Quests @@ -43,8 +42,7 @@ class ThormacDialogue(player: Player? = null) : DialoguePlugin(player) { ) ENCHANT_DIALOGUE -> { - val cost = if (player.equipment.contains(Items.SEERS_HEADBAND_14631, 1)) 27 else 40 - npcl(FacialExpression.HAPPY, "Yes, it'll cost you $cost,000 coins for the materials needed though. " + + npcl(FacialExpression.HAPPY, "Yes, it'll cost you 40,000 coins for the materials needed though. " + "Which sort of staff did you want enchanting?").also { stage++ } } ENCHANT_DIALOGUE + 1 -> { From 2471584c427ef27eee84091c549ea23a37856e08 Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Mon, 18 Aug 2025 11:26:45 +0000 Subject: [PATCH 057/117] Implemented Murder Mystery --- Server/data/configs/ground_spawns.json | 8 + Server/data/configs/item_configs.json | 22 +- Server/data/configs/npc_configs.json | 13 +- Server/data/configs/npc_spawns.json | 66 ++- .../thefremenniktrials/PoisonSalesman.kt | 53 ++- .../quest/murdermystery/AnnaDialogue.kt | 60 +++ .../quest/murdermystery/BobDialogue.kt | 61 +++ .../quest/murdermystery/CarolDialogue.kt | 59 +++ .../quest/murdermystery/DavidDialogue.kt | 60 +++ .../quest/murdermystery/DonovanDialogue.kt | 57 +++ .../quest/murdermystery/ElizabethDialogue.kt | 64 +++ .../quest/murdermystery/FlypaperDialogue.kt | 26 ++ .../quest/murdermystery/FountainDialogue.kt | 15 + .../quest/murdermystery/FrankDialogue.kt | 60 +++ .../quest/murdermystery/GossipDialogue.kt | 133 ++++++ .../quest/murdermystery/GuardDialogue.kt | 220 +++++++++ .../quest/murdermystery/HobbesDialogue.kt | 62 +++ .../quest/murdermystery/LouisaDialogue.kt | 59 +++ .../quest/murdermystery/MaryDialogue.kt | 59 +++ .../quest/murdermystery/MurderMystery.kt | 156 +++++++ .../murdermystery/MurderMysteryListeners.kt | 442 ++++++++++++++++++ .../quest/murdermystery/PierreDialogue.kt | 58 +++ .../quest/murdermystery/StanfordDialogue.kt | 58 +++ .../seers/dialogue/CamelotGuardDialogue.java | 2 +- .../main/core/game/dialogue/DialogueFile.kt | 2 +- 25 files changed, 1858 insertions(+), 17 deletions(-) create mode 100644 Server/src/main/content/region/kandarin/quest/murdermystery/AnnaDialogue.kt create mode 100644 Server/src/main/content/region/kandarin/quest/murdermystery/BobDialogue.kt create mode 100644 Server/src/main/content/region/kandarin/quest/murdermystery/CarolDialogue.kt create mode 100644 Server/src/main/content/region/kandarin/quest/murdermystery/DavidDialogue.kt create mode 100644 Server/src/main/content/region/kandarin/quest/murdermystery/DonovanDialogue.kt create mode 100644 Server/src/main/content/region/kandarin/quest/murdermystery/ElizabethDialogue.kt create mode 100644 Server/src/main/content/region/kandarin/quest/murdermystery/FlypaperDialogue.kt create mode 100644 Server/src/main/content/region/kandarin/quest/murdermystery/FountainDialogue.kt create mode 100644 Server/src/main/content/region/kandarin/quest/murdermystery/FrankDialogue.kt create mode 100644 Server/src/main/content/region/kandarin/quest/murdermystery/GossipDialogue.kt create mode 100644 Server/src/main/content/region/kandarin/quest/murdermystery/GuardDialogue.kt create mode 100644 Server/src/main/content/region/kandarin/quest/murdermystery/HobbesDialogue.kt create mode 100644 Server/src/main/content/region/kandarin/quest/murdermystery/LouisaDialogue.kt create mode 100644 Server/src/main/content/region/kandarin/quest/murdermystery/MaryDialogue.kt create mode 100644 Server/src/main/content/region/kandarin/quest/murdermystery/MurderMystery.kt create mode 100644 Server/src/main/content/region/kandarin/quest/murdermystery/MurderMysteryListeners.kt create mode 100644 Server/src/main/content/region/kandarin/quest/murdermystery/PierreDialogue.kt create mode 100644 Server/src/main/content/region/kandarin/quest/murdermystery/StanfordDialogue.kt diff --git a/Server/data/configs/ground_spawns.json b/Server/data/configs/ground_spawns.json index ca2b6f773..ce207e4db 100644 --- a/Server/data/configs/ground_spawns.json +++ b/Server/data/configs/ground_spawns.json @@ -415,6 +415,14 @@ "item_id": "1785", "loc_data": "{1,2822,3355,0,150}-" }, + { + "item_id": "1812", + "loc_data": "{1,2747,3579,0,100}" + }, + { + "item_id": "1813", + "loc_data": "{1,2746,3578,0,100}" + }, { "item_id": "1856", "loc_data": "{1,2638,3292,0,150}-" diff --git a/Server/data/configs/item_configs.json b/Server/data/configs/item_configs.json index 34d0d7b90..b0887b94a 100644 --- a/Server/data/configs/item_configs.json +++ b/Server/data/configs/item_configs.json @@ -18946,7 +18946,7 @@ "equipment_slot": "2" }, { - "examine": "Anna's shiny silver coated necklace.", + "examine": "Anna's shiny silver coated necklace coated with a thin layer of flour.", "durability": null, "name": "Silver necklace", "weight": "1", @@ -18964,7 +18964,7 @@ "id": "1798" }, { - "examine": "Bob's shiny silver coated tea cup.", + "examine": "Bob's shiny silver coated tea cup coated with a thin layer of flour.", "durability": null, "name": "Silver cup", "tradeable": "false", @@ -18982,7 +18982,7 @@ "id": "1800" }, { - "examine": "Carol's shiny silver coated bottle.", + "examine": "Carol's shiny silver coated bottle coated with a thin layer of flour.", "durability": null, "name": "Silver bottle", "tradeable": "false", @@ -19000,7 +19000,7 @@ "id": "1802" }, { - "examine": "David's shiny silver coated book.", + "examine": "David's shiny silver coated book coated with a thin layer of flour.", "durability": null, "name": "Silver book", "tradeable": "false", @@ -19018,7 +19018,7 @@ "id": "1804" }, { - "examine": "Elizabeth's shiny silver coated needle.", + "examine": "Elizabeth's shiny silver coated needle coated with a thin layer of flour.", "durability": null, "name": "Silver needle", "tradeable": "false", @@ -19036,7 +19036,7 @@ "id": "1806" }, { - "examine": "Frank's shiny silver coated pot.", + "examine": "Frank's shiny silver coated pot coated with a thin layer of flour.", "durability": null, "name": "Silver pot", "tradeable": "false", @@ -19045,7 +19045,7 @@ "id": "1807" }, { - "examine": "Some (colour) thread found at the murder scene.", + "examine": "Some red thread found at the murder scene.", "durability": null, "name": "Criminal's thread", "tradeable": "false", @@ -19054,7 +19054,7 @@ "id": "1808" }, { - "examine": "Some (colour) thread found at the murder scene.", + "examine": "Some green thread found at the murder scene.", "durability": null, "name": "Criminal's thread", "tradeable": "false", @@ -19063,7 +19063,7 @@ "id": "1809" }, { - "examine": "Some (colour) thread found at the murder scene.", + "examine": "Some blue thread found at the murder scene.", "durability": null, "name": "Criminal's thread", "tradeable": "false", @@ -19090,7 +19090,7 @@ "id": "1812" }, { - "examine": "A flimsy-looking dagger found at the crime scene./A flimsy looking dagger found at the crime scene coated with a thin layer of flour.", + "examine": "A flimsy-looking dagger found at the crime scene.", "attack_audios": "2517,2517,2500,2517", "durability": null, "name": "Criminal's dagger", @@ -19098,7 +19098,7 @@ "id": "1813" }, { - "examine": "A flimsy-looking dagger found at the crime scene./A flimsy looking dagger found at the crime scene coated with a thin layer of flour.", + "examine": "A flimsy looking dagger found at the crime scene coated with a thin layer of flour.", "attack_audios": "2517,2517,2500,2517", "durability": null, "name": "Criminal's dagger", diff --git a/Server/data/configs/npc_configs.json b/Server/data/configs/npc_configs.json index 123422daf..f8b2ab47d 100644 --- a/Server/data/configs/npc_configs.json +++ b/Server/data/configs/npc_configs.json @@ -8816,7 +8816,7 @@ "attack_level": "1" }, { - "examine": "Big", + "examine": "Big, noisy, and scary looking!", "melee_animation": "0", "range_animation": "0", "defence_animation": "0", @@ -73944,46 +73944,57 @@ "id": "797" }, { + "examine": "His job doesn't look very fun...", "name": "Pierre", "id": "807" }, { + "examine": "He looks kind of stuck up...", "name": "Hobbes", "id": "808" }, { + "examine": "She looks like she enjoys her job.", "name": "Louisa", "id": "809" }, { + "examine": "She looks very nervous...", "name": "Mary", "id": "810" }, { + "examine": "He looks like he spends a lot of time outdoor.", "name": "Stanford", "id": "811" }, { + "examine": "She's dressed in a red top and green trousers.", "name": "Anna", "id": "814" }, { + "examine": "He's dressed all in red.", "name": "Bob", "id": "815" }, { + "examine": "She's wearing a blue top and red trousers.", "name": "Carol", "id": "816" }, { + "examine": "He's dressed all in green.", "name": "David", "id": "817" }, { + "examine": "She's wearing a green top and blue trousers.", "name": "Elizabeth", "id": "818" }, { + "examine": "He's dressed all in blue.", "name": "Frank", "id": "819" }, diff --git a/Server/data/configs/npc_spawns.json b/Server/data/configs/npc_spawns.json index 4c566b7dd..b038cb929 100644 --- a/Server/data/configs/npc_spawns.json +++ b/Server/data/configs/npc_spawns.json @@ -2351,14 +2351,46 @@ "npc_id": "805", "loc_data": "{2936,3288,0,1,4}-" }, + { + "npc_id": "806", + "loc_data": "{2745,3576,1,0,4}-" + }, + { + "npc_id": "807", + "loc_data": "{2750,3576,0,0,4}-" + }, + { + "npc_id": "808", + "loc_data": "{2736,3575,0,1,1}-" + }, + { + "npc_id": "809", + "loc_data": "{2734,3581,0,1,1}-" + }, + { + "npc_id": "810", + "loc_data": "{2736,3576,1,1,1}-" + }, + { + "npc_id": "811", + "loc_data": "{2731,3579,0,1,4}-" + }, { "npc_id": "812", - "loc_data": "{2738,3473,0,0,1}-{2733,3473,0,0,1}-" + "loc_data": "{2741,3562,0,1,0}-" + }, + { + "npc_id": "813", + "loc_data": "{2741,3552,0,1,1}-" }, { "npc_id": "820", "loc_data": "{2697,3496,0,0,0}-" }, + { + "npc_id": "821", + "loc_data": "{2750,3580,0,1,0}-" + }, { "npc_id": "822", "loc_data": "{3302,9466,0,1,6}-" @@ -10463,6 +10495,38 @@ "npc_id": "6190", "loc_data": "{1906,4270,0,1,0}-" }, + { + "npc_id": "6191", + "loc_data": "{2739,3577,0,1,0}-{2739,3579,1,1,0}-" + }, + { + "npc_id": "6192", + "loc_data": "{2734,3575,0,1,0}-" + }, + { + "npc_id": "6193", + "loc_data": "{2748,3559,0,1,0}-" + }, + { + "npc_id": "6194", + "loc_data": "{2734,3581,1,1,0}-" + }, + { + "npc_id": "6195", + "loc_data": "{2739,3581,0,1,0}-" + }, + { + "npc_id": "6196", + "loc_data": "{2746,3581,1,1,0}-" + }, + { + "npc_id": "6197", + "loc_data": "{2742,3577,0,1,0}-" + }, + { + "npc_id": "6199", + "loc_data": "{2737,3466,0,1,0}-" + }, { "npc_id": "6200", "loc_data": "{2947,3366,0,0,1}-{2949,3366,0,0,1}-{2946,3366,0,0,1}-{2948,3366,0,0,1}-{2945,3366,0,0,1}-{3010,3353,0,0,1}-{3011,3353,0,0,1}-{3012,3353,0,0,1}-{3013,3353,0,0,1}-{3014,3353,0,0,1}-{3015,3353,0,0,1}-" diff --git a/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/PoisonSalesman.kt b/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/PoisonSalesman.kt index f214c66d2..2e0fd8671 100644 --- a/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/PoisonSalesman.kt +++ b/Server/src/main/content/region/fremennik/rellekka/quest/thefremenniktrials/PoisonSalesman.kt @@ -1,11 +1,18 @@ package content.region.fremennik.rellekka.quest.thefremenniktrials +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributePoisonClue +import core.api.inInventory +import core.api.setAttribute import core.game.dialogue.DialoguePlugin +import core.game.dialogue.FacialExpression +import core.game.dialogue.IfTopic +import core.game.dialogue.Topic import core.game.node.entity.player.Player import core.game.node.item.Item import core.plugin.Initializable import core.tools.END_DIALOGUE import core.tools.START_DIALOGUE +import org.rs09.consts.Items import content.data.Quests @Initializable @@ -18,12 +25,18 @@ class PoisonSalesman(player: Player? = null) : DialoguePlugin(player) { } override fun handle(interfaceId: Int, buttonId: Int): Boolean { - //val murderMysteryStage = player.questRepository.isComplete(Quests.MURDER_MYSTERY) + val murderMysteryStage = player.questRepository.getStage(Quests.MURDER_MYSTERY) val fremennikTrialsStage = player.questRepository.getStage(Quests.THE_FREMENNIK_TRIALS) when (stage) { START_DIALOGUE -> when (buttonId) { - 1 -> { player("Err... nevermind"); stage = END_DIALOGUE } + 1 -> { + when (murderMysteryStage) { + 0 -> npcl(FacialExpression.NEUTRAL, "I'm afraid I'm all sold out of poison at the moment. People know a bargain when they see it!").also { stage = END_DIALOGUE } + 1 -> playerl(FacialExpression.NEUTRAL, "I'm investigating the murder at the Sinclair house.").also { stage = 50 } + 100 -> npcl(FacialExpression.NEUTRAL, "I hear you're pretty smart to have solved the Sinclair Murder!").also { stage = END_DIALOGUE } + } + } 2 -> { player("Hello."); stage = 10 } } @@ -88,6 +101,42 @@ class PoisonSalesman(player: Player? = null) : DialoguePlugin(player) { stage++ } 45 -> { npc("Well come back when you do!"); stage = END_DIALOGUE } + + //Murder Mystery + 50 -> npcl(FacialExpression.NEUTRAL, "There was a murder at the Sinclair house??? That's terrible! And I was only there the other day too! They bought the last of my Patented Multi Purpose Poison!").also { stage++ } + 51 -> showTopics( + Topic("Patented Multi Purpose Poison?", 52), + Topic("Who did you sell Poison to at the house?", 61), + Topic("Can I buy some Poison?", 65), + IfTopic("I have this pot I found at the murder scene...", 69, inInventory(player, Items.PUNGENT_POT_1812)) + ) + 52 -> npcl(FacialExpression.NEUTRAL, "Aaaaah... a miracle of modern apothecaries!").also { stage++ } + 53 -> npcl(FacialExpression.NEUTRAL, "This exclusive concoction has been tested on all known forms of life and been proven to kill them all in varying dilutions from cockroaches to king dragons!").also { stage++ } + 54 -> npcl(FacialExpression.NEUTRAL, "So incredibly versatile, it can be used as pest control, a cleansing agent, drain cleaner, metal polish and washes whiter than white,").also { stage++ } + 55 -> npcl(FacialExpression.NEUTRAL, "all with our uniquely fragrant concoction that is immediately recognisable across the land as Peter Potter's Patented Poison potion!!!").also { stage++ } + 56 -> sendDialogue("The salesman stops for breath.").also { stage ++ } + 57 -> npcl(FacialExpression.NEUTRAL, "I'd love to sell you some but I've sold out recently. That's just how good it is! Three hundred and twenty eight people in this area alone cannot be wrong!").also { stage++ } + 58 -> npcl(FacialExpression.NEUTRAL, "Nine out of Ten poisoners prefer it in controlled tests!").also { stage++ } + 59 -> npcl(FacialExpression.NEUTRAL, "Can I help you with anything else? Perhaps I can take your name and add it to our mailing list of poison users? We will only send you information related to the use of poison and other Peter Potter Products!").also { stage++ } + 60 -> playerl(FacialExpression.NEUTRAL, "Uh... no, it's ok. Really.").also { stage = END_DIALOGUE } + + 61 -> npcl(FacialExpression.HAPPY, "Well, Peter Potter's Patented Multi Purpose Poison is a product of such obvious quality that I am glad to say I managed to sell a bottle to each of the Sinclairs!").also { stage++ } + 62 -> npcl(FacialExpression.HAPPY, "Anna, Bob, Carol, David, Elizabeth and Frank all bought a bottle! In fact they bought the last of my supplies!").also { stage++ } + 63 -> npcl(FacialExpression.HAPPY, "Maybe I can take your name and address and I will personally come and visit you when stocks return?").also { stage++ } + 64 -> playerl(FacialExpression.THINKING, "Uh...no, it's ok.") + .also { setAttribute(player, attributePoisonClue, 1)} + .also { stage = END_DIALOGUE } + + 65 -> npcl(FacialExpression.NEUTRAL, "I'm afraid I am totally out of stock at the moment after my successful trip to the Sinclairs' House the other day.").also { stage++ } + 66 -> npcl(FacialExpression.NEUTRAL, "But don't worry! Our factories are working overtime to produce Peter Potter's Patented Multi Purpose Poison!").also { stage++ } + 67 -> npcl(FacialExpression.NEUTRAL, "Possibly the finest multi purpose poison and cleaner yet available to the general market.").also { stage++ } + 68 -> npcl(FacialExpression.NEUTRAL, "And its unique fragrance makes it the number one choice for cleaners and exterminators the whole country over!").also { stage = END_DIALOGUE } + + 69 -> sendDialogue("You show the poison salesman the pot you found at the murder", "scene with the unusual smell.").also { stage ++ } + 70 -> npcl(FacialExpression.THINKING, "Hmmm... yes, that smells exactly like my Patented Multi Purpose Poison, but I don't see how it could be. It quite clearly says on the label of all bottles").also { stage++ } + 71 -> npcl(FacialExpression.THINKING, "'Not to be taken internally - EXTREMELY POISONOUS'.").also { stage++ } + 72 -> playerl(FacialExpression.THINKING, "Perhaps someone else put it in his wine?").also { stage++ } + 73 -> npcl(FacialExpression.THINKING, "Yes... I suppose that could have happened...").also { stage = END_DIALOGUE } END_DIALOGUE -> end() } return true diff --git a/Server/src/main/content/region/kandarin/quest/murdermystery/AnnaDialogue.kt b/Server/src/main/content/region/kandarin/quest/murdermystery/AnnaDialogue.kt new file mode 100644 index 000000000..7998584f2 --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/murdermystery/AnnaDialogue.kt @@ -0,0 +1,60 @@ +package content.region.kandarin.quest.murdermystery + +import content.data.Quests +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributeAskPoisonAnna +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributePoisonClue +import core.api.* +import core.game.dialogue.* +import core.game.node.entity.player.Player +import core.plugin.Initializable +import core.tools.END_DIALOGUE +import org.rs09.consts.Items +import org.rs09.consts.NPCs + +@Initializable +class AnnaDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, AnnaDialogueFile(), npc) + return true + } + override fun newInstance(player: Player): DialoguePlugin { + return AnnaDialogue(player) + } + override fun getIds(): IntArray { + return intArrayOf(NPCs.ANNA_814, NPCs.ANNA_6192) + } +} + +class AnnaDialogueFile : DialogueFile() { + override fun handle(componentID: Int, buttonID: Int) { + when (getQuestStage(player!!, Quests.MURDER_MYSTERY)){ + 0 -> sendDialogue(player!!, "They are ignoring you.").also { stage = END_DIALOGUE } + 1 -> when (stage) { + 0 -> playerl(FacialExpression.NEUTRAL, "I'm here to help the guards with their investigation.").also { stage++ } + 1 -> npcl(FacialExpression.ASKING, "Oh really? What do you want to know then?").also { stage++ } + 2 -> showTopics( + Topic("Who do you think is responsible?", 3), + Topic( "Where were you when the murder happened?", 6), + IfTopic("Do you recognise this thread?", 7, inInventory(player!!, Items.CRIMINALS_THREAD_1808) || inInventory(player!!, Items.CRIMINALS_THREAD_1809) || inInventory(player!!, Items.CRIMINALS_THREAD_1810)), + IfTopic("Why'd you buy poison the other day?", 10, getAttribute(player!!, attributePoisonClue, 0) > 0) + ) + 3 -> npcl("It was clearly an intruder.").also { stage++ } + 4 -> playerl("Well, I don't think it was.").also { stage++ } + 5 -> npcl("It was one of our lazy servants then.").also { stage = END_DIALOGUE } + + 6 -> npcl("In the library. No one else was there so you'll just have to take my word for it.").also { stage = END_DIALOGUE } + + 7 -> sendDialogue(player!!, "You show Anna the thread from the study.") + .also { if (inInventory(player!!, Items.CRIMINALS_THREAD_1809)) stage++ else stage = 9 } + 8 -> npcl("It's some Green thread. It's not exactly uncommon is it? My trousers are made of the same material.").also { stage = END_DIALOGUE } + + 9 -> npcl("Not really, no. Thread is fairly common.").also { stage = END_DIALOGUE } + + 10 -> npcl(FacialExpression.ANNOYED, "That useless Gardener Stanford let his compost heap fester. It's an eyesore to the garden! So I bought some poison from a travelling salesman so that I could kill off some of the wildlife living in it.") + .also { setAttribute(player!!, attributeAskPoisonAnna, true)} + .also { stage = END_DIALOGUE } + } + 100 -> npcl("Apparently you aren't as stupid as you look.").also { stage = END_DIALOGUE } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/murdermystery/BobDialogue.kt b/Server/src/main/content/region/kandarin/quest/murdermystery/BobDialogue.kt new file mode 100644 index 000000000..75455b51d --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/murdermystery/BobDialogue.kt @@ -0,0 +1,61 @@ +package content.region.kandarin.quest.murdermystery + +import content.data.Quests +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributeAskPoisonBob +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributePoisonClue +import core.api.* +import core.game.dialogue.* +import core.game.node.entity.player.Player +import core.plugin.Initializable +import core.tools.END_DIALOGUE +import org.rs09.consts.Items +import org.rs09.consts.NPCs + +@Initializable +class BobDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, BobDialogueFile(), npc) + return true + } + override fun newInstance(player: Player): DialoguePlugin { + return BobDialogue(player) + } + override fun getIds(): IntArray { + return intArrayOf(NPCs.BOB_815, NPCs.BOB_6193) + } +} + +class BobDialogueFile : DialogueFile() { + override fun handle(componentID: Int, buttonID: Int) { + when (getQuestStage(player!!, Quests.MURDER_MYSTERY)){ + 0 -> sendDialogue(player!!, "They are ignoring you.").also { stage = END_DIALOGUE } + 1 -> when (stage) { + 0 -> playerl(FacialExpression.NEUTRAL, "I'm here to help the guards with their investigation.").also { stage++ } + 1 -> npcl("I suppose I had better talk to you then.").also { stage++ } + 2 -> showTopics( + Topic("Who do you think is responsible?", 3), + Topic( "Where were you when the murder happened?", 4), + IfTopic(FacialExpression.THINKING, "Do you recognise this thread?", 7, inInventory(player!!, Items.CRIMINALS_THREAD_1808) || inInventory(player!!, Items.CRIMINALS_THREAD_1809) || inInventory(player!!, Items.CRIMINALS_THREAD_1810)), + IfTopic("Why'd you buy poison the other day?", 10, getAttribute(player!!, attributePoisonClue, 0) > 0) + ) + 3 -> npcl("I don't really care as long as no one thinks it's me. Maybe it was that strange poison seller who headed towards the seers village.").also { stage = END_DIALOGUE } + + 4 -> npcl("I was walking by myself in the garden.").also { stage++ } + 5 -> playerl("And can anyone vouch for that?").also { stage++ } + 6 -> npcl("No. But I was.").also { stage = END_DIALOGUE } + + 7 -> sendDialogue(player!!, "You show him the thread you discovered.") + .also { if (inInventory(player!!, Items.CRIMINALS_THREAD_1808)) stage++ else stage = 9 } + 8 -> npcl("It's some red thread. I suppose you think that's some kind of clue? It looks like the material my trousers are made of.").also { stage = END_DIALOGUE } + + 9 -> npcl(FacialExpression.THINKING, "It's some thread. Great clue. No, really.").also { stage = END_DIALOGUE } + + 10 -> npcl(FacialExpression.THINKING, "What's it to you anyway?").also { stage++ } + 11 -> npcl(FacialExpression.ANGRY, "If you absolutely must know, we had a problem with the beehive in the garden, and as all of our servants are so pathetically useless, I decided I would deal with it myself. So I did.") + .also { setAttribute(player!!, attributeAskPoisonBob, true)} + .also { stage = END_DIALOGUE } + } + 100 -> npcl("Apparently you aren't as stupid as you look.").also { stage = END_DIALOGUE } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/murdermystery/CarolDialogue.kt b/Server/src/main/content/region/kandarin/quest/murdermystery/CarolDialogue.kt new file mode 100644 index 000000000..66b283ccf --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/murdermystery/CarolDialogue.kt @@ -0,0 +1,59 @@ +package content.region.kandarin.quest.murdermystery + +import content.data.Quests +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributeAskPoisonCarol +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributePoisonClue +import core.api.* +import core.game.dialogue.* +import core.game.node.entity.player.Player +import core.plugin.Initializable +import core.tools.END_DIALOGUE +import org.rs09.consts.Items +import org.rs09.consts.NPCs + +@Initializable +class CarolDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, CarolDialogueFile(), npc) + return true + } + override fun newInstance(player: Player): DialoguePlugin { + return CarolDialogue(player) + } + override fun getIds(): IntArray { + return intArrayOf(NPCs.CAROL_816, NPCs.CAROL_6194) + } +} + +class CarolDialogueFile : DialogueFile() { + override fun handle(componentID: Int, buttonID: Int) { + when (getQuestStage(player!!, Quests.MURDER_MYSTERY)){ + 0 -> sendDialogue(player!!, "They are ignoring you.").also { stage = END_DIALOGUE } + 1 -> when (stage) { + 0 -> playerl(FacialExpression.NEUTRAL, "I'm here to help the guards with their investigation.").also { stage++ } + 1 -> npcl("Well, ask what you want to know then.").also { stage++ } + 2 -> showTopics( + Topic("Who do you think is responsible?", 3), + Topic( "Where were you when the murder happened?", 4), + IfTopic("Do you recognise this thread?", 5, inInventory(player!!, Items.CRIMINALS_THREAD_1808) || inInventory(player!!, Items.CRIMINALS_THREAD_1809) || inInventory(player!!, Items.CRIMINALS_THREAD_1810)), + IfTopic("Why'd you buy poison the other day?", 8, getAttribute(player!!, attributePoisonClue, 0) > 0) + ) + 3 -> npcl("I don't know. I think it's very convenient that you have arrived here so soon after it happened. Maybe it was you.").also { stage = END_DIALOGUE } + + 4 -> npcl("Why? Are you accusing me of something? You seem to have a very high opinion of yourself. I was in my room if you must know, alone.").also { stage = END_DIALOGUE } + + 5 -> sendDialogue(player!!, "You show Carol the thread found at the crime scene.") + .also { if (inInventory(player!!, Items.CRIMINALS_THREAD_1808)) stage++ else stage = 7 } + 6 -> npcl("It's some red thread... it kind of looks like the Same material as my trousers. But obviously it's not.").also { stage = END_DIALOGUE } + + 7 -> npcl("It's some thread. Sorry, do you have a point here? Or do you just enjoy wasting peoples time?").also { stage = END_DIALOGUE } + + 8 -> npcl(FacialExpression.THINKING, "I don't see what on earth it has to do with you, but the drain outside was").also { stage++ } + 9 -> npcl(FacialExpression.ANNOYED, "blocked, and as nobody else here has the intelligence to even unblock a simple drain I felt I had to do it myself.") + .also { setAttribute(player!!, attributeAskPoisonCarol, true)} + .also { stage = END_DIALOGUE } + } + 100 -> npcl("Apparently you aren't as stupid as you look.").also { stage = END_DIALOGUE } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/murdermystery/DavidDialogue.kt b/Server/src/main/content/region/kandarin/quest/murdermystery/DavidDialogue.kt new file mode 100644 index 000000000..b25862064 --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/murdermystery/DavidDialogue.kt @@ -0,0 +1,60 @@ +package content.region.kandarin.quest.murdermystery + +import content.data.Quests +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributeAskPoisonDavid +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributePoisonClue +import core.api.* +import core.game.dialogue.* +import core.game.node.entity.player.Player +import core.plugin.Initializable +import core.tools.END_DIALOGUE +import org.rs09.consts.Items +import org.rs09.consts.NPCs + +@Initializable +class DavidDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, DavidDialogueFile(), npc) + return true + } + override fun newInstance(player: Player): DialoguePlugin { + return DavidDialogue(player) + } + override fun getIds(): IntArray { + return intArrayOf(NPCs.DAVID_817, NPCs.DAVID_6195) + } +} + +class DavidDialogueFile : DialogueFile() { + override fun handle(componentID: Int, buttonID: Int) { + when (getQuestStage(player!!, Quests.MURDER_MYSTERY)){ + 0 -> sendDialogue(player!!, "They are ignoring you.").also { stage = END_DIALOGUE } + 1 -> when (stage) { + 0 -> playerl(FacialExpression.NEUTRAL, "I'm here to help the guards with their investigation.").also { stage++ } + 1 -> npcl("And? Make this quick. I have better things to do than be interrogated by halfwits all day.").also { stage++ } + 2 -> showTopics( + Topic("Who do you think is responsible?", 3), + Topic( "Where were you when the murder happened?", 5), + IfTopic("Do you recognise this thread?", 6, inInventory(player!!, Items.CRIMINALS_THREAD_1808) || inInventory(player!!, Items.CRIMINALS_THREAD_1809) || inInventory(player!!, Items.CRIMINALS_THREAD_1810)), + IfTopic("Why'd you buy poison the other day?", 9, getAttribute(player!!, attributePoisonClue, 0) > 0) + ) + 3 -> npcl("I don't really know or care. Frankly, the old man deserved to die.").also { stage++ } + 4 -> npcl("There was a suspicious red headed man who came to the house the other day selling poison now I think about it. Last I saw he was headed towards the tavern in the Seers village.").also { stage = END_DIALOGUE } + + 5 -> npcl("That is none of your business. Are we finished now, or are you just going to stand there irritating me with your idiotic questions all day?").also { stage = END_DIALOGUE } + + 6 -> sendDialogue(player!!, "You show him the thread you found on the study window.") + .also { if (inInventory(player!!, Items.CRIMINALS_THREAD_1809)) stage++ else stage = 8 } + 7 -> npcl("It's some Green thread, like my trousers are made of. Are you finished? I'm not sure which I dislike more bout you, your face or your general bad odour.").also { stage = END_DIALOGUE } + + 8 -> npcl("No. Can I go yet? Your face irritates me.").also { stage = END_DIALOGUE } + + 9 -> npcl(FacialExpression.ANGRY, "There was a nest of spiders upstairs between the two servants' quarters. Obviously I had to kill them before our pathetic servants whined at my father some more.").also { stage++ } + 10 -> npcl(FacialExpression.THINKING, "Honestly, it's like they expect to be treated like royalty! If I had my way I would fire the whole workshy lot of them!") + .also { setAttribute(player!!, attributeAskPoisonDavid, true)} + .also { stage = END_DIALOGUE } + } + 100 -> npcl("Apparently you aren't as stupid as you look.").also { stage = END_DIALOGUE } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/murdermystery/DonovanDialogue.kt b/Server/src/main/content/region/kandarin/quest/murdermystery/DonovanDialogue.kt new file mode 100644 index 000000000..28fae9bbf --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/murdermystery/DonovanDialogue.kt @@ -0,0 +1,57 @@ +package content.region.kandarin.quest.murdermystery + +import content.data.Quests +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributeNoiseClue +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributePoisonClue +import core.api.* +import core.game.dialogue.* +import core.game.node.entity.player.Player +import core.plugin.Initializable +import core.tools.END_DIALOGUE +import org.rs09.consts.NPCs + +@Initializable +class DonovanDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, DonovanDialogueFile(), npc) + return true + } + override fun newInstance(player: Player): DialoguePlugin { + return DonovanDialogue(player) + } + override fun getIds(): IntArray { + return intArrayOf(NPCs.DONOVAN_THE_FAMILY_HANDYMAN_806) + } +} + +class DonovanDialogueFile : DialogueFile() { + override fun handle(componentID: Int, buttonID: Int) { + when (getQuestStage(player!!, Quests.MURDER_MYSTERY)){ + 0 -> npcl("I have no interest in talking to gawkers.").also { stage = END_DIALOGUE } + 1 -> when (stage) { + 0 -> playerl("I'm here to help the guards with their investigation.").also { stage++ } + 1 -> npcl("How can I help?").also { stage++ } + 2 -> showTopics( + Topic("Who do you think is responsible?", 3), + Topic( "Where were you at the time of the murder?", 5), + IfTopic("Did you hear any suspicious noises at all?", 6, getAttribute(player!!, attributeNoiseClue, false)), + IfTopic("Do you know why so much poison was bought recently?", 9, getAttribute(player!!, attributePoisonClue, 0) > 0) + ) + 3 -> npcl("Oh... I really couldn't say. I wouldn't really want to point any fingers at anybody. If I had to make a guess I'd have to say it was probably Bob though.").also { stage++ } + 4 -> npcl("I saw him arguing with Lord Sinclair about some missing silverware from the Kitchen. It was a very heated argument.").also { stage = END_DIALOGUE } + + 5 -> npcl("Me? I was sound asleep here in the servants Quarters. It's very hard work as a handyman around here. There's always something to do!").also { stage = END_DIALOGUE } + + 6 -> npcl("Hmmm... No, I didn't, but I sleep very soundly at night.").also { stage++ } + 7 -> playerl("So you didn't hear any sounds of a struggle or any barking from the guard dog next to his study window?").also { stage++ } + 8 -> npcl("Now you mention it, no. It is odd I didn't hear anything like that. But I do sleep very soundly as I said and wouldn't necessarily have heard it if there was any such noise.").also { stage = END_DIALOGUE } + + 9 -> npcl("Well, I do know Frank bought some poison recently to clean the family crest that's outside.").also { stage++ } + 10 -> npcl("It's very old and rusty, and I couldn't clean it myself, so he said he would buy some cleaner and clean it himself. He probably just got some from that Poison Salesman who came to the door the other day...").also { stage++ } + 11 -> npcl("You'd really have to ask him though.").also { stage = END_DIALOGUE } + } + 100 -> npcl("Thank you for all your help in solving the murder.").also { stage = END_DIALOGUE } + } + } +} + diff --git a/Server/src/main/content/region/kandarin/quest/murdermystery/ElizabethDialogue.kt b/Server/src/main/content/region/kandarin/quest/murdermystery/ElizabethDialogue.kt new file mode 100644 index 000000000..faacbbe69 --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/murdermystery/ElizabethDialogue.kt @@ -0,0 +1,64 @@ +package content.region.kandarin.quest.murdermystery + +import content.data.Quests +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributeAskPoisonElizabeth +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributePoisonClue +import core.api.* +import core.game.dialogue.* +import core.game.node.entity.player.Player +import core.plugin.Initializable +import core.tools.END_DIALOGUE +import org.rs09.consts.Items +import org.rs09.consts.NPCs + +@Initializable +class ElizabethDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, ElizabethDialogueFile(), npc) + return true + } + override fun newInstance(player: Player): DialoguePlugin { + return ElizabethDialogue(player) + } + override fun getIds(): IntArray { + return intArrayOf(NPCs.ELIZABETH_818, NPCs.ELIZABETH_6196) + } +} + +class ElizabethDialogueFile : DialogueFile() { + override fun handle(componentID: Int, buttonID: Int) { + when (getQuestStage(player!!, Quests.MURDER_MYSTERY)){ + 0 -> sendDialogue(player!!, "They are ignoring you.").also { stage = END_DIALOGUE } + 1 -> when (stage) { + 0 -> playerl(FacialExpression.NEUTRAL, "I'm here to help the guards with their investigation.").also { stage++ } + 1 -> npcl("What's so important you need to bother me with then?").also { stage++ } + 2 -> showTopics( + Topic("Who do you think is responsible?", 3), + Topic( "Where were you when the murder happened?", 4), + IfTopic("Do you recognise this thread?", 7, inInventory(player!!, Items.CRIMINALS_THREAD_1808) || inInventory(player!!, Items.CRIMINALS_THREAD_1809) || inInventory(player!!, Items.CRIMINALS_THREAD_1810)), + IfTopic("Why'd you buy poison the other day?", 12, getAttribute(player!!, attributePoisonClue, 0) > 0) + ) + 3 -> npcl("Could have been anyone. The old man was an idiot. He's been asking for it for years.").also { stage = END_DIALOGUE } + + 4 -> npcl("I was out.").also { stage++ } + 5 -> playerl("Care to be any more specific?").also { stage++ } + 6 -> npcl("Not really. I don't have to justify myself to the likes of you, you know. I know the King personally you know. Now are we finished here?").also { stage = END_DIALOGUE } + + 7 -> sendDialogue(player!!, "You show her the thread from the study window.") + .also { if (inInventory(player!!, Items.CRIMINALS_THREAD_1810)) stage++ else stage = 11 } + 8 -> npcl(" Looks like Blue thread to me. If you can't work that out for yourself I don't hold much hope of you solving this crime.").also { stage++ } + 9 -> playerl("It looks a lot like the material your trousers are made of doesn't it?").also { stage++ } + 10 -> npcl("I suppose it does. So what?").also { stage = END_DIALOGUE } + + 11 -> npcl(" It's some thread. You're not very good at this whole investigation thing are you?").also { stage = END_DIALOGUE } + + 12 -> npcl("There was a nest of mosquitos under the fountain in the garden, which I killed with poison the other day. You can see for yourself if you're capable of managing that, which I somehow doubt.").also { stage++ } + 13 -> playerl(FacialExpression.ANNOYED, "I hate mosquitos!").also { stage++ } + 14 -> npcl("Doesn't everyone?") + .also { setAttribute(player!!, attributeAskPoisonElizabeth, true)} + .also { stage = END_DIALOGUE } + } + 100 -> npcl("Apparently you aren't as stupid as you look.").also { stage = END_DIALOGUE } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/murdermystery/FlypaperDialogue.kt b/Server/src/main/content/region/kandarin/quest/murdermystery/FlypaperDialogue.kt new file mode 100644 index 000000000..952be8a03 --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/murdermystery/FlypaperDialogue.kt @@ -0,0 +1,26 @@ +package content.region.kandarin.quest.murdermystery + +import core.api.* +import core.game.dialogue.DialogueFile +import core.tools.END_DIALOGUE +import org.rs09.consts.Items + +class FlypaperDialogue : DialogueFile() { + override fun handle(componentID: Int, buttonID: Int) { + when (stage) { + 0 -> sendDialogue(player!!, "There's some flypaper in there. Should I take it?").also { stage++ } + 1 -> options("Yes, it might be useful.", "No, I don't see any need for it.").also { stage++ } + 2 -> when(buttonID) { + 1 -> if (addItem(player!!, Items.FLYPAPER_1811)){ + sendDialogue(player!!, "You take a piece of fly paper. There is still plenty of fly paper left.") + .also { stage = END_DIALOGUE } + } + else { + sendDialogue(player!!, "You don't have enough space in your inventory.") + .also { stage = END_DIALOGUE } + } + 2 -> sendDialogue(player!!, "You leave the paper in the sack.").also { stage = END_DIALOGUE } + } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/murdermystery/FountainDialogue.kt b/Server/src/main/content/region/kandarin/quest/murdermystery/FountainDialogue.kt new file mode 100644 index 000000000..95ef1d79e --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/murdermystery/FountainDialogue.kt @@ -0,0 +1,15 @@ +package content.region.kandarin.quest.murdermystery + +import core.api.* +import core.game.dialogue.DialogueFile +import core.tools.END_DIALOGUE + +class FountainDialogue : DialogueFile() { + override fun handle(componentID: Int, buttonID: Int) { + when (stage) { + 0 -> sendDialogue(player!!, "The fountain is swarming with mosquitos. There's a nest of them underneath the fountain.").also { stage++ } + 1 -> playerl("I hate mosquitos, they're so annoying!").also { stage++ } + 2 -> sendDialogue(player!!, "It's certainly clear nobody's used poison here.").also { stage = END_DIALOGUE } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/murdermystery/FrankDialogue.kt b/Server/src/main/content/region/kandarin/quest/murdermystery/FrankDialogue.kt new file mode 100644 index 000000000..15e8af9bb --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/murdermystery/FrankDialogue.kt @@ -0,0 +1,60 @@ +package content.region.kandarin.quest.murdermystery + +import content.data.Quests +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributeAskPoisonFrank +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributePoisonClue +import core.api.* +import core.game.dialogue.* +import core.game.node.entity.player.Player +import core.plugin.Initializable +import core.tools.END_DIALOGUE +import org.rs09.consts.Items +import org.rs09.consts.NPCs + +@Initializable +class FrankDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, FrankDialogueFile(), npc) + return true + } + override fun newInstance(player: Player): DialoguePlugin { + return FrankDialogue(player) + } + override fun getIds(): IntArray { + return intArrayOf(NPCs.FRANK_819, NPCs.FRANK_6197) + } +} + +class FrankDialogueFile : DialogueFile() { + override fun handle(componentID: Int, buttonID: Int) { + when (getQuestStage(player!!, Quests.MURDER_MYSTERY)){ + 0 -> sendDialogue(player!!, "They are ignoring you.").also { stage = END_DIALOGUE } + 1 -> when (stage) { + 0 -> playerl(FacialExpression.NEUTRAL, "I'm here to help the guards with their investigation.").also { stage++ } + 1 -> npcl("Good for you. Now what do you want?").also { stage++ } + 2 -> npcl(FacialExpression.SAD, "...And can you spare me any money? I'm a little short...").also { stage++ } + 3 -> showTopics( + Topic("Who do you think is responsible?", 4), + Topic( "Where were you when the murder happened?", 5), + IfTopic("Do you recognise this thread?", 6, inInventory(player!!, Items.CRIMINALS_THREAD_1808) || inInventory(player!!, Items.CRIMINALS_THREAD_1809) || inInventory(player!!, Items.CRIMINALS_THREAD_1810)), + IfTopic("Why'd you buy poison the other day?", 9, getAttribute(player!!, attributePoisonClue, 0) > 0) + ) + 4 -> npcl("I don't know. You don't know how long it takes an inheritance to come through do you? I could really use that money pretty soon...").also { stage = END_DIALOGUE } + + 5 -> npcl("I don't know, somewhere around here probably. Could you spare me a few coins? I'll be able to pay you back double tomorrow it's just there's this poker night tonight in town...").also { stage = END_DIALOGUE } + + 6 -> sendDialogue(player!!, "Frank examines the thread from the crime scene.") + .also { if (inInventory(player!!, Items.CRIMINALS_THREAD_1810)) stage++ else stage = 8 } + 7 -> npcl("It kind of looks like the same material as my trousers are made of... same colour anyway. Think it's worth anything? Can I have it? Or just some money?").also { stage = END_DIALOGUE } + + 8 -> npcl("It looks like thread to me, but I'm not exactly an expert. Is it worth something? Can I have it? Actually, can you spare me a few gold?").also { stage = END_DIALOGUE } + + 9 -> npcl("Would you like to buy some? I'm kind of strapped for cash right now. I'll sell it to you cheap. It's hardly been used at all.").also { stage++ } + 10 -> npcl("I just used a bit to clean that family crest outside up a bit. Do you think I could get much money for the family crest, actually? It's cleaned up a bit now.") + .also { setAttribute(player!!, attributeAskPoisonFrank, true)} + .also { stage = END_DIALOGUE } + } + 100 -> npcl("Apparently you aren't as stupid as you look.").also { stage = END_DIALOGUE } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/murdermystery/GossipDialogue.kt b/Server/src/main/content/region/kandarin/quest/murdermystery/GossipDialogue.kt new file mode 100644 index 000000000..095fd175d --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/murdermystery/GossipDialogue.kt @@ -0,0 +1,133 @@ +package content.region.kandarin.quest.murdermystery + +import content.data.Quests +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributeNoiseClue +import core.api.* +import core.game.dialogue.* +import core.game.node.entity.player.Player +import core.plugin.Initializable +import core.tools.END_DIALOGUE +import core.tools.RandomFunction +import org.rs09.consts.NPCs + +@Initializable +class GossipDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, GossipDialogueFile(), npc) + return true + } + override fun newInstance(player: Player): DialoguePlugin { + return GossipDialogue(player) + } + override fun getIds(): IntArray { + return intArrayOf(NPCs.GOSSIP_813) + } +} + +class GossipDialogueFile : DialogueFile() { + override fun handle(componentID: Int, buttonID: Int) { + when (getQuestStage(player!!, Quests.MURDER_MYSTERY)){ + 0 -> npcl("There's some kind of commotion up at the Sinclair place I hear. Not surprising all things considered.").also { stage = END_DIALOGUE} + 1 -> when (stage) { + 0 -> playerl("I'm investigating the murder up at the Sinclair place.").also { stage++ } + 1 -> npcl("Murder is it? Well, I'm not really surprised...").also { stage++ } + 2 -> options("What can you tell me about the Sinclairs?", "Who do you think was responsible?", "I think the butler did it.", "I am so confused about who did it.").also { stage++ } + 3 -> when(buttonID) { + 1 -> playerl("What can you tell me about the Sinclairs?").also { stage = 4 } + 2 -> playerl("Who do you think was responsible?").also { stage = 40 } + 3 -> playerl("I think the butler did it.").also { stage = 42 } + 4 -> playerl("I am so confused about who did it.").also { stage = 43 } + } + 4 -> npcl("Well, what do you want to know?").also { stage++ } + 5 -> options("Tell me about Lord Sinclair.", "Why do the Sinclairs live so far from town?", "What can you tell me about his sons?", "What can you tell me about his daughters?").also { stage++ } + 6 -> when(buttonID) { + 1 -> playerl("Tell me about Lord Sinclair.").also { stage = 7 } + 2 -> playerl("Why do the Sinclairs live so far from town?").also { stage = 10 } + 3 -> playerl("What can you tell me about his sons?").also { stage = 12 } + 4 -> playerl("What can you tell me about his daughters?").also { stage = 15 } + } + 7 -> npcl("Old Lord Sinclair was a great man with a lot of respect around these parts. More than his worthless children have anyway.").also { stage++ } + 8 -> playerl("His children? They have something to gain by his death?").also { stage++ } + 9 -> npcl("Yes. You could say that. Not that I am one to gossip.").also { stage = END_DIALOGUE } + + 10 -> npcl("Well, they used to live in the big castle, but old Lord Sinclair gave it up so that those strange knights could live there instead. So the king built him a new house to the North.").also { stage++ } + 11 -> npcl("It's more cramped than his old place, but he seemed to like it. His children were furious at him for doing it though!").also { stage = END_DIALOGUE } + + 12 -> npcl("His sons eh? They all have their own skeletons in their cupboards. You'll have to be more specific. Who are you interested in exactly?").also { stage++ } + 13 -> options("Tell me about Bob.", "Tell me about David.", "Tell me about Frank.").also { stage++ } + 14 -> when(buttonID) { + 1 -> playerl("Tell me about Bob.").also { stage = 18 } + 2 -> playerl("Tell me about David.").also { stage = 23 } + 3 -> playerl("Tell me about Frank.").also { stage = 27 } + } + + 15 -> npcl("His daughters eh? They're all nasty pieces of work, which of them specifically did you want to know about?").also { stage++ } + 16 -> options("Tell me about Anna.", "Tell me about Carol.", "Tell me about Elizabeth.").also { stage++ } + 17 -> when(buttonID) { + 1 -> playerl("Tell me about Anna.").also { stage = 30 } + 2 -> playerl("Tell me about Carol.").also { stage = 33 } + 3 -> playerl("Tell me about Elizabeth.").also { stage = 36 } + } + + 18 -> npcl("Bob is an odd character indeed... I'm not one to gossip, but I heard Bob is addicted to Tea. He can't make").also { stage++ } + 19 -> npcl("it through the day without having at least 20 cups!").also { stage++ } + 20 -> npcl("You might not think that's such a big thing, but he has spent thousands of gold to feed his habit!").also { stage++ } + 21 -> npcl("At one point he stole a lot of silverware from the kitchen and pawned it just so he could afford to buy his daily tea allowance.").also { stage++ } + 22 -> npcl("If his father ever found out, he would be in so much trouble... he might even get disowned!").also { stage = END_DIALOGUE } + + 23 -> npcl("David... oh David... not many people know this, but David really has an anger problem. He's always screaming and shouting").also { stage++ } + 24 -> npcl("at the household servants when he's angry, and they live in a state of fear, always walking on eggshells around him, but none of them have the courage").also { stage++ } + 25 -> npcl("to talk to his father about his behaviour. If they did, Lord Sinclair would almost certainly").also { stage++ } + 26 -> npcl("kick him out of the house, as some of the servants have been there longer than he has, and he definitely has no right to treat them like he does... but I'm not one to gossip about people.").also { stage = END_DIALOGUE } + + 27 -> npcl("I'm not one to talk ill of people behind their back, but Frank is a real piece of work. He is an absolutely terrible gambler...he can't pass 2 dogs in the street without putting a bet on which one will bark first!").also { stage++ } + 28 -> npcl("He has already squandered all of his allowance, and I heard he had stolen a number of paintings of his fathers to sell to try and cover his debts, but he still owes a lot of people a lot of").also { stage++ } + 29 -> npcl("money. If his father ever found out, he would stop his income, and then he would be in serious trouble!").also { stage = END_DIALOGUE } + + 30 -> npcl("Anna... ah yes... Anna has 2 great loves:").also { stage++ } + 31 -> npcl("Sewing and gardening. But one thing she has kept secret is that she once had an affair with Stanford the gardener, and tried to get him fired when they broke up,").also { stage++ } + 32 -> npcl("by killing all of the flowers in the garden. If her father ever found out she had done that he would be so furious he would probably disown her.").also { stage = END_DIALOGUE } + + 33 -> npcl("Oh Carol... she is such a fool. You didn't hear this from me, but I heard a while ago she was conned out of a lot of money by a travelling salesman who sold her a box full").also { stage++ } + 34 -> npcl("of beans by telling her they were magic. But they weren't. She sold some rare books from the library to cover her debts, but").also { stage++ } + 35 -> npcl("her father would be incredibly annoyed if he ever found out - he might even throw her out of the house!").also { stage = END_DIALOGUE } + + 36 -> npcl("Elizabeth? Elizabeth has a strange problem... She cannot help herself, but is always stealing small objects - it's pretty sad that she is rich enough to afford to buy things, but would rather steal them instead.").also { stage++ } + 37 -> npcl("Now, I don't want to spread stories, but I heard she even stole a silver needle from her father that had great sentimental value for him.").also { stage++ } + 38 -> npcl("He was devastated when it was lost, and cried for a week thinking he had lost it!").also { stage++ } + 39 -> npcl("If he ever found out that it was her who had stolen it he would go absolutely mental, maybe even disowning her!").also { stage = END_DIALOGUE } + + 40 -> npcl("Well, I guess it could have been an intruder, but with that big guard dog of theirs I seriously doubt it. I suspect it was someone closer to home...").also { stage ++ } + 41 -> npcl("Especially as I heard that the poison salesman in the Seers' village made a big sale to one of the family the other day.") + .also { setAttribute(player!!, attributeNoiseClue, true)} + .also { stage = END_DIALOGUE } + + 42 -> npcl("And I think you've been reading too many cheap detective novels. Hobbes is kind of uptight, but his loyalty to old Lord Sinclair is beyond question.").also { stage = END_DIALOGUE } + + 43 -> playerl("Think you could give me any hints?") + .also { when (RandomFunction.random(4)) { //RS3 wiki seems to indicate this is fully random, not based off of progression + 0 -> stage = 44 + 1 -> stage = 46 + 2 -> stage = 47 + 3 -> stage = 48 + 4 -> stage = 51 + } + } + 44 -> npcl("Well, I don't know if it's related, but I heard from that Poison Salesman in town that he sold some poison to one of the Sinclair family").also { stage++ } + 45 -> npcl("the other day. I don't think he has any stock left now though...").also { stage = END_DIALOGUE } + + 46 -> npcl("Well, I don't know how much help this is, but I heard their guard dog will bark loudly at anyone it doesn't recognise. Maybe you should find out if anyone heard anything suspicious?") + .also { stage = END_DIALOGUE } + + 47 -> npcl("Well, this might be of some help to you. My father was in the guards when he was younger and he always said that there isn't a crime that can't be solved through careful examination of the crime scene and all surrounding areas.").also { stage = END_DIALOGUE } + + 48 -> npcl("I don't know how much help this is to you, but my dad was in the guard once and he told me the marks on your hands are totally unique. He calls them 'finger prints'.").also { stage++ } + 49 -> npcl("He said you can find them easily on any shiny metallic surface, by using a fine powder to mark out where the marks are and then using some sticky paper to lift the print from the object.").also { stage++ } + 50 -> npcl("I bet if you could find a way to get everyone's 'finger prints' you could solve the crime pretty easily!").also { stage = END_DIALOGUE } + + 51 -> npcl("My father used to be in the guard, he always wrote himself notes on a piece of paper so he could keep track of information easily.").also { stage++ } + 52 -> npcl("Maybe you should try that? Don't forget to thank me if I help you solve the case!").also { stage = END_DIALOGUE } + } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/murdermystery/GuardDialogue.kt b/Server/src/main/content/region/kandarin/quest/murdermystery/GuardDialogue.kt new file mode 100644 index 000000000..d661a6aae --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/murdermystery/GuardDialogue.kt @@ -0,0 +1,220 @@ +package content.region.kandarin.quest.murdermystery + +import content.data.Quests +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributePoisonClue +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributeRandomMurderer +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.clueCount +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.solvedMystery +import core.ServerConstants.Companion.SERVER_NAME +import core.api.* +import core.game.dialogue.* +import core.game.node.entity.player.Player +import core.plugin.Initializable +import core.tools.END_DIALOGUE +import core.tools.RandomFunction +import org.rs09.consts.Items +import org.rs09.consts.NPCs + +@Initializable +class GuardDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, GuardDialogueFile(), npc) + return true + } + override fun newInstance(player: Player): DialoguePlugin { + return GuardDialogue(player) + } + override fun getIds(): IntArray { + return intArrayOf(NPCs.GUARD_812, NPCs.GUARD_6191) + } +} + +class GuardDialogueFile : DialogueFile() { + override fun handle(componentID: Int, buttonID: Int) { + val KILLER = when (getAttribute(player!!, attributeRandomMurderer, 0)) { + 0 -> "Anna" + 1 -> "Bob" + 2 -> "Carol" + 3 -> "David" + 4 -> "Elizabeth" + 5 -> "Frank" + else -> "Anna" + } + val POISONSPOT = when (getAttribute(player!!, attributeRandomMurderer, 0)) { + 0 -> "compost heap" + 1 -> "beehive" + 2 -> "drain" + 3 -> "spiders nest" + 4 -> "fountain" + 5 -> "Sinclair Crest" + else -> "compost heap" + } + val KILLERMALE = when (getAttribute(player!!, attributeRandomMurderer, 0)) { + 1, 3, 5 -> true + else -> false + } + val EVIDENCE = (1796..1822).toList().toIntArray() //All Murder Mystery related items are between these item ids + + when (getQuestStage(player!!, Quests.MURDER_MYSTERY)){ + 0 -> when (stage) { + 0 -> playerl(FacialExpression.NEUTRAL, "What's going on here?").also { stage++ } + 1 -> npcl(FacialExpression.SAD, "Oh, it's terrible! Lord Sinclair has been murdered and we don't have any clues as to who or why. We're totally baffled!").also { stage++ } + 2 -> npcl(FacialExpression.SAD, "If you can help us we will be very grateful.").also { stage++ } + 3 -> options("Sure, I'll help.", "You should do your own dirty work.").also { stage++ } + 4 -> when (buttonID) { + 1 -> playerl(FacialExpression.HAPPY, "Sure, I'll help.").also { stage++ } + 2 -> playerl("You should do your own dirty work.").also { stage = 8 } + } + 5 -> npcl(FacialExpression.HAPPY, "Thanks a lot!").also { stage++ } + 6 -> playerl(FacialExpression.NEUTRAL, "What should I be doing to help?").also { stage++ } + 7 -> npcl(FacialExpression.NEUTRAL, "Look around and investigate who might be responsible. The Sarge said every murder leaves clues to who done it, but frankly we're out of our depth here.") + .also { setAttribute(player!!, attributeRandomMurderer, RandomFunction.random(5))} + .also { setQuestStage(player!!, Quests.MURDER_MYSTERY, 1) } + .also { stage = END_DIALOGUE } + + 8 -> npcl("Get lost then, this is private property! ...Unless you'd like to be taken in for questioning yourself?").also { stage = END_DIALOGUE } + } + 1 -> when (stage) { + 0 -> options("What should I be doing to help again?", "How did Lord Sinclair die?", "I know who did it!").also { stage++ } + 1 -> when(buttonID) { + 1 -> playerl("What should I be doing to help?").also { stage = 2 } + 2 -> playerl("How did Lord Sinclair die?").also { stage = 3 } + 3 -> playerl(FacialExpression.HAPPY, "I know who did it!") + .also { + stage = if (solvedMystery(player!!)) 46 + else if (clueCount(player!!) == 1) { + if (inInventory(player!!, Items.KILLERS_PRINT_1815)) { + 41 + } + else if (getAttribute(player!!, attributePoisonClue,0) == 2) { + 37 + } + else 33 + } + else if (clueCount(player!!) > 1) 32 + else 6 + } + } + 2 -> npcl("Look around and investigate who might be responsible. The Sarge said every murder leaves clues to who done it, but frankly we're out of our depth here.").also { stage = END_DIALOGUE } + + 3 -> npcl("Well, it's all very mysterious. Mary, the maid, found the body in the study next to his bedroom on the east wing of the ground floor.").also { stage++ } + 4 -> npcl("The door was found locked from the inside, and he seemed to have been stabbed, but there was an odd smell in the room. Frankly, I'm stumped.").also { stage = END_DIALOGUE } + + 5 -> npcl("Really? That was quick work! Who?").also { stage++ } + 6 -> options("It was an intruder!", "The butler did it!", "It was one of the servants!", "It was one of his family!").also { stage++ } + 7 -> when (buttonID) { + 1 -> playerl("It was an intruder!").also { stage = 10 } + 2 -> playerl("The butler did it!").also { stage = 8 } + 3 -> playerl("It was one of the servants!").also { stage = 13 } + 4 -> playerl("It was one of his family!").also { stage = 20 } + } + 8 -> npcl("I hope you have proof to that effect. We have to arrest someone for this and it seems to me that only the actual murderer would gain by falsely accusing someone.").also { stage++ } + 9 -> npcl("Although having said that the butler is kind of shifty looking...").also { stage = END_DIALOGUE } + + 10 -> npcl("That's what we were thinking too. That someone broke in to steal something, was discovered by Lord Sinclair, stabbed him and ran.").also { stage++ } + 11 -> npcl("It's odd that apparently nothing was stolen though... Find out something has been stolen,").also { stage++ } + 12 -> npcl("and the case is closed, but the murdered man was a friend of the King and it's more than my job's worth not to investigate fully.").also { stage = END_DIALOGUE } + + 13 -> npcl("Oh really? Which one?").also { stage++ } + 14 -> options("It was one of the women.", "It was one of the men.").also { stage++ } + 15 -> when (buttonID) { + 1 -> playerl("It was one of the women.").also { stage = 16 } + 2 -> playerl("It was one of the men.").also { stage = 18 } + } + 16 -> options("It was so obviously Louisa The Cook.", "It must have been Mary The Maid.").also { stage++ } + 17 -> when (buttonID) { + 1 -> playerl("It was so obviously Louisa The Cook.").also { stage = 27 } + 2 -> playerl("It must have been Mary The Maid.").also { stage = 27 } + } + + 18 -> options("It can only be Donovan the Handyman.", "Pierre the Dog Handler. No question", "Hobbes the Butler. The butler *always* did it.", "You must know it was Stanford The Gardener.").also { stage++ } + 19-> when (buttonID) { + 1 -> playerl("It can only be Donovan the Handyman.").also { stage = 27 } + 2 -> playerl("Pierre the Dog Handler. No question.").also { stage = 27 } + 3 -> playerl("Hobbes the Butler. The butler *always* did it.").also { stage = 8 } + 4 -> playerl("You must know it was Stanford The Gardener.").also { stage = 27 } + } + + 20 -> npcl("Oh really? Which one?").also { stage++ } + 21 -> options("It was one of the women.", "It was one of the men.").also { stage++ } + 22 -> when (buttonID) { + 1 -> playerl("It was one of the women.").also { stage = 23 } + 2 -> playerl("It was one of the men.").also { stage = 25 } + } + 23 -> options("I know it was Anna.", "I am so sure it was Carol.", "I'll bet you anything it was Elizabeth.").also { stage++ } + 24 -> when (buttonID) { + 1 -> playerl("I know it was Anna.").also { stage = 27 } + 2 -> playerl("I am so sure it was Carol.").also { stage = 27 } + 3 -> playerl("I'll bet you anything it was Elizabeth.").also { stage = 27 } + } + 25 -> options("I'm certain it was Bob.", "It was David. No doubt about it.", "If it wasn't Frank I'll eat my shoes.").also { stage++ } + 26 -> when (buttonID) { + 1 -> playerl("I'm certain it was Bob.").also { stage = 27 } + 2 -> playerl("It was David. No doubt about it.").also { stage = 27 } + 3 -> playerl("If it wasn't Frank I'll eat my shoes.").also { stage = 27 } + } + 27 -> sendDialogue(player!!, "You tell the guard who you suspect of the crime.").also { stage++ } + 28 -> npcl("Great work, show me the evidence and we'll take them to the dungeons.").also { stage++ } + 29 -> npcl("You *DO* have evidence of their crime, right?").also { stage++ } + 30 -> playerl("Uh...").also { stage++ } + 31 -> npcl("Tch. You wouldn't last a day in the guards with sloppy thinking like that. Come see me when you have some proof of your accusations.").also { stage = END_DIALOGUE } + + 32 -> showTopics( + IfTopic("I have proof that it wasn't any of the servants.", 33, inInventory(player!!, Items.CRIMINALS_THREAD_1808) || inInventory(player!!, Items.CRIMINALS_THREAD_1809) || inInventory(player!!, Items.CRIMINALS_THREAD_1810), true), + IfTopic("I have proof one of the family lied about the poison.", 37, getAttribute(player!!, attributePoisonClue, 0) == 2, true), + IfTopic("I have the finger prints of the culprit.", 41, inInventory(player!!, Items.KILLERS_PRINT_1815), true) + ) + 33 -> playerl("I have proof that it wasn't any of the servants!").also { stage++ } + 34 -> sendDialogue(player!!, "You show the guard the thread you found on the window.").also { stage ++ } + 35 -> playerl("All the servants dress in black so it couldn't have been one of them.").also { stage++ } + 36 -> npcl("That's some good work there. I guess it wasn't a servant. You still haven't proved who did do it though.").also { stage = END_DIALOGUE } + + 37 -> playerl("I have proof that $KILLER is lying about the poison.").also { stage++ } + 38 -> npcl("Oh really? How did you get that?").also { stage++ } + 39 -> sendDialogue(player!!, "You tell the guard about the " + + when (getAttribute(player!!, attributeRandomMurderer, 0)) { + 4 -> "mosquitos at the fountain." + 5 -> "tarnished family crest." + else -> "$POISONSPOT." + }).also { stage++ } + 40 -> npcl("Hmm. That's some good detective work there, we need more evidence before we can close the case though. Keep up the good work.").also { stage = END_DIALOGUE } + + 41 -> playerl("I have the fingerprints of the culprit!").also { stage++ } + 42 -> playerl("I have $KILLER's fingerprints here, you can see for yourself they match the fingerprints on the murder weapon exactly.").also { stage++ } + 43 -> sendDialogue(player!!,"You show the guard the finger prints evidence.").also { stage++ } + 44 -> npcl("... I'm impressed. How on earth did you think of something like that? I've never heard of such a technique for finding criminals before!").also { stage++ } + 45 -> npcl("This will come in very handy in the future but we can't arrest someone on just this. I'm afraid you'll still need to find more evidence before we can close this case completely.").also { stage = END_DIALOGUE } + + 46 -> playerl(FacialExpression.HAPPY, "I have conclusive proof who the killer was.").also { stage++ } + 47 -> npcl(FacialExpression.HAPPY, "You do? That's excellent work. Let's hear it then.").also { stage++ } + 48 -> playerl("I don't think it was an intruder, and I don't think Lord Sinclair was killed by being stabbed.").also { stage++ } + 49 -> npcl("Hmmm? Really? Why not?").also { stage++ } + 50 -> playerl(FacialExpression.HAPPY, "Nobody heard the guard dog barking, which it would have if it had been an intruder who was responsible.").also { stage++ } + 51 -> playerl("Nobody heard any signs of a struggle either. I think the knife was there to throw suspicion away from the real culprit.").also { stage++ } + 52 -> npcl("Yes, that makes sense. But who did do it then?").also { stage++ } + 53 -> sendDialogue(player!!, "You prove to the guard the thread matches $KILLER's clothes.").also { stage++ } + 54 -> npcl("Yes, I'd have to agree with that... but we need more evidence!").also { stage++ } + 55 -> sendDialogue(player!!, "You prove to the guard $KILLER did not use the poison on the $POISONSPOT.").also { stage++ } + 56 -> npcl("Excellent work - have you considered a career as a detective? But I'm afraid it's still not quite enough...").also { stage++ } + 57 -> sendDialogue(player!!, "You match $KILLER's finger prints with those on the dagger found in the body of Lord Sinclair.").also { stage++ } + 58 -> npcl(FacialExpression.HAPPY, "Yes. There's no doubt about it. It must have been $KILLER who killed " + if (KILLERMALE) {"his"} else {"her"} + " father. All of the guards must congratulate you on your excellent work in helping us to solve this case.").also { stage++ } + 59 -> npcl("We don't have many murders here in $SERVER_NAME and I'm afraid we wouldn't have been able to solve it by ourselves. We will hold " + if (KILLERMALE) {"him"} else {"her"} + " here under house arrest until such time as we bring " + if (KILLERMALE) {"him"} else {"her"} + " to trial.").also { stage++ } + 60 -> npcl("You have our gratitude, and I'm sure the rest of the family's as well, in helping to apprehend the murderer. I'll just take the evidence from you now.").also { stage++ } + 61 -> sendDialogue(player!!, "You hand over all the evidence.").also { stage++ } + 62 -> npcl(FacialExpression.HAPPY, "Please accept this reward from the family!").also { stage++ } + 63 -> finishQuest(player!!, Quests.MURDER_MYSTERY) + .also { + player!!.inventory.removeAll(EVIDENCE) + player!!.bank.removeAll(EVIDENCE) + player!!.equipment.removeAll(EVIDENCE) + } + .also { stage = END_DIALOGUE } + } + 100 -> when (stage) { + 0 -> npcl("Excellent work on solving the murder! All of the guards I know are very impressed, and don't worry, we have the murderer under guard until they can be taken to trial.").also { stage++ } + 1 -> playerl("Is there anything else I can do? Seems awfully quiet up at the house, considering their sibling has just been arrested.").also { stage++ } + 2 -> npcl("Nothing right now, we have it all under control.").also { stage = END_DIALOGUE } + } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/murdermystery/HobbesDialogue.kt b/Server/src/main/content/region/kandarin/quest/murdermystery/HobbesDialogue.kt new file mode 100644 index 000000000..75e67d2d6 --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/murdermystery/HobbesDialogue.kt @@ -0,0 +1,62 @@ +package content.region.kandarin.quest.murdermystery + +import content.data.Quests +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributeNoiseClue +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributePoisonClue +import core.api.* +import core.game.dialogue.* +import core.game.node.entity.player.Player +import core.plugin.Initializable +import core.tools.END_DIALOGUE +import org.rs09.consts.NPCs + +@Initializable +class HobbesDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, HobbesDialogueFile(), npc) + return true + } + override fun newInstance(player: Player): DialoguePlugin { + return HobbesDialogue(player) + } + override fun getIds(): IntArray { + return intArrayOf(NPCs.HOBBES_808) + } +} + +class HobbesDialogueFile : DialogueFile() { + override fun handle(componentID: Int, buttonID: Int) { + when (getQuestStage(player!!, Quests.MURDER_MYSTERY)){ + 0 -> npcl("This is private property! Please leave!").also { stage = END_DIALOGUE } + 1 -> when (stage) { + 0 -> playerl(FacialExpression.NEUTRAL, "I'm here to help the guards with their investigation.").also { stage++ } + 1 -> npcl(FacialExpression.ASKING, "How can I help?").also { stage++ } + 2 -> showTopics( + Topic("Who do you think is responsible?", 3), + Topic( "Where were you at the time of the murder?", 6), + IfTopic("Did you hear any suspicious noises at all?", 7, getAttribute(player!!, attributeNoiseClue, false)), + IfTopic("Do you know why so much poison was bought recently?", 12, getAttribute(player!!, attributePoisonClue, 0) > 0) + ) + + 3 -> npcl("Well, in my considered opinion it must be David. The man is nothing more than a bully And I happen to know that poor Lord Sinclair and David had a massive argument in the living").also { stage++ } + 4 -> npcl("room about the way he treats the staff, the other day. I did not intend to overhear their conversation, but they were shouting so loudly I could not help but Overhear it. David definitely used the words").also { stage++ } + 5 -> npcl("'I am going to kill you!' as well. I think he should be the prime suspect. He has a nasty temper that one.").also { stage = END_DIALOGUE } + + 6 -> npcl("I was assisting the cook with the evening meal. I gave Mary His Lordships' dinner, and sent her to take it to him, then heard the scream as she found the body.").also { stage = END_DIALOGUE } + + 7 -> npcl("How do you mean 'suspicious'?").also { stage++ } + 8 -> playerl("Any sounds of a struggle with Lord Sinclair?").also { stage++ } + 9 -> npcl("No, I definitely didn't hear anything like that.").also { stage++ } + 10 -> playerl("How about the guard dog barking at all?").also { stage++ } + 11 -> npcl("You know, now you come to mention it I don't believe I did. I suppose that is Proof enough that it could not have been an intruder who is responsible.").also { stage = END_DIALOGUE } + + 12 -> npcl("Well, I do know that Elizabeth was extremely annoyed by the mosquito nest under the fountain in the garden, and was going to do something about it. I suspect any poison she bought would have been").also { stage++ } + 13 -> npcl ("enough to get rid of it. A Good job, too. I hate mosquitos.").also { stage++ } + 14 -> playerl("Yeah, so do I.").also { stage++ } + 15 -> npcl("You'd really have to ask her though.").also { stage = END_DIALOGUE } + } + 100 -> npcl("Thank you for all your help in solving the murder.").also { stage = END_DIALOGUE } + } + } +} + diff --git a/Server/src/main/content/region/kandarin/quest/murdermystery/LouisaDialogue.kt b/Server/src/main/content/region/kandarin/quest/murdermystery/LouisaDialogue.kt new file mode 100644 index 000000000..f8c7306a7 --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/murdermystery/LouisaDialogue.kt @@ -0,0 +1,59 @@ +package content.region.kandarin.quest.murdermystery + +import content.data.Quests +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributeNoiseClue +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributePoisonClue +import core.api.* +import core.game.dialogue.* +import core.game.node.entity.player.Player +import core.plugin.Initializable +import core.tools.END_DIALOGUE +import org.rs09.consts.NPCs + +@Initializable +class LouisaDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, LouisaDialogueFile(), npc) + return true + } + override fun newInstance(player: Player): DialoguePlugin { + return LouisaDialogue(player) + } + override fun getIds(): IntArray { + return intArrayOf(NPCs.LOUISA_809) + } +} + +class LouisaDialogueFile : DialogueFile() { + override fun handle(componentID: Int, buttonID: Int) { + when (getQuestStage(player!!, Quests.MURDER_MYSTERY)){ + 0 -> npcl("I'm far too upset to talk to random people right now.").also { stage = END_DIALOGUE } + 1 -> when (stage) { + 0 -> playerl(FacialExpression.NEUTRAL, "I'm here to help the guards with their investigation.").also { stage++ } + 1 -> npcl(FacialExpression.ASKING, "How can I help?").also { stage++ } + 2 -> showTopics( + Topic("Who do you think is responsible?", 3), + Topic( "Where were you at the time of the murder?", 6), + IfTopic("Did you hear any suspicious noises at all?", 7, getAttribute(player!!, attributeNoiseClue, false)), + IfTopic("Do you know why so much poison was bought recently?", 12, getAttribute(player!!, attributePoisonClue, 0) > 0) + ) + + 3 -> npcl("Elizabeth.").also { stage ++ } + 4 -> npcl("Her father confronted her about her constant petty thieving, and was devastated to find she had stolen a silver needle which had meant a lot to him.").also { stage++ } + 5 -> npcl("You could hear their argument from Lumbridge!").also { stage = END_DIALOGUE } + + 6 -> npcl("I was right here with Hobbes and Mary. You can't suspect me surely!").also { stage = END_DIALOGUE } + + 7 -> npcl("Suspicious? What do you mean suspicious?").also { stage++ } + 8 -> playerl("Any sounds of a struggle with an intruder for example?").also { stage++ } + 9 -> npcl("No, I'm sure I don't recall any such thing.").also { stage++ } + 10 -> playerl("How about the guard dog barking at an intruder?").also { stage++ } + 11 -> npcl("No, I didn't. If you don't have anything else to ask can You go and leave me alone now? I have a lot of cooking to do for this evening.").also { stage = END_DIALOGUE } + + 12 -> npcl("I told Carol to buy some from that strange poison salesman and clean the drains before they began to smell any worse. She was the one who blocked them in the first place with a load").also { stage++ } + 13 -> npcl("of beans that she bought for some reason. There were far too many to eat, and they were almost rotten when she bought them anyway! You'd really have to ask her though.").also { stage = END_DIALOGUE } + } + 100 -> npcl("Thank you for all your help in solving the murder.").also { stage = END_DIALOGUE } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/murdermystery/MaryDialogue.kt b/Server/src/main/content/region/kandarin/quest/murdermystery/MaryDialogue.kt new file mode 100644 index 000000000..07388707c --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/murdermystery/MaryDialogue.kt @@ -0,0 +1,59 @@ +package content.region.kandarin.quest.murdermystery + +import content.data.Quests +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributeNoiseClue +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributePoisonClue +import core.api.* +import core.game.dialogue.* +import core.game.node.entity.player.Player +import core.plugin.Initializable +import core.tools.END_DIALOGUE +import org.rs09.consts.NPCs + +@Initializable +class MaryDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, MaryDialogueFile(), npc) + return true + } + override fun newInstance(player: Player): DialoguePlugin { + return MaryDialogue(player) + } + override fun getIds(): IntArray { + return intArrayOf(NPCs.MARY_810) + } +} + +class MaryDialogueFile : DialogueFile() { + override fun handle(componentID: Int, buttonID: Int) { + when (getQuestStage(player!!, Quests.MURDER_MYSTERY)){ + 0 -> sendDialogue(player!!, "They are ignoring you.").also { stage = END_DIALOGUE } + 1 -> when (stage) { + 0 -> playerl(FacialExpression.NEUTRAL, "I'm here to help the guards with their investigation.").also { stage++ } + 1 -> npcl(FacialExpression.ASKING, "How can I help?").also { stage++ } + 2 -> showTopics( + Topic("Who do you think is responsible?", 3), + Topic( "Where were you at the time of the murder?", 5), + IfTopic("Did you hear any suspicious noises at all?", 7, getAttribute(player!!, attributeNoiseClue, false)), + IfTopic("Do you know why so much poison was bought recently?", 12, getAttribute(player!!, attributePoisonClue, 0) > 0) + ) + + 3 -> npcl("Oh I don't know... Frank was acting kind of funny... After that big argument him and the Lord had the other day by the beehive... so").also { stage ++ } + 4 -> npcl("I guess maybe him... but it's really scary to think someone here might have been responsible. I actually hope it was a burglar...").also { stage = END_DIALOGUE } + + 5 -> npcl("I was with Hobbes and Louisa in the Kitchen helping to prepare Lord Sinclair's meal, and then when I took it to his study... I saw... oh, it was horrible... he was...").also { stage++ } + 6 -> sendDialogue(player!!, "She seems to be on the verge of crying. You decide not to push her anymore for details.").also { stage = END_DIALOGUE} + + 7 -> npcl("I don't really remember hearing anything out of the ordinary.").also { stage++ } + 8 -> playerl("No sounds of a struggle then?").also { stage++ } + 9 -> npcl("No, I don't remember hearing anything like that.").also { stage++ } + 10 -> playerl("How about the guard dog barking?").also { stage++ } + 11 -> npcl("Oh that horrible dog is always barking at noting but I don't think I did...").also { stage = END_DIALOGUE } + + 12 -> npcl("I overheard Anna saying to Stanford that if he didn't do something about the state of his compost heap, she was going to.").also { stage++ } + 13 -> npcl("She really doesn't get on well with Stanford, I really have no idea why. You'd really have to ask her though.").also { stage = END_DIALOGUE } + } + 100 -> npcl("Thank you for all your help in solving the murder.").also { stage = END_DIALOGUE } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/murdermystery/MurderMystery.kt b/Server/src/main/content/region/kandarin/quest/murdermystery/MurderMystery.kt new file mode 100644 index 000000000..23ec627b1 --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/murdermystery/MurderMystery.kt @@ -0,0 +1,156 @@ +package content.region.kandarin.quest.murdermystery + +import content.data.Quests +import core.api.* +import core.game.node.entity.player.link.quest.Quest +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.plugin.Initializable +import org.rs09.consts.Items + +// https://www.youtube.com/watch?v=f1Eou5i5hBo at 2:29 +// https://www.youtube.com/watch?v=BwO4JhZdQKo at 25:20 +// https://www.youtube.com/watch?v=u5Osw_jas4A at 26:35 +// https://www.youtube.com/watch?v=f1Eou5i5hBo +// https://www.youtube.com/watch?v=9Q2aLUg44SQ at 10:02 +// https://www.youtube.com/watch?v=_lTUessdfOM 6:18 + +/** + * Murder Mystery Quest + */ +@Initializable +class MurderMystery : Quest(Quests.MURDER_MYSTERY, 93, 92, 3, 192, 0, 1, 2) { + + companion object { + const val questName = "Murder Mystery" + + const val attributeRandomMurderer = "/save:quest:murdermystery-randommurderer" //Alphabetical, 0 for Anna, 1 for Bob, 2 for Carol, 3 for David, 4 for Elizabeth, 5 for Frank + const val attributeTakenThread = "/save:quest:murdermystery-takenthread" //true after taking thread for the first time from window + const val attributeNoiseClue = "/save:quest:murdermystery-noiseclue" //true on learning of the barking dog for the first time + const val attributePoisonClue = "/save:quest:murdermystery-poisonclue" //1 after learning poison was bought, 2 after finding liar + const val attributeAskPoisonAnna = "/save:quest:murdermystery-askpoisonanna" //true after asking Anna about poison + const val attributeAskPoisonBob = "/save:quest:murdermystery-askpoisonbob" //true after asking Bob about poison + const val attributeAskPoisonCarol = "/save:quest:murdermystery-askpoisoncarol" //true after asking Carol about poison + const val attributeAskPoisonDavid = "/save:quest:murdermystery-askpoisondavid" //true after asking David about poison + const val attributeAskPoisonElizabeth = "/save:quest:murdermystery-askpoisonelizabeth" //true after asking Elizabeth about poison + const val attributeAskPoisonFrank = "/save:quest:murdermystery-askpoisonfrank" //true after asking Anna about poison + + fun clueCount(player: Player) : Int { + var count = 0 + if (inInventory(player, Items.CRIMINALS_THREAD_1808) || inInventory(player, Items.CRIMINALS_THREAD_1809) || inInventory(player, Items.CRIMINALS_THREAD_1810)) { + count++ + } + if (inInventory(player, Items.KILLERS_PRINT_1815)) { + count ++ + } + if (getAttribute(player, attributePoisonClue,0) == 2) { + count ++ + } + return count + } + + fun solvedMystery(player: Player) : Boolean { + return ( + ( inInventory(player, Items.CRIMINALS_THREAD_1808) || inInventory(player, Items.CRIMINALS_THREAD_1809) || inInventory(player, Items.CRIMINALS_THREAD_1810) ) + && inInventory(player, Items.KILLERS_PRINT_1815) + && getAttribute(player, attributePoisonClue,0) == 2 + && getAttribute(player, attributeNoiseClue, false) + ) + } + } + override fun drawJournal(player: Player, stage: Int) { + super.drawJournal(player, stage) + var line = 12 + var stage = getStage(player) + + var started = getQuestStage(player, Quests.MURDER_MYSTERY) > 0 + + if (!started) { + line(player, "I can start this quest by speaking to one of the !!Guards?? at", line++, false) + line(player, "the !!Sinclair Mansion??, North of the !!Seer's Village??.", line++, false) + } else if (stage < 100) { + line(player, "Lord Sinclair, a prominent nobleman, had been horribly", line++, true) + line(player, "murdered at his mansion. The guards had been sent to", line++, true) + line(player, "investigate his murder, but have been completely stuck.", line++, true) + + if (solvedMystery(player)) { + line(player, "One of the guards asked me for my help in solving the", line++, true) + line(player, "murder. After careful examination of the crime scene and", line++, true) + line(player, "interrogating all suspects, I worked out who was guilty.", line++, true) + } else { + line(player, "One of the !!guards?? has asked me for my help in solving the", line++, false) + line(player, "murder. I should !!examine the crime scene?? very closely for", line++, false) + line(player, "evidence, and !!interrogate everybody?? in the area carefully.", line++, false) + } + + // This may not be a stage but an attribute when all the evidence is collected. + if (solvedMystery(player)) { + line(player, "I have !!indisputable evidence?? of who the murderer must be.", line++, false) + line(player, "I should take it to one of the !!Guards?? immediately.", line++, false) + } else { + line++ + if (inInventory(player, Items.CRIMINALS_THREAD_1808) || inInventory(player, Items.CRIMINALS_THREAD_1809) || inInventory(player, Items.CRIMINALS_THREAD_1810)) { + line(player, "I have found some !!coloured thread??. It might be useful.", line++, false) + } + if (inInventory(player, Items.CRIMINALS_DAGGER_1813) || inInventory(player, Items.CRIMINALS_DAGGER_1814)) { + line(player, "I have taken the !!murder weapon??. I think it might help me.", line++, false) + } + if (inInventory(player, Items.PUNGENT_POT_1812)) { + line(player, "I have a !!strange smelling pot??. It seems like a clue.", line++, false) + } + } + } else { + line(player, "One of the guards asked me for my help in solving the", line++, true) + line(player, "murder. After careful examination of the crime scene and", line++, true) + line(player, "interrogating all suspects, I worked out who was guilty.", line++, true) + line(player, "I took the evidence I had collected to the Guards and", line++, true) + line(player, "explained how it could identify the killer. Impressed", line++, true) + line(player, "with my deductions, the killer was arrested and I was", line++, true) + line(player, "given a fair reward for my help in solving the crime.", line++, true) + line++ + line(player,"QUEST COMPLETE!", line) + } + + } + + override fun reset(player: Player) { + removeAttribute(player, attributeRandomMurderer) + removeAttribute(player, attributeNoiseClue) + removeAttribute(player, attributePoisonClue) + removeAttribute(player, attributeTakenThread) + removeAttribute(player, attributeAskPoisonAnna) + removeAttribute(player, attributeAskPoisonBob) + removeAttribute(player, attributeAskPoisonCarol) + removeAttribute(player, attributeAskPoisonDavid) + removeAttribute(player, attributeAskPoisonElizabeth) + removeAttribute(player, attributeAskPoisonFrank) + } + + override fun finish(player: Player) { + var ln = 10 + super.finish(player) + player.packetDispatch.sendString("You have completed the Murder Mystery quest!", 277, 4) + player.packetDispatch.sendItemZoomOnInterface(Items.COINS_617, 230, 277, 5) + + drawReward(player, "3 Quest Points", ln++) + drawReward(player, "2000 coins", ln++) + drawReward(player, "1406 Crafting XP", ln++) + + addItem(player, Items.COINS_995, 2000) + rewardXP(player, Skills.CRAFTING, 1406.0) + + removeAttribute(player, attributeNoiseClue) + removeAttribute(player, attributePoisonClue) + removeAttribute(player, attributeTakenThread) + removeAttribute(player, attributeAskPoisonAnna) + removeAttribute(player, attributeAskPoisonBob) + removeAttribute(player, attributeAskPoisonCarol) + removeAttribute(player, attributeAskPoisonDavid) + removeAttribute(player, attributeAskPoisonElizabeth) + removeAttribute(player, attributeAskPoisonFrank) + } + + override fun newInstance(`object`: Any?): Quest { + return this + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/murdermystery/MurderMysteryListeners.kt b/Server/src/main/content/region/kandarin/quest/murdermystery/MurderMysteryListeners.kt new file mode 100644 index 000000000..bf9184584 --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/murdermystery/MurderMysteryListeners.kt @@ -0,0 +1,442 @@ +package content.region.kandarin.quest.murdermystery + +import content.data.Quests +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributeAskPoisonAnna +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributeAskPoisonBob +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributeAskPoisonCarol +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributeAskPoisonDavid +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributeAskPoisonElizabeth +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributeAskPoisonFrank +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributeNoiseClue +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributePoisonClue +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributeRandomMurderer +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributeTakenThread +import core.api.* +import core.game.interaction.InteractionListener +import core.game.node.item.GroundItem +import core.game.node.item.Item +import org.rs09.consts.Items +import org.rs09.consts.Scenery + +class MurderMysteryListeners : InteractionListener { + companion object { + val UNDUSTEDEVIDENCE = intArrayOf( + Items.CRIMINALS_DAGGER_1813, + Items.PUNGENT_POT_1812, + Items.SILVER_NECKLACE_1796, + Items.SILVER_CUP_1798, + Items.SILVER_BOTTLE_1800, + Items.SILVER_BOOK_1802, + Items.SILVER_NEEDLE_1804, + Items.SILVER_POT_1806 + ) + val DUSTEDEVIDENCE = intArrayOf( + Items.CRIMINALS_DAGGER_1814, + Items.SILVER_NECKLACE_1797, + Items.SILVER_CUP_1799, + Items.SILVER_BOTTLE_1801, + Items.SILVER_BOOK_1803, + Items.SILVER_NEEDLE_1805, + Items.SILVER_POT_1807 + ) + val SILVERBARRELS = intArrayOf( + Scenery.ANNA_S_BARREL_2656, + Scenery.BOB_S_BARREL_2657, + Scenery.CAROL_S_BARREL_2658, + Scenery.DAVID_S_BARREL_2659, + Scenery.ELIZABETH_S_BARREL_2660, + Scenery.FRANK_S_BARREL_2661 + ) + val POISONSPOTS = intArrayOf( + Scenery.SINCLAIR_FAMILY_COMPOST_HEAP_26120, + Scenery.SINCLAIR_FAMILY_BEEHIVE_26121, + Scenery.SINCLAIR_MANSION_DRAIN_2843, + Scenery.SPIDERS__NEST_26109, + Scenery.SINCLAIR_FAMILY_FOUNTAIN_2654, + Scenery.SINCLAIR_FAMILY_CREST_2655 + ) + val SUSPECTPRINTS = intArrayOf( + Items.ANNAS_PRINT_1816, + Items.BOBS_PRINT_1817, + Items.CAROLS_PRINT_1818, + Items.DAVIDS_PRINT_1819, + Items.ELIZABETHS_PRINT_1820, + Items.FRANKS_PRINT_1821 + ) + } + + override fun defineListeners() { + on(Items.CRIMINALS_DAGGER_1813, GROUNDITEM, "take") { player, groundItem -> + when (getQuestStage(player, Quests.MURDER_MYSTERY)) { + 1 -> { + if (inInventory(player, Items.CRIMINALS_DAGGER_1813) || inInventory(player, Items.CRIMINALS_DAGGER_1814)) { + sendDialogue(player, "I already have the murder weapon.") + } + else if (addItem(player, Items.CRIMINALS_DAGGER_1813)) { + removeGroundItem(groundItem as GroundItem) + sendDialogue(player, "This knife doesn't seem sturdy enough to have killed Lord Sinclair.") + } + return@on true + } + else -> { + sendDialogue(player, "You need the guards' permission to do that.") + return@on true + } + } + } + on(Items.PUNGENT_POT_1812, GROUNDITEM, "take") { player, groundItem -> + when (getQuestStage(player, Quests.MURDER_MYSTERY)) { + 1 -> { + if (inInventory(player, Items.PUNGENT_POT_1812)) { + sendDialogue(player, "I already have the poisoned pot.") + } else if (addItem(player, Items.PUNGENT_POT_1812)) { + removeGroundItem(groundItem as GroundItem) + sendDialogue(player, "It seems like Lord Sinclair was drinking from this before he died.") + } + return@on true + } + else -> { + sendDialogue(player, "You need the guards' permission to do that.") + return@on true + } + } + } + on(intArrayOf(Scenery.STURDY_WOODEN_GATE_2664, Scenery.STURDY_WOODEN_GATE_2665), SCENERY, "investigate") { player, _ -> + when (getQuestStage(player, Quests.MURDER_MYSTERY)) { + 1 -> { + sendDialogue(player, "As you approach the gate the guard dog starts barking loudly at you. There is no way an intruder could have committed the murder. It must have been someone the dog knew to get past it quietly.") + setAttribute(player, attributeNoiseClue, true) + return@on true + } + else -> { + sendDialogue(player, "You need the guards' permission to do that.") + return@on true + } + } + } + on(Scenery.SMASHED_WINDOW_26110, SCENERY, "investigate") { player, _ -> + when (getQuestStage(player, Quests.MURDER_MYSTERY)) { + 1 -> { + sendDialogue(player, "Some thread seems to have been caught on a loose nail on the window.") + if (inInventory(player, Items.CRIMINALS_THREAD_1808) || inInventory(player, Items.CRIMINALS_THREAD_1809) || inInventory(player, Items.CRIMINALS_THREAD_1810)) { + sendMessage(player, "You have already taken the thread.") + } + else if (getAttribute(player, attributeTakenThread, false)) { + if (hasSpaceFor(player, Item(Items.CRIMINALS_THREAD_1808))) { + when (getAttribute(player, attributeRandomMurderer, 0)) { + 1, 2 -> addItem(player, Items.CRIMINALS_THREAD_1808) + 0, 3 -> addItem(player, Items.CRIMINALS_THREAD_1809) + else -> addItem(player, Items.CRIMINALS_THREAD_1810) + } + sendMessage(player, "Lucky for you there's some thread left. You should be less careless in the future.") + } + } + else { + if (hasSpaceFor(player, Item(Items.CRIMINALS_THREAD_1808))) { + when (getAttribute(player, attributeRandomMurderer, 0)) { + 1, 2 -> addItem(player, Items.CRIMINALS_THREAD_1808) + 0, 3 -> addItem(player, Items.CRIMINALS_THREAD_1809) + else -> addItem(player, Items.CRIMINALS_THREAD_1810) + } + sendMessage(player, "You take the thread.") + setAttribute(player, attributeTakenThread, true) + } + } + return@on true + } + else -> { + sendDialogue(player, "You need the guards' permission to do that.") + return@on true + } + } + } + on(Scenery.SMASHED_WINDOW_26110, SCENERY, "break") { player, _ -> + sendDialogue(player, "You don't want to damage evidence!") + return@on true + } + on(Scenery.SACKS_2663, SCENERY, "investigate") { player, _ -> + when (getQuestStage(player, Quests.MURDER_MYSTERY)) { + 1 -> { + openDialogue(player, FlypaperDialogue()) + return@on true + } + else -> { + sendDialogue(player, "You need the guards' permission to do that.") + return@on true + } + } + } + on(SILVERBARRELS, SCENERY, "search") { player, node -> + when (getQuestStage(player, Quests.MURDER_MYSTERY)) { + 1 -> { + when (node.id) { + Scenery.ANNA_S_BARREL_2656 -> { + if (inInventory(player, Items.SILVER_NECKLACE_1796) || inInventory(player, Items.SILVER_NECKLACE_1797)) { + sendDialogue(player, "I already have Anna's Necklace.") + } + else if (addItem(player, Items.SILVER_NECKLACE_1796)){ + sendDialogue(player, "There's something shiny hidden at the bottom. You take Anna's Silver Necklace.") + } + } + Scenery.BOB_S_BARREL_2657 -> { + if (inInventory(player, Items.SILVER_CUP_1798) || inInventory(player, Items.SILVER_CUP_1799)) { + sendDialogue(player, "I already have Bob's cup.") + } + else if (addItem(player, Items.SILVER_CUP_1798)){ + sendDialogue(player, "There's something shiny hidden at the bottom. You take Bob's silver cup.") + } + } + Scenery.CAROL_S_BARREL_2658 -> { + if (inInventory(player, Items.SILVER_BOTTLE_1800) || inInventory(player, Items.SILVER_BOTTLE_1801)) { + sendDialogue(player, "I already have Carol's bottle.") + } + else if (addItem(player, Items.SILVER_BOTTLE_1800)){ + sendDialogue(player, "There's something shiny hidden at the bottom. You take Carol's silver bottle.") + } + } + Scenery.DAVID_S_BARREL_2659 -> { + if (inInventory(player, Items.SILVER_BOOK_1802) || inInventory(player, Items.SILVER_BOOK_1803)) { + sendDialogue(player, "I already have David's book.") + } + else if (addItem(player, Items.SILVER_BOOK_1802)){ + sendDialogue(player, "There's something shiny hidden at the bottom. You take David's silver book.") + } + } + Scenery.ELIZABETH_S_BARREL_2660 -> { + if (inInventory(player, Items.SILVER_NEEDLE_1804) || inInventory(player, Items.SILVER_NEEDLE_1805)) { + sendDialogue(player, "I already have Elizabeth's Needle.") + } + else if (addItem(player, Items.SILVER_NEEDLE_1804)){ + sendDialogue(player, "There's something shiny hidden at the bottom. You take Elizabeth's silver needle.") + } + } + Scenery.FRANK_S_BARREL_2661 -> { + if (inInventory(player, Items.SILVER_POT_1806) || inInventory(player, Items.SILVER_POT_1807)) { + sendDialogue(player, "I already have Frank's pot.") + } + else if (addItem(player, Items.SILVER_POT_1806)){ + sendDialogue(player, "There's something shiny hidden at the bottom. You take Frank's silver pot.") + } + } + } + return@on true + } + else -> { + sendDialogue(player, "You need the guards' permission to do that.") + return@on true + } + } + } + on(POISONSPOTS, SCENERY, "investigate") { player, node -> + when (node.id) { + Scenery.SINCLAIR_FAMILY_COMPOST_HEAP_26120 -> { + if (!getAttribute(player, attributeAskPoisonAnna, false)) { + sendDialogue(player, "It's a heap of compost.") + } + else if (getAttribute(player, attributeRandomMurderer, 0) == 0) { + sendDialogue(player, "The compost is teeming with maggots. Somebody should really do something about it. It's certainly clear nobody's used poison here.") + setAttribute(player, attributePoisonClue, 2) + } + else { + sendDialogue(player, "There is a faint smell of poison behind the smell of the compost.") + } + } + Scenery.SINCLAIR_FAMILY_BEEHIVE_26121 -> { + if (!getAttribute(player, attributeAskPoisonBob, false)) { + sendDialogue(player, "It's a very old beehive.") + } + else if (getAttribute(player, attributeRandomMurderer, 0) == 1) { + sendDialogue(player, "The beehive buzzes with activity. These bees definitely don't seem poisoned at all.") + setAttribute(player, attributePoisonClue, 2) + } + else { + sendDialogue(player, "The hive is empty. There are a few dead bees and a faint smell of poison.") + } + } + Scenery.SINCLAIR_MANSION_DRAIN_2843 -> { + if (!getAttribute(player, attributeAskPoisonCarol, false)) { + sendDialogue(player, "It's the drains from the kitchen.") + } + else if (getAttribute(player, attributeRandomMurderer, 0) == 2) { + sendDialogue(player, "The drain is totally blocked. It really stinks. No, it REALLY smells bad. It's certainly clear nobody's cleaned it recently.") + setAttribute(player, attributePoisonClue, 2) + } + else { + sendDialogue(player, "The drain seems to have been recently cleaned. You can still smell the faint aroma of poison.") + } + } + Scenery.SPIDERS__NEST_26109 -> { + if (!getAttribute(player, attributeAskPoisonDavid, false)) { + sendDialogue(player, "It looks like a spiders' nest of some kind...") + } + else if (getAttribute(player, attributeRandomMurderer, 0) == 3) { + sendDialogue(player, "There is a spiders' nest here. You estimate there must be at least a few hundred spiders ready to hatch. It's certainly clear nobody's used poison here.") + setAttribute(player, attributePoisonClue, 2) + } + else { + sendDialogue(player, "A faint smell of poison and a few dead spiders is all that remains of the spiders nest.") + } + } + Scenery.SINCLAIR_FAMILY_FOUNTAIN_2654 -> { + if (!getAttribute(player, attributeAskPoisonElizabeth, false)) { + sendDialogue(player, "A fountain with large numbers of insects around the base.") + } + else if (getAttribute(player, attributeRandomMurderer, 0) == 4) { + openDialogue(player, FountainDialogue()) + setAttribute(player, attributePoisonClue, 2) + } + else { + sendDialogue(player, "There are a lot of dead mosquitos around the base of the fountain. A faint smell of poison is in the air, but the water seems clean.") + } + } + Scenery.SINCLAIR_FAMILY_CREST_2655 -> { + if (!getAttribute(player, attributeAskPoisonFrank, false)) { + sendDialogue(player, "The Sinclair Family Crest is hung up here.") + } + else if (getAttribute(player, attributeRandomMurderer, 0) == 5) { + sendDialogue(player, "It looks like the Sinclair family crest but it is very dirty. You can barely make it out under all of the grime. It's certainly clear nobody's cleaned it recently.") + setAttribute(player, attributePoisonClue, 2) + } + else { + sendDialogue(player, "The Sinclair family crest. It's shiny and freshly polished and has a slight smell of poison.") + } + } + + } + return@on true + } + onUseWith(ITEM, UNDUSTEDEVIDENCE, Items.POT_OF_FLOUR_1933) { player, used, _ -> + when (used.id) { + Items.CRIMINALS_DAGGER_1813 -> { + removeItem(player, used) + addItem(player, Items.CRIMINALS_DAGGER_1814) + sendMessage(player, "You sprinkle a small amount of flour on the murder weapon.") + sendMessage(player, "The murder weapon is now coated with a thin layer of flour.") + } + Items.PUNGENT_POT_1812 -> { + sendMessage(player, "You sprinkle a small amount of flour on the strange smelling pot.") + sendMessage(player, "The surface isn't shiny enough to take a fingerprint from.") + } + Items.SILVER_NECKLACE_1796 -> { + removeItem(player, used) + addItem(player, Items.SILVER_NECKLACE_1797) + sendMessage(player, "You sprinkle the flour on Anna's necklace.") + sendMessage(player, "The necklace is now coated with a thin layer of flour.") + } + Items.SILVER_CUP_1798 -> { + removeItem(player, used) + addItem(player, Items.SILVER_CUP_1799) + sendMessage(player, "You sprinkle the flour on Bob's cup.") + sendMessage(player, "The cup is now coated with a thin layer of flour.") + } + Items.SILVER_BOTTLE_1800 -> { + removeItem(player, used) + addItem(player, Items.SILVER_BOTTLE_1801) + sendMessage(player, "You sprinkle the flour on Carol's bottle.") + sendMessage(player, "The bottle is now coated with a thin layer of flour.") + } + Items.SILVER_BOOK_1802 -> { + removeItem(player, used) + addItem(player, Items.SILVER_BOOK_1803) + sendMessage(player, "You sprinkle the flour on David's book.") + sendMessage(player, "The Book is now coated with a thin layer of flour.") + } + Items.SILVER_NEEDLE_1804 -> { + removeItem(player, used) + addItem(player, Items.SILVER_NEEDLE_1805) + sendMessage(player, "You sprinkle the flour on Elizabeth's needle.") + sendMessage(player, "The Needle is now coated with a thin layer of flour.") + } + Items.SILVER_POT_1806 -> { + removeItem(player, used) + addItem(player, Items.SILVER_POT_1807) + sendMessage(player, "You sprinkle the flour on Frank's pot") + sendMessage(player, "The Pot is now coated with a thin layer of flour.") + } + } + removeItem(player, Items.POT_OF_FLOUR_1933) + addItem(player, Items.EMPTY_POT_1931) + return@onUseWith true + } + onUseWith(ITEM, DUSTEDEVIDENCE, Items.FLYPAPER_1811) { player, used, _ -> + removeItem(player, Items.FLYPAPER_1811) + when (used.id) { + Items.CRIMINALS_DAGGER_1814 -> { + removeItem(player, used) + addItem(player, Items.CRIMINALS_DAGGER_1813) + addItem(player, Items.UNKNOWN_PRINT_1822) + sendMessage(player, "You use the flypaper on the floury dagger.") + sendMessage(player, "You have a clean impression of the murderer's finger prints.") + } + Items.SILVER_NECKLACE_1797 -> { + removeItem(player, used) + addItem(player, Items.SILVER_NECKLACE_1796) + addItem(player, Items.ANNAS_PRINT_1816) + sendMessage(player, "You use the flypaper on the flour covered necklace.") + sendMessage(player, "You have a clean impression of Anna's finger prints.") + } + Items.SILVER_CUP_1799 -> { + removeItem(player, used) + addItem(player, Items.SILVER_CUP_1798) + addItem(player, Items.BOBS_PRINT_1817) + sendMessage(player, "You use the flypaper on the flour covered cup.") + sendMessage(player, "You have a clean impression of Bob's finger prints.") + } + Items.SILVER_BOTTLE_1801 -> { + removeItem(player, used) + addItem(player, Items.SILVER_BOTTLE_1800) + addItem(player, Items.CAROLS_PRINT_1818) + sendMessage(player, "You use the flypaper on the flour covered bottle.") + sendMessage(player, "You have a clean impression of Carol's finger prints.") + } + Items.SILVER_BOOK_1803 -> { + removeItem(player, used) + addItem(player, Items.SILVER_BOOK_1802) + addItem(player, Items.DAVIDS_PRINT_1819) + sendMessage(player, "You use the flypaper on the flour covered book.") + sendMessage(player, "You have a clean impression of David's finger prints.") + } + Items.SILVER_NEEDLE_1805 -> { + removeItem(player, used) + addItem(player, Items.SILVER_NEEDLE_1804) + addItem(player, Items.ELIZABETHS_PRINT_1820) + sendMessage(player, "You use the flypaper on the flour covered needle.") + sendMessage(player, "You have a clean impression of Elizabeth's finger prints.") + } + Items.SILVER_POT_1807 -> { + removeItem(player, used) + addItem(player, Items.SILVER_POT_1806) + addItem(player, Items.FRANKS_PRINT_1821) + sendMessage(player, "You use the flypaper on the flour covered pot.") + sendMessage(player, "You have a clean impression of Frank's finger prints.") + } + } + return@onUseWith true + } + onUseWith(ITEM, SUSPECTPRINTS, Items.UNKNOWN_PRINT_1822) { player, used, with -> + val SUSPECT = when (used.id) { + Items.ANNAS_PRINT_1816 -> "Anna" + Items.BOBS_PRINT_1817 -> "Bob" + Items.CAROLS_PRINT_1818 -> "Carol" + Items.DAVIDS_PRINT_1819 -> "David" + Items.ELIZABETHS_PRINT_1820 -> "Elizabeth" + Items.FRANKS_PRINT_1821 -> "Frank" + else -> "Anna" + } + if (used.id == getAttribute(player, attributeRandomMurderer, 0) + Items.ANNAS_PRINT_1816) { + sendDialogue(player, "The fingerprints are an exact match to $SUSPECT's.") + removeItem(player, with.id) + addItem(player, Items.KILLERS_PRINT_1815) + } + else { + sendDialogue(player, "They don't seem to be the same. I guess that clears $SUSPECT of the crime. You destroy the useless fingerprint.") + removeItem(player, used.id) + } + return@onUseWith true + } + onUseWith(SCENERY, intArrayOf(Items.SILVER_POT_1806, Items.PUNGENT_POT_1812), Scenery.BARREL_OF_FLOUR_26122) { player, _, _ -> + sendDialogue(player, "You probably shouldn't use evidence from a crime scene to keep flour in.") + return@onUseWith true + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/murdermystery/PierreDialogue.kt b/Server/src/main/content/region/kandarin/quest/murdermystery/PierreDialogue.kt new file mode 100644 index 000000000..7d94c0677 --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/murdermystery/PierreDialogue.kt @@ -0,0 +1,58 @@ +package content.region.kandarin.quest.murdermystery + +import content.data.Quests +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributeNoiseClue +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributePoisonClue +import core.api.* +import core.game.dialogue.* +import core.game.node.entity.player.Player +import core.plugin.Initializable +import core.tools.END_DIALOGUE +import org.rs09.consts.NPCs + +@Initializable +class PierreDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, PierreDialogueFile(), npc) + return true + } + override fun newInstance(player: Player): DialoguePlugin { + return PierreDialogue(player) + } + override fun getIds(): IntArray { + return intArrayOf(NPCs.PIERRE_807) + } +} + +class PierreDialogueFile : DialogueFile() { + override fun handle(componentID: Int, buttonID: Int) { + when (getQuestStage(player!!, Quests.MURDER_MYSTERY)){ + 0 -> npcl("The Guards told me not to talk to anyone.").also { stage = END_DIALOGUE } + 1 -> when (stage) { + 0 -> playerl(FacialExpression.NEUTRAL, "I'm here to help the guards with their investigation.").also { stage++ } + 1 -> npcl(FacialExpression.ASKING, "How can I help?").also { stage++ } + 2 -> showTopics( + Topic("Who do you think is responsible?", 3), + Topic( "Where were you at the time of the murder?", 5), + IfTopic("Did you hear any suspicious noises at all?", 6, getAttribute(player!!, attributeNoiseClue, false)), + IfTopic("Do you know why so much poison was bought recently?", 11, getAttribute(player!!, attributePoisonClue, 0) > 0) + ) + + 3 -> npcl("Honestly? I think it was Carol.").also { stage++ } + 4 -> npcl("I saw her in a huge argument with Lord Sinclair in the library the other day. It was something to do with stolen books. She definitely seemed upset enough to have done it afterwards.").also { stage = END_DIALOGUE } + + 5 -> npcl("I was in town at the inn. When I got back the house was swarming with guards who told me what had happened. Sorry.").also { stage = END_DIALOGUE } + + 6 -> npcl("Well, like what?").also { stage++ } + 7 -> playerl("Any sounds of a struggle with Lord Sinclair?").also { stage++ } + 8 -> npcl("No, I don't remember hearing anything like that.").also { stage++ } + 9 -> playerl("How about the guard dog barking at all?").also { stage++ } + 10 -> npcl("I hear him bark all the time. It's one of his favourite things to do. I can't say I did the night of the murder though as I wasn't close enough to hear either way.").also { stage = END_DIALOGUE } + + 11 -> npcl("Well, I know David said that he was going to do something about the spiders' nest that's between the two servants' quarters upstairs.").also { stage++ } + 12 -> npcl("He made a big deal about it to Mary the Maid, calling her useless and incompetent. I felt quite sorry for her actually. You'd really have to ask him though.").also { stage = END_DIALOGUE } + } + 100 -> npcl("Thank you for all your help in solving the murder.").also { stage = END_DIALOGUE } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/murdermystery/StanfordDialogue.kt b/Server/src/main/content/region/kandarin/quest/murdermystery/StanfordDialogue.kt new file mode 100644 index 000000000..ea70f1a92 --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/murdermystery/StanfordDialogue.kt @@ -0,0 +1,58 @@ +package content.region.kandarin.quest.murdermystery + +import content.data.Quests +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributeNoiseClue +import content.region.kandarin.quest.murdermystery.MurderMystery.Companion.attributePoisonClue +import core.api.* +import core.game.dialogue.* +import core.game.node.entity.player.Player +import core.plugin.Initializable +import core.tools.END_DIALOGUE +import org.rs09.consts.NPCs + +@Initializable +class StanfordDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, StanfordDialogueFile(), npc) + return true + } + override fun newInstance(player: Player): DialoguePlugin { + return StanfordDialogue(player) + } + override fun getIds(): IntArray { + return intArrayOf(NPCs.STANFORD_811) + } +} + +class StanfordDialogueFile : DialogueFile() { + override fun handle(componentID: Int, buttonID: Int) { + when (getQuestStage(player!!, Quests.MURDER_MYSTERY)){ + 0 -> npcl("Have you no shame? We are all grieving at the moment.").also { stage = END_DIALOGUE } + 1 -> when (stage) { + 0 -> playerl(FacialExpression.NEUTRAL, "I'm here to help the guards with their investigation.").also { stage++ } + 1 -> npcl(FacialExpression.ASKING, "How can I help?").also { stage++ } + 2 -> showTopics( + Topic("Who do you think is responsible?", 3), + Topic( "Where were you at the time of the murder?", 5), + IfTopic("Did you hear any suspicious noises at all?", 6, getAttribute(player!!, attributeNoiseClue, false)), + IfTopic("Do you know why so much poison was bought recently?", 11, getAttribute(player!!, attributePoisonClue, 0) > 0) + ) + + 3 -> npcl("It was Anna. She is seriously unbalanced. She trashed the garden once then tried to blame it on me! I bet it was her. It's just the kind of thing she'd do!").also { stage++ } + 4 -> npcl("She really hates me and was arguing with Lord Sinclair about trashing the garden a few days ago.").also { stage = END_DIALOGUE } + + 5 -> npcl("Right here, by my little shed. It's very cosy to sit and think in.").also { stage = END_DIALOGUE } + + 6 -> npcl("Not that I remember.").also { stage++ } + 7 -> playerl("So no sounds of a struggle between Lord Sinclair and an intruder?").also { stage++ } + 8 -> npcl("Not to the best of my recollection.").also { stage++ } + 9 -> playerl("How about the guard dog barking?").also { stage++ } + 10 -> npcl("Not that I can recall.").also { stage = END_DIALOGUE } + + 11 -> npcl("Well, Bob mentioned to me the other day he wanted to get rid of the bees in that hive over there. I think I saw him buying poison").also { stage++ } + 12 -> npcl("from that poison salesman the other day. I assume it was to sort out those bees. You'd really have to ask him though.").also { stage = END_DIALOGUE } + } + 100 -> npcl("Thank you for all your help in solving the murder.").also { stage = END_DIALOGUE } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/seers/dialogue/CamelotGuardDialogue.java b/Server/src/main/content/region/kandarin/seers/dialogue/CamelotGuardDialogue.java index d8f856276..5cc97311a 100644 --- a/Server/src/main/content/region/kandarin/seers/dialogue/CamelotGuardDialogue.java +++ b/Server/src/main/content/region/kandarin/seers/dialogue/CamelotGuardDialogue.java @@ -52,6 +52,6 @@ public final class CamelotGuardDialogue extends DialoguePlugin { @Override public int[] getIds() { - return new int[] { 812 }; + return new int[] { 6183, 6184 }; } } diff --git a/Server/src/main/core/game/dialogue/DialogueFile.kt b/Server/src/main/core/game/dialogue/DialogueFile.kt index 226c962f2..c73e3dbab 100644 --- a/Server/src/main/core/game/dialogue/DialogueFile.kt +++ b/Server/src/main/core/game/dialogue/DialogueFile.kt @@ -161,7 +161,7 @@ abstract class DialogueFile { return true } else if (validTopics.size == 1) { - val topic = topics[0] + val topic = topics.filter { it.text == validTopics[0] }[0] if(topic.toStage is DialogueFile) { val topicFile = topic.toStage as DialogueFile interpreter!!.dialogue.loadFile(topicFile) From a0bf0d1d507a7b7376e4b38c8b30397e224ae693 Mon Sep 17 00:00:00 2001 From: Michael Veit Date: Mon, 18 Aug 2025 11:30:01 +0000 Subject: [PATCH 058/117] Implemented Gnome Barman dialogue --- .../dialogue/GnomeBarmanDialogue.kt | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 Server/src/main/content/region/kandarin/gnomestronghold/dialogue/GnomeBarmanDialogue.kt diff --git a/Server/src/main/content/region/kandarin/gnomestronghold/dialogue/GnomeBarmanDialogue.kt b/Server/src/main/content/region/kandarin/gnomestronghold/dialogue/GnomeBarmanDialogue.kt new file mode 100644 index 000000000..40b59d721 --- /dev/null +++ b/Server/src/main/content/region/kandarin/gnomestronghold/dialogue/GnomeBarmanDialogue.kt @@ -0,0 +1,110 @@ +package content.region.kandarin.gnomestronghold.dialogue + +import core.api.addItem +import core.api.inInventory +import core.api.openDialogue +import core.api.openNpcShop +import core.api.removeItem +import core.api.sendMessage +import core.game.dialogue.ChatAnim +import core.game.dialogue.DialogueLabeller +import core.game.dialogue.DialogueOption +import core.game.dialogue.DialoguePlugin +import core.game.node.entity.player.Player +import core.game.node.item.Item +import core.plugin.Initializable +import org.rs09.consts.Items +import org.rs09.consts.NPCs + +private const val INGREDIENT_COST = 20 +private const val NOT_ENOUGH_MONEY_MESSAGE = "You do not have enough money to buy that." + +/** + * Handles the dialogue for the Gnome Barman NPCs in the Tree Gnome Stronghold. + * @author Broseki + */ +@Initializable +class GnomeBarmanDialogue(player: Player? = null) : DialoguePlugin(player) { + override fun newInstance(player: Player?): DialoguePlugin { + return GnomeBarmanDialogue(player) + } + + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, GnomeBarmanDialogueStart(), npc) + return false + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.BARMAN_849) + } +} + +class GnomeBarmanDialogueStart : DialogueLabeller() { + override fun addConversation() { + npc(ChatAnim.OLD_HAPPY, "Good day to you. What can I get you to drink?") + options( + DialogueOption("store", "What do you have?", expression = ChatAnim.FRIENDLY, spokenText = "What do you have."), + DialogueOption("nothing", "Nothing thanks.", expression = ChatAnim.FRIENDLY), + DialogueOption( + "ingredients", "Can I buy some ingredients?", + spokenText = "I was just wanting to buy a cocktail ingredient actually.", + expression = ChatAnim.FRIENDLY + ), + ) + + label("store") + npc(ChatAnim.OLD_HAPPY, "Here, take a look at our menu.") + exec { player, npc -> + openNpcShop(player, npc.id) + } + + label("nothing") + npc(ChatAnim.OLD_HAPPY, "Okay, take it easy.") + + label("ingredients") + npc(ChatAnim.OLD_HAPPY, "Sure thing, what did you want?") + options( + DialogueOption("lemon", "A lemon.", expression = ChatAnim.FRIENDLY), + DialogueOption("orange", "An orange.", expression = ChatAnim.FRIENDLY), + DialogueOption("shaker", "A cocktail shaker.", expression = ChatAnim.FRIENDLY), + DialogueOption( + "buynothing", "Nothing thanks.", + spokenText = "Actually nothing thanks.", + expression = ChatAnim.FRIENDLY + ), + ) + + label("lemon") + npc(ChatAnim.OLD_HAPPY, "$INGREDIENT_COST coins please.") + exec { player, _ -> + if (removeItem(player, Item(Items.COINS_995, INGREDIENT_COST))) { + addItem(player, Items.LEMON_2102) + } else { + sendMessage(player, NOT_ENOUGH_MONEY_MESSAGE) + } + } + + label("orange") + npc(ChatAnim.OLD_HAPPY, "$INGREDIENT_COST coins please.") + exec { player, _ -> + if (removeItem(player, Item(Items.COINS_995, INGREDIENT_COST))) { + addItem(player, Items.ORANGE_2108) + } else { + sendMessage(player, NOT_ENOUGH_MONEY_MESSAGE) + } + } + + label("shaker") + npc(ChatAnim.OLD_HAPPY, "$INGREDIENT_COST coins please.") + exec { player, _ -> + if (removeItem(player, Item(Items.COINS_995, INGREDIENT_COST))) { + addItem(player, Items.COCKTAIL_SHAKER_2025) + } else { + sendMessage(player, NOT_ENOUGH_MONEY_MESSAGE) + } + } + + label("buynothing") + // Just end the conversation here without any further dialogue + } +} \ No newline at end of file From 80b1b4b01e0fa53d8512a4ba69d053fbf7475d52 Mon Sep 17 00:00:00 2001 From: damighty <27978131-real_damighty@users.noreply.gitlab.com> Date: Mon, 18 Aug 2025 14:34:42 +0300 Subject: [PATCH 059/117] Removed Grand Exchange privacy --- Server/src/main/core/game/ge/GrandExchange.kt | 10 +--------- Server/src/main/core/game/ge/GrandExchangeOffer.kt | 5 +---- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/Server/src/main/core/game/ge/GrandExchange.kt b/Server/src/main/core/game/ge/GrandExchange.kt index 9d5546721..9fba63030 100644 --- a/Server/src/main/core/game/ge/GrandExchange.kt +++ b/Server/src/main/core/game/ge/GrandExchange.kt @@ -101,13 +101,6 @@ class GrandExchange : StartupListener, Commands { PriceIndex.allowItem(id) notify(player, "Allowed ${getItemName(id)} for GE trade.") } - - define("geprivacy", Privilege.STANDARD) {player, _ -> - val current = getAttribute(player, "ge-exclude", false) - val new = !current - notify(player, "Your name is now ${if (new) colorize("%RHIDDEN") else colorize("%RSHOWN")}.") - setAttribute(player, "/save:ge-exclude", new) - } } companion object { @@ -231,8 +224,7 @@ class GrandExchange : StartupListener, Commands { //GrandExchangeRecords.getInstance(player).update(offer) if (offer.sell && !player.isArtificial) { - val username = if (getAttribute(player, "ge-exclude", false)) "?????" else player.username - Repository.sendNews(username + " just offered " + offer.amount + " " + getItemName(offer.itemID) + " on the GE.") + sendNews(player.username + " just offered " + offer.amount + " " + getItemName(offer.itemID) + " on the GE.") } if (ServerConstants.I_AM_A_CHEATER) { diff --git a/Server/src/main/core/game/ge/GrandExchangeOffer.kt b/Server/src/main/core/game/ge/GrandExchangeOffer.kt index 919427567..eee8594dd 100644 --- a/Server/src/main/core/game/ge/GrandExchangeOffer.kt +++ b/Server/src/main/core/game/ge/GrandExchangeOffer.kt @@ -146,10 +146,7 @@ class GrandExchangeOffer() { visualize(player) stmt.close() - val username = if (getAttribute(player ?: return@run, "ge-exclude", false)) "?????" - else player?.username ?: "?????" - - Discord.postNewOffer(sell, itemID, offeredValue, amount, username) + Discord.postNewOffer(sell, itemID, offeredValue, amount, player?.username ?: return@run) } } } From 891cdf7de1e7460020be8d0f9c5ee572b0587400 Mon Sep 17 00:00:00 2001 From: damighty <27978131-real_damighty@users.noreply.gitlab.com> Date: Mon, 18 Aug 2025 14:39:38 +0300 Subject: [PATCH 060/117] Fixed PK news announcement breaking due to incorrect time processing --- .../src/main/core/game/node/entity/player/Player.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Server/src/main/core/game/node/entity/player/Player.java b/Server/src/main/core/game/node/entity/player/Player.java index dfced34d2..1592d0b0e 100644 --- a/Server/src/main/core/game/node/entity/player/Player.java +++ b/Server/src/main/core/game/node/entity/player/Player.java @@ -617,10 +617,13 @@ public class Player extends Entity { if (this.isArtificial() && killer instanceof NPC) { return; } - if (killer instanceof Player && killer.getName() != getName() /* happens if you died via typeless damage from an external cause, e.g. bugs in a dark cave without a light source */ && getWorldTicks() - killer.getAttribute("/save:last-murder-news", 0) >= 500) { - Item wep = getItemFromEquipment((Player) killer, EquipmentSlot.WEAPON); - sendNews(killer.getUsername() + " has murdered " + getUsername() + " with " + (wep == null ? "their fists." : (StringUtils.isPlusN(wep.getName()) ? "an " : "a ") + wep.getName())); - killer.setAttribute("/save:last-murder-news", getWorldTicks()); + if (killer instanceof Player && killer.getName() != getName()) { // the latter happens if you died via typeless damage from an external cause, e.g. bugs in a dark cave without a light source + long unixSeconds = System.currentTimeMillis() / 1000L; + if (unixSeconds - killer.getAttribute("/save:last-murder-news", 0L) >= 300) { + Item wep = getItemFromEquipment((Player) killer, EquipmentSlot.WEAPON); + sendNews(killer.getUsername() + " has murdered " + getUsername() + " with " + (wep == null ? "their fists." : (StringUtils.isPlusN(wep.getName()) ? "an " : "a ") + wep.getName())); + killer.setAttribute("/save:last-murder-news", unixSeconds); + } } getPacketDispatch().sendMessage("Oh dear, you are dead!"); incrementAttribute("/save:"+STATS_BASE+":"+STATS_DEATHS); From 1106cbac2503f9e1fb2445b1c5fab17a8a14d3e4 Mon Sep 17 00:00:00 2001 From: Lucid Enigma Date: Mon, 18 Aug 2025 11:50:24 +0000 Subject: [PATCH 061/117] Fixed Varrock essence miner bot Essence miner bot will now sell the acquired pure essence on the GE after it reaches at least 500 --- .../content/global/bots/VarrockEssenceMiner.kt | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Server/src/main/content/global/bots/VarrockEssenceMiner.kt b/Server/src/main/content/global/bots/VarrockEssenceMiner.kt index 39ce02c55..38a9e6b5a 100644 --- a/Server/src/main/content/global/bots/VarrockEssenceMiner.kt +++ b/Server/src/main/content/global/bots/VarrockEssenceMiner.kt @@ -1,10 +1,13 @@ package content.global.bots +import content.data.Quests import core.game.bots.* import core.game.interaction.DestinationFlag import core.game.interaction.IntType import core.game.interaction.InteractionListeners import core.game.interaction.MovementPulse +import core.game.node.entity.skill.Skills +import core.game.node.item.Item import core.game.world.map.Location import core.game.world.map.zone.ZoneBorders import org.rs09.consts.Items @@ -24,6 +27,11 @@ class VarrockEssenceMiner : Script(){ when(state){ State.TO_ESSENCE -> { bot.interfaceManager.close() + if (bot.bank.getAmount(Items.PURE_ESSENCE_7936) > 500) { + state = State.TELE_GE + return + } + if(!auburyZone.insideBorder(bot)) scriptAPI.walkTo(auburyZone.randomLoc) else { @@ -99,7 +107,6 @@ class VarrockEssenceMiner : Script(){ scriptAPI.sellOnGE(Items.PURE_ESSENCE_7936) state = State.TO_ESSENCE } - } } @@ -118,4 +125,10 @@ class VarrockEssenceMiner : Script(){ script.bot = SkillingBotAssembler().produce(SkillingBotAssembler.Wealth.POOR,bot.startLocation) return script } + + init { + quests.add(Quests.RUNE_MYSTERIES) + inventory.add(Item(Items.ADAMANT_PICKAXE_1271)) + skills[Skills.MINING] = 31 + } } From 12049d8ffbb48822c4ec835ccebafee7529f7059 Mon Sep 17 00:00:00 2001 From: Player Name Date: Mon, 18 Aug 2025 12:04:40 +0000 Subject: [PATCH 062/117] Corrected all four DT bosses' despawn behaviors Added the missing music definition for the eastern half of Damis's cave Buffed Kamil's ice barrage attack to always hit two 5s Fixed a bug where Fareed's weapon unequip message would fire even if you did not have a weapon equipped --- Server/data/configs/music_regions.json | 4 +++ .../quest/deserttreasure/DamisBehavior.kt | 32 +++++++++---------- .../quest/deserttreasure/DessousBehavior.kt | 28 ++++++++-------- .../DiamondOfShadowListeners.kt | 1 - .../quest/deserttreasure/FareedBehavior.kt | 17 +++++----- .../quest/deserttreasure/KamilBehavior.kt | 21 +++++++----- 6 files changed, 57 insertions(+), 46 deletions(-) diff --git a/Server/data/configs/music_regions.json b/Server/data/configs/music_regions.json index ebfb70c82..51d9a41ae 100644 --- a/Server/data/configs/music_regions.json +++ b/Server/data/configs/music_regions.json @@ -1095,6 +1095,10 @@ "region": "10829", "id": "378" }, + { + "region": "10831", + "id": "393" + }, { "region": "10833", "id": "249" diff --git a/Server/src/main/content/region/desert/quest/deserttreasure/DamisBehavior.kt b/Server/src/main/content/region/desert/quest/deserttreasure/DamisBehavior.kt index c8b9af6d9..1b996dafc 100644 --- a/Server/src/main/content/region/desert/quest/deserttreasure/DamisBehavior.kt +++ b/Server/src/main/content/region/desert/quest/deserttreasure/DamisBehavior.kt @@ -14,8 +14,23 @@ import org.rs09.consts.Items import org.rs09.consts.NPCs class DamisBehavior : NPCBehavior(NPCs.DAMIS_1974, NPCs.DAMIS_1975) { + private var disappearing = false - var clearTime = 0 + override fun tick(self: NPC): Boolean { + if (disappearing) { + return true + } + val player: Player? = getAttribute(self, "target", null) + if (player == null || !self.location.withinDistance(self.properties.spawnLocation, self.walkRadius)) { + if (player != null && !disappearing) { + disappearing = true + sendMessage(player, "Damis has vanished once more into the shadows...") + removeAttribute(player, DesertTreasure.attributeDamisInstance) + } + poofClear(self) + } + return true + } override fun canBeAttackedBy(self: NPC, attacker: Entity, style: CombatStyle, shouldSendMessage: Boolean): Boolean { if (attacker is Player) { @@ -27,19 +42,6 @@ class DamisBehavior : NPCBehavior(NPCs.DAMIS_1974, NPCs.DAMIS_1975) { return false } - override fun tick(self: NPC): Boolean { - val player: Player? = getAttribute(self, "target", null) - if (clearTime++ > 800) { - clearTime = 0 - if (player != null) { - sendMessage(player, "Damis has vanished once more into the shadows...") - removeAttribute(player, DesertTreasure.attributeDamisInstance) - } - poofClear(self) - } - return true - } - override fun beforeDamageReceived(self: NPC, attacker: Entity, state: BattleState) { if (attacker is Player) { if (state.estimatedHit + Integer.max(state.secondaryHit, 0) >= self.skills.lifepoints && self.id == NPCs.DAMIS_1974) { @@ -65,8 +67,6 @@ class DamisBehavior : NPCBehavior(NPCs.DAMIS_1974, NPCs.DAMIS_1975) { } } - - override fun onDeathFinished(self: NPC, killer: Entity) { if (killer is Player) { if (self.id == NPCs.DAMIS_1975) { diff --git a/Server/src/main/content/region/desert/quest/deserttreasure/DessousBehavior.kt b/Server/src/main/content/region/desert/quest/deserttreasure/DessousBehavior.kt index 5c521e6b6..ec4e8a142 100644 --- a/Server/src/main/content/region/desert/quest/deserttreasure/DessousBehavior.kt +++ b/Server/src/main/content/region/desert/quest/deserttreasure/DessousBehavior.kt @@ -20,8 +20,7 @@ import core.tools.END_DIALOGUE import org.rs09.consts.NPCs class DessousMeleeBehavior : NPCBehavior(NPCs.DESSOUS_1914, NPCs.DESSOUS_1915) { - - var clearTime = 0 + private var disappearing = false; override fun canBeAttackedBy(self: NPC, attacker: Entity, style: CombatStyle, shouldSendMessage: Boolean): Boolean { if (attacker is Player) { @@ -34,12 +33,24 @@ class DessousMeleeBehavior : NPCBehavior(NPCs.DESSOUS_1914, NPCs.DESSOUS_1915) { } override fun tick(self: NPC): Boolean{ + if (disappearing) { + return true + } + val player: Player? = getAttribute(self, "target", null) + if (player == null || !self.location.withinDistance(self.properties.spawnLocation, self.walkRadius)) { + if (player != null && !disappearing) { + disappearing = true + sendMessage(player, "Dessous returns to his grave, bored of toying with you.") + removeAttribute(player, DesertTreasure.attributeDessousInstance) + } + poofClear(self) + } + // Dessous just continually hisses independently of projectile fires. if (self.id == NPCs.DESSOUS_1915 && self.properties.combatPulse.isInCombat) { animate(self, Animation(1914)) } // This is probably the prayer flicking nonsense. - val player: Player? = getAttribute(self, "target", null) if (self.id == NPCs.DESSOUS_1914 && player != null && player.prayer.get(PrayerType.PROTECT_FROM_MELEE)) { self.transform(NPCs.DESSOUS_1915) Graphics.send(Graphics(86), self.location) @@ -47,16 +58,7 @@ class DessousMeleeBehavior : NPCBehavior(NPCs.DESSOUS_1914, NPCs.DESSOUS_1915) { self.transform(NPCs.DESSOUS_1914) Graphics.send(Graphics(86), self.location) } - if (clearTime++ > 800) { - self.transform(NPCs.DESSOUS_1914) - clearTime = 0 - if (player != null) { - removeAttribute(player, DesertTreasure.attributeDessousInstance) - sendMessage(player, "Dessous returns to his grave, bored of toying with you.") - } - poofClear(self) - } - return false + return true } override fun getSwingHandlerOverride(self: NPC, original: CombatSwingHandler): CombatSwingHandler { diff --git a/Server/src/main/content/region/desert/quest/deserttreasure/DiamondOfShadowListeners.kt b/Server/src/main/content/region/desert/quest/deserttreasure/DiamondOfShadowListeners.kt index 615a9b22c..3ca6e6a60 100644 --- a/Server/src/main/content/region/desert/quest/deserttreasure/DiamondOfShadowListeners.kt +++ b/Server/src/main/content/region/desert/quest/deserttreasure/DiamondOfShadowListeners.kt @@ -261,5 +261,4 @@ class ShadowDungeonAttack : MapArea { } } } - } \ No newline at end of file diff --git a/Server/src/main/content/region/desert/quest/deserttreasure/FareedBehavior.kt b/Server/src/main/content/region/desert/quest/deserttreasure/FareedBehavior.kt index be98d963c..71db43e71 100644 --- a/Server/src/main/content/region/desert/quest/deserttreasure/FareedBehavior.kt +++ b/Server/src/main/content/region/desert/quest/deserttreasure/FareedBehavior.kt @@ -14,8 +14,7 @@ import org.rs09.consts.Items import org.rs09.consts.NPCs class FareedBehavior : NPCBehavior(NPCs.FAREED_1977) { - - var clearTime = 0 + private var disappearing = false override fun canBeAttackedBy(self: NPC, attacker: Entity, style: CombatStyle, shouldSendMessage: Boolean): Boolean { if (attacker is Player) { @@ -28,10 +27,13 @@ class FareedBehavior : NPCBehavior(NPCs.FAREED_1977) { } override fun tick(self: NPC): Boolean { + if (disappearing) { + return true + } val player: Player? = getAttribute(self, "target", null) - if (clearTime++ > 800) { - clearTime = 0 - if (player != null) { + if (player == null || !self.location.withinDistance(self.properties.spawnLocation, self.walkRadius)) { + if (player != null && !disappearing) { + disappearing = true sendMessage(player, "Fareed has lost interest in you, and returned to his flames.") removeAttribute(player, DesertTreasure.attributeFareedInstance) } @@ -50,14 +52,14 @@ class FareedBehavior : NPCBehavior(NPCs.FAREED_1977) { if (victim is Player) { if (!inEquipment(victim, Items.ICE_GLOVES_1580)) { val weapon = getItemFromEquipment(victim, EquipmentSlot.WEAPON) - if(weapon != null) { + if (weapon != null) { EquipHandler.unequip(victim, EquipmentContainer.SLOT_WEAPON, weapon.id) + sendMessage(victim, "The heat from the warrior causes you to drop your weapon.") } // val weapon = getItemFromEquipment(victim, EquipmentSlot.WEAPON) // if(weapon != null && removeItem(victim, weapon.id, Container.EQUIPMENT)) { // addItemOrDrop(victim, weapon.id) // } - sendMessage(victim, "The heat from the warrior causes you to drop your weapon.") } } } @@ -72,5 +74,4 @@ class FareedBehavior : NPCBehavior(NPCs.FAREED_1977) { } } } - } \ No newline at end of file diff --git a/Server/src/main/content/region/desert/quest/deserttreasure/KamilBehavior.kt b/Server/src/main/content/region/desert/quest/deserttreasure/KamilBehavior.kt index e5ddbdae1..c5de39024 100644 --- a/Server/src/main/content/region/desert/quest/deserttreasure/KamilBehavior.kt +++ b/Server/src/main/content/region/desert/quest/deserttreasure/KamilBehavior.kt @@ -14,8 +14,7 @@ import org.rs09.consts.NPCs // https://www.youtube.com/watch?v=xeu6Ncmt1fY class KamilBehavior : NPCBehavior(NPCs.KAMIL_1913) { - - var clearTime = 0 + private var disappearing = false override fun canBeAttackedBy(self: NPC, attacker: Entity, style: CombatStyle, shouldSendMessage: Boolean): Boolean { if (attacker is Player) { @@ -28,10 +27,13 @@ class KamilBehavior : NPCBehavior(NPCs.KAMIL_1913) { } override fun tick(self: NPC): Boolean { + if (disappearing) { + return true + } val player: Player? = getAttribute(self, "target", null) - if (clearTime++ > 800) { - clearTime = 0 - if (player != null) { + if (player == null || !self.location.withinDistance(self.properties.spawnLocation, (self.walkRadius*1.5).toInt())) { + if (player != null && !disappearing) { + disappearing = true sendMessage(player, "Kamil vanishes on an icy wind...") removeAttribute(player, DesertTreasure.attributeKamilInstance) } @@ -64,16 +66,19 @@ class KamilCombatHandler: MultiSwingHandler( // This is following RevenantCombatHandler.java, no idea if this is good. // I can't be bothered to fix fucking frozen. The player can hit through frozen. What the fuck is frozen for then, to glue his fucking legs??? if (RandomFunction.roll(3) && !hasTimerActive(victim, "frozen") && !hasTimerActive(victim, "frozen:immunity")) { + sendChat(entity as NPC, "Sallamakar Ro!") // Salad maker roll. + impact(victim, 5) + impact(victim, 5) registerTimer(victim, spawnTimer("frozen", 7, true)) sendMessage(victim, "You've been frozen!") - sendChat(entity as NPC, "Sallamakar Ro!") // Salad maker roll. + // FIXME: before the below vfx hits, there should be another one that looks kinda like a wind wave exploding at the player's feet. Hope somebody finds the id. sendGraphics(539, victim.location) victim.properties.combatPulse.stop() // Force the victim to stop fighting. Whatever. - // Audio? + // FIXME: sfx }else { animate(entity!!, Animation(440)) } } super.impact(entity, victim, state) } -} \ No newline at end of file +} From 75fd1dc47511b59545fea3ceebfde5538c26f0cf Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Mon, 18 Aug 2025 12:06:35 +0000 Subject: [PATCH 063/117] Fixed the wall beasts in the lumbridge swamp dungeon --- Server/data/configs/npc_configs.json | 6 +- Server/data/configs/npc_spawns.json | 6 +- .../slayer/dungeon/LumbridgeDungeon.java | 1 - .../lumbridge/handlers/WallBeastBehavior.kt | 145 ++++++++++++++++++ 4 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 Server/src/main/content/region/misthalin/lumbridge/handlers/WallBeastBehavior.kt diff --git a/Server/data/configs/npc_configs.json b/Server/data/configs/npc_configs.json index f8b2ab47d..79f9f7098 100644 --- a/Server/data/configs/npc_configs.json +++ b/Server/data/configs/npc_configs.json @@ -21678,7 +21678,7 @@ "range_animation": "0", "combat_audio": "931,923,922", "attack_speed": "5", - "respawn_delay": "60", + "respawn_delay": "0", "defence_animation": "0", "weakness": "7", "slayer_exp": "30", @@ -68948,13 +68948,13 @@ "examine": "A big, scary hand! ", "melee_animation": "1802", "attack_speed": "6", - "respawn_delay": "60", + "respawn_delay": "0", "defence_animation": "1803", "slayer_exp": "105", "death_animation": "1804", "name": "Wall Beast", "defence_level": "38", - "movement_radius": "1", + "movement_radius": "0", "safespot": null, "lifepoints": "105", "strength_level": "38", diff --git a/Server/data/configs/npc_spawns.json b/Server/data/configs/npc_spawns.json index b038cb929..cee858845 100644 --- a/Server/data/configs/npc_spawns.json +++ b/Server/data/configs/npc_spawns.json @@ -4895,6 +4895,10 @@ "npc_id": "2057", "loc_data": "{2442,9436,2,1,0}-{2458,9413,2,1,0}-{2470,9435,2,1,0}-{2472,9460,2,1,0}-{2483,9413,2,1,0}-{2485,9447,2,1,0}-{2480,3046,0,1,0}-{2453,9393,2,1,0}-{2461,9403,2,1,0}-{2464,9380,2,1,0}-" }, + { + "npc_id": "2058", + "loc_data": "{3161,9547,0,0,7}-{3164,9556,0,0,7}-{3162,9574,0,0,7}-{3198,9554,0,0,7}-{3198,9572,0,0,7}-{3215,9560,0,0,7}-{3216,9588,0,0,7}-" + }, { "npc_id": "2059", "loc_data": "{2588,3088,0,1,3}-" @@ -12133,7 +12137,7 @@ }, { "npc_id": "7823", - "loc_data": "{3161,9547,0,0,3}-{3164,9556,0,0,4}-{3162,9574,0,0,3}-{3198,9554,0,0,7}-{3198,9572,0,0,1}-{3215,9560,0,0,1}-{3216,9588,0,0,1}-" + "loc_data": "" }, { "npc_id": "7891", diff --git a/Server/src/main/content/global/skill/slayer/dungeon/LumbridgeDungeon.java b/Server/src/main/content/global/skill/slayer/dungeon/LumbridgeDungeon.java index c47cfff3c..af34faff2 100644 --- a/Server/src/main/content/global/skill/slayer/dungeon/LumbridgeDungeon.java +++ b/Server/src/main/content/global/skill/slayer/dungeon/LumbridgeDungeon.java @@ -28,7 +28,6 @@ import java.util.Map; * Handles the lumbridge dungeon. * @author Vexia */ -@Initializable public final class LumbridgeDungeon extends MapZone implements Plugin { /** diff --git a/Server/src/main/content/region/misthalin/lumbridge/handlers/WallBeastBehavior.kt b/Server/src/main/content/region/misthalin/lumbridge/handlers/WallBeastBehavior.kt new file mode 100644 index 000000000..309fc9e0e --- /dev/null +++ b/Server/src/main/content/region/misthalin/lumbridge/handlers/WallBeastBehavior.kt @@ -0,0 +1,145 @@ +package content.region.misthalin.lumbridge.handlers + +import core.api.* +import core.game.interaction.MovementPulse +import core.game.interaction.QueueStrength +import core.game.node.entity.Entity +import core.game.node.entity.combat.ImpactHandler.HitsplatType +import core.game.node.entity.npc.NPC +import core.game.node.entity.npc.NPCBehavior +import core.game.node.entity.player.Player +import core.game.world.map.Location +import core.game.world.map.zone.ZoneBorders +import core.game.world.repository.Repository +import org.rs09.consts.Items +import org.rs09.consts.NPCs + +/** + * NPCs.WALL_BEAST_7823 starts out as NPCs.HOLE_IN_THE_WALL_2058 first. + * On death, it will respawn a new hole in the wall. + * + * h2QAZzGds9o + * + * npc anim 1807 + * {3161,9547,0,0,3}-{3164,9556,0,0,4}-{3162,9574,0,0,3}-{3198,9554,0,0,7}-{3198,9572,0,0,1}-{3215,9560,0,0,1}-{3216,9588,0,0,1}- + */ +class WallBeastBehavior : NPCBehavior(NPCs.HOLE_IN_THE_WALL_2058, NPCs.WALL_BEAST_7823) { + override fun onCreation(self: NPC) { + // This NPC should never move. + self.walkRadius = 0 + self.isNeverWalks = true + } + + override fun onDeathFinished(self: NPC, entity: Entity) { + self.reTransform() + // I don't think this is needed since the respawn will be immediate. + } +} + +class WallBeastNTrigger : MapArea { + + companion object { + /** Finds the nearest NPC of npcId to location. */ + fun findNearestNPC(location: Location, npcId: Int): NPC? { + // maybe replace contentAPI's version of this + return Repository.npcs + .filter { it.id == npcId } + .toMutableList() + .sortedWith { a, b -> + return@sortedWith (Location.getDistance(a.location, location) - Location.getDistance(b.location, location)).toInt() + } + .firstOrNull() + } + + fun isHelmetInEquipment(entity: Player): Boolean { + return inEquipment(entity, Items.SPINY_HELMET_4551) || + inEquipment(entity, Items.SLAYER_HELMET_13263) || + inEquipment(entity, Items.SPIKED_HELMET_13105) || + inEquipment(entity, Items.SLAYER_HELMET_E_14636) || + inEquipment(entity, Items.SLAYER_HELMET_CHARGED_14637) + } + } + override fun defineAreaBorders(): Array { + // The goals here are in order + return arrayOf( + ZoneBorders(3161,9546,3161,9546), + ZoneBorders(3164,9555,3164,9555), + ZoneBorders(3162,9573,3162,9573), + ZoneBorders(3198,9553,3198,9553), + ZoneBorders(3198,9571,3198,9571), + ZoneBorders(3215,9559,3215,9559), + ZoneBorders(3216,9587,3216,9587), + ) + } + + override fun areaEnter(entity: Entity) { + if (entity is Player) { + val nearbyWallbeast = findNearestNPC(entity.location, NPCs.HOLE_IN_THE_WALL_2058) // wallbeasts are always 1 square north. + // println(nearbyWallbeast) + if (nearbyWallbeast != null) { + lock(entity, 2) + stopWalk(entity) + // Stop walk doesn't stop the player from walking on. + // You have to clear the movement pulse that is continually feeding the walk location. + val movementPulse = entity.getPulseManager().current; + if (movementPulse is MovementPulse) { + movementPulse.stop(); + } + face(entity, nearbyWallbeast.location) + queueScript(nearbyWallbeast, 2, QueueStrength.STRONG) { stage: Int -> + when (stage) { + 0 -> { + if (isHelmetInEquipment(entity)) { + animate(nearbyWallbeast, 1805) + } else { + animate(nearbyWallbeast, 1806) + } + return@queueScript delayScript(nearbyWallbeast, 1) + } + 1 -> { + if (isHelmetInEquipment(entity)) { + // You can only attack the wallbeast if you have a helmet + transformNpc(nearbyWallbeast, NPCs.WALL_BEAST_7823, 300) + return@queueScript stopExecuting(nearbyWallbeast) + } else { + animate(nearbyWallbeast, 1807) + return@queueScript delayScript(nearbyWallbeast, 4) + } + } + 2 -> { + animate(nearbyWallbeast, 1808) + return@queueScript stopExecuting(nearbyWallbeast) + } + else -> return@queueScript stopExecuting(nearbyWallbeast) + } + } + queueScript(entity, 2, QueueStrength.STRONG) { stage: Int -> + when (stage) { + 0 -> { + if (isHelmetInEquipment(entity)) { + return@queueScript stopExecuting(entity) + } + lock(entity, 5) + return@queueScript delayScript(entity, 1) + } + + 1 -> { + animate(entity, 1810) // 734 + return@queueScript delayScript(entity, 4) + } + + 2 -> { + // I don't know how much they are supposed to hit... osrs says up to 18... + impact(entity, (13 .. 18).random() , HitsplatType.NORMAL) + animate(entity, 1811) // 734 + return@queueScript stopExecuting(entity) + } + + else -> return@queueScript stopExecuting(entity) + } + } + } + } + } + +} \ No newline at end of file From 79b3f431c1cd61aa10ec25e279828efe1fae8511 Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Sun, 31 Aug 2025 08:24:24 +0000 Subject: [PATCH 064/117] Corrected list of god items --- .../desert/bandits/handlers/BanditNPC.kt | 4 ++- Server/src/main/core/api/God.kt | 27 +++---------------- 2 files changed, 6 insertions(+), 25 deletions(-) diff --git a/Server/src/main/content/region/desert/bandits/handlers/BanditNPC.kt b/Server/src/main/content/region/desert/bandits/handlers/BanditNPC.kt index f8c69da7c..29a7809d1 100644 --- a/Server/src/main/content/region/desert/bandits/handlers/BanditNPC.kt +++ b/Server/src/main/content/region/desert/bandits/handlers/BanditNPC.kt @@ -2,6 +2,7 @@ package content.region.desert.bandits.handlers import core.api.God import core.api.hasGodItem +import core.api.inEquipment import core.game.node.entity.Entity import core.game.node.entity.combat.BattleState import core.game.node.entity.npc.AbstractNPC @@ -9,6 +10,7 @@ import core.game.node.entity.player.Player import core.game.world.map.Location import core.game.world.map.RegionManager import core.plugin.Initializable +import org.rs09.consts.Items import org.rs09.consts.NPCs @Initializable @@ -28,7 +30,7 @@ class BanditNPC(id: Int = NPCs.BANDIT_1926, location: Location? = null) : Abstra sendChat("Time to die, Saradominist filth!") attack(player) break - } else if (hasGodItem(player, God.ZAMORAK)) { + } else if (hasGodItem(player, God.ZAMORAK) || inEquipment(player, Items.DAGONHAI_HAT_14499) || inEquipment(player, Items.DAGONHAI_ROBE_TOP_14497) || inEquipment(player, Items.DAGONHAI_ROBE_BOTTOM_14501)) { sendChat("Prepare to suffer, Zamorakian scum!") attack(player) break diff --git a/Server/src/main/core/api/God.kt b/Server/src/main/core/api/God.kt index c2e16c32d..70eb2b0fd 100644 --- a/Server/src/main/core/api/God.kt +++ b/Server/src/main/core/api/God.kt @@ -20,14 +20,11 @@ enum class God(vararg val validItems: Int) { Items.BANDOS_TASSETS_11726, ), SARADOMIN( - Items.ANCIENT_SYMBOL_11181, - Items.GILDED_KITESHIELD_3489, + Items.DAMAGED_BOOK_3839, Items.HOLY_BOOK_3840, Items.HOLY_SYMBOL_1718, - Items.HOLY_SYMBOL_4682, Items.MONKS_ROBE_542, Items.MONKS_ROBE_544, - Items.RUNE_HERALDIC_HELM_8488, Items.SARADOMIN_BRACERS_10384, Items.SARADOMIN_CAPE_2412, Items.SARADOMIN_CHAPS_10388, @@ -43,26 +40,16 @@ enum class God(vararg val validItems: Int) { Items.SARADOMIN_PLATEBODY_2661, Items.SARADOMIN_PLATELEGS_2663, Items.SARADOMIN_PLATESKIRT_3479, - Items.SARADOMIN_PLATESKIRT_3675, Items.SARADOMIN_ROBE_LEGS_10464, Items.SARADOMIN_ROBE_TOP_10458, Items.SARADOMIN_STAFF_2415, Items.SARADOMIN_STOLE_10470, Items.SARADOMIN_SWORD_11730, - Items.SARADOMIN_SYMBOL_8055, - Items.STEEL_HERALDIC_HELM_8706, ), ZAMORAK( - Items.DAGONHAI_HAT_14499, - Items.DAGONHAI_ROBE_TOP_14497, - Items.DAGONHAI_ROBE_BOTTOM_14501, Items.DAMAGED_BOOK_3841, - Items.RUNE_HERALDIC_HELM_8494, - Items.STEEL_HERALDIC_HELM_8712, Items.UNHOLY_BOOK_3842, Items.UNHOLY_SYMBOL_1724, - Items.UNHOLY_SYMBOL_3852, - Items.UNHOLY_SYMBOL_4683, Items.ZAMORAK_BRACERS_10368, Items.ZAMORAK_CAPE_2414, Items.ZAMORAK_CHAPS_10372, @@ -81,19 +68,17 @@ enum class God(vararg val validItems: Int) { Items.ZAMORAK_PLATEBODY_2653, Items.ZAMORAK_PLATELEGS_2655, Items.ZAMORAK_PLATESKIRT_3478, - Items.ZAMORAK_PLATESKIRT_3674, Items.ZAMORAK_ROBE_LEGS_10468, Items.ZAMORAK_ROBE_TOP_10460, Items.ZAMORAK_ROBE_TOP_10786, Items.ZAMORAK_STAFF_2417, Items.ZAMORAK_STOLE_10474, - Items.ZAMORAK_SYMBOL_8056, Items.ZAMORAK_ROBE_1033, Items.ZAMORAK_ROBE_1035, ), GUTHIX( - Items.STEEL_HERALDIC_HELM_8692, - Items.RUNE_HERALDIC_HELM_8474, + Items.DAMAGED_BOOK_3843, + Items.BOOK_OF_BALANCE_3844, Items.GUTHIX_BRACERS_10376, Items.GUTHIX_CAPE_10720, Items.GUTHIX_CHAPS_10380, @@ -102,24 +87,18 @@ enum class God(vararg val validItems: Int) { Items.GUTHIX_CROZIER_10442, Items.GUTHIX_DRAGONHIDE_10378, Items.GUTHIX_DRAGONHIDE_10794, - Items.GUTHIX_FULL_HELM_13833, Items.GUTHIX_FULL_HELM_2673, - Items.GUTHIX_KITESHIELD_13834, Items.GUTHIX_KITESHIELD_2675, Items.GUTHIX_MITRE_10454, Items.GUTHIX_MJOLNIR_6760, Items.GUTHIX_PLATEBODY_10780, Items.GUTHIX_PLATEBODY_2669, - Items.GUTHIX_PLATEBODY_13830, Items.GUTHIX_PLATELEGS_2671, - Items.GUTHIX_PLATELEGS_13831, - Items.GUTHIX_PLATESKIRT_13832, Items.GUTHIX_PLATESKIRT_3676, Items.GUTHIX_ROBE_LEGS_10466, Items.GUTHIX_ROBE_TOP_10462, Items.GUTHIX_ROBE_TOP_10788, Items.GUTHIX_STAFF_2416, Items.GUTHIX_STOLE_10472, - Items.GUTHIX_SYMBOL_8057, ) } From 36267b7a67a565de2005484e9644c5efc1669083 Mon Sep 17 00:00:00 2001 From: damighty <27978131-real_damighty@users.noreply.gitlab.com> Date: Sun, 31 Aug 2025 11:31:56 +0300 Subject: [PATCH 065/117] Corrected attack, defence and death animations used by Waterfiends (idle attack animation is an authentic bug) --- Server/data/configs/npc_configs.json | 10 +++++----- .../content/global/skill/slayer/WaterfiendBehavior.kt | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Server/data/configs/npc_configs.json b/Server/data/configs/npc_configs.json index 79f9f7098..45e204c3c 100644 --- a/Server/data/configs/npc_configs.json +++ b/Server/data/configs/npc_configs.json @@ -46877,15 +46877,15 @@ { "examine": "A fiendish embodiment of water.", "combat_style": "1", - "melee_animation": "1582", - "range_animation": "1582", + "melee_animation": "299", + "range_animation": "299", "combat_audio": "3774,3773,3772", "attack_speed": "4", "magic_level": "105", - "defence_animation": "1581", + "defence_animation": "301", "weakness": "4", - "magic_animation": "1582", - "death_animation": "1580", + "magic_animation": "299", + "death_animation": "300", "name": "Waterfiend", "defence_level": "128", "poison_immune": "true", diff --git a/Server/src/main/content/global/skill/slayer/WaterfiendBehavior.kt b/Server/src/main/content/global/skill/slayer/WaterfiendBehavior.kt index e814eafcf..a1d216746 100644 --- a/Server/src/main/content/global/skill/slayer/WaterfiendBehavior.kt +++ b/Server/src/main/content/global/skill/slayer/WaterfiendBehavior.kt @@ -16,7 +16,7 @@ class WaterfiendBehavior : NPCBehavior(*Tasks.WATERFIENDS.ids) { true, SwitchAttack( CombatStyle.MAGIC.swingHandler, - Animation(1581, Priority.HIGH), + Animation(302, Priority.HIGH), // 299 = attack animation, 302 = idle animation. (authentic bug) null, null, Projectile.create( @@ -33,7 +33,7 @@ class WaterfiendBehavior : NPCBehavior(*Tasks.WATERFIENDS.ids) { ), SwitchAttack( CombatStyle.RANGE.swingHandler, - Animation(1581, Priority.HIGH), + Animation(302, Priority.HIGH), // 299 = attack animation, 302 = idle animation. (authentic bug) null, null, Projectile.create( From f39e432432209f8d3870731b499e42a94b575e9f Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Sun, 31 Aug 2025 08:41:06 +0000 Subject: [PATCH 066/117] Removed inauthentic pets Removed option to obtain inauthentic TzRek-Jad pet --- .../main/content/data/skill/SkillingPets.java | 103 ------ .../skill/gather/fishing/FishingPulse.kt | 2 - .../skill/gather/mining/MiningListener.kt | 2 - .../skill/gather/mining/MiningSkillPulse.kt | 2 - .../gather/woodcutting/WoodcuttingListener.kt | 2 - .../woodcutting/WoodcuttingSkillPulse.java | 2 - .../skill/hunter/TrapDismantlePulse.java | 8 - .../content/global/skill/hunter/TrapNode.java | 5 - .../global/skill/summoning/pet/Pets.java | 25 -- .../karamja/tzhaar/handlers/TzHaarMejJah.java | 313 ------------------ .../karamja/tzhaar/handlers/TzHaarMejJal.kt | 167 ++++++++++ .../karamja/tzhaar/handlers/TzRekJadNPC.java | 263 --------------- 12 files changed, 167 insertions(+), 727 deletions(-) delete mode 100644 Server/src/main/content/data/skill/SkillingPets.java delete mode 100644 Server/src/main/content/region/karamja/tzhaar/handlers/TzHaarMejJah.java create mode 100644 Server/src/main/content/region/karamja/tzhaar/handlers/TzHaarMejJal.kt delete mode 100644 Server/src/main/content/region/karamja/tzhaar/handlers/TzRekJadNPC.java diff --git a/Server/src/main/content/data/skill/SkillingPets.java b/Server/src/main/content/data/skill/SkillingPets.java deleted file mode 100644 index 4ea03edd9..000000000 --- a/Server/src/main/content/data/skill/SkillingPets.java +++ /dev/null @@ -1,103 +0,0 @@ -package content.data.skill; - -import core.game.node.entity.skill.Skills; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; -import core.game.world.repository.Repository; - -/** - * Represents the skilling pets obtained randomly. - * @author Empathy - * - */ -public enum SkillingPets { - - BABY_RED_CHINCHOMPA(new Item(14823), "Baby Chinchompa", Skills.HUNTER), - BABY_GREY_CHINCHOMPA(new Item(14824), "Baby Chinchompa", Skills.HUNTER), - BEAVER(new Item(14821), "Beaver", Skills.WOODCUTTING), - GOLEM(new Item(14822), "Rock Golem", Skills.MINING), - HERON(new Item(14827), "Heron", Skills.FISHING); - - /** - * The pet item drop. - */ - private final Item pet; - - /** - * The name. - */ - private final String name; - - /** - * The skill. - */ - private final int skill; - - /** - * Constructs a new {@code SkillingPets} object. - * @param skill The skill id. - * @param pet The pet item. - */ - SkillingPets(Item pet, String name, int skill) { - this.pet = pet; - this.name = name; - this.skill = skill; - } - - /** - * Checks the pet drop. - * @param player The player. - * @param pet The pet drop to check. - */ - public static void checkPetDrop(Player player, SkillingPets pet) { - if (pet == null) { - return; - } - int defaultChance = 15000; - int newChance = (defaultChance / player.getSkills().getStaticLevel(pet.getSkill()) * 55); - int outOf = (newChance > defaultChance ? defaultChance : newChance); - int getChance = outOf; - if (getChance != 1) { - return; - } - if (player.hasItem(pet.getPet())) { - return; - } - if (player.getFamiliarManager().hasFamiliar() && player.getInventory().isFull()) { - return; - } - if (player.getFamiliarManager().hasFamiliar()) { - if (player.getFamiliarManager().getFamiliar().getName().equalsIgnoreCase(pet.getName())) { - return; - } - player.getInventory().add(pet.getPet()); - player.sendNotificationMessage("You feel something weird sneaking into your backpack."); - } else { - player.getFamiliarManager().summon(pet.getPet(), true); - player.sendNotificationMessage("You have a funny feeling like you're being followed."); - } - Repository.sendNews(player.getUsername() + " has found a " + pet.getPet().getName() + "!"); - } - - - /** - * @return the pet - */ - public Item getPet() { - return pet; - } - - /** - * @return the pet name. - */ - public String getName() { - return name; - } - - /** - * @return the skill. - */ - public int getSkill() { - return skill; - } -} diff --git a/Server/src/main/content/global/skill/gather/fishing/FishingPulse.kt b/Server/src/main/content/global/skill/gather/fishing/FishingPulse.kt index 58bb1ac02..3e309fb65 100644 --- a/Server/src/main/content/global/skill/gather/fishing/FishingPulse.kt +++ b/Server/src/main/content/global/skill/gather/fishing/FishingPulse.kt @@ -1,6 +1,5 @@ package content.global.skill.gather.fishing -import content.data.skill.SkillingPets import content.global.skill.fishing.Fish import content.global.skill.fishing.FishingOption import content.global.skill.skillcapeperks.SkillcapePerks @@ -124,7 +123,6 @@ class FishingPulse(player: Player?, npc: NPC, private val option: FishingOption? updateSkillTask() } player.dispatch(ResourceProducedEvent(fish!!.id, 1, node!!)) - SkillingPets.checkPetDrop(player, SkillingPets.HERON) val item = fish!! if (isActive(SkillcapePerks.GREAT_AIM, player) && RandomFunction.random(100) <= 5) { addItemOrDrop(player, item.id) diff --git a/Server/src/main/content/global/skill/gather/mining/MiningListener.kt b/Server/src/main/content/global/skill/gather/mining/MiningListener.kt index 1e6bb3aa3..dffafdb7f 100644 --- a/Server/src/main/content/global/skill/gather/mining/MiningListener.kt +++ b/Server/src/main/content/global/skill/gather/mining/MiningListener.kt @@ -1,6 +1,5 @@ package content.global.skill.gather.mining -import content.data.skill.SkillingPets import content.data.skill.SkillingTool import content.global.skill.skillcapeperks.SkillcapePerks import content.global.activity.shootingstar.StarBonus @@ -67,7 +66,6 @@ class MiningListener : InteractionListener { rewardAmount = calculateRewardAmount(player, isEssence, reward) // calculate amount player.dispatch(ResourceProducedEvent(reward, rewardAmount, node)) - SkillingPets.checkPetDrop(player, SkillingPets.GOLEM) // roll for pet // Reward mining experience val experience = resource!!.experience * rewardAmount diff --git a/Server/src/main/content/global/skill/gather/mining/MiningSkillPulse.kt b/Server/src/main/content/global/skill/gather/mining/MiningSkillPulse.kt index 58464672e..4c26fac4d 100644 --- a/Server/src/main/content/global/skill/gather/mining/MiningSkillPulse.kt +++ b/Server/src/main/content/global/skill/gather/mining/MiningSkillPulse.kt @@ -3,7 +3,6 @@ package content.global.skill.gather.mining import core.api.* import core.game.event.ResourceProducedEvent import core.cache.def.impl.ItemDefinition -import content.data.skill.SkillingPets import core.game.node.Node import core.game.node.entity.impl.Animator import core.game.node.entity.npc.drop.DropFrequency @@ -132,7 +131,6 @@ class MiningSkillPulse(private val player: Player, private val node: Node) : Pul rewardAmount = calculateRewardAmount(reward) // calculate amount player.dispatch(ResourceProducedEvent(reward, rewardAmount, node)) - SkillingPets.checkPetDrop(player, SkillingPets.GOLEM) // roll for pet // Reward mining experience val experience = resource!!.experience * rewardAmount diff --git a/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt b/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt index 6914f02fb..af8ef7398 100644 --- a/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt +++ b/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt @@ -1,6 +1,5 @@ package content.global.skill.gather.woodcutting -import content.data.skill.SkillingPets import content.data.skill.SkillingTool import content.data.tables.BirdNest import content.global.skill.farming.FarmingPatch.Companion.forObject @@ -109,7 +108,6 @@ class WoodcuttingListener : InteractionListener { if (reward > 0) { rewardAmount = calculateRewardAmount(player, reward) // calculate amount - SkillingPets.checkPetDrop(player, SkillingPets.BEAVER) // roll for pet //add experience val experience: Double = calculateExperience(player, resource, rewardAmount) diff --git a/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingSkillPulse.java b/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingSkillPulse.java index bdb54f1fe..1fe03050c 100644 --- a/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingSkillPulse.java +++ b/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingSkillPulse.java @@ -8,7 +8,6 @@ import core.game.event.ResourceProducedEvent; import core.cache.def.impl.ItemDefinition; import core.game.container.impl.EquipmentContainer; import core.game.dialogue.FacialExpression; -import content.data.skill.SkillingPets; import core.game.node.entity.impl.Animator; import core.game.node.entity.impl.Projectile; import core.game.node.entity.player.Player; @@ -157,7 +156,6 @@ public class WoodcuttingSkillPulse extends Pulse { if (reward > 0) { reward = calculateReward(reward); // calculate rewards rewardAmount = calculateRewardAmount(reward); // calculate amount - SkillingPets.checkPetDrop(player, SkillingPets.BEAVER); // roll for pet //add experience double experience = calculateExperience(resource.reward, rewardAmount); diff --git a/Server/src/main/content/global/skill/hunter/TrapDismantlePulse.java b/Server/src/main/content/global/skill/hunter/TrapDismantlePulse.java index 0492f937b..8d3493c28 100644 --- a/Server/src/main/content/global/skill/hunter/TrapDismantlePulse.java +++ b/Server/src/main/content/global/skill/hunter/TrapDismantlePulse.java @@ -1,6 +1,5 @@ package content.global.skill.hunter; -import content.data.skill.SkillingPets; import core.game.node.entity.skill.SkillPulse; import core.game.node.entity.skill.Skills; import core.game.node.entity.player.Player; @@ -95,13 +94,6 @@ public final class TrapDismantlePulse extends SkillPulse { if (wrapper.getType().getSettings().clear(wrapper, 1)) { instance.deregister(wrapper); if (wrapper.isCaught()) { - if (wrapper.getType().equals(Traps.BOX_TRAP)) { - for (int i : wrapper.getReward().getNpcIds()) { - if (i == 5080 || i == 5079) { - SkillingPets.checkPetDrop(player, i == 5080 ? SkillingPets.BABY_RED_CHINCHOMPA : SkillingPets.BABY_GREY_CHINCHOMPA); - } - } - } player.getSkills().addExperience(Skills.HUNTER, wrapper.getReward().getExperience(), true); } player.getPacketDispatch().sendMessage("You dismantle the trap."); diff --git a/Server/src/main/content/global/skill/hunter/TrapNode.java b/Server/src/main/content/global/skill/hunter/TrapNode.java index ad8608779..d2cc48e01 100644 --- a/Server/src/main/content/global/skill/hunter/TrapNode.java +++ b/Server/src/main/content/global/skill/hunter/TrapNode.java @@ -1,6 +1,5 @@ package content.global.skill.hunter; -import content.data.skill.SkillingPets; import core.game.node.entity.npc.NPC; import core.game.node.entity.player.Player; import core.game.node.entity.skill.Skills; @@ -123,8 +122,4 @@ public class TrapNode { return objectIds; } - public SkillingPets getPet() { - return null; - } - } diff --git a/Server/src/main/content/global/skill/summoning/pet/Pets.java b/Server/src/main/content/global/skill/summoning/pet/Pets.java index 7f0e3d92a..3258099fc 100644 --- a/Server/src/main/content/global/skill/summoning/pet/Pets.java +++ b/Server/src/main/content/global/skill/summoning/pet/Pets.java @@ -55,31 +55,6 @@ public enum Pets { */ TERRIER(12512, 12513, -1, 6958, 6959, -1, 0.0033333333333333, 4, 2132, 2134, 2136, 2138, 10816, 9986, 9978, 526), TERRIER_1(12700, 12701, -1, 7237, 7238, -1, 0.0033333333333333, 4, 2132, 2134, 2136, 2138, 10816, 9986, 9978, 526), TERRIER_2(12702, 12703, -1, 7239, 7240, -1, 0.0033333333333333, 4, 2132, 2134, 2136, 2138, 10816, 9986, 9978, 526), - /** - * A creeping hand pet. - */ - //CREEPING_HAND(14652, -1, -1, 8619, -1, -1, 0.0033333333333333, 4, 1059), - - /** - * Minitrice pet. - */ - //MINITRICE(14653, -1, -1, 8620, -1, -1, 0.0033333333333333, 4, 225), - - /** - * Baby basilisk pet. - */ - //BABY_BASILISK(14654, -1, -1, 8621, -1, -1, 0.0033333333333333, 4, 221), - - /** - * Baby kurask pet. - */ - //BABY_KURASK(14655, -1, -1, 8622, -1, -1, 0.0033333333333333, 4, 526), - - /** - * Abyssal minion pet. - */ - //ABYSSAL_MINION(14651, -1, -1, 8624, -1, -1, 0.0033333333333333, 4, 592), - /** * Gecko pet. */ diff --git a/Server/src/main/content/region/karamja/tzhaar/handlers/TzHaarMejJah.java b/Server/src/main/content/region/karamja/tzhaar/handlers/TzHaarMejJah.java deleted file mode 100644 index 09653c566..000000000 --- a/Server/src/main/content/region/karamja/tzhaar/handlers/TzHaarMejJah.java +++ /dev/null @@ -1,313 +0,0 @@ -package content.region.karamja.tzhaar.handlers; - -import core.game.dialogue.DialogueInterpreter; -import core.game.dialogue.DialoguePlugin; -import core.game.dialogue.FacialExpression; -import core.game.node.entity.npc.NPC; -import core.game.node.item.Item; -import core.plugin.Initializable; -import core.game.node.entity.player.Player; -import core.game.world.GameWorld; - -/** - * Handles the TzHaarMejJal dialogue. - * @author 'Vexia - * @author Empathy - * @author Logg - */ -@Initializable -public class TzHaarMejJah extends DialoguePlugin { - private static final Item APPEARANCE_FEE = new Item(6529, 8000); // 8000 tokkul, about the same as you get from failing jad - - public TzHaarMejJah() { - - } - - public TzHaarMejJah(Player player) { - super(player); - } - - @Override - public DialoguePlugin newInstance(Player player) { - - return new TzHaarMejJah(player); - } - - @Override - public boolean open(Object... args) { - npc = (NPC) args[0]; - interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "You want help JalYt-Ket-" + player.getUsername() + "?"); - stage = 0; - return true; - } - - @Override - public boolean handle(int interfaceId, int buttonId) { - switch (stage) { - case 0: - if (GameWorld.getSettings().getJad_practice_enabled()) { - if (player.getAttribute("fc_practice_jad", false)) { - interpreter.sendOptions("Select an Option", player.getInventory().containItems(6570) ? "I have a fire cape here." : "What is this place?", "What did you call me?", "About my challenge...", "No I'm fine thanks."); - } else { - interpreter.sendOptions("Select an Option", player.getInventory().containItems(6570) ? "I have a fire cape here." : "What is this place?", "What did you call me?", "I want to challenge Jad directly.", "No I'm fine thanks."); - } - } else { - interpreter.sendOptions("Select an Option", player.getInventory().containItems(6570) ? "I have a fire cape here." : "What is this place?", "What did you call me?", "No I'm fine thanks."); - } - stage = 1; - break; - case 1: - switch (buttonId) { - case 1: - if (player.getInventory().containItems(6570)) { - interpreter.open(DialogueInterpreter.getDialogueKey("firecape-exchange"), npc); - break; - } - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "What is this place?"); - stage = 10; - break; - case 2: - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "What did you call me?"); - stage = 20; - break; - case 3: - if (GameWorld.getSettings().getJad_practice_enabled()) { - if (player.getAttribute("fc_practice_jad", false)) { - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "About my challenge..."); - stage = 64; - } else { - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "The challenge is too long.", "I want to challenge Jad directly."); - stage = 50; - } - } else { - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "No I'm fine thanks."); - stage = 30; - } - break; - case 4: - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "No I'm fine thanks."); - stage = 30; - break; - } - break; - case 10: - interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "This is the fight caves, TzHaar-Xil made it for practice,", "but many JalYt come here to fight too.", "Just enter the cave and make sure you're prepared."); - stage = 11; - break; - case 11: - interpreter.sendOptions("Select an Option", "Are there any rules?", "Ok thanks."); - stage = 12; - break; - case 12: - switch (buttonId) { - case 1: - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "Are there any rules?"); - stage = 14; - break; - case 2: - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "Ok thanks."); - stage = 13; - break; - } - break; - case 13: - end(); - break; - case 14: - interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "Rules? Survival is the only rule in there."); - stage = 15; - break; - case 15: - interpreter.sendOptions("Select an Option", "Do I win anything?", "Sounds good."); - stage = 16; - break; - case 16: - switch (buttonId) { - case 1: - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "Do I win anything?"); - stage = 17; - break; - case 2: - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "Sounds good."); - stage = 13; - break; - - } - break; - case 17: - interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "You ask a lot of questions.", "Might give you TokKul if you last long enough."); - stage = 18; - break; - case 18: - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "..."); - stage = 19; - break; - case 19: - interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "Before you ask, TokKul is like your Coins."); - stage = 500; - break; - case 500: - interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "Gold is like you JalYt, soft and easily broken, we use", "hard rock forged in fire like TzHaar!"); - stage = 501; - break; - case 501: - end(); - break; - case 20: - interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "Are you not JalYt-Ket?"); - stage = 21; - break; - case 21: - interpreter.sendOptions("Select an Option", "What's a 'JalYt-Ket'?", "I guess so...", "No I'm not!"); - stage = 22; - break; - case 22: - switch (buttonId) { - case 1: - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "What's a 'JalYt-Ket'?"); - stage = 100; - break; - case 2: - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "I guess so..."); - stage = 200; - break; - case 3: - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "No I'm not!"); - stage = 300; - break; - } - break; - case 100: - interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "That what you are... you tough and strong no?"); - stage = 101; - break; - case 101: - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "Well yes I suppose I am..."); - stage = 102; - break; - case 102: - end(); - break; - case 200: - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "I guess so...."); - stage = 201; - break; - case 201: - end(); - break; - case 300: - end(); - break; - case 30: - end(); - break; - - case 50: - interpreter.sendDialogues(npc, FacialExpression.DISGUSTED_HEAD_SHAKE, "I thought you strong and tough", "but you want skip endurance training?"); - stage = 57; - break; - case 57: - interpreter.sendDialogues(npc, FacialExpression.NEUTRAL, "Maybe you not JalYt-Ket afterall."); - stage = 58; - break; - case 58: - interpreter.sendOptions("Select an Option", "I don't have time for it, man.", "No, I'm JalYt-Ket!"); - stage = 51; - break; - case 51: - switch (buttonId) { - case 1: - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "I don't have time for it, man."); - stage = 52; - break; - case 2: - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "No, I'm JalYt-Ket! I swear!", "I'll do the training properly."); - stage = 30; - break; - } - break; - case 52: - interpreter.sendDialogues(npc, FacialExpression.DISGUSTED_HEAD_SHAKE, "JalYt, you know you not get reward","if you not do training properly, ok?"); - stage = 56; - break; - case 56: - interpreter.sendOptions("Select an Option", "That's okay, I don't need a reward.", "Oh, nevermind then."); - stage = 53; - break; - case 53: - switch (buttonId) { - case 1: - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "That's okay, I don't need a reward."); - stage = 54; - break; - case 2: - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "Oh, nevermind then."); - stage = 30; - break; - } - break; - case 54: - interpreter.sendDialogues(player, FacialExpression.NEUTRAL, "I just wanna fight the big guy."); - stage = 55; - break; - case 55: - interpreter.sendDialogues(npc, FacialExpression.NEUTRAL, "Okay JalYt.","TzTok-Jad not show up for just anyone."); - stage = 59; - break; - case 59: - interpreter.sendDialogues(npc, FacialExpression.NEUTRAL, "You give 8000 TokKul, TzTok-Jad know you serious.", "You get it back if you victorious."); - stage = 60; - break; - case 60: - interpreter.sendOptions("Select an Option", "That's fair, here's 8000 TokKul.", "I don't have that much on me, but I'll go get it.", "TzTok-Jad must be old and tired to not just accept my challenge."); - stage = 61; - case 61: - switch (buttonId) { - case 1: - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "That's fair, here's 8000 TokKul."); - if (!player.getInventory().containsItem(APPEARANCE_FEE)) { - stage = 62; - break; - } - if (player.getInventory().remove(APPEARANCE_FEE)) { - stage = 69; - } else { - stage = 62; - } - break; - case 2: - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "I don't have that much on me, but I'll go get it."); - stage = 30; - break; - case 3: - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "TzTok-Jad must be old and tired", "to not just accept my challenge."); - stage = 63; - break; - } - break; - case 62: - interpreter.sendDialogues(npc, FacialExpression.NEUTRAL, "JalYt, you not have the TokKul.", "You come back when you are serious."); - stage = 30; - break; - case 63: - interpreter.sendDialogues(npc, FacialExpression.ANGRY, "JalYt-Mor, you the old and tired one.", "You the one not want to do proper training."); - stage = 30; - break; - case 64: - interpreter.sendDialogues(npc, FacialExpression.NEUTRAL, "TzTok-Jad is waiting for you.", "Do not make TzTok-Jad wait long."); - stage = 30; - break; - case 69: - interpreter.sendDialogues(npc, FacialExpression.NEUTRAL, "Okay JalYt. Enter cave when you are prepared.", "You find TzTok-Jad waiting for JalYt challenger."); - player.setAttribute("fc_practice_jad", true); - stage = 30; - break; - } - return true; - } - - @Override - public int[] getIds() { - return new int[] { DialogueInterpreter.getDialogueKey("tzhaar-mej"), 2617 }; - } -} diff --git a/Server/src/main/content/region/karamja/tzhaar/handlers/TzHaarMejJal.kt b/Server/src/main/content/region/karamja/tzhaar/handlers/TzHaarMejJal.kt new file mode 100644 index 000000000..a0b865ca7 --- /dev/null +++ b/Server/src/main/content/region/karamja/tzhaar/handlers/TzHaarMejJal.kt @@ -0,0 +1,167 @@ +package content.region.karamja.tzhaar.handlers + +import core.api.* +import core.game.dialogue.* +import core.game.node.entity.npc.NPC +import core.game.node.entity.player.Player +import core.game.node.item.Item +import core.game.world.GameWorld.settings +import core.plugin.Initializable +import core.tools.END_DIALOGUE +import org.rs09.consts.Items +import org.rs09.consts.NPCs + +/** + * Handles the TzHaarMejJal dialogue. + * @author 'Vexia + * @author Empathy + * @author Logg + */ +@Initializable +class TzHaarMejJal(player: Player? = null) : DialoguePlugin(player){ + + override fun open(vararg args: Any): Boolean { + npc = args[0] as NPC + npcl(FacialExpression.HALF_GUILTY, "You want help JalYt-Ket-${player.username}?").also { stage = 0 } + return true + } + + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + when (stage) { + 0 -> showTopics( + if (inInventory(player, Items.FIRE_CAPE_6570)) { + Topic(FacialExpression.HALF_GUILTY, "I have a fire cape here.", 100) + } else { + Topic(FacialExpression.HALF_GUILTY, "What is this place?", 10) + }, + Topic(FacialExpression.HALF_GUILTY, "What did you call me?", 20), + IfTopic(FacialExpression.HALF_GUILTY, "About my challenge...", 50, getAttribute(player, "fc_practice_jad", false) && settings!!.jad_practice_enabled), + IfTopic("I want to challenge Jad directly.", 30, !getAttribute(player, "fc_practice_jad", false) && settings!!.jad_practice_enabled, skipPlayer = true), + Topic(FacialExpression.HALF_GUILTY, "No I'm fine thanks.", END_DIALOGUE) + ) + + 10 -> npc(FacialExpression.HALF_GUILTY, + "This is the fight caves, TzHaar-Xil made it for practice,", + "but many JalYt come here to fight too.", + "Just enter the cave and make sure you're prepared." + ).also { stage++ } + + 11 -> showTopics( + Topic(FacialExpression.HALF_GUILTY, "Are there any rules?", 12), + Topic(FacialExpression.HALF_GUILTY, "Ok thanks.", END_DIALOGUE) + ) + + 12 -> npcl(FacialExpression.HALF_GUILTY, "Rules? Survival is the only rule in there.").also { stage++ } + 13 -> showTopics( + Topic(FacialExpression.HALF_GUILTY, "Do I win anything?", 14), + Topic(FacialExpression.HALF_GUILTY, "Sounds good.", END_DIALOGUE) + ) + + 14 -> npc(FacialExpression.HALF_GUILTY, + "You ask a lot of questions.", + "Might give you TokKul if you last long enough." + ).also { stage++ } + 15 -> playerl(FacialExpression.HALF_GUILTY, "...").also { stage ++ } + 16 -> npcl(FacialExpression.HALF_GUILTY, "Before you ask, TokKul is like your Coins.").also { stage++ } + 17 -> npc(FacialExpression.HALF_GUILTY, + "Gold is like you JalYt, soft and easily broken, we use", + "hard rock forged in fire like TzHaar!" + ).also{ stage = END_DIALOGUE } + + 20 -> npcl(FacialExpression.HALF_GUILTY, "Are you not JalYt-Ket?").also { stage++ } + 21 -> showTopics( + Topic(FacialExpression.HALF_GUILTY, "What's a 'JalYt-Ket'?", 22), + Topic(FacialExpression.HALF_GUILTY, "I guess so...", END_DIALOGUE), + Topic(FacialExpression.HALF_GUILTY, "No I'm not!", END_DIALOGUE) + ) + + 22 -> npcl(FacialExpression.HALF_GUILTY, "That what you are... you tough and strong no?").also { stage++ } + 23 -> playerl(FacialExpression.HALF_GUILTY, "Well yes I suppose I am...").also { stage = END_DIALOGUE } + + 30 -> player(FacialExpression.HALF_GUILTY, + "The challenge is too long.", + "I want to challenge Jad directly." + ).also { stage++ } + 31 -> npc(FacialExpression.DISGUSTED_HEAD_SHAKE, + "I thought you strong and tough", + "but you want skip endurance training?" + ).also { stage++ } + 32 -> npcl(FacialExpression.NEUTRAL, "Maybe you not JalYt-Ket afterall.").also { stage++ } + 33 -> showTopics( + Topic(FacialExpression.HALF_GUILTY, "I don't have time for it, man.", 35), + Topic("No, I'm JalYt-Ket!", 34, skipPlayer = true) + ) + + 34 -> player(FacialExpression.HALF_GUILTY, + "No, I'm JalYt-Ket! I swear!", + "I'll do the training properly." + ).also { stage = END_DIALOGUE } + + 35 -> npc(FacialExpression.DISGUSTED_HEAD_SHAKE, + "JalYt, you know you not get reward", + "if you not do training properly, ok?" + ).also { stage++ } + 36 -> showTopics( + Topic(FacialExpression.HALF_GUILTY, "That's okay, I don't need a reward.", 37), + Topic(FacialExpression.HALF_GUILTY, "Oh, nevermind then.", END_DIALOGUE) + ) + + 37 -> playerl(FacialExpression.NEUTRAL, "I just wanna fight the big guy.").also { stage++ } + 38 -> npc(FacialExpression.NEUTRAL, + "Okay JalYt.", + "TzTok-Jad not show up for just anyone." + ).also { stage++ } + 39 -> npc(FacialExpression.NEUTRAL, + "You give 8000 TokKul, TzTok-Jad know you serious.", + "You get it back if you victorious." + ).also { stage++ } + 40 -> showTopics( + Topic(FacialExpression.HALF_GUILTY, "That's fair, here's 8000 TokKul.", 41), + Topic(FacialExpression.HALF_GUILTY, "I don't have that much on me, but I'll go get it.", END_DIALOGUE), + Topic("TzTok-Jad must be old and tired to not just accept my challenge.", 42, skipPlayer = true) + ) + + 41 -> if (removeItem(player, Item(Items.TOKKUL_6529, 8000))) { + npc(FacialExpression.NEUTRAL, + "Okay JalYt. Enter cave when you are prepared.", + "You find TzTok-Jad waiting for JalYt challenger." + ) + .also { setAttribute(player, "fc_practice_jad", true) } + .also { stage = END_DIALOGUE } + } else npc(FacialExpression.NEUTRAL, + "JalYt, you not have the TokKul.", + "You come back when you are serious." + ).also { stage = END_DIALOGUE } + + 42 -> player(FacialExpression.HALF_GUILTY, + "TzTok-Jad must be old and tired", + "to not just accept my challenge." + ).also { stage++ } + 43 -> npc(FacialExpression.ANGRY, + "JalYt-Mor, you the old and tired one.", + "You the one not want to do proper training." + ).also { stage = END_DIALOGUE } + + 50 -> npc(FacialExpression.NEUTRAL, + "TzTok-Jad is waiting for you.", + "Do not make TzTok-Jad wait long." + ).also { stage = END_DIALOGUE } + + 100 -> sendDialogueOptions(player, "Sell your fire cape?", "Yes, sell it for 8,000 TokKul.", "No, keep it.").also { stage++ } + 101 -> when (buttonId) { + 1 -> npcl(FacialExpression.OLD_NORMAL, "Hand your cape here, young JalYte.").also { stage++ } + 2 -> end() + } + 102 -> end().also { if (removeItem(player, Items.FIRE_CAPE_6570)) addItem(player, Items.TOKKUL_6529, 8000) } + } + return true + } + + override fun newInstance(player: Player): DialoguePlugin { + return TzHaarMejJal(player) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.TZHAAR_MEJ_JAL_2617) + } +} diff --git a/Server/src/main/content/region/karamja/tzhaar/handlers/TzRekJadNPC.java b/Server/src/main/content/region/karamja/tzhaar/handlers/TzRekJadNPC.java deleted file mode 100644 index 2c5993906..000000000 --- a/Server/src/main/content/region/karamja/tzhaar/handlers/TzRekJadNPC.java +++ /dev/null @@ -1,263 +0,0 @@ -package content.region.karamja.tzhaar.handlers; - -import core.cache.def.impl.NPCDefinition; -import core.game.dialogue.DialogueInterpreter; -import core.game.dialogue.DialoguePlugin; -import core.game.dialogue.FacialExpression; -import core.game.interaction.OptionHandler; -import core.game.node.Node; -import core.game.node.entity.npc.NPC; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; -import core.game.world.repository.Repository; -import core.plugin.Plugin; -import core.plugin.ClassScanner; -import core.plugin.Initializable; -import core.tools.RandomFunction; - -/** - * Handles everything in regards to jad pet. - * @author Empathy - * - */ -@Initializable -public class TzRekJadNPC extends OptionHandler { - - /** - * The tzhaar mej id. - */ - private static final int TZHAAR_MEJ_ID = 2617; - - /** - * The firecape item. - */ - private static final Item FIRECAPE = new Item(6570); - - /** - * The tokkul item. - */ - private static final Item TOKKUL = new Item(6529); - - /** - * The jad item. - */ - private static final Item JAD_PET = new Item(14828); - - @Override - public Plugin newInstance(Object arg) throws Throwable { - NPCDefinition.forId(TZHAAR_MEJ_ID).getHandlers().put("option:exchange fire cape", this); - ClassScanner.definePlugins(new TzhaarMejJalDialogue(), new TzRekJadDialogue()); - return this; - } - - @Override - public boolean handle(Player player, Node node, String option) { - switch (option) { - case "exchange fire cape": - player.getDialogueInterpreter().open(DialogueInterpreter.getDialogueKey(player.getInventory().containsItem(FIRECAPE) ? "firecape-exchange" : "tzhaar-mej"), node.asNpc()); - break; - } - return true; - } - - /** - * Handles the TzhaarMejJal Dialogue to gamble for jad pet. - * @author Empathy - * - */ - public final class TzhaarMejJalDialogue extends DialoguePlugin { - - /** - * Constructs a new {@code TzhaarMejJalDialogue} {@code Object}. - */ - public TzhaarMejJalDialogue() { - /** - * empty. - */ - } - - /** - * Constructs a new {@code TzhaarMejJalDialogue} {@code Object}. - * - * @param player the player. - */ - public TzhaarMejJalDialogue(Player player) { - super(player); - } - - @Override - public DialoguePlugin newInstance(Player player) { - return new TzhaarMejJalDialogue(player); - } - - @Override - public boolean open(Object... args) { - npc = (NPC) args[0]; - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "I have a fire cape here."); - stage = 0; - return true; - } - - @Override - public boolean handle(int interfaceId, int buttonId) { - switch (stage) { - case 0: - interpreter.sendOptions("Sell your fire cape?", "Yes, sell it for 8,000 TokKul.", "No, keep it.", "Bargain for TzRek-Jad."); - stage = 1; - break; - case 1: - switch(buttonId) { - case 1: - interpreter.sendDialogues(npc, FacialExpression.OLD_NORMAL, player.getInventory().containsItems(FIRECAPE) ? "Hand your cape here, young JalYte." : "You not have firecape, JalYt."); - stage = 10; - break; - case 2: - end(); - break; - case 3: - interpreter.sendOptions("Sacrifice your firecape?", "Yes, I know I won't get my cape back.", "No, I like my cape!"); - stage = 20; - break; - } - break; - case 10: - if (player.getInventory().containsItem(FIRECAPE)) { - if (player.getInventory().remove(FIRECAPE)) { - TOKKUL.setAmount(8000); - player.getInventory().add(TOKKUL); - TOKKUL.setAmount(1); - } - } - end(); - break; - case 20: - switch (buttonId) { - case 1: - if (player.hasItem(JAD_PET)) { - interpreter.sendDialogues(npc, FacialExpression.OLD_NORMAL, "Best you train one TzRek-Jad only."); - stage = 21; - break; - } - if (player.getFamiliarManager().hasFamiliar()) { - if (player.getFamiliarManager().getFamiliar().getId() == 8650) { - interpreter.sendDialogues(npc, FacialExpression.OLD_NORMAL, "Best you train one TzRek-Jad only."); - stage = 21; - break; - } - } - if (player.getInventory().remove(FIRECAPE)) { - int r = RandomFunction.getRandom(200); - if (r == 1) { - interpreter.sendDialogues(npc, FacialExpression.OLD_NORMAL, "You lucky. Better train him good else TzTok-Jad find", "you, JalYt."); - if (!player.getFamiliarManager().hasFamiliar()) { - player.getFamiliarManager().summon(JAD_PET, true); - player.sendNotificationMessage("You have a funny feeling like you're being followed."); - } else if (player.getInventory().freeSlots() > 0) { - player.getInventory().add(JAD_PET); - player.sendNotificationMessage("You feel something weird sneaking into your backpack."); - } - Repository.sendNews(player.getUsername() + " now commands a miniature TzTok-Jad!"); - } else { - interpreter.sendDialogues(npc, FacialExpression.OLD_NORMAL, "You not lucky. Maybe next time, JalYt."); - } - } - stage = 21; - break; - case 2: - end(); - break; - } - break; - case 21: - end(); - break; - } - return true; - } - - @Override - public int[] getIds() { - return new int[] { DialogueInterpreter.getDialogueKey("firecape-exchange") }; - } - } - - /** - * Handles the TzRekJad dialogue. - * @author Empathy - * - */ - public final class TzRekJadDialogue extends DialoguePlugin { - - /** - * Constructs a new {@code TzRekJadDialogue} {@code Object}. - */ - public TzRekJadDialogue() { - /** - * empty. - */ - } - - /** - * Constructs a new {@code TzRekJadDialogue} {@code Object}. - * - * @param player the player. - */ - public TzRekJadDialogue(Player player) { - super(player); - } - - @Override - public DialoguePlugin newInstance(Player player) { - return new TzRekJadDialogue(player); - } - - @Override - public boolean open(Object... args) { - npc = (NPC) args[0]; - int i = RandomFunction.getRandom(1); - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, i == 1 ? "Do you miss your people?" : "Are you hungry?"); - stage = (i == 1 ? 0 : 5); - return true; - } - - @Override - public boolean handle(int interfaceId, int buttonId) { - switch (stage) { - case 0: - interpreter.sendDialogues(npc, FacialExpression.OLD_NORMAL, "Mej-TzTok-Jad Kot-Kl!"); - stage = 1; - break; - case 1: - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "No.. I don't think so."); - stage = 2; - break; - case 2: - interpreter.sendDialogues(npc, FacialExpression.OLD_NORMAL, "Jal-Zek Kl?"); - stage = 3; - break; - case 3: - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "No, no, I wouldn't hurt you."); - stage = 4; - break; - case 4: - end(); - break; - case 5: - interpreter.sendDialogues(npc, FacialExpression.OLD_NORMAL, "Kl-Kra!"); - stage = 6; - break; - case 6: - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "Ooookay..."); - stage = 4; - break; - } - return true; - } - - @Override - public int[] getIds() { - return new int[] { 8650 }; - } - } - -} From a5b5fdf4d6ae8cd98d32b73e0ba0070642e3220b Mon Sep 17 00:00:00 2001 From: damighty <27978131-real_damighty@users.noreply.gitlab.com> Date: Sun, 31 Aug 2025 12:03:14 +0300 Subject: [PATCH 067/117] Fixed Digsite quest items that damage player on drop potentially dealing more damage than intended --- .../quest/thedigsite/TheDigSiteListeners.kt | 60 +++++++++++-------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/Server/src/main/content/region/misthalin/digsite/quest/thedigsite/TheDigSiteListeners.kt b/Server/src/main/content/region/misthalin/digsite/quest/thedigsite/TheDigSiteListeners.kt index 470451817..a79a331bd 100644 --- a/Server/src/main/content/region/misthalin/digsite/quest/thedigsite/TheDigSiteListeners.kt +++ b/Server/src/main/content/region/misthalin/digsite/quest/thedigsite/TheDigSiteListeners.kt @@ -826,43 +826,53 @@ class TheDigSiteListeners : InteractionListener { } on(Items.UNIDENTIFIED_LIQUID_702, ITEM, "drop") { player, node -> - removeItem(player, node) - impact(player, 25) - sendChat(player, "Ow! The liquid exploded!") - sendMessage(player, "You were injured by the burning liquid.") - return@on true + if (removeItem(player, node)) { + impact(player, 25) + sendChat(player, "Ow! The liquid exploded!") + sendMessage(player, "You were injured by the burning liquid.") + return@on true + } + return@on false } on(Items.NITROGLYCERIN_703, ITEM, "drop") { player, node -> - removeItem(player, node) - impact(player, 35) - sendChat(player, "Ow! The nitroglycerin exploded!") - sendMessage(player, "You were injured by the burning liquid.") - return@on true + if (removeItem(player, node)) { + impact(player, 35) + sendChat(player, "Ow! The nitroglycerin exploded!") + sendMessage(player, "You were injured by the burning liquid.") + return@on true + } + return@on false } on(Items.MIXED_CHEMICALS_705, ITEM, "drop") { player, node -> - removeItem(player, node) - impact(player, 45) - sendChat(player, "Ow! The liquid exploded!") - sendMessage(player, "You were injured by the burning liquid.") - return@on true + if (removeItem(player, node)) { + impact(player, 45) + sendChat(player, "Ow! The liquid exploded!") + sendMessage(player, "You were injured by the burning liquid.") + return@on true + } + return@on false } on(Items.MIXED_CHEMICALS_706, ITEM, "drop") { player, node -> - removeItem(player, node) - impact(player, 55) - sendChat(player, "Ow! The liquid exploded!") - sendMessage(player, "You were injured by the burning liquid.") - return@on true + if (removeItem(player, node)) { + impact(player, 55) + sendChat(player, "Ow! The liquid exploded!") + sendMessage(player, "You were injured by the burning liquid.") + return@on true + } + return@on false } on(Items.CHEMICAL_COMPOUND_707, ITEM, "drop") { player, node -> - removeItem(player, node) - impact(player, 65) - sendChat(player, "Ow! The liquid exploded!") - sendMessage(player, "You were injured by the burning liquid.") - return@on true + if (removeItem(player, node)) { + impact(player, 65) + sendChat(player, "Ow! The liquid exploded!") + sendMessage(player, "You were injured by the burning liquid.") + return@on true + } + return@on false } // Scenery not tied to quest From 3dd189d0f35c654acd7eee741988aaf59ac28047 Mon Sep 17 00:00:00 2001 From: Sinipelto <7580610-Sinipelto@users.noreply.gitlab.com> Date: Sun, 31 Aug 2025 12:10:54 +0300 Subject: [PATCH 068/117] Improved docker support Updated README for docker Cleaned up run and build bash scripts --- .dockerignore | 30 +++++++++++++++ .gitignore | 12 +++++- Dockerfile | 10 +---- README.md | 72 +++++++++++++++++++++++++++++++++++ build | 93 +++++++++++----------------------------------- docker-compose.yml | 48 +++++++++++++----------- mysql.env.example | 20 ++++++++++ run | 47 ++++------------------- run-ms.bat | 8 ---- 9 files changed, 191 insertions(+), 149 deletions(-) create mode 100644 .dockerignore create mode 100644 mysql.env.example delete mode 100644 run-ms.bat diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..a85e0e9ed --- /dev/null +++ b/.dockerignore @@ -0,0 +1,30 @@ +# Local volumes +/config +/data +/db + +# Updated through volumes +Server/data +Server/worldprops + +# Env files containing secrets +/.env +/*.env + +# Log files +logs +*.log + +# Git files +.git +.gitattributes +.gitignore + +# Basic stuff +*.md +LICENSE +.gitlab-ci.yml + +# Docker files +docker-compose.yml +Dockerfile diff --git a/.gitignore b/.gitignore index e1cc944e2..6ecd66985 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,8 @@ Server/data/global_kill_stats.json Server/ge_test.db Server/latestdump.txt Management-Server/managementprops/ -Server/worldprops/ +Server/worldprops/* +!Server/worldprops/default.conf **/.idea/workspace.xml **/.idea/tasks.xml @@ -32,3 +33,12 @@ gradle build/kotlin/sessions/ **/*.iml Server/hasRan.txt + +# Local volumes +/config +/data +/db + +# Env files containing secrets +/.env +/*.env diff --git a/Dockerfile b/Dockerfile index 7078b8c93..5195e5bf4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,14 +4,8 @@ FROM maven:3-openjdk-11-slim # Set working directory to /app WORKDIR /app -# Update apt; install git and git-lfs -RUN apt-get update && apt-get -qq -y install git git-lfs - -# Clone the 2009scape repository -RUN git clone --depth=1 https://gitlab.com/2009scape/2009scape.git - -# Fake it til you make it - let's go home -WORKDIR /app/2009scape +# Copy all sources etc +COPY . . # Make sure ./run has permissions RUN chmod +x run diff --git a/README.md b/README.md index caf98bfe5..e986e4311 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,78 @@ Start the game server with the included run script. Use `./run -h` for more info Start the game server with `run-server.bat` +#### Docker + +Make sure [Docker Engine](https://docs.docker.com/engine/install/) & [Docker Compose](https://docs.docker.com/compose/install/) plugin are installed first: + +Go to the project root where the git repository is cloned into: + +```bash +cd /path/to/project-dir +``` + +To configure the database, copy the mysql env file template to a env file in the project root: + +```bash +cp mysql.env.example mysql.env +``` + +Customize the env file however necessary. + +Create a new directory called 'config' into the project root. + +```bash +mkdir config +``` + +Copy the server config file template in the config directory: + +```bash +cp Server/worldprops/default.conf config/default.conf +``` + +Edit the server configuration file as per needed. + +Go to the project root directory and execute: + +```bash +docker compose up --build +``` + +Which will build the docker image using the local Dockerfile and starts the server and database containers. + +The previous up command should be run every time the server sources are modified to propagate the changes to the container. + +For the first time, the server compilation process takes a long time. Grab some coffee in the meanwhile. + +You can check (and follow) the containers logs by running the following in the project root directory (-f for follow and Ctrl+C to stop): + +```bash +docker compose logs -f +``` + +Any compilation or runtime errors will be logged here. + +If you have already compiled the server and made no changes to the sources, you can simply run without the build option (however it will be cached anyways): + +```bash +docker compose up +``` + +To later restart the server to apply simple configuration changes, in the project root directory run: + +```bash +docker compose restart server +``` + +To shut down / take down the server, in the project root directory run: + +```bash +docker compose up --build +``` + +The database files, build cache and config files will be persisted on the host filesystem for easy backup management. + ### License We use the AGPL 3.0 license, which can be found [here](https://www.gnu.org/licenses/agpl-3.0.en.html). Please be sure to read and understand the license. Failure to follow the guidelines outlined in the license will result in legal action. If you know or hear of anyone breaking this license, please send a report, with proof, to Red Bracket#8151, ceikry#2724, or woahscam#8535 on discord or email woahscam@hotmail.com. **We WILL NOT change the license to fit your needs.** diff --git a/build b/build index 86471b26f..43f08b78b 100755 --- a/build +++ b/build @@ -2,139 +2,90 @@ SCRIPT_DIR=$(cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P) -BUILD_MS=0 +CLEAN=0 BUILD_GS=0 -CLEAN_MS=0 -CLEAN_GS=0 SKIPTEST="" GS_SRC=$SCRIPT_DIR/Server -MS_SRC=$SCRIPT_DIR/Management-Server -CLEANOPT="" _JAR_DIR="$SCRIPT_DIR/builddir" help() { - echo "Usage: $0 [-h] <-m | -g> [-c ] [-o ]"; + echo "Usage: $0 [-h] [-q] <-g|-c> [-o ]"; echo " -h: Display this message."; - echo " -m: Build the management server."; echo " -g: Build the game server."; - echo " -c: Clean: m: management, g: gameserver. " - echo " Can specify both. Need at least one if present."; - echo " -o: Specify jar-file directory." + echo " -c: Clean previous build."; + echo " -o: Specify jar-file directory."; echo " -q: Quick build - will skip tests."; } error() { - echo $1; - exit -1; -} - -clean_ms() -{ - cd $MS_SRC - - if [[ "$CLEANOPT" == *"m"* ]]; then - echo "Cleaning the management server." - sh mvnw clean || error "Failed to clean the MS." - fi + echo "$1"; + exit 1; } clean_gs() { - cd $GS_SRC; - if [[ "$CLEANOPT" == *"g"* ]]; then + cd $GS_SRC || error "Could not change dir to game server directory." echo "Cleaning the game server." - sh mvnw clean || error "Failed to clean the game server. Giving up." - fi -} - -build_ms() -{ - cd $MS_SRC - - sh mvnw package $SKIPTEST || \ - error "Failed to build the management server. Giving up"; + sh mvnw clean || error "Failed to clean the game server. Giving up." } build_gs() { - cd $GS_SRC; + cd "$GS_SRC" || error "ERROR: Could not chdir to server src. Abort." sh mvnw package $SKIPTEST || \ error "Failed to build the game server. Giving up." } -while getopts "hqmgc:o:d" arg; do +while getopts "hqgc:o:d" arg; do case $arg in h) help exit 0 ;; - m) - BUILD_MS=1 - ;; g) BUILD_GS=1 ;; c) - CLEANOPT=${OPTARG} + CLEAN=1 ;; o) _JAR_DIR=${OPTARG} ;; d) - echo "BUILD_MS=$BUILD_MS" - echo "BUILD_GS=$BUILD_GS" - echo "CLEANOPT=$CLEANOPT" echo "_JAR_DIR=$_JAR_DIR" ;; q) SKIPTEST="-DskipTests" ;; + *) + error "Argument -$arg is not a known or valid argument." + ;; esac done -if [[ $CLEANOPT != "m" ]] \ - && [[ $CLEANOPT != "g" ]] \ - && [[ $CLEANOPT != "mg" ]] \ - && [[ $CLEANOPT != "" ]]; -then - error "Invalid cleaning option '$CLEANOPT'. Valid options are 'm', 'g', 'mg' or none." +if [ $BUILD_GS -eq 0 ] && [ $CLEAN -eq 0 ]; then + error "Need to either build or clean at least. See -h for details." fi -if [ $BUILD_MS -eq 0 ] && [ $BUILD_GS -eq 0 ] && [[ $CLEANOPT == "" ]]; then - error "Need to build or clean at least one of the modules. See -h for details." -fi +# Inside func is cd so mgmt prob fails if no condition +(( CLEAN )) && clean_gs -# Conditionals inside the functions. -clean_gs -clean_ms # ----------- -if [ -d $_JAR_DIR ]; then - rm -r $_JAR_DIR +if [ -d "$_JAR_DIR" ]; then + rm -r "$_JAR_DIR" fi -if [ $BUILD_MS -ne 1 ] && [ $BUILD_GS -ne 1 ]; then - echo "No build options specified. Stop." - exit 0 -fi - -mkdir -p $_JAR_DIR || error "Failed to create build directory."; - -if [ $BUILD_MS -eq 1 ]; then - build_ms - - # Will never execute if build_ms fails because it quits upon failure. - mv -v $MS_SRC/target/*-with-dependencies.jar $_JAR_DIR/ms.jar -fi +mkdir -p "$_JAR_DIR" || error "Failed to create build directory."; if [ $BUILD_GS -eq 1 ]; then build_gs # Will never execute if build_gs fails because it quits upon failure. - mv -v $GS_SRC/target/*-with-dependencies.jar $_JAR_DIR/server.jar + mv -v "$GS_SRC"/target/*-with-dependencies.jar "$_JAR_DIR"/server.jar fi diff --git a/docker-compose.yml b/docker-compose.yml index 02614f351..3c192f791 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,29 +1,33 @@ -version: '3.3' services: - app: + server: build: . - container_name: "2009scape_app" depends_on: - - database - restart: unless-stopped - volumes: - - "2009scape_app:/app" - ports: - - "43595:43595" - - database: - image: mysql:5.7 - container_name: "2009scape_db" + - db restart: always - ports: - - "3306:3306" volumes: - - "2009scape_db:/var/lib/mysql" + - "bcache:/app/Server/target" + - "mcache:/root/.m2" + - "./Server/data:/app/Server/data" + - "./config:/app/Server/worldprops" + ports: + - "43594-43600:43594-43600" + + db: + # current LTS (Long-Term Support) version + image: mariadb:11.4-noble + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + start_period: 30s + interval: 60s + timeout: 10s + retries: 3 + restart: always + volumes: + - "./db:/var/lib/mysql" - "./Server/db_exports/global.sql:/docker-entrypoint-initdb.d/global.sql" - environment: - MYSQL_ALLOW_EMPTY_PASSWORD: "yes" - MYSQL_ROOT_PASSWORD: "" - MYSQL_ROOT_USER: "root" + env_file: + - mysql.env + volumes: - 2009scape_app: - 2009scape_db: + bcache: + mcache: diff --git a/mysql.env.example b/mysql.env.example new file mode 100644 index 000000000..8772412d4 --- /dev/null +++ b/mysql.env.example @@ -0,0 +1,20 @@ +# Prefer no and generating a strong password for the root (+ special) user +MYSQL_ALLOW_EMPTY_PASSWORD="no" + +# You can change the db name also for obscurity. +# Has to be changed in the default.conf and in the global.sql init file then also. +MYSQL_DATABASE="global" + +# Beware: if not using root user, the database 'global' requires granting permissions for that user +# Root user is created automatically so dont provide it here. +#MYSQL_USER="dbuser" +#MYSQL_PASSWORD="CHANGEMEIFNOTUSINGROOTANDINGLOBALCONF" + +# Comment this out if you are using random root pw (below). +MYSQL_ROOT_PASSWORD="ALWAYSCHANGEMEHEREANDINGLOBALCONFIFUSINGROOT" + +# Or you can use a special non-root db user and generate a random pw for the root user instead. +# To improve security +# However, either initial root user access +# or editing Server/db_exports/global.sql to add grants for 'global' to the special user above. +MYSQL_RANDOM_ROOT_PASSWORD="no" diff --git a/run b/run index e1c4fdc10..a854c3200 100755 --- a/run +++ b/run @@ -3,15 +3,11 @@ SCRIPT_DIR=$(cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P) LOG_DIR=$SCRIPT_DIR/logs BUILD_DIR=$SCRIPT_DIR/builddir GS_SRC=$SCRIPT_DIR/Server -MS_SRC=$SCRIPT_DIR/Management-Server FORCE_REBUILD=0 REFRESH_BUILD=1 -GAMESERVER_ONLY=1 - GS_EXEC="cd $GS_SRC && java -Dnashorn.args=--no-deprecation-warning -jar $BUILD_DIR/server.jar" -MS_EXEC="cd $MS_SRC && java -Dnashorn.args=--no-deprecation-warning -jar $BUILD_DIR/ms.jar" # 0: parallel # 1: fancy tmux @@ -20,13 +16,12 @@ RUN_MODE=0 help() { - echo "Usage: $0 [-h] [-q] [-r] [-t] [-x] [-g] [-e ] [-o ]" + echo "Usage: $0 [-h] [-q] [-r] [-t] [-x] [-e ] [-o ]" echo " -h: Display this message." echo " -q: Don't perform the incremental build." echo " -r: Force clean rebuild." echo " -t: Only run tests, not the JARs." echo " -x: Run in a fancy tmux session." - echo " -g: Build + run only the game server (no management server)." echo " -e: Write STDERR to 'logs/filename' as well as the terminal."; echo " -o: Write STDOUT to 'logs/filename' as well as the terminal." } @@ -39,31 +34,17 @@ error() rebuild_project() { - if [ $GAMESERVER_ONLY ]; then - $SCRIPT_DIR/build -qgcg - else - $SCRIPT_DIR/build -qmgcmg - fi + "$SCRIPT_DIR/build" -qgc } refresh_project() { - if [ $GAMESERVER_ONLY ]; then - $SCRIPT_DIR/build -qg - else - $SCRIPT_DIR/build -qmg - fi + "$SCRIPT_DIR/build" -qg } verify_binaries_exist() { - if ! [ $GAMESERVER_ONLY ]; then - if [ ! -f $SCRIPT_DIR/builddir/ms.jar ]; then - error "Management server binary does not exist."; - fi - fi - - if [ ! -f $SCRIPT_DIR/build/server.jar ]; then + if [ ! -f "$SCRIPT_DIR/build/server.jar" ]; then error "Game server binary does not exist."; fi } @@ -76,11 +57,7 @@ run_server_parallel() echo "Running in parallel. All logs redirected to proper files." - if [ $GAMESERVER_ONLY ]; then - sh -c "$GS_EXEC" & wait - else - sh -c "$MS_EXEC > $LOG_DIR/ms_stdout.log 2> $LOG_DIR/ms_stderr.log & $GS_EXEC" & wait - fi + sh -c "$GS_EXEC" & wait } run_server_tmux() @@ -89,21 +66,16 @@ run_server_tmux() error "tmux is not installed. Install tmux or don't use this option." fi - if [ $GAMESERVER_ONLY ]; then - tmux new-session "$GS_EXEC" - else - tmux new-session "$MS_EXEC" \; \ - split-window -h "$GS_EXEC" - fi + tmux new-session "$GS_EXEC" } run_server_tests() { - cd "$GS_SRC" || error "Could not change to GameServer source directory." + cd "$GS_SRC" || error "Could not change to game server source directory." sh mvnw test } -while getopts ":ghqrtxe:o:" arg; do +while getopts ":hqrtxe:o:" arg; do case $arg in h) help @@ -127,9 +99,6 @@ while getopts ":ghqrtxe:o:" arg; do x) RUN_MODE=1 ;; - g) - GAMESERVER_ONLY=0 - ;; *) error "Argument -$arg is not a known or valid argument." ;; diff --git a/run-ms.bat b/run-ms.bat deleted file mode 100644 index 494ccb692..000000000 --- a/run-ms.bat +++ /dev/null @@ -1,8 +0,0 @@ -@echo off -cd Management-Server - -if NOT exist DoNotCreateThisFile.txt ( - .\mvnw.cmd package -DskipTests - xcopy /Y target\*-with-dependencies.jar ms.jar* - java -jar ms.jar -) \ No newline at end of file From 2ba11746782f0cb5655450db3332fc9662d1a696 Mon Sep 17 00:00:00 2001 From: damighty <27978131-real_damighty@users.noreply.gitlab.com> Date: Sun, 31 Aug 2025 12:23:58 +0300 Subject: [PATCH 069/117] Fixed multihit spells incorrectly targeting familiars Fixed multihit spells incorrectly being limited to exclusively players or exclusively NPCs per cast --- .../node/entity/combat/spell/CombatSpell.java | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/Server/src/main/core/game/node/entity/combat/spell/CombatSpell.java b/Server/src/main/core/game/node/entity/combat/spell/CombatSpell.java index 3601d6849..537262cf9 100644 --- a/Server/src/main/core/game/node/entity/combat/spell/CombatSpell.java +++ b/Server/src/main/core/game/node/entity/combat/spell/CombatSpell.java @@ -1,5 +1,6 @@ package core.game.node.entity.combat.spell; +import content.global.skill.summoning.familiar.Familiar; import core.game.node.entity.combat.BattleState; import core.game.node.entity.combat.CombatStyle; import core.game.node.entity.combat.InteractionType; @@ -13,6 +14,7 @@ import core.game.node.entity.player.link.SpellBookManager; import core.game.node.entity.player.link.audio.Audio; import core.game.node.item.Item; import core.game.world.map.RegionManager; +import core.game.world.map.zone.impl.WildernessZone; import core.game.world.update.flag.context.Animation; import core.game.world.update.flag.context.Graphics; import org.rs09.consts.Sounds; @@ -106,27 +108,42 @@ public abstract class CombatSpell extends MagicSpell { } - /** - * Gets a list of possible targets for a multihitting spell. - * @param entity The caster of the spell. - * @param target The victim. - * @param max The max amount of victims. - * @return The list of targets. - */ - public List getMultihitTargets(Entity entity, Entity target, int max) { - List list = new ArrayList<>(20); - list.add(target); - boolean npc = target instanceof NPC; - for (Entity e : npc ? RegionManager.getSurroundingNPCs(target) : RegionManager.getSurroundingPlayers(target)) { - if (e != target && e != entity && CombatStyle.MAGIC.getSwingHandler().canSwing(entity, e) != InteractionType.NO_INTERACT) { - list.add(e); + /** + * Gets a list of valid targets for a multihitting spell. + * @param entity The caster of the spell. + * @param target The primary victim. + * @param max The maximum number of extra victims that may be hit. + * @return The list of targets (the primary target is always at index 0). + */ + public List getMultihitTargets(Entity entity, Entity target, int max) { + List victims = new ArrayList<>(20); + victims.add(target); + + List surrounding = new ArrayList<>(); + surrounding.addAll(RegionManager.getSurroundingPlayers(target)); + surrounding.addAll(RegionManager.getSurroundingNPCs(target)); + + for (Entity e : surrounding) { + if (e == target || e == entity) { + continue; + } + if (CombatStyle.MAGIC.getSwingHandler().canSwing(entity, e) == InteractionType.NO_INTERACT) { + continue; + } + if (e instanceof Familiar) { + Player owner = ((Familiar) e).getOwner(); + if (owner != entity && WildernessZone.getInstance().continueAttack(entity, owner, CombatStyle.MAGIC, true)) { + victims.add(e); + } + } else { + victims.add(e); } if (--max < 1) { break; } } - return list; - } + return victims; + } /** * Visualizes the impact. @@ -235,4 +252,4 @@ public abstract class CombatSpell extends MagicSpell { return SPLASH_GRAPHIC; } -} +} \ No newline at end of file From a6eb6f5af65cbb73e806737c564c75726450b453 Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Sun, 31 Aug 2025 10:47:08 +0000 Subject: [PATCH 070/117] Fixed dagon'hai robe top and bottom missing prayer bonus Fixed saradomin bracers requiring 70 defense Added missing animations and sounds to blessed axe --- Server/data/configs/item_configs.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Server/data/configs/item_configs.json b/Server/data/configs/item_configs.json index b0887b94a..fe39aa2a3 100644 --- a/Server/data/configs/item_configs.json +++ b/Server/data/configs/item_configs.json @@ -89019,7 +89019,7 @@ "id": "10383" }, { - "requirements": "{1,70}-{4,40}", + "requirements": "{1,40}-{4,70}", "ge_buy_limit": "2", "examine": "Saradomin blessed dragonhide vambraces.", "durability": null, @@ -90178,9 +90178,13 @@ "weight": "1.1", "attack_speed": "5", "weapon_interface": "2", + "equip_audio": "2229", "render_anim": "2586", + "defence_anim": "397", "equipment_slot": "3", + "attack_anims": "395,395,401,395", "destroy_message": "I can obtain a replacement axe from the Burthorpe Slayer Master.", + "attack_audios": "2498,2498,2497,2498", "name": "Blessed axe", "tradeable": "false", "archery_ticket_price": "0", @@ -130463,7 +130467,7 @@ "tradeable": "true", "archery_ticket_price": "0", "id": "14497", - "bonuses": "0,0,0,20,0,0,0,0,20,0,20,0,0,2,0" + "bonuses": "0,0,0,20,0,0,0,0,20,0,20,0,2,0,0" }, { "shop_price": "120000", @@ -130519,7 +130523,7 @@ "tradeable": "true", "archery_ticket_price": "0", "id": "14501", - "bonuses": "0,0,0,15,0,0,0,0,15,0,15,0,0,2,0" + "bonuses": "0,0,0,15,0,0,0,0,15,0,15,0,2,0,0" }, { "shop_price": "80000", From d09bdacf949b460d4c5dfadb343f09497643edb6 Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Mon, 1 Sep 2025 10:20:25 +0000 Subject: [PATCH 071/117] Fixed bug where All Fired Up beacons could be repaired with insufficient items Fixed bug where All Fired Up beacons would consider boosted/dynamic skill level instead of actual skill level Removed random inauthentic needle break when repairing All Fired Up beacon --- .../allfiredup/AFURepairClimbHandler.kt | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/Server/src/main/content/minigame/allfiredup/AFURepairClimbHandler.kt b/Server/src/main/content/minigame/allfiredup/AFURepairClimbHandler.kt index 03e5c45f1..22e87dfdc 100644 --- a/Server/src/main/content/minigame/allfiredup/AFURepairClimbHandler.kt +++ b/Server/src/main/content/minigame/allfiredup/AFURepairClimbHandler.kt @@ -13,7 +13,6 @@ import org.rs09.consts.Items import core.game.interaction.InteractionListener import core.game.interaction.IntType import core.api.* -import java.util.* /** * Handles repairing and climbing of the 3 beacon shortcuts needed to access them @@ -55,8 +54,8 @@ class AFURepairClimbHandler : InteractionListener { private fun repair(player: Player,rco: RepairClimbObject){ if (rco == RepairClimbObject.TEMPLE){ // You can do this 2 different ways - val hasSmithingLevel = getDynLevel(player, Skills.SMITHING) >= 70 - val hasConstructionLevel = getDynLevel(player, Skills.CONSTRUCTION) >= 59 + val hasSmithingLevel = getStatLevel(player, Skills.SMITHING) >= 70 + val hasConstructionLevel = getStatLevel(player, Skills.CONSTRUCTION) >= 59 if (!hasConstructionLevel && !hasSmithingLevel){ sendDialogue(player, "You need level 70 smithing or 59 construction for this.") @@ -93,8 +92,8 @@ class AFURepairClimbHandler : InteractionListener { } val skill = rco.levelRequirement?.first ?: 0 val level = rco.levelRequirement?.second ?: 0 - if(player.skills.getLevel(skill) < level){ - player.dialogueInterpreter.sendDialogue("You need level $level ${Skills.SKILL_NAME[skill]} for this.") + if(getStatLevel(player, skill) < level){ + sendDialogue(player, "You need level $level ${Skills.SKILL_NAME[skill]} for this.") return } @@ -102,40 +101,37 @@ class AFURepairClimbHandler : InteractionListener { val requiredItems = when(rco){ RepairClimbObject.DEATH_PLATEAU -> { - arrayOf(Item(Items.PLANK_960,2)) + Item(Items.PLANK_960,2) } RepairClimbObject.BURTHORPE -> { - arrayOf(Item(Items.IRON_BAR_2351,2)) + Item(Items.IRON_BAR_2351,2) } RepairClimbObject.GWD -> { requiresNeedle = true - arrayOf(Item(Items.JUTE_FIBRE_5931,3)) + Item(Items.JUTE_FIBRE_5931,3) } else -> return } if(requiresNeedle){ - if(player.inventory.containsItem(Item(Items.NEEDLE_1733)) && player.inventory.containItems(*requiredItems.map { it.id }.toIntArray())) { - player.inventory.remove(*requiredItems) - if (Random().nextBoolean()) player.inventory.remove(Item(Items.NEEDLE_1733)) - } else { - player.dialogueInterpreter.sendDialogue("You need a needle and ${requiredItems.map { "${it.amount} ${it.name.lowercase()}s" }.toString().replace("[","").replace("]","")} for this.") + if (!inInventory(player, Items.NEEDLE_1733) || !removeItem(player, requiredItems)) { + sendDialogue(player, "You need a needle and ${requiredItems.amount} ${requiredItems.name.lowercase()}s for this.") return } } else { - if(player.inventory.containsItem(Item(Items.HAMMER_2347)) && player.inventory.containItems(*requiredItems.map { it.id }.toIntArray())) { + if(inInventory(player, Items.HAMMER_2347) && inInventory(player, requiredItems.id, requiredItems.amount)) { val nails = NailType.get(player,4) if(nails == null && rco == RepairClimbObject.DEATH_PLATEAU){ - player.dialogueInterpreter.sendDialogue("You need 4 nails for this.") + sendDialogue(player, "You need 4 nails for this.") return } else if (rco == RepairClimbObject.DEATH_PLATEAU){ - player.inventory.remove(Item(nails.itemId,4)) + removeItem(player, Item(nails.itemId,4)) } - player.inventory.remove(*requiredItems) + removeItem(player, requiredItems) } else { - player.dialogueInterpreter.sendDialogue("You need a hammer and ${requiredItems.map { "${it.amount} ${it.name.lowercase()}s" }.toString().replace("[","").replace("]","")} for this.") + sendDialogue(player, "You need a hammer and ${requiredItems.amount} ${requiredItems.name.lowercase()}s for this.") return } } From d03df027e01704adb467c5773974df3a90620ee1 Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Mon, 1 Sep 2025 10:26:00 +0000 Subject: [PATCH 072/117] Fixed minor regression in dialogue code causing "line1" to appear briefly before dialogue is shown --- .../main/core/game/dialogue/DialogueInterpreter.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Server/src/main/core/game/dialogue/DialogueInterpreter.java b/Server/src/main/core/game/dialogue/DialogueInterpreter.java index 43b4566d6..a64838e33 100644 --- a/Server/src/main/core/game/dialogue/DialogueInterpreter.java +++ b/Server/src/main/core/game/dialogue/DialogueInterpreter.java @@ -328,7 +328,6 @@ public final class DialogueInterpreter { public Component sendItemMessage(int itemId, String... messages) { // Select interface based on number of messages - 241 (1 line) to 244 (4 lines) int interfaceId = 240 + messages.length; - player.getInterfaceManager().openChatbox(interfaceId); player.getPacketDispatch().sendInterfaceConfig(interfaceId, 1, false); player.getPacketDispatch().sendInterfaceConfig(interfaceId, 2, false); // Hide or empty the title, since double item messages do not have them. (Child 3) @@ -344,6 +343,8 @@ public final class DialogueInterpreter { player.getPacketDispatch().sendRepositionOnInterface(interfaceId, 2, 45, 46); // Hide the second item which seems to be used for double items (child 1) player.getPacketDispatch().sendInterfaceConfig(interfaceId, 1, true); + // Open the chatbox only after everything is set to avoid lag and flashing default strings (Line 1, Title) + player.getInterfaceManager().openChatbox(interfaceId); // These are old interfaces which made no sense to use them. // player.getPacketDispatch().sendString(message, 131, 1); // player.getPacketDispatch().sendItemOnInterface(itemId, 1, 131, 2); @@ -385,7 +386,6 @@ public final class DialogueInterpreter { public Component sendDoubleItemMessage(Item first, Item second, String... messages) { // Select interface based on number of messages - 241 (1 line) to 244 (4 lines) int interfaceId = 240 + messages.length; - player.getInterfaceManager().openChatbox(interfaceId); player.getPacketDispatch().sendInterfaceConfig(interfaceId, 1, false); player.getPacketDispatch().sendInterfaceConfig(interfaceId, 2, false); // Hide or empty the title, since double item messages do not have them. @@ -404,7 +404,8 @@ public final class DialogueInterpreter { player.getPacketDispatch().sendItemOnInterface(second.getId(), second.getAmount(), interfaceId, 2); player.getPacketDispatch().sendAngleOnInterface(interfaceId, 2, (int)(itemDef2.getModelZoom() / 1.5), itemDef2.getModelRotationX(), itemDef2.getModelRotationY()); player.getPacketDispatch().sendRepositionOnInterface(interfaceId, 2, 60, 65); - + // Open the chatbox only after everything is set to avoid lag and flashing default strings (Line 1, Title) + player.getInterfaceManager().openChatbox(interfaceId); return player.getInterfaceManager().getChatbox(); } @@ -518,7 +519,6 @@ public final class DialogueInterpreter { if (expression == -1) { expression = FacialExpression.HALF_GUILTY.getAnimationId(); } - player.getInterfaceManager().openChatbox(interfaceId); player.getPacketDispatch().sendInterfaceConfig(interfaceId, 1, true); player.getPacketDispatch().sendInterfaceConfig(interfaceId, 2, false); player.getPacketDispatch().sendAnimationInterface(expression, interfaceId, 2); @@ -537,6 +537,8 @@ public final class DialogueInterpreter { player.getPacketDispatch().sendString(doSubstitutions(player, messages[i]), interfaceId, (i + 4)); } player.getPacketDispatch().sendInterfaceConfig(player.getInterfaceManager().getChatbox().getId(), 3, false); + // Open the chatbox only after everything is set to avoid lag and flashing default strings (Line 1, Title) + player.getInterfaceManager().openChatbox(interfaceId); return player.getInterfaceManager().getChatbox(); } @@ -556,6 +558,7 @@ public final class DialogueInterpreter { for (int i = 0; i < options.length; i++) { player.getPacketDispatch().sendString(options[i].toString(), interfaceId, i + 2); } + // Open the chatbox only after everything is set to avoid lag and flashing default strings (Line 1, Title) player.getInterfaceManager().openChatbox(interfaceId); return player.getInterfaceManager().getChatbox(); } From 74cbec067c5d168e21060e5c5e7f06413a8bf500 Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Tue, 2 Sep 2025 05:06:37 +0000 Subject: [PATCH 073/117] Made Bork poison immune --- Server/data/configs/npc_configs.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Server/data/configs/npc_configs.json b/Server/data/configs/npc_configs.json index 45e204c3c..15ac290d8 100644 --- a/Server/data/configs/npc_configs.json +++ b/Server/data/configs/npc_configs.json @@ -64576,6 +64576,7 @@ "death_animation": "8756", "name": "Bork", "defence_level": "80", + "poison_immune": "true", "lifepoints": "300", "strength_level": "90", "id": "7133", @@ -64693,6 +64694,7 @@ "name": "Bork", "defence_level": "1", "safespot": null, + "poison_immune": "true", "lifepoints": "300", "strength_level": "1", "id": "7134", From 676397f3fdca27a0898d8380f461f41e1e3d7f7b Mon Sep 17 00:00:00 2001 From: Bishop Date: Tue, 2 Sep 2025 10:43:02 +0000 Subject: [PATCH 074/117] Fixed bug causing herb and uncommon seed drop tables to only roll once when they should have rolled multiple times --- .../main/core/api/utils/WeightBasedTable.kt | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/Server/src/main/core/api/utils/WeightBasedTable.kt b/Server/src/main/core/api/utils/WeightBasedTable.kt index 79466bffe..a890d4d15 100644 --- a/Server/src/main/core/api/utils/WeightBasedTable.kt +++ b/Server/src/main/core/api/utils/WeightBasedTable.kt @@ -42,27 +42,48 @@ open class WeightBasedTable : ArrayList() { return roll(receiver, 1) } - open fun roll(receiver: Entity? = null, times: Int = 1): ArrayList{ - val items = ArrayList((guaranteedItems.size + 1) * times) + open fun roll(receiver: Entity?, times: Int = 1): ArrayList { + val weightedItemsToDrop = ArrayList() - for (i in 0 until times) { - items.addAll(guaranteedItems) + repeat(times) { + // Guaranteed items + for (item in guaranteedItems) { + if (isSpecialSlot(item.id)) { + val rolls = RandomFunction.random(item.minAmt, item.maxAmt) + repeat(rolls) { + weightedItemsToDrop.add(item) + } + } else { + // Add just one instance with amount set by getItem() + weightedItemsToDrop.add(item) + } + } - if (size == 1) { - items.add(get(0)) - } else if (isNotEmpty()) { + // Weighted roll (one per table roll) + if (isNotEmpty()) { var rngWeight = RandomFunction.randomDouble(totalWeight) for (item in this) { rngWeight -= item.weight if (rngWeight <= 0) { - items.add(item) + if (isSpecialSlot(item.id)) { + val rolls = RandomFunction.random(item.minAmt, item.maxAmt) + repeat(rolls) { + weightedItemsToDrop.add(item) + } + } else { + weightedItemsToDrop.add(item) + } break } } } } - return convertWeightedItems(items, receiver) + return convertWeightedItems(weightedItemsToDrop, receiver) + } + + private fun isSpecialSlot(id: Int): Boolean { + return id == SLOT_HDT || id == SLOT_USDT // extend this if a drop table is not dropping multiple times } fun convertWeightedItems(weightedItems: ArrayList, receiver: Entity?): ArrayList { From 6d8ef4247c56419af7a4d5e99327f87269714696 Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Tue, 23 Sep 2025 13:35:52 +0000 Subject: [PATCH 075/117] Poison corrections ::poison command (admin) will now automatically remove poison immunity timers for faster testing Poison will no longer give an inauthentic warning when it is about to expire Newly applied poison timers will only overwrite old poison timers if they are greater in severity Fixed up some logic for monster examine checking poison immunity Fixed numerous poison stats --- Server/data/configs/ammo_configs.json | 224 +++++++++--------- Server/data/configs/npc_configs.json | 114 +++++---- .../skill/magic/lunar/LunarListeners.kt | 8 +- .../gwd/GWDTsutsarothSwingHandler.java | 2 +- .../region/karamja/handlers/TribesmanNPC.java | 48 ---- .../revenants/RevenantCombatHandler.java | 6 +- Server/src/main/core/api/ContentAPI.kt | 8 +- .../node/entity/combat/MeleeSwingHandler.kt | 13 +- .../node/entity/combat/RangeSwingHandler.kt | 7 + .../main/core/game/node/entity/npc/NPC.java | 8 + .../core/game/system/timer/impl/Poison.kt | 8 - .../game/system/timer/impl/PoisonImmunity.kt | 6 +- 12 files changed, 219 insertions(+), 233 deletions(-) delete mode 100644 Server/src/main/content/region/karamja/handlers/TribesmanNPC.java diff --git a/Server/data/configs/ammo_configs.json b/Server/data/configs/ammo_configs.json index 4cc7dbc0a..47da425c3 100644 --- a/Server/data/configs/ammo_configs.json +++ b/Server/data/configs/ammo_configs.json @@ -125,7 +125,7 @@ "start_graphic": "232,96", "darkbow_graphic": "", "projectile": "226,40,36,32,32,15,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "813", @@ -133,7 +133,7 @@ "start_graphic": "233,96", "darkbow_graphic": "", "projectile": "227,40,36,32,32,15,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "814", @@ -141,7 +141,7 @@ "start_graphic": "234,96", "darkbow_graphic": "", "projectile": "228,40,36,32,32,15,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "815", @@ -149,7 +149,7 @@ "start_graphic": "235,96", "darkbow_graphic": "", "projectile": "229,40,36,32,32,15,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "816", @@ -157,7 +157,7 @@ "start_graphic": "236,96", "darkbow_graphic": "", "projectile": "230,40,36,32,32,15,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "817", @@ -165,7 +165,7 @@ "start_graphic": "237,96", "darkbow_graphic": "", "projectile": "231,40,36,32,32,15,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "825", @@ -221,7 +221,7 @@ "start_graphic": "206,96", "darkbow_graphic": "", "projectile": "200,40,36,32,32,15,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "832", @@ -229,7 +229,7 @@ "start_graphic": "207,96", "darkbow_graphic": "", "projectile": "201,40,36,32,32,15,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "833", @@ -237,7 +237,7 @@ "start_graphic": "208,96", "darkbow_graphic": "", "projectile": "202,40,36,32,32,15,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "834", @@ -245,7 +245,7 @@ "start_graphic": "209,96", "darkbow_graphic": "", "projectile": "203,40,36,32,32,15,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "835", @@ -253,7 +253,7 @@ "start_graphic": "210,96", "darkbow_graphic": "", "projectile": "204,40,36,32,32,15,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "836", @@ -261,7 +261,7 @@ "start_graphic": "211,96", "darkbow_graphic": "", "projectile": "205,40,36,32,32,15,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "863", @@ -325,7 +325,7 @@ "start_graphic": "219,96", "darkbow_graphic": "", "projectile": "212,40,36,32,32,15,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "871", @@ -333,7 +333,7 @@ "start_graphic": "220,96", "darkbow_graphic": "", "projectile": "213,40,36,32,32,15,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "872", @@ -341,7 +341,7 @@ "start_graphic": "221,96", "darkbow_graphic": "", "projectile": "214,40,36,32,32,15,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "873", @@ -349,7 +349,7 @@ "start_graphic": "223,96", "darkbow_graphic": "", "projectile": "216,40,36,32,32,15,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "874", @@ -357,7 +357,7 @@ "start_graphic": "222,96", "darkbow_graphic": "", "projectile": "215,40,36,32,32,15,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "875", @@ -365,7 +365,7 @@ "start_graphic": "224,96", "darkbow_graphic": "", "projectile": "217,40,36,32,32,15,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "876", @@ -373,7 +373,7 @@ "start_graphic": "225,96", "darkbow_graphic": "", "projectile": "218,40,36,32,32,15,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "877", @@ -389,7 +389,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "27,38,36,41,32,5,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "879", @@ -421,7 +421,7 @@ "start_graphic": "19,96", "darkbow_graphic": "1104,96", "projectile": "10,40,36,41,46,5,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "884", @@ -437,7 +437,7 @@ "start_graphic": "18,96", "darkbow_graphic": "1105,96", "projectile": "9,40,36,41,46,5,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "886", @@ -453,7 +453,7 @@ "start_graphic": "20,96", "darkbow_graphic": "1106,96", "projectile": "11,40,36,41,46,5,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "888", @@ -469,7 +469,7 @@ "start_graphic": "21,96", "darkbow_graphic": "1107,96", "projectile": "12,40,36,41,46,5,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "890", @@ -485,7 +485,7 @@ "start_graphic": "22,96", "darkbow_graphic": "1108,96", "projectile": "13,40,36,41,46,5,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "892", @@ -501,7 +501,7 @@ "start_graphic": "24,96", "darkbow_graphic": "1109,96", "projectile": "15,40,36,41,46,5,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "2532", @@ -565,7 +565,7 @@ "start_graphic": "273,96", "darkbow_graphic": "", "projectile": "227,40,36,32,32,15,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "4160", @@ -725,7 +725,7 @@ "start_graphic": "19,96", "darkbow_graphic": "1104,96", "projectile": "10,40,36,41,46,5,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "5617", @@ -733,7 +733,7 @@ "start_graphic": "18,96", "darkbow_graphic": "1105,96", "projectile": "9,40,36,41,46,5,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "5618", @@ -741,7 +741,7 @@ "start_graphic": "20,96", "darkbow_graphic": "1106,96", "projectile": "11,40,36,41,46,5,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "5619", @@ -749,7 +749,7 @@ "start_graphic": "21,96", "darkbow_graphic": "1107,96", "projectile": "12,40,36,41,46,5,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "5620", @@ -757,7 +757,7 @@ "start_graphic": "22,96", "darkbow_graphic": "1108,96", "projectile": "13,40,36,41,46,5,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "5621", @@ -773,7 +773,7 @@ "start_graphic": "19,96", "darkbow_graphic": "1104,96", "projectile": "10,40,36,41,46,5,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5623", @@ -781,7 +781,7 @@ "start_graphic": "18,96", "darkbow_graphic": "1105,96", "projectile": "9,40,36,41,46,5,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5624", @@ -789,7 +789,7 @@ "start_graphic": "20,96", "darkbow_graphic": "1106,96", "projectile": "11,40,36,41,46,5,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5625", @@ -797,7 +797,7 @@ "start_graphic": "21,96", "darkbow_graphic": "1107,96", "projectile": "12,40,36,41,46,5,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5626", @@ -805,7 +805,7 @@ "start_graphic": "22,96", "darkbow_graphic": "1108,96", "projectile": "13,40,36,41,46,5,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5627", @@ -813,7 +813,7 @@ "start_graphic": "24,96", "darkbow_graphic": "1109,96", "projectile": "15,40,36,41,46,5,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5628", @@ -821,7 +821,7 @@ "start_graphic": "232,96", "darkbow_graphic": "", "projectile": "226,40,36,32,32,15,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "5629", @@ -829,7 +829,7 @@ "start_graphic": "233,96", "darkbow_graphic": "", "projectile": "227,40,36,32,32,15,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "5630", @@ -837,7 +837,7 @@ "start_graphic": "235,96", "darkbow_graphic": "", "projectile": "229,40,36,32,32,15,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "5631", @@ -845,7 +845,7 @@ "start_graphic": "273,96", "darkbow_graphic": "", "projectile": "227,40,36,32,32,15,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "5633", @@ -853,7 +853,7 @@ "start_graphic": "236,96", "darkbow_graphic": "", "projectile": "230,40,36,32,32,15,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "5634", @@ -861,7 +861,7 @@ "start_graphic": "237,96", "darkbow_graphic": "", "projectile": "231,40,36,32,32,15,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "5635", @@ -869,7 +869,7 @@ "start_graphic": "232,96", "darkbow_graphic": "", "projectile": "226,40,36,32,32,15,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5636", @@ -877,7 +877,7 @@ "start_graphic": "233,96", "darkbow_graphic": "", "projectile": "227,40,36,32,32,15,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5637", @@ -885,7 +885,7 @@ "start_graphic": "234,96", "darkbow_graphic": "", "projectile": "228,40,36,32,32,15,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5638", @@ -893,7 +893,7 @@ "start_graphic": "273,96", "darkbow_graphic": "", "projectile": "227,40,36,32,32,15,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5639", @@ -901,7 +901,7 @@ "start_graphic": "235,96", "darkbow_graphic": "", "projectile": "229,40,36,32,32,15,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5640", @@ -909,7 +909,7 @@ "start_graphic": "236,96", "darkbow_graphic": "", "projectile": "230,40,36,32,32,15,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5641", @@ -917,7 +917,7 @@ "start_graphic": "237,96", "darkbow_graphic": "", "projectile": "231,40,36,32,32,15,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5642", @@ -925,7 +925,7 @@ "start_graphic": "206,96", "darkbow_graphic": "", "projectile": "200,40,36,32,32,15,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "5643", @@ -933,7 +933,7 @@ "start_graphic": "207,96", "darkbow_graphic": "", "projectile": "201,40,36,32,32,15,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "5644", @@ -941,7 +941,7 @@ "start_graphic": "208,96", "darkbow_graphic": "", "projectile": "202,40,36,32,32,15,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "5645", @@ -949,7 +949,7 @@ "start_graphic": "209,96", "darkbow_graphic": "", "projectile": "203,40,36,32,32,15,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "5646", @@ -957,7 +957,7 @@ "start_graphic": "210,96", "darkbow_graphic": "", "projectile": "204,40,36,32,32,15,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "5647", @@ -965,7 +965,7 @@ "start_graphic": "211,96", "darkbow_graphic": "", "projectile": "205,40,36,32,32,15,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "5648", @@ -973,7 +973,7 @@ "start_graphic": "206,96", "darkbow_graphic": "", "projectile": "200,40,36,32,32,15,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5649", @@ -981,7 +981,7 @@ "start_graphic": "207,96", "darkbow_graphic": "", "projectile": "201,40,36,32,32,15,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5650", @@ -989,7 +989,7 @@ "start_graphic": "208,96", "darkbow_graphic": "", "projectile": "202,40,36,32,32,15,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5651", @@ -997,7 +997,7 @@ "start_graphic": "209,96", "darkbow_graphic": "", "projectile": "203,40,36,32,32,15,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5652", @@ -1005,7 +1005,7 @@ "start_graphic": "210,96", "darkbow_graphic": "", "projectile": "204,40,36,32,32,15,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5653", @@ -1013,7 +1013,7 @@ "start_graphic": "211,96", "darkbow_graphic": "", "projectile": "205,40,36,32,32,15,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5654", @@ -1021,7 +1021,7 @@ "start_graphic": "219,96", "darkbow_graphic": "", "projectile": "212,40,36,32,32,15,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "5655", @@ -1029,7 +1029,7 @@ "start_graphic": "220,96", "darkbow_graphic": "", "projectile": "213,40,36,32,32,15,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "5656", @@ -1037,7 +1037,7 @@ "start_graphic": "221,96", "darkbow_graphic": "", "projectile": "214,40,36,32,32,15,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "5657", @@ -1045,7 +1045,7 @@ "start_graphic": "223,96", "darkbow_graphic": "", "projectile": "216,40,36,32,32,15,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "5658", @@ -1053,7 +1053,7 @@ "start_graphic": "222,96", "darkbow_graphic": "", "projectile": "215,40,36,32,32,15,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "5659", @@ -1061,7 +1061,7 @@ "start_graphic": "224,96", "darkbow_graphic": "", "projectile": "217,40,36,32,32,15,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "5660", @@ -1069,7 +1069,7 @@ "start_graphic": "225,96", "darkbow_graphic": "", "projectile": "218,40,36,32,32,15,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "5661", @@ -1077,7 +1077,7 @@ "start_graphic": "219,96", "darkbow_graphic": "", "projectile": "212,40,36,32,32,15,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5662", @@ -1085,7 +1085,7 @@ "start_graphic": "220,96", "darkbow_graphic": "", "projectile": "213,40,36,32,32,15,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5663", @@ -1093,7 +1093,7 @@ "start_graphic": "221,96", "darkbow_graphic": "", "projectile": "214,40,36,32,32,15,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5664", @@ -1101,7 +1101,7 @@ "start_graphic": "223,96", "darkbow_graphic": "", "projectile": "216,40,36,32,32,15,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5665", @@ -1109,7 +1109,7 @@ "start_graphic": "222,96", "darkbow_graphic": "", "projectile": "215,40,36,32,32,15,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5666", @@ -1117,7 +1117,7 @@ "start_graphic": "224,96", "darkbow_graphic": "", "projectile": "217,40,36,32,32,15,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "5667", @@ -1125,7 +1125,7 @@ "start_graphic": "225,96", "darkbow_graphic": "", "projectile": "218,40,36,32,32,15,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "6061", @@ -1133,7 +1133,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "27,38,36,41,32,5,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "6062", @@ -1141,7 +1141,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "27,38,36,41,32,5,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "6522", @@ -1293,7 +1293,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "27,38,36,41,32,5,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "9287", @@ -1301,7 +1301,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "27,38,36,41,32,5,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "9288", @@ -1309,7 +1309,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "27,38,36,41,32,5,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "9289", @@ -1317,7 +1317,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "27,38,36,41,32,5,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "9290", @@ -1325,7 +1325,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "27,38,36,41,32,5,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "9291", @@ -1333,7 +1333,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "27,38,36,41,32,5,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "9293", @@ -1341,7 +1341,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "27,38,36,41,32,5,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "9294", @@ -1349,7 +1349,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "27,38,36,41,32,5,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "9295", @@ -1357,7 +1357,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "27,38,36,41,32,5,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "9296", @@ -1365,7 +1365,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "27,38,36,41,32,5,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "9297", @@ -1373,7 +1373,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "27,38,36,41,32,5,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "9298", @@ -1381,7 +1381,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "27,38,36,41,32,5,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "9300", @@ -1389,7 +1389,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "27,38,36,41,32,5,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "9301", @@ -1397,7 +1397,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "27,38,36,41,32,5,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "9302", @@ -1405,7 +1405,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "27,38,36,41,32,5,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "9303", @@ -1413,7 +1413,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "27,38,36,41,32,5,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "9304", @@ -1421,7 +1421,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "27,38,36,41,32,5,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "9305", @@ -1429,7 +1429,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "27,38,36,41,32,5,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "9335", @@ -1557,7 +1557,7 @@ "start_graphic": "1116,96", "darkbow_graphic": "1114,96", "projectile": "1120,40,36,41,46,5,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "11228", @@ -1565,7 +1565,7 @@ "start_graphic": "1116,96", "darkbow_graphic": "1114,96", "projectile": "1120,40,36,41,46,5,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "11229", @@ -1573,7 +1573,7 @@ "start_graphic": "1116,96", "darkbow_graphic": "1114,96", "projectile": "1120,40,36,41,46,5,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "11230", @@ -1589,7 +1589,7 @@ "start_graphic": "1123,96", "darkbow_graphic": "", "projectile": "1122,40,36,32,32,15,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "11233", @@ -1597,7 +1597,7 @@ "start_graphic": "1123,96", "darkbow_graphic": "", "projectile": "1122,40,36,32,32,15,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "11234", @@ -1605,7 +1605,7 @@ "start_graphic": "1123,96", "darkbow_graphic": "", "projectile": "1122,40,36,32,32,15,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "13083", @@ -1621,7 +1621,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "27,38,36,41,32,5,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "13085", @@ -1629,7 +1629,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "27,38,36,41,32,5,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "13086", @@ -1637,7 +1637,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "27,38,36,41,32,5,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "13280", @@ -1661,7 +1661,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "1837,40,36,32,32,15,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "13881", @@ -1669,7 +1669,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "1837,40,36,32,32,15,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "13882", @@ -1677,7 +1677,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "1837,40,36,32,32,15,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "13883", @@ -1701,7 +1701,7 @@ "start_graphic": "-1,0", "darkbow_graphic": "", "projectile": "1837,40,36,32,32,15,0", - "poison_damage": "28" + "poison_damage": "10" }, { "itemId": "13955", @@ -1709,7 +1709,7 @@ "start_graphic": "1837,96", "darkbow_graphic": "", "projectile": "1840,40,36,32,32,15,0", - "poison_damage": "38" + "poison_damage": "15" }, { "itemId": "13956", @@ -1717,7 +1717,7 @@ "start_graphic": "1837,96", "darkbow_graphic": "", "projectile": "1840,40,36,32,32,15,0", - "poison_damage": "48" + "poison_damage": "20" }, { "itemId": "13957", diff --git a/Server/data/configs/npc_configs.json b/Server/data/configs/npc_configs.json index 15ac290d8..87b666074 100644 --- a/Server/data/configs/npc_configs.json +++ b/Server/data/configs/npc_configs.json @@ -2006,7 +2006,7 @@ "defence_animation": "6255", "weakness": "0", "slayer_exp": "23", - "poison_amount": "3", + "poison_amount": "15", "magic_animation": "0", "death_animation": "6256", "name": "Poison Scorpion", @@ -2517,7 +2517,7 @@ "respawn_delay": "30", "defence_animation": "5328", "weakness": "2", - "poison_amount": "6", + "poison_amount": "30", "magic_animation": "0", "death_animation": "5329", "name": "Poison spider", @@ -3330,7 +3330,7 @@ "respawn_delay": "50", "defence_animation": "0", "weakness": "8", - "poison_amount": "11", + "poison_amount": "55", "magic_animation": "0", "death_animation": "836", "name": "Tribesman", @@ -10513,7 +10513,7 @@ "respawn_delay": "30", "defence_animation": "5328", "weakness": "2", - "poison_amount": "6", + "poison_amount": "30", "magic_animation": "0", "death_animation": "5329", "name": "Poison spider", @@ -12353,7 +12353,7 @@ "defence_animation": "6227", "weakness": "7", "slayer_exp": "90", - "poison_amount": "4", + "poison_amount": "20", "magic_animation": "0", "death_animation": "6228", "name": "Kalphite Soldier", @@ -12377,7 +12377,7 @@ "respawn_delay": "65", "defence_animation": "6232", "weakness": "7", - "poison_amount": "6", + "poison_amount": "30", "magic_animation": "6223", "death_animation": "6230", "name": "Kalphite Guardian", @@ -12424,7 +12424,7 @@ "respawn_delay": "65", "defence_animation": "6232", "weakness": "7", - "poison_amount": "6", + "poison_amount": "30", "magic_animation": "6223", "death_animation": "6230", "name": "Kalphite Guardian", @@ -12444,7 +12444,7 @@ "melee_animation": "6241", "range_animation": "6241", "attack_speed": "4", - "poisonous": "true", + "poisonous": "", "magic_level": "150", "respawn_delay": "0", "defence_animation": "6232", @@ -13455,6 +13455,7 @@ "respawn_delay": "60", "defence_animation": "0", "weakness": "3", + "poison_amount": "30", "magic_animation": "811", "death_animation": "836", "name": "Saradomin wizard", @@ -16454,6 +16455,7 @@ "defence_animation": "267", "weakness": "1", "slayer_exp": "22", + "poison_amount": "40", "magic_animation": "266", "death_animation": "265", "name": "Cave crawler", @@ -16476,6 +16478,7 @@ "respawn_delay": "25", "defence_animation": "267", "slayer_exp": "22", + "poison_amount": "40", "magic_animation": "266", "death_animation": "265", "name": "Cave crawler", @@ -16498,6 +16501,7 @@ "respawn_delay": "25", "defence_animation": "267", "slayer_exp": "22", + "poison_amount": "40", "magic_animation": "266", "death_animation": "265", "name": "Cave crawler", @@ -16520,6 +16524,7 @@ "respawn_delay": "25", "defence_animation": "267", "slayer_exp": "22", + "poison_amount": "40", "magic_animation": "266", "death_animation": "265", "name": "Cave crawler", @@ -19313,7 +19318,7 @@ "defence_animation": "0", "weakness": "1", "slayer_exp": "25", - "poison_amount": "3", + "poison_amount": "15", "magic_animation": "0", "death_animation": "1792", "name": "Cave slime", @@ -20627,6 +20632,7 @@ "poisonous": "true", "defence_animation": "1946", "weakness": "7", + "poison_amount": "15", "magic_animation": "0", "death_animation": "5464", "name": "Scarabs", @@ -20964,6 +20970,7 @@ "respawn_delay": "60", "defence_animation": "0", "weakness": "6", + "poison_amount": "15", "magic_animation": "0", "death_animation": "1946", "name": "Scarab swarm", @@ -25076,7 +25083,7 @@ "respawn_delay": "60", "defence_animation": "0", "weakness": "1", - "poison_amount": "11", + "poison_amount": "55", "magic_animation": "0", "death_animation": "278", "name": "Bush snake", @@ -25095,7 +25102,7 @@ "poisonous": "true", "respawn_delay": "60", "defence_animation": "276", - "poison_amount": "11", + "poison_amount": "55", "death_animation": "278", "name": "Bush snake", "defence_level": "1", @@ -25211,7 +25218,7 @@ "respawn_delay": "50", "defence_animation": "0", "weakness": "9", - "poison_amount": "11", + "poison_amount": "55", "magic_animation": "0", "death_animation": "836", "name": "Tribesman", @@ -29571,6 +29578,7 @@ "respawn_delay": "60", "defence_animation": "2937", "weakness": "9", + "poison_amount": "40", "magic_animation": "2936", "death_animation": "2938", "name": "Jogre Champion", @@ -29609,7 +29617,7 @@ "examine": "Champion of the jogres.", "melee_animation": "5485", "range_animation": "5493", - "poisonous": "true", + "poisonous": "", "respawn_delay": "60", "defence_animation": "5493", "weakness": "9", @@ -32631,6 +32639,7 @@ "respawn_delay": "60", "defence_animation": "0", "weakness": "10", + "poison_amount": "35", "magic_animation": "0", "death_animation": "7249", "name": "Wild jade vine", @@ -32652,6 +32661,7 @@ "respawn_delay": "60", "defence_animation": "0", "weakness": "10", + "poison_amount": "35", "magic_animation": "0", "death_animation": "7249", "name": "Wild jade vine", @@ -32673,6 +32683,7 @@ "respawn_delay": "60", "defence_animation": "0", "weakness": "10", + "poison_amount": "35", "magic_animation": "0", "death_animation": "7249", "name": "Wild jade vine", @@ -32694,6 +32705,7 @@ "respawn_delay": "60", "defence_animation": "0", "weakness": "10", + "poison_amount": "35", "magic_animation": "0", "death_animation": "7249", "name": "Wild jade vine", @@ -33773,7 +33785,7 @@ "respawn_delay": "60", "defence_animation": "6227", "weakness": "6", - "poison_amount": "4", + "poison_amount": "20", "magic_animation": "0", "death_animation": "6228", "name": "Kalphite Soldier", @@ -53840,11 +53852,11 @@ "examine": "A servant of the god Zamorak. ", "melee_animation": "6945", "attack_speed": "6", - "poisonous": "true", + "poisonous": "", "respawn_delay": "150", "weakness": "9", "slayer_exp": "350", - "poison_amount": "16", + "poison_amount": "", "magic_animation": "6945", "death_animation": "6946", "lifepoints": "255", @@ -54943,7 +54955,7 @@ "combat_style": "2", "melee_animation": "811", "range_animation": "811", - "poisonous": "true", + "poisonous": "", "magic_level": "60", "respawn_delay": "25", "end_gfx": "76", @@ -58366,11 +58378,11 @@ "examine": "A ghost of a knight slain during the god wars.", "melee_animation": "7441", "range_animation": "7522", - "poisonous": "true", + "poisonous": "", "magic_level": "80", "defence_animation": "7443", "slayer_exp": "143", - "poison_amount": "8", + "poison_amount": "", "magic_animation": "7508", "death_animation": "7442", "name": "Revenant knight", @@ -58522,11 +58534,11 @@ "examine": "A ghost of a knight slain during the god wars.", "melee_animation": "7441", "range_animation": "7522", - "poisonous": "true", + "poisonous": "", "magic_level": "80", "defence_animation": "7443", "slayer_exp": "143", - "poison_amount": "8", + "poison_amount": "", "magic_animation": "7508", "death_animation": "7442", "name": "Revenant knight", @@ -58681,11 +58693,11 @@ "examine": "A ghost of a knight slain during the god wars.", "melee_animation": "7441", "range_animation": "7522", - "poisonous": "true", + "poisonous": "", "magic_level": "80", "defence_animation": "7443", "slayer_exp": "143", - "poison_amount": "8", + "poison_amount": "", "magic_animation": "7508", "death_animation": "7442", "name": "Revenant knight", @@ -58742,11 +58754,11 @@ "examine": "A ghost of a knight slain during the god wars.", "melee_animation": "7441", "range_animation": "7522", - "poisonous": "true", + "poisonous": "", "magic_level": "80", "defence_animation": "7443", "slayer_exp": "143", - "poison_amount": "8", + "poison_amount": "", "magic_animation": "7508", "death_animation": "7442", "name": "Revenant knight", @@ -58822,11 +58834,11 @@ "examine": "A ghost of a knight slain during the god wars.", "melee_animation": "7441", "range_animation": "7522", - "poisonous": "true", + "poisonous": "", "magic_level": "80", "defence_animation": "7443", "slayer_exp": "143", - "poison_amount": "8", + "poison_amount": "", "magic_animation": "7508", "death_animation": "7442", "name": "Revenant knight", @@ -59145,11 +59157,11 @@ "examine": "A ghost of a knight slain during the god wars.", "melee_animation": "7441", "range_animation": "7522", - "poisonous": "true", + "poisonous": "", "magic_level": "80", "defence_animation": "7443", "slayer_exp": "143", - "poison_amount": "8", + "poison_amount": "", "magic_animation": "7508", "death_animation": "7442", "name": "Revenant knight", @@ -59225,11 +59237,11 @@ "examine": "A ghost of a knight slain during the god wars.", "melee_animation": "7441", "range_animation": "7522", - "poisonous": "true", + "poisonous": "", "magic_level": "80", "defence_animation": "7443", "slayer_exp": "143", - "poison_amount": "8", + "poison_amount": "", "magic_animation": "7508", "death_animation": "7442", "name": "Revenant knight", @@ -59427,6 +59439,7 @@ "examine": "A ghost of an ork slain during the god wars.", "melee_animation": "7411", "range_animation": "7518", + "poisonous": "", "magic_level": "70", "defence_animation": "7413", "magic_animation": "7505", @@ -59446,11 +59459,11 @@ "examine": "A ghost of a knight slain during the god wars.", "melee_animation": "7441", "range_animation": "7522", - "poisonous": "true", + "poisonous": "", "magic_level": "80", "defence_animation": "7443", "slayer_exp": "143", - "poison_amount": "8", + "poison_amount": "", "magic_animation": "7508", "death_animation": "7442", "name": "Revenant knight", @@ -59605,11 +59618,11 @@ "examine": "A ghost of a knight slain during the god wars.", "melee_animation": "7441", "range_animation": "7522", - "poisonous": "true", + "poisonous": "", "magic_level": "80", "defence_animation": "7443", "slayer_exp": "143", - "poison_amount": "8", + "poison_amount": "", "magic_animation": "7508", "death_animation": "7442", "name": "Revenant knight", @@ -59620,7 +59633,8 @@ "id": "6673", "clue_level": "2", "range_level": "80", - "attack_level": "80" + "attack_level": "80", + "prj_height": "" }, { "spawn_animation": "7426", @@ -59666,11 +59680,11 @@ "examine": "A ghost of a knight slain during the god wars.", "melee_animation": "7441", "range_animation": "7522", - "poisonous": "true", + "poisonous": "", "magic_level": "80", "defence_animation": "7443", "slayer_exp": "143", - "poison_amount": "8", + "poison_amount": "", "magic_animation": "7508", "death_animation": "7442", "name": "Revenant knight", @@ -59989,11 +60003,11 @@ "examine": "A ghost of a knight slain during the god wars.", "melee_animation": "7441", "range_animation": "7522", - "poisonous": "true", + "poisonous": "", "magic_level": "80", "defence_animation": "7443", "slayer_exp": "143", - "poison_amount": "8", + "poison_amount": "", "magic_animation": "7508", "death_animation": "7442", "name": "Revenant knight", @@ -60426,11 +60440,11 @@ "examine": "A ghost of a knight slain during the god wars.", "melee_animation": "7441", "range_animation": "7522", - "poisonous": "true", + "poisonous": "", "magic_level": "80", "defence_animation": "7443", "slayer_exp": "143", - "poison_amount": "8", + "poison_amount": "", "magic_animation": "7508", "death_animation": "7442", "name": "Revenant knight", @@ -60667,11 +60681,11 @@ "examine": "A ghost of a knight slain during the god wars.", "melee_animation": "7441", "range_animation": "7522", - "poisonous": "true", + "poisonous": "", "magic_level": "80", "defence_animation": "7443", "slayer_exp": "143", - "poison_amount": "8", + "poison_amount": "", "magic_animation": "7508", "death_animation": "7442", "name": "Revenant knight", @@ -60746,11 +60760,11 @@ "examine": "A ghost of a knight slain during the god wars.", "melee_animation": "7441", "range_animation": "7522", - "poisonous": "true", + "poisonous": "", "magic_level": "80", "defence_animation": "7443", "slayer_exp": "143", - "poison_amount": "8", + "poison_amount": "", "magic_animation": "7508", "death_animation": "7442", "name": "Revenant knight", @@ -63810,9 +63824,9 @@ "melee_animation": "8591", "range_animation": "8594", "combat_audio": "408,410,409", - "poisonous": "true", + "poisonous": "", "defence_animation": "8592", - "poison_amount": "8", + "poison_amount": "", "magic_animation": "8594", "death_animation": "8593", "name": "Revenant dragon", @@ -63831,9 +63845,9 @@ "melee_animation": "8591", "range_animation": "8594", "combat_audio": "408,410,409", - "poisonous": "true", + "poisonous": "", "defence_animation": "8592", - "poison_amount": "8", + "poison_amount": "", "magic_animation": "8594", "death_animation": "8593", "name": "Revenant dragon", diff --git a/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt b/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt index bb81bff18..632173652 100644 --- a/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt +++ b/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt @@ -21,6 +21,7 @@ import core.game.node.scenery.Scenery import core.game.system.command.Privilege import core.game.system.config.NPCConfigParser import core.game.system.task.Pulse +import core.game.system.timer.impl.PoisonImmunity import core.game.world.map.Location import core.game.world.map.RegionManager import core.game.world.repository.Repository @@ -379,7 +380,7 @@ class LunarListeners : SpellListener("lunar"), Commands { setInterfaceText(player, "Hitpoints : ${npc.definition.handlers[NPCConfigParser.LIFEPOINTS] ?: 0}", Components.DREAM_MONSTER_STAT_522, 2) setInterfaceText(player, "Max hit : ${npc.getSwingHandler(false).calculateHit(npc, player, 1.0)}", Components.DREAM_MONSTER_STAT_522, 3) - val poisonStatus = if(npc.definition.handlers.getOrDefault(NPCConfigParser.POISON_IMMUNE,false) == true){ + val poisonStatus = if(npc.isPoisonImmune){ "This creature is immune to poison." } else "This creature is not immune to poison." @@ -818,7 +819,10 @@ class LunarListeners : SpellListener("lunar"), Commands { return@define } if(dmg != null) { - p.let { applyPoison(it, it, dmg) } + p.let { + removeTimer(it) + applyPoison(it, it, dmg) + } } else { sendMessage(player, "Damage must be an integer. Format:") sendMessage(player, "::poison username damage") diff --git a/Server/src/main/content/region/asgarnia/trollheim/handlers/gwd/GWDTsutsarothSwingHandler.java b/Server/src/main/content/region/asgarnia/trollheim/handlers/gwd/GWDTsutsarothSwingHandler.java index d90bd3f35..8a4de99a8 100644 --- a/Server/src/main/content/region/asgarnia/trollheim/handlers/gwd/GWDTsutsarothSwingHandler.java +++ b/Server/src/main/content/region/asgarnia/trollheim/handlers/gwd/GWDTsutsarothSwingHandler.java @@ -73,7 +73,7 @@ public final class GWDTsutsarothSwingHandler extends CombatSwingHandler { hit = RandomFunction.random(max); state.setMaximumHit(max); if (style == CombatStyle.MELEE) { - applyPoison(victim, entity, 16); + applyPoison(victim, entity, 80); } if (special) { ((Player) victim).getSkills().decrementPrayerPoints((double) hit / 2); diff --git a/Server/src/main/content/region/karamja/handlers/TribesmanNPC.java b/Server/src/main/content/region/karamja/handlers/TribesmanNPC.java deleted file mode 100644 index 184357c1d..000000000 --- a/Server/src/main/content/region/karamja/handlers/TribesmanNPC.java +++ /dev/null @@ -1,48 +0,0 @@ -package content.region.karamja.handlers; - -import core.game.node.entity.npc.AbstractNPC; -import core.game.world.map.Location; -import core.plugin.Initializable; -import core.game.system.config.NPCConfigParser; - -/** - * Represents the tribesamn npc. - * @author 'Vexia - * @version 1.0 - */ -@Initializable -public final class TribesmanNPC extends AbstractNPC { - - /** - * Represents the npc ids. - */ - private static final int[] IDS = new int[] { 191, 2496, 2497 }; - - /** - * Constructs a new {@code TribesmanNPC} {@code Object}. - * @param id the id. - * @param location the location. - */ - public TribesmanNPC(int id, Location location) { - super(id, location, true); - getDefinition().getHandlers().put(NPCConfigParser.POISONOUS, true); - } - - /** - * Constructs a new {@code TribesmanNPC} {@code Object}. - */ - public TribesmanNPC() { - super(0, null); - } - - @Override - public AbstractNPC construct(int id, Location location, Object... objects) { - return new TribesmanNPC(id, location); - } - - @Override - public int[] getIds() { - return IDS; - } - -} diff --git a/Server/src/main/content/region/wilderness/handlers/revenants/RevenantCombatHandler.java b/Server/src/main/content/region/wilderness/handlers/revenants/RevenantCombatHandler.java index 01040043e..0c509175f 100644 --- a/Server/src/main/content/region/wilderness/handlers/revenants/RevenantCombatHandler.java +++ b/Server/src/main/content/region/wilderness/handlers/revenants/RevenantCombatHandler.java @@ -74,8 +74,10 @@ public class RevenantCombatHandler extends MultiSwingHandler { } } } - if (!isPoisoned(victim) && (WildernessZone.getWilderness(entity) >= 50 || entity.getId() == 6998)) { - applyPoison(victim, entity, 6); + if (entity.getId() == 6998) { + applyPoison(victim, entity, 40); + } else if (WildernessZone.getWilderness(entity) >= 50) { + applyPoison(victim, entity, 30); } super.impact(entity, victim, state); } diff --git a/Server/src/main/core/api/ContentAPI.kt b/Server/src/main/core/api/ContentAPI.kt index 0767a9c91..615e51996 100644 --- a/Server/src/main/core/api/ContentAPI.kt +++ b/Server/src/main/core/api/ContentAPI.kt @@ -3044,8 +3044,12 @@ fun applyPoison (entity: Entity, source: Entity, severity: Int) { val existingTimer = getTimer(entity) if (existingTimer != null) { - existingTimer.severity = severity - existingTimer.damageSource = source + if (existingTimer.severity > severity) { + return + } else { + existingTimer.severity = severity + existingTimer.damageSource = source + } } else { registerTimer(entity, spawnTimer(source, severity)) } diff --git a/Server/src/main/core/game/node/entity/combat/MeleeSwingHandler.kt b/Server/src/main/core/game/node/entity/combat/MeleeSwingHandler.kt index b00afddb0..a20d7f5d8 100644 --- a/Server/src/main/core/game/node/entity/combat/MeleeSwingHandler.kt +++ b/Server/src/main/core/game/node/entity/combat/MeleeSwingHandler.kt @@ -115,16 +115,23 @@ open class MeleeSwingHandler (vararg flags: SwingHandlerFlag) val name = entity.equipment.getNew(3).name var damage = -1 if (name.contains("(p++") || name.contains("(s)") || name.contains("(kp)")) { - damage = 68 + damage = 30 } else if (name.contains("(p+)")) { - damage = 58 + damage = 25 } else if (name.contains("(p)")) { - damage = 48 + damage = 20 } if (damage > -1 && RandomFunction.random(10) < 4) { applyPoison (victim, entity, damage) } } + } else if (entity is NPC) { + val poisonous = entity.isPoisonous + val damage = entity.poisonSeverity() + + if (poisonous && damage > -1 && RandomFunction.random(10) < 4) { + applyPoison (victim, entity, damage) + } } super.adjustBattleState(entity, victim, state) } diff --git a/Server/src/main/core/game/node/entity/combat/RangeSwingHandler.kt b/Server/src/main/core/game/node/entity/combat/RangeSwingHandler.kt index a197d8b9c..335a32abc 100644 --- a/Server/src/main/core/game/node/entity/combat/RangeSwingHandler.kt +++ b/Server/src/main/core/game/node/entity/combat/RangeSwingHandler.kt @@ -134,6 +134,13 @@ open class RangeSwingHandler (vararg flags: SwingHandlerFlag) : CombatSwingHandl if (state.estimatedHit > 0 && damage > 8 && RandomFunction.random(10) < 4) { applyPoison(victim, entity, damage) } + } else if (entity is NPC) { + val poisonous = entity.isPoisonous + val damage = entity.poisonSeverity() + + if (poisonous && damage > -1 && RandomFunction.random(10) < 4) { + applyPoison (victim, entity, damage) + } } super.adjustBattleState(entity, victim, state) } diff --git a/Server/src/main/core/game/node/entity/npc/NPC.java b/Server/src/main/core/game/node/entity/npc/NPC.java index b1d4aba99..8c2600642 100644 --- a/Server/src/main/core/game/node/entity/npc/NPC.java +++ b/Server/src/main/core/game/node/entity/npc/NPC.java @@ -563,6 +563,14 @@ public class NPC extends Entity { return definition.getConfiguration(NPCConfigParser.POISON_IMMUNE, false); } + public boolean isPoisonous() { + return definition.getConfiguration(NPCConfigParser.POISONOUS, false); + } + + public int poisonSeverity() { + return definition.getConfiguration(NPCConfigParser.POISON_AMOUNT, 0); + } + @Override public void finalizeDeath(Entity killer) { super.finalizeDeath(killer); diff --git a/Server/src/main/core/game/system/timer/impl/Poison.kt b/Server/src/main/core/game/system/timer/impl/Poison.kt index 6f87d40a9..7dbf9f2b0 100644 --- a/Server/src/main/core/game/system/timer/impl/Poison.kt +++ b/Server/src/main/core/game/system/timer/impl/Poison.kt @@ -20,14 +20,6 @@ class Poison : PersistTimer (30, "poison", flags = arrayOf(TimerFlag.ClearOnDeat lateinit var damageSource: Entity var severity = 0 - set (value) { - if (value != field - 1 && value % 10 == 8) {//This was Arios's incorrect attempt at replicating severity, convert it to correct values. - (damageSource as? Player)?.debug ("[PoisonTimer] Warning: Converting suspect Arios severity into true severity. If numbers look wrong, this could be why.") - field = (value / 10) * 5 - (damageSource as? Player)?.debug ("[PoisonTimer] Warning: New Severity: $field.") - } else field = value - } - override fun save (root: JSONObject, entity: Entity) { root["source-uid"] = (damageSource as? Player)?.details?.uid ?: -1 root["severity"] = severity.toString() diff --git a/Server/src/main/core/game/system/timer/impl/PoisonImmunity.kt b/Server/src/main/core/game/system/timer/impl/PoisonImmunity.kt index 1695fce7b..c90608e58 100644 --- a/Server/src/main/core/game/system/timer/impl/PoisonImmunity.kt +++ b/Server/src/main/core/game/system/timer/impl/PoisonImmunity.kt @@ -31,11 +31,7 @@ class PoisonImmunity : PersistTimer (1, "poison:immunity", isSoft = true, flags override fun run (entity: Entity) : Boolean { ticksRemaining-- - if (entity is Player && ticksRemaining == secondsToTicks(30)) { - sendMessage(entity, colorize("%RYou have 30 seconds remaining on your poison immunity.")) - playAudio(entity, Sounds.CLOCK_TICK_1_3120, 0, 3) - } - else if (entity is Player && ticksRemaining == 0) { + if (entity is Player && ticksRemaining == 0) { sendMessage(entity, colorize("%RYour poison immunity has expired.")) playAudio(entity, Sounds.DRAGON_POTION_FINISHED_2607) } From efe936500b815294de3766cf60246d4651f157a1 Mon Sep 17 00:00:00 2001 From: oftheshire Date: Sat, 27 Sep 2025 01:08:34 +0000 Subject: [PATCH 076/117] Fixed potato cactus spawns Fixed 3 spawns in Kalphite Lair first floor Fixed 5 spawns in Kalphite Lair bottom floor --- Server/data/configs/ground_spawns.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Server/data/configs/ground_spawns.json b/Server/data/configs/ground_spawns.json index ce207e4db..632bf2fc7 100644 --- a/Server/data/configs/ground_spawns.json +++ b/Server/data/configs/ground_spawns.json @@ -233,7 +233,7 @@ }, { "item_id": "946", - "loc_data": "{1,2903,3148,0,80}-{1,3205,3212,0,80}-{1,3224,3202,0,80}-{1,3218,3416,1,90}-{1,2820,3450,0,90}-{1,3106,3956,0,60}-{1,2566,9526,0,30}-{1,3215,9625,0,80}-{1,3218,9887,0,33}-{1,2700,3407,0,100}-" + "loc_data": "{1,2903,3148,0,80}-{1,3205,3212,0,80}-{1,3224,3202,0,80}-{1,3218,3416,1,90}-{1,2820,3450,0,90}-{1,2700,3407,0,100}-{1,3106,3956,0,60}-{1,2566,9526,0,30}-{1,3215,9625,0,80}-{1,3218,9887,0,33}-" }, { "item_id": "952", @@ -605,7 +605,7 @@ }, { "item_id": "3138", - "loc_data": "{1,3463,9478,2,45}-{1,3461,9480,2,45}-{1,3461,9482,2,45}-{1,3461,9484,2,45}-" + "loc_data": "{1,3461,9480,2,30}-{1,3460,9484,2,30}-{1,3465,9477,2,30}-{1,3467,9493,0,30}-{1,3486,9517,0,30}-{1,3474,9509,0,30}-{1,3470,9502,0,30}-{1,3480,9483,0,30}-" }, { "item_id": "3711", From b644a72bfc36d77468d792a2d10aba743f652268 Mon Sep 17 00:00:00 2001 From: Player Name Date: Tue, 7 Oct 2025 11:50:55 +0000 Subject: [PATCH 077/117] Corrected poison dagger attack animations for all metal daggers --- Server/data/configs/item_configs.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Server/data/configs/item_configs.json b/Server/data/configs/item_configs.json index fe39aa2a3..951d647f5 100644 --- a/Server/data/configs/item_configs.json +++ b/Server/data/configs/item_configs.json @@ -51573,6 +51573,7 @@ "equipment_slot": "3", "grand_exchange_price": "483", "attack_audios": "2517,2517,2500,2517", + "attack_anims": "400,400,401,400", "name": "Iron dagger(p+)", "tradeable": "true", "archery_ticket_price": "0", @@ -51601,6 +51602,7 @@ "equipment_slot": "3", "grand_exchange_price": "350", "attack_audios": "2517,2517,2500,2517", + "attack_anims": "400,400,401,400", "name": "Bronze dagger(p+)", "tradeable": "true", "archery_ticket_price": "0", @@ -51630,6 +51632,7 @@ "equipment_slot": "3", "grand_exchange_price": "575", "attack_audios": "2517,2517,2500,2517", + "attack_anims": "400,400,401,400", "name": "Steel dagger(p+)", "tradeable": "true", "archery_ticket_price": "0", @@ -51659,6 +51662,7 @@ "equipment_slot": "3", "grand_exchange_price": "538", "attack_audios": "2517,2517,2500,2517", + "attack_anims": "400,400,401,400", "name": "Mithril dagger(p+)", "tradeable": "true", "archery_ticket_price": "0", @@ -51684,6 +51688,7 @@ "equipment_slot": "3", "grand_exchange_price": "800", "attack_audios": "2517,2517,2500,2517", + "attack_anims": "400,400,401,400", "name": "Adamant dagger(p+)", "tradeable": "true", "archery_ticket_price": "0", @@ -51711,6 +51716,7 @@ "equip_audio": "2248", "render_anim": "2584", "equipment_slot": "3", + "attack_anims": "400,400,401,400", "lendable": "true", "grand_exchange_price": "4538", "attack_audios": "2517,2517,2500,2517", @@ -51779,6 +51785,7 @@ "equipment_slot": "3", "grand_exchange_price": "664", "attack_audios": "2517,2517,2500,2517", + "attack_anims": "400,400,401,400", "name": "Black dagger(p+)", "tradeable": "true", "archery_ticket_price": "0", @@ -51826,6 +51833,7 @@ "equipment_slot": "3", "grand_exchange_price": "4128", "attack_audios": "2517,2517,2500,2517", + "attack_anims": "400,400,401,400", "name": "Iron dagger(p++)", "tradeable": "true", "archery_ticket_price": "0", @@ -51854,6 +51862,7 @@ "equipment_slot": "3", "grand_exchange_price": "5088", "attack_audios": "2517,2517,2500,2517", + "attack_anims": "400,400,401,400", "name": "Br'ze dagger(p++)", "tradeable": "true", "archery_ticket_price": "0", @@ -51883,6 +51892,7 @@ "equipment_slot": "3", "grand_exchange_price": "4729", "attack_audios": "2517,2517,2500,2517", + "attack_anims": "400,400,401,400", "name": "Steel dagger(p++)", "tradeable": "true", "archery_ticket_price": "0", @@ -51912,6 +51922,7 @@ "equipment_slot": "3", "grand_exchange_price": "3476", "attack_audios": "2517,2517,2500,2517", + "attack_anims": "400,400,401,400", "name": "Mithril dagger(p++)", "tradeable": "true", "archery_ticket_price": "0", @@ -51937,6 +51948,7 @@ "equipment_slot": "3", "grand_exchange_price": "4612", "attack_audios": "2517,2517,2500,2517", + "attack_anims": "400,400,401,400", "name": "Adamant dagger(p++)", "tradeable": "true", "archery_ticket_price": "0", @@ -51967,6 +51979,7 @@ "lendable": "true", "grand_exchange_price": "5061", "attack_audios": "2517,2517,2500,2517", + "attack_anims": "400,400,401,400", "name": "Rune dagger(p++)", "tradeable": "true", "archery_ticket_price": "0", @@ -52032,6 +52045,7 @@ "equipment_slot": "3", "grand_exchange_price": "4921", "attack_audios": "2517,2517,2500,2517", + "attack_anims": "400,400,401,400", "name": "Black dagger(p++)", "tradeable": "true", "archery_ticket_price": "0", @@ -60306,6 +60320,7 @@ "equipment_slot": "3", "grand_exchange_price": "497", "attack_audios": "2517,2517,2500,2517", + "attack_anims": "400,400,401,400", "name": "White dagger(p)", "tradeable": "true", "archery_ticket_price": "0", @@ -60335,6 +60350,7 @@ "equipment_slot": "3", "grand_exchange_price": "982", "attack_audios": "2517,2517,2500,2517", + "attack_anims": "400,400,401,400", "name": "White dagger(p+)", "tradeable": "true", "archery_ticket_price": "0", @@ -60364,6 +60380,7 @@ "equipment_slot": "3", "grand_exchange_price": "8422", "attack_audios": "2517,2517,2500,2517", + "attack_anims": "400,400,401,400", "name": "White dagger(p++)", "tradeable": "true", "archery_ticket_price": "0", From 9eac712626223d79d375595f3964491920b83d93 Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Tue, 7 Oct 2025 12:08:24 +0000 Subject: [PATCH 078/117] Fixed Cabin Fever charter discount; awarded via quest requirement system Fixed charter boat exception Fixed Mos Le'Harmless charter boat discount and prices --- .../global/travel/ship/ShipCharter.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Server/src/main/content/global/travel/ship/ShipCharter.java b/Server/src/main/content/global/travel/ship/ShipCharter.java index 9e04a8878..4058fc375 100644 --- a/Server/src/main/content/global/travel/ship/ShipCharter.java +++ b/Server/src/main/content/global/travel/ship/ShipCharter.java @@ -85,7 +85,7 @@ public final class ShipCharter { */ public static int getCost(final Player player, Destination destination) { int cost = destination.getCost(player, destination); - if (player.getQuestRepository().isComplete(Quests.CABIN_FEVER)) { + if (hasRequirement(player, Quests.CABIN_FEVER)) { cost -= Math.round((cost / 2.)); } if (player.getEquipment().containsItem(RING_OF_CHAROS)) { @@ -139,7 +139,7 @@ public final class ShipCharter { * @author 'Vexia */ public enum Destination { - CATHERBY(Location.create(2792, 3417, 1), 25, new int[] { 480, 0, 480, 625, 1600, 3250, 1000, 1600, 3200, 3400 }, Location.create(2797, 3414, 0), 3, 14), + CATHERBY(Location.create(2792, 3417, 1), 25, new int[] { 480, 0, 480, 1250, 1600, 3250, 1000, 1600, 3200, 3400 }, Location.create(2797, 3414, 0), 3, 14), PORT_PHASMATYS(Location.create(3705, 3503, 1), 24, new int[] { 3650, 3250, 1850, 0, 0, 0, 2050, 1850, 3200, 1100 }, Location.create(3702, 3502, 0), 2, 13) { @Override public boolean checkTravel(Player player) { @@ -152,7 +152,7 @@ public final class ShipCharter { return requireQuest(player, Quests.DRAGON_SLAYER, "to go there."); } }, - BRIMHAVEN(Location.create(2763, 3238, 1), 28, new int[] { 0, 480, 480, 925, 400, 3650, 1600, 400, 3200, 3800 }, Location.create(2760, 3238, 0), 6, 17){ + BRIMHAVEN(Location.create(2763, 3238, 1), 28, new int[] { 0, 480, 480, 1950, 400, 3650, 1600, 400, 3200, 3800 }, Location.create(2760, 3238, 0), 6, 17){ @Override public int getCost(Player player, Destination destination) { boolean hasGloves = DiaryType.KARAMJA.hasRewardEquipment(player); @@ -160,7 +160,7 @@ public final class ShipCharter { return super.getCost(player, destination); } }, - PORT_SARIM(Location.create(3038, 3189, 1), 30, new int[] { 1600, 1000, 0, 325, 1280, 650, 1280, 400, 3200, 1400 }, Location.create(3039, 3193, 0), 8, 19){ + PORT_SARIM(Location.create(3038, 3189, 1), 30, new int[] { 1600, 1000, 0, 650, 1280, 650, 1280, 400, 3200, 1400 }, Location.create(3039, 3193, 0), 8, 19){ @Override public int getCost(Player player, Destination destination) { boolean hasGloves = DiaryType.KARAMJA.hasRewardEquipment(player); @@ -168,14 +168,14 @@ public final class ShipCharter { return super.getCost(player, destination); } }, - PORT_TYRAS(Location.create(2142, 3122, 0), 23, new int[] { 3200, 3200, 3200, 1600, 3200, 3200, 3200, 3200, 0, 3200 }, Location.create(2143, 3122, 0), 1, 12) { + PORT_TYRAS(Location.create(2142, 3122, 0), 23, new int[] { 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 0, 3200 }, Location.create(2143, 3122, 0), 1, 12) { @Override public boolean checkTravel(Player player) { return hasRequirement(player, Quests.REGICIDE); } }, - KARAMJA(Location.create(2957, 3158, 1), 27, new int[] { 200, 480, 0, 225, 400, 1850, 0, 200, 3200, 2000 }, Location.create(2954, 3156, 0), 5, 16) { + KARAMJA(Location.create(2957, 3158, 1), 27, new int[] { 200, 480, 0, 450, 400, 1850, 0, 200, 3200, 2000 }, Location.create(2954, 3156, 0), 5, 16) { @Override public int getCost(Player player, Destination destination) { boolean hasGloves = DiaryType.KARAMJA.hasRewardEquipment(player); @@ -183,7 +183,7 @@ public final class ShipCharter { return super.getCost(player, destination); } }, - PORT_KHAZARD(Location.create(2674, 3141, 1), 29, new int[] { 1600, 1000, 0, 325, 180, 650, 1280, 400, 3200, 1400 }, Location.create(2674, 3144, 0), 7, 18){ + PORT_KHAZARD(Location.create(2674, 3141, 1), 29, new int[] { 1600, 1000, 0, 2050, 180, 650, 1280, 400, 3200, 1400 }, Location.create(2674, 3144, 0), 7, 18){ @Override public int getCost(Player player, Destination destination) { boolean hasGloves = DiaryType.KARAMJA.hasRewardEquipment(player); @@ -191,14 +191,14 @@ public final class ShipCharter { return super.getCost(player, destination); } }, - SHIPYARD(Location.create(3001, 3032, 0), 26, new int[] { 400, 1600, 200, 225, 720, 1850, 400, 0, 3200, 900 }, Location.create(3001, 3032, 0), 4, 15) { + SHIPYARD(Location.create(3001, 3032, 0), 26, new int[] { 400, 1600, 200, 450, 720, 1850, 400, 0, 3200, 900 }, Location.create(3001, 3032, 0), 4, 15) { @Override public boolean checkTravel(Player player) { return requireQuest(player, Quests.THE_GRAND_TREE, "to go there."); } }, - OO_GLOG(Location.create(2623, 2857, 0), 33, new int[] { 300, 3400, 2000, 550, 5000, 2800, 1400, 900, 3200, 0}, Location.create(2622, 2857, 0), 11, 22), - MOS_LE_HARMLESS(Location.create(3671, 2931, 0), 31, new int[] { 725, 625, 1025, 0, 1025, 0, 325, 275, 1600, 500 }, Location.create(3671, 2933, 0), 9, 20) { + OO_GLOG(Location.create(2623, 2857, 0), 33, new int[] { 300, 3400, 2000, 1100, 5000, 2800, 1400, 900, 3200, 0}, Location.create(2622, 2857, 0), 11, 22), + MOS_LE_HARMLESS(Location.create(3671, 2931, 0), 31, new int[] { 1550, 1250, 2050, 0, 2050, 0, 650, 550, 3200, 1000 }, Location.create(3671, 2933, 0), 9, 20) { @Override public boolean checkTravel(Player player) { return hasRequirement(player, Quests.CABIN_FEVER); From d6f32c56fc1ee6a6a3639d0adc39e8f7736eabc5 Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Tue, 7 Oct 2025 12:45:32 +0000 Subject: [PATCH 079/117] Chinchompa long fuse now properly splits ranged/defense XP --- .../handlers/item/equipment/special/ChinchompaSwingHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/src/main/content/global/handlers/item/equipment/special/ChinchompaSwingHandler.java b/Server/src/main/content/global/handlers/item/equipment/special/ChinchompaSwingHandler.java index 0bd2e6d33..34b52b9c0 100644 --- a/Server/src/main/content/global/handlers/item/equipment/special/ChinchompaSwingHandler.java +++ b/Server/src/main/content/global/handlers/item/equipment/special/ChinchompaSwingHandler.java @@ -93,7 +93,7 @@ public final class ChinchompaSwingHandler extends RangeSwingHandler { } } entity.getSkills().addExperience(Skills.HITPOINTS, hit * 1.33, true); - if (entity.getProperties().getAttackStyle().getStyle() == WeaponInterface.STYLE_DEFENSIVE) { + if (entity.getProperties().getAttackStyle().getStyle() == WeaponInterface.STYLE_LONG_RANGE) { entity.getSkills().addExperience(Skills.RANGE, hit * 2, true); entity.getSkills().addExperience(Skills.DEFENCE, hit * 2, true); } else { From 5719d03e4deb70115d3d5bbd2539e8cba782983a Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Tue, 7 Oct 2025 12:46:13 +0000 Subject: [PATCH 080/117] Farmed trees will now correctly regrow based on the same timer as other trees, rather than being tied to growth cycles Implemented admin ::instachop command --- .../content/global/skill/farming/Patch.kt | 2 - .../content/global/skill/farming/Stump.kt | 5 ++ .../skill/farming/timers/StumpGrowth.kt | 59 +++++++++++++++++++ .../gather/woodcutting/WoodcuttingListener.kt | 7 ++- .../game/system/command/sets/FunCommandSet.kt | 8 +++ 5 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 Server/src/main/content/global/skill/farming/Stump.kt create mode 100644 Server/src/main/content/global/skill/farming/timers/StumpGrowth.kt diff --git a/Server/src/main/content/global/skill/farming/Patch.kt b/Server/src/main/content/global/skill/farming/Patch.kt index 4fde65b5e..eacc91bde 100644 --- a/Server/src/main/content/global/skill/farming/Patch.kt +++ b/Server/src/main/content/global/skill/farming/Patch.kt @@ -373,8 +373,6 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl setCurrentState(getCurrentState() + 1) isWatered = false } - - regrowIfTreeStump() } fun regrowIfTreeStump() { diff --git a/Server/src/main/content/global/skill/farming/Stump.kt b/Server/src/main/content/global/skill/farming/Stump.kt new file mode 100644 index 000000000..60c9bab8a --- /dev/null +++ b/Server/src/main/content/global/skill/farming/Stump.kt @@ -0,0 +1,5 @@ +package content.global.skill.farming + +class Stump(val varbit: Int, val TTL: Long) { + +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/farming/timers/StumpGrowth.kt b/Server/src/main/content/global/skill/farming/timers/StumpGrowth.kt new file mode 100644 index 000000000..616bde43a --- /dev/null +++ b/Server/src/main/content/global/skill/farming/timers/StumpGrowth.kt @@ -0,0 +1,59 @@ +package content.global.skill.farming.timers + +import core.game.node.entity.Entity +import core.game.system.timer.* +import core.game.node.entity.player.Player +import content.global.skill.farming.* +import core.tools.ticksToSeconds +import java.util.concurrent.TimeUnit +import org.json.simple.* + +class StumpGrowth : PersistTimer (1, "farming:stump", isSoft = true) { + val stumps = ArrayList() + lateinit var player: Player + + fun addStump(varbit: Int, ttl: Int){ + stumps.add( + Stump( + varbit, + System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(ticksToSeconds(ttl).toLong()) + ) + ) + } + + override fun onRegister (entity: Entity) { + player = (entity as? Player)!! + } + + override fun run (entity: Entity) : Boolean { + val removeList = ArrayList() + for (stump in stumps) { + if (System.currentTimeMillis() > stump.TTL) { + FarmingPatch.patches[stump.varbit]?.getPatchFor(player)?.regrowIfTreeStump() + removeList.add(stump) + } + } + stumps.removeAll(removeList) + return stumps.isNotEmpty() + } + + override fun save(root: JSONObject, entity: Entity) { + val stumpArray = JSONArray() + for(s in stumps){ + val seed = JSONObject() + seed["patch"] = s.varbit + seed["ttl"] = s.TTL + stumpArray.add(seed) + } + root.put("stumps",stumpArray) + } + + override fun parse(root: JSONObject, entity: Entity) { + (root["stumps"] as JSONArray).forEach { + val s = it as JSONObject + val id = s["patch"].toString().toInt() + val ttl = s["ttl"].toString().toLong() + stumps.add(Stump(id,ttl)) + } + } +} diff --git a/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt b/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt index af8ef7398..87efe8614 100644 --- a/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt +++ b/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt @@ -3,6 +3,7 @@ package content.global.skill.gather.woodcutting import content.data.skill.SkillingTool import content.data.tables.BirdNest import content.global.skill.farming.FarmingPatch.Companion.forObject +import content.global.skill.farming.timers.StumpGrowth import content.global.skill.firemaking.Log import content.global.skill.skillcapeperks.SkillcapePerks import content.global.skill.skillcapeperks.SkillcapePerks.Companion.isActive @@ -71,7 +72,7 @@ class WoodcuttingListener : InteractionListener { if (clockReady(player, Clocks.SKILLING)) { animateWoodcutting(player) - if (!checkReward(player, resource, tool)) + if (!checkReward(player, resource, tool) && !getAttribute(player, "instachop", false)) return delayClock(player, Clocks.SKILLING, 3) val reward = resource.getReward() @@ -156,7 +157,7 @@ class WoodcuttingListener : InteractionListener { //OSRS: https://oldschool.runescape.wiki/w/Woodcutting scroll down to the mechanics section //RS3 : https://runescape.wiki/w/Woodcutting scroll down to the mechanics section, and expand the tree felling chances table if (resource.getRespawnRate() > 0) { - if (RandomFunction.roll(8) || listOf(1, 2, 3, 4, 6).contains(resource.identifier.toInt())){ + if (RandomFunction.roll(8) || listOf(1, 2, 3, 4, 6).contains(resource.identifier.toInt()) || getAttribute(player, "instachop", false)){ if (resource.isFarming()) { val fPatch = forObject(node.asScenery()) if (fPatch != null) { @@ -168,6 +169,8 @@ class WoodcuttingListener : InteractionListener { node.isActive = true return@queueScript stopExecuting(player) } + val stumps = getOrStartTimer (player) + stumps.addStump(fPatch.varbit, resource.respawnDuration) } return true } diff --git a/Server/src/main/core/game/system/command/sets/FunCommandSet.kt b/Server/src/main/core/game/system/command/sets/FunCommandSet.kt index 498fe6ce5..9ebd0ff36 100644 --- a/Server/src/main/core/game/system/command/sets/FunCommandSet.kt +++ b/Server/src/main/core/game/system/command/sets/FunCommandSet.kt @@ -239,6 +239,14 @@ class FunCommandSet : CommandSet(Privilege.ADMIN) { } } + /** + * Toggle instant woodcutting. + */ + define("instachop", Privilege.ADMIN, "", "Fells trees after a single log is gathered."){ player, _ -> + player.setAttribute("instachop", !player.getAttribute("instachop", false)) + notify(player,"Instachop mode " + if (player.getAttribute("instachop", false)) "on." else "off.") + } + } fun bury(player: Player){ From 18f274be8e69633298154283a03e9a154ebfb318 Mon Sep 17 00:00:00 2001 From: damighty <27978131-real_damighty@users.noreply.gitlab.com> Date: Tue, 7 Oct 2025 16:16:21 +0300 Subject: [PATCH 081/117] Implemented search command, ::commandsearch --- .../system/command/sets/MiscCommandSet.kt | 191 ++++++++++++++---- 1 file changed, 154 insertions(+), 37 deletions(-) diff --git a/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt b/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt index b959495bd..44348e1b2 100644 --- a/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt +++ b/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt @@ -19,6 +19,7 @@ import core.game.node.entity.player.info.Rights import core.game.node.entity.skill.Skills import core.game.node.item.Item import core.game.node.scenery.Scenery +import core.game.system.command.Command import core.game.system.command.CommandMapping import core.game.system.command.Privilege import core.game.system.communication.CommunicationInfo @@ -223,11 +224,8 @@ class MiscCommandSet : CommandSet(Privilege.ADMIN){ */ define("commands", Privilege.STANDARD, "::commands page", "Lists all the commands (you are here.)"){player, args -> val page = if (args.size > 1) (args[1].toIntOrNull() ?: 1) - 1 else 0 - var lineid = 11 - var pages = CommandMapping.getPageIndices(player.rights.ordinal) - var end = if (page < (pages.size - 1)) pages[page + 1] else CommandMapping.getCommands().size - - player.interfaceManager.close() + val pages = CommandMapping.getPageIndices(player.rights.ordinal) + val end = if (page < (pages.size - 1)) pages[page + 1] else CommandMapping.getCommands().size if (page < 0) { reject(player, "Usage: ::commands page") @@ -237,44 +235,106 @@ class MiscCommandSet : CommandSet(Privilege.ADMIN){ reject(player, "That page number is too high, you don't have access to that many.") } - for (i in 0..310) { - player.packetDispatch.sendString("", Components.QUESTJOURNAL_SCROLL_275, i) - } + val title = "Commands" + if (pages.size > 1) " (${page + 1}/${pages.size})" else "" + var lineId = setupScrollInterface(player, title) - player.packetDispatch.sendString( - "Commands" + if (pages.size > 1) " (${page + 1}/${pages.size})" else "", - Components.QUESTJOURNAL_SCROLL_275, - 2 - ) - - for(i in pages[page] until end) { - var command = CommandMapping.getCommands()[i] - var title = "${command.name}" - var rights = command.privilege.ordinal - var icon = rights - 1 + val command = CommandMapping.getCommands()[i] + lineId = displayCommandInScroll(player, command, player.rights.ordinal, lineId) - - if (rights > player.rights.ordinal) continue - - if (rights > 0) - title = "() $title" - - player.packetDispatch.sendString(title, Components.QUESTJOURNAL_SCROLL_275, lineid++) - - if (command.usage.isNotEmpty()) - player.packetDispatch.sendString("Usage: ${command.usage}", Components.QUESTJOURNAL_SCROLL_275, lineid++) - - if (command.description.isNotEmpty()) - player.packetDispatch.sendString(command.description, Components.QUESTJOURNAL_SCROLL_275, lineid++) - - player.packetDispatch.sendString("-------------------------------", Components.QUESTJOURNAL_SCROLL_275, lineid++) - - if (lineid > 306) { - player.packetDispatch.sendString("To view the next page, use ::commands ${page + 2}", Components.QUESTJOURNAL_SCROLL_275, lineid) + if (lineId > 306) { + player.packetDispatch.sendString("To view the next page, use ::commands ${page + 2}", Components.QUESTJOURNAL_SCROLL_275, lineId) break } + } + player.interfaceManager.open(Component(Components.QUESTJOURNAL_SCROLL_275)) + } + /* + * Search for commands + */ + define("commandsearch", Privilege.STANDARD, "::commandsearch search term [--chat]", "Searches for commands containing the given term."){player, args -> + if (args.size < 2) { + reject(player, "Usage: ::commandsearch [--chat]") + return@define + } + val rawArguments = args.copyOfRange(1, args.size) + val chatMode = rawArguments.any { it.equals("--chat", ignoreCase = true) } + val searchRaw = rawArguments.filterNot { it.equals("--chat", ignoreCase = true) } + if (searchRaw.isEmpty()) { + reject(player, "Usage: ::commandsearch [--chat]") + return@define + } + val search = searchRaw.joinToString(" ").lowercase() + if (search.length < 2) { + reject(player, "Search term must be at least 2 characters long") + return@define + } + val playerRights = player.rights.ordinal + val allMatchingCommands = CommandMapping.getCommands() + .filter { command -> + command.name.lowercase().contains(search) || + command.usage.lowercase().contains(search) || + command.description.lowercase().contains(search) + } + if (allMatchingCommands.isEmpty()) { + notify(player, "No commands found matching '$search'") + return@define + } + val accessibleCommands = allMatchingCommands + .filter { it.privilege.ordinal <= playerRights } + .sortedBy { it.name } + val inaccessibleCount = allMatchingCommands.count { it.privilege.ordinal > playerRights } + if (chatMode) { + for (command in accessibleCommands) { + val cmdargs = if (command.usage.isNotEmpty()) { + val parts = command.usage.split(" ", limit = 2) + if (parts.size > 1) parts[1] else "" + } else { + "" + } + val formattedMessage = if (cmdargs.isNotEmpty()) { + "CMD: ${command.name} ARGS: $cmdargs DESC: ${command.description}" + } else { + "CMD: ${command.name} DESC: ${command.description}" + } + val chunks = chunkText(formattedMessage, 70) + chunks.forEach { notify(player, it) } + } + if (accessibleCommands.isEmpty()) { + notify(player, "Found $inaccessibleCount command(s) matching '$search' but you don't have permission to use any of them") + } else { + val summary = "Found ${accessibleCommands.size} accessible command(s) for '$search'" + if (inaccessibleCount > 0) { + notify(player, summary) + notify(player, "($inaccessibleCount additional command(s) require higher privileges)") + } else { + notify(player, summary) + } + } + return@define + } + var lineId = setupScrollInterface(player, "Command Search: '$search'") + if (accessibleCommands.isEmpty()) { + player.packetDispatch.sendString("No accessible commands found.", Components.QUESTJOURNAL_SCROLL_275, lineId++) + } + for (command in accessibleCommands) { + lineId = displayCommandInScroll(player, command, playerRights, lineId) + if (lineId > 306) { + player.packetDispatch.sendString( + "Results truncated. Use ::commandsearch $search --chat", + Components.QUESTJOURNAL_SCROLL_275, + lineId + ) + break + } + } + if (inaccessibleCount > 0 && lineId <= 306) { + player.packetDispatch.sendString( + "$inaccessibleCount more command(s) need higher privileges.", + Components.QUESTJOURNAL_SCROLL_275, + lineId + ) } player.interfaceManager.open(Component(Components.QUESTJOURNAL_SCROLL_275)) } @@ -640,6 +700,63 @@ class MiscCommandSet : CommandSet(Privilege.ADMIN){ } } + fun setupScrollInterface(player: Player, title: String): Int { + player.interfaceManager.close() + for (i in 0..310) { + player.packetDispatch.sendString("", Components.QUESTJOURNAL_SCROLL_275, i) + } + player.packetDispatch.sendString(title, Components.QUESTJOURNAL_SCROLL_275, 2) + return 11 + } + + fun displayCommandInScroll(player: Player, command: Command, playerRights: Int, lineId: Int): Int { + val privilegeLevel = command.privilege.ordinal + if (privilegeLevel > playerRights) return lineId + val titleIcon = privilegeLevel - 1 + var title = command.name + if (privilegeLevel > 0) { + title = "() $title" + } + var currentLineId = lineId + player.packetDispatch.sendString(title, Components.QUESTJOURNAL_SCROLL_275, currentLineId++) + if (command.usage.isNotEmpty()) { + val usageText = "Usage: ${command.usage}" + val usageChunks = chunkText(usageText, 70) + for (chunk in usageChunks) { + player.packetDispatch.sendString(chunk, Components.QUESTJOURNAL_SCROLL_275, currentLineId++) + } + } + if (command.description.isNotEmpty()) { + val descChunks = chunkText(command.description, 70) + for (chunk in descChunks) { + player.packetDispatch.sendString(chunk, Components.QUESTJOURNAL_SCROLL_275, currentLineId++) + } + } + player.packetDispatch.sendString("-------------------------------", Components.QUESTJOURNAL_SCROLL_275, currentLineId++) + return currentLineId + } + + fun chunkText(text: String, maxLength: Int = 70): List { + if (text.length <= maxLength) return listOf(text) + val chunks = mutableListOf() + var remaining = text + while (remaining.length > maxLength) { + var breakPoint = maxLength + for (i in maxLength downTo 1) { + if (remaining[i - 1] == ' ') { + breakPoint = i + break + } + } + chunks.add(remaining.take(breakPoint).trim()) + remaining = remaining.substring(breakPoint).trim() + } + if (remaining.isNotEmpty()) { + chunks.add(remaining) + } + return chunks + } + fun setPlaqueReadStatus(player: Player, status: Boolean){ // For some reason the loop has to be this way to have read write access for (i in 0 until player.savedData.globalData.readPlaques.size){ From c1358776808e7e64083079be76988bd70a1baa09 Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Tue, 7 Oct 2025 13:19:48 +0000 Subject: [PATCH 082/117] Catching a horned graahk now finishes the appropriate Karamja task Checking the health of a fruit tree in Brimhaven now finishes the appropriate Karamja task --- .../src/main/content/global/skill/farming/HealthChecker.kt | 4 ++++ .../content/global/skill/hunter/pitfall/HunterPitfall.kt | 2 ++ .../core/game/node/entity/player/link/diary/DiaryType.java | 6 +++--- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Server/src/main/content/global/skill/farming/HealthChecker.kt b/Server/src/main/content/global/skill/farming/HealthChecker.kt index 23b53864b..eb6d03256 100644 --- a/Server/src/main/content/global/skill/farming/HealthChecker.kt +++ b/Server/src/main/content/global/skill/farming/HealthChecker.kt @@ -5,6 +5,7 @@ import core.cache.def.impl.SceneryDefinition import core.game.interaction.OptionHandler import core.game.node.Node import core.game.node.entity.player.Player +import core.game.node.entity.player.link.diary.DiaryType import core.game.node.entity.skill.Skills import core.plugin.Initializable import core.plugin.Plugin @@ -42,6 +43,9 @@ class HealthChecker : OptionHandler() { } PatchType.FRUIT_TREE_PATCH -> { patch.setCurrentState(patch.getCurrentState() - 14) + if (fPatch == FarmingPatch.BRIMHAVEN_FRUIT_TREE) { + player.achievementDiaryManager.finishTask(player, DiaryType.KARAMJA, 1, 12) + } sendMessage(player, "You examine the tree for signs of disease and find that it is in perfect health.") } PatchType.SPIRIT_TREE_PATCH -> { diff --git a/Server/src/main/content/global/skill/hunter/pitfall/HunterPitfall.kt b/Server/src/main/content/global/skill/hunter/pitfall/HunterPitfall.kt index f4959cbae..bdc9611a9 100644 --- a/Server/src/main/content/global/skill/hunter/pitfall/HunterPitfall.kt +++ b/Server/src/main/content/global/skill/hunter/pitfall/HunterPitfall.kt @@ -21,6 +21,7 @@ import org.rs09.consts.Items import org.rs09.consts.NPCs import core.game.interaction.InteractionListener import core.game.interaction.IntType +import core.game.node.entity.player.link.diary.DiaryType import core.game.world.GameWorld import org.rs09.consts.Sounds @@ -262,6 +263,7 @@ class PitfallListeners : InteractionListener { } on(GRAAHK_PIT, IntType.SCENERY, "dismantle") { player, node -> lootCorpse(player, node as Scenery, 240.0, Items.GRAAHK_FUR_10099, Items.TATTY_GRAAHK_FUR_10097) + player.achievementDiaryManager.finishTask(player, DiaryType.KARAMJA, 1, 13) sendMessage(player, "You've caught a horned graahk!") return@on true } diff --git a/Server/src/main/core/game/node/entity/player/link/diary/DiaryType.java b/Server/src/main/core/game/node/entity/player/link/diary/DiaryType.java index a91f4b5bc..8a8e51827 100644 --- a/Server/src/main/core/game/node/entity/player/link/diary/DiaryType.java +++ b/Server/src/main/core/game/node/entity/player/link/diary/DiaryType.java @@ -34,14 +34,14 @@ public enum DiaryType { "Use Vigroy and Hajedy's cart service", "Earn 100% favour in the village of Tai Bwo Wannai Cleanup", // todo tai bwo wannai cleanup "Cook a spider on stick", // todo tai bwo wannai cleanup - "Charter the Lady of the Waves from Cairn Isle to Port Khazard", // todo verify + "Charter the Lady of the Waves from Cairn Isle to Port Khazard", "Cut a log from a teak tree", "Cut a log from a mahogany tree", "Catch a karambwan", // todo need Tai Bwo Wannai Trio "Exchange gems, a gout tuber, trading sticks for a machete", // todo "Use the gnome glider to travel to Karamja", - "Grow a healthy fruit tree in the patch near Brimhaven", // todo verify - "Trap a Horned Graahk", // todo need to implement pitfall trapping + "Grow a healthy fruit tree in the patch near Brimhaven", + "Trap a Horned Graahk", "Chop the vines to gain deeper access to Brimhaven Dungeon", "Cross the lava using the stepping stones within Brimhaven

Dungeon", "Climb the stairs within Brimhaven Dungeon", From 7a23f6f0a37bc01e0cb06421a3079c94742990a9 Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Tue, 7 Oct 2025 13:41:10 +0000 Subject: [PATCH 083/117] First phase of TzHaar rewrite --- .../karamja/tzhaar/handlers/TzHaarDialogue.kt | 49 +++++ .../tzhaar/handlers/TzhaarCityPlugin.java | 203 ------------------ .../handlers/TzhaarFightCavesPlugin.java | 2 +- .../tzhaar/handlers/TzhaarListeners.kt | 36 ++++ 4 files changed, 86 insertions(+), 204 deletions(-) create mode 100644 Server/src/main/content/region/karamja/tzhaar/handlers/TzHaarDialogue.kt delete mode 100644 Server/src/main/content/region/karamja/tzhaar/handlers/TzhaarCityPlugin.java create mode 100644 Server/src/main/content/region/karamja/tzhaar/handlers/TzhaarListeners.kt diff --git a/Server/src/main/content/region/karamja/tzhaar/handlers/TzHaarDialogue.kt b/Server/src/main/content/region/karamja/tzhaar/handlers/TzHaarDialogue.kt new file mode 100644 index 000000000..bac301a6a --- /dev/null +++ b/Server/src/main/content/region/karamja/tzhaar/handlers/TzHaarDialogue.kt @@ -0,0 +1,49 @@ +package content.region.karamja.tzhaar.handlers + +import core.api.openNpcShop +import core.game.dialogue.DialoguePlugin +import core.game.dialogue.FacialExpression +import core.game.dialogue.Topic +import core.game.node.entity.npc.NPC +import core.game.node.entity.player.Player +import core.plugin.Initializable +import core.tools.END_DIALOGUE +import org.rs09.consts.NPCs + +@Initializable +class TzHaarDialogue(player: Player? = null) : DialoguePlugin(player) { + override fun open(vararg args: Any): Boolean { + npc = args[0] as NPC + npcl(FacialExpression.HALF_GUILTY, "Can I help you JalYt-Ket-${player.username}?").also { stage = 0 } + return true + } + + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + when (stage) { + 0 -> showTopics( + Topic(FacialExpression.HALF_GUILTY, "What do you have to trade?", 10, true), + Topic(FacialExpression.HALF_GUILTY, "What did you call me?", 20), + Topic(FacialExpression.HALF_GUILTY, "No I'm fine thanks.", END_DIALOGUE), + ) + 10 -> end().also { openNpcShop(player, npc.id) } + 20 -> npcl(FacialExpression.HALF_GUILTY, "Are you not JalYt-Ket?").also { stage++ } + 21 -> showTopics( + Topic(FacialExpression.HALF_GUILTY, "What's a 'JalYt-Ket'?", 22), + Topic(FacialExpression.HALF_GUILTY, "I guess so...", 25), + Topic(FacialExpression.HALF_GUILTY, "No I'm not!", END_DIALOGUE) + ) + + 22 -> npcl(FacialExpression.HALF_GUILTY, "That what you are... you tough and strong no?").also { stage++ } + 23 -> playerl(FacialExpression.HALF_GUILTY, "Well yes I suppose I am...").also { stage++ } + 24 -> npcl(FacialExpression.HALF_GUILTY, "Then you JalYt-Ket!").also { stage = END_DIALOGUE } + + 25 -> npcl(FacialExpression.HALF_GUILTY, "Well then, no problems.").also { stage = END_DIALOGUE } + } + return true + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.TZHAAR_HUR_TEL_2620, NPCs.TZHAAR_HUR_LEK_2622, NPCs.TZHAAR_MEJ_ROH_2623) + } + +} \ No newline at end of file diff --git a/Server/src/main/content/region/karamja/tzhaar/handlers/TzhaarCityPlugin.java b/Server/src/main/content/region/karamja/tzhaar/handlers/TzhaarCityPlugin.java deleted file mode 100644 index 669df926a..000000000 --- a/Server/src/main/content/region/karamja/tzhaar/handlers/TzhaarCityPlugin.java +++ /dev/null @@ -1,203 +0,0 @@ -package content.region.karamja.tzhaar.handlers; - -import core.cache.def.impl.SceneryDefinition; -import core.game.activity.ActivityManager; -import core.game.dialogue.DialoguePlugin; -import core.game.interaction.OptionHandler; -import core.game.node.Node; -import core.game.node.entity.npc.NPC; -import core.game.node.entity.player.Player; -import core.game.node.scenery.Scenery; -import core.game.world.map.Location; -import core.plugin.Initializable; -import core.plugin.Plugin; - -/** - * Represents the plugin used for tzhaar city. - * @author 'Vexia - * @version 1.0 - */ -@Initializable -public final class TzhaarCityPlugin extends OptionHandler { - - /** - * Represents the locations to use. - */ - private static final Location[] LOCATIONS = new Location[] { Location.create(2480, 5175, 0), Location.create(2866, 9571, 0) }; - - @Override - public Plugin newInstance(Object arg) throws Throwable { - SceneryDefinition.forId(31284).getHandlers().put("option:enter", this); //karamja cave. - SceneryDefinition.forId(9359).getHandlers().put("option:enter", this); //tzhaar exit - SceneryDefinition.forId(9356).getHandlers().put("option:enter", this); - SceneryDefinition.forId(9369).getHandlers().put("option:pass", this); - SceneryDefinition.forId(31292).getHandlers().put("option:go-through", this); //unimplemented door near fairy ring - new TzhaarDialogue().init(); - return this; - } - - @Override - public boolean handle(Player player, Node node, String option) { - int id = ((Scenery) node).getId(); - switch (option) { - case "enter": - switch (id) { - case 31284: - player.getProperties().setTeleportLocation(LOCATIONS[0]); - break; - case 9359: - player.getProperties().setTeleportLocation(LOCATIONS[1]); - break; - case 9356: - if (player.getFamiliarManager().hasFamiliar()) { - player.getPacketDispatch().sendMessage("You can't enter this with a follower."); - break; - } - ActivityManager.start(player, "fight caves", false); - break; - } - break; - case "pass": - switch (id) { - case 9369: - ActivityManager.start(player, "fight pits", false); - break; - } - break; - case "go-through": - switch (id) { - case 31292: - return false; - } - break; - } - return true; - } - - /** - * Represents the dialogue plugin used for the tzhaar npcs. - * @author 'Vexia - * @version 1.0 - */ - public static final class TzhaarDialogue extends DialoguePlugin { - - /** - * Constructs a new {@code TzhaarDialogue} {@code Object}. - */ - public TzhaarDialogue() { - /** - * empty. - */ - } - - /** - * Constructs a new {@code TzhaarDialogue} {@code Object}. - * @param player the player. - */ - public TzhaarDialogue(Player player) { - super(player); - } - - @Override - public DialoguePlugin newInstance(Player player) { - return new TzhaarDialogue(player); - } - - @Override - public boolean open(Object... args) { - npc = (NPC) args[0]; - npc("Can I help you JalYt-Ket-" + player.getUsername() + "?"); - return true; - } - - @Override - public boolean handle(int interfaceId, int buttonId) { - switch (stage) { - case 0: - options("What do you have to trade?", "What did you call me?", "No I'm fine thanks."); - stage = 1; - break; - case 1: - switch (buttonId) { - case 1: - end(); - npc.openShop(player); - break; - case 2: - player("What did you call me?"); - stage = 20; - break; - case 3: - player("No I'm fine thanks."); - stage = 30; - break; - } - break; - case 10: - break; - case 20: - npc("Are you not JalYt-Ket?"); - stage = 21; - break; - case 21: - options("What's a 'JalYt-Ket'?", "I guess so...", "No I'm not!"); - stage = 22; - break; - case 22: - switch (buttonId) { - case 1: - player("What's a 'JalYt-Ket'?"); - stage = 100; - break; - case 2: - player("I guess so..."); - stage = 120; - break; - case 3: - player("No I'm not!"); - stage = 130; - break; - } - break; - case 100: - npc("That what you are... you tough and strong no?"); - stage = 101; - break; - case 101: - player("Well yes I suppose I am..."); - stage = 102; - break; - case 102: - npc("Then you JalYt-Ket!"); - stage = 103; - break; - case 103: - end(); - break; - case 120: - npc("Well then, no problems."); - stage = 121; - break; - case 121: - end(); - break; - case 130: - end(); - break; - case 23: - end(); - break; - case 30: - end(); - break; - } - return true; - } - - @Override - public int[] getIds() { - return new int[] { 2620, 2622, 2623 }; - } - - } -} diff --git a/Server/src/main/content/region/karamja/tzhaar/handlers/TzhaarFightCavesPlugin.java b/Server/src/main/content/region/karamja/tzhaar/handlers/TzhaarFightCavesPlugin.java index e1baca76a..7559b9362 100644 --- a/Server/src/main/content/region/karamja/tzhaar/handlers/TzhaarFightCavesPlugin.java +++ b/Server/src/main/content/region/karamja/tzhaar/handlers/TzhaarFightCavesPlugin.java @@ -62,7 +62,7 @@ public final class TzhaarFightCavesPlugin extends ActivityPlugin { * @param player The player. */ public TzhaarFightCavesPlugin(Player player) { - super("fight caves", true, true, true, ZoneRestriction.CANNON, ZoneRestriction.RANDOM_EVENTS); + super("fight caves", true, true, true, ZoneRestriction.CANNON, ZoneRestriction.RANDOM_EVENTS, ZoneRestriction.FOLLOWERS); super.player = player; } diff --git a/Server/src/main/content/region/karamja/tzhaar/handlers/TzhaarListeners.kt b/Server/src/main/content/region/karamja/tzhaar/handlers/TzhaarListeners.kt new file mode 100644 index 000000000..f785ecd0b --- /dev/null +++ b/Server/src/main/content/region/karamja/tzhaar/handlers/TzhaarListeners.kt @@ -0,0 +1,36 @@ +package content.region.karamja.tzhaar.handlers + +import core.api.sendNPCDialogueLines +import core.api.teleport +import core.game.activity.ActivityManager +import core.game.dialogue.FacialExpression +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.world.map.Location +import org.rs09.consts.NPCs + +class TzhaarListeners : InteractionListener { + override fun defineListeners() { + on(intArrayOf(31284, 9359, 9356), IntType.SCENERY, "enter") { player, node -> + when (node.id) { + 31284 -> teleport(player, Location.create(2480, 5175, 0)) + 9359 -> teleport(player, Location.create(2866, 9571, 0)) + 9356 -> { + if (player.familiarManager.hasFamiliar()) { + sendNPCDialogueLines(player, NPCs.TZHAAR_MEJ_JAL_2617, FacialExpression.ANGRY, false, "No Kimit-Zil in the cave! This is a fight for YOU,", "not your friends!") + } else ActivityManager.start(player, "fight caves", false) + } + } + return@on true + } + + on(9369, IntType.SCENERY, "pass") { player, _ -> + ActivityManager.start(player, "fight pits", false) + return@on true + } + + on(31292, IntType.SCENERY, "go-through") { _, _ -> + return@on false + } + } +} \ No newline at end of file From d1cfe299c6c14415bf7fbf00a482ad5f588f5b61 Mon Sep 17 00:00:00 2001 From: Ceikry Date: Fri, 17 Oct 2025 22:07:14 +0000 Subject: [PATCH 084/117] Add default issue template now that Gitlab has changed how things work --- .gitlab/.gitkeep | 0 .gitlab/issue_templates/.gitkeep | 0 .gitlab/issue_templates/Default.md | 17 +++++++++++++++++ 3 files changed, 17 insertions(+) create mode 100644 .gitlab/.gitkeep create mode 100644 .gitlab/issue_templates/.gitkeep create mode 100644 .gitlab/issue_templates/Default.md diff --git a/.gitlab/.gitkeep b/.gitlab/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/.gitlab/issue_templates/.gitkeep b/.gitlab/issue_templates/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/.gitlab/issue_templates/Default.md b/.gitlab/issue_templates/Default.md new file mode 100644 index 000000000..051106623 --- /dev/null +++ b/.gitlab/issue_templates/Default.md @@ -0,0 +1,17 @@ +What I did: + +What I expected to happen: + +What actually happened: + +IDs of related NPCs/items: + +2009-era source (if relevant): + +Screenshots or video: + +LIVE SERVER username affected by this issue: + +**Bug reports are not accepted by SP users. SP is often out of date and results in invalid bug reports.** + +**If the bug is exploitable make sure you tick the confidential checkbox below** \ No newline at end of file From f9a193c8d76ff1d7dec04632a04868f7c8c639df Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Mon, 10 Nov 2025 11:43:16 +0000 Subject: [PATCH 085/117] Implemented the castle wars manual book --- .../global/handlers/iface/BookInterface.kt | 20 ++- .../minigame/castlewars/CastlewarsBook.kt | 146 ++++++++++++++++++ 2 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 Server/src/main/content/minigame/castlewars/CastlewarsBook.kt diff --git a/Server/src/main/content/global/handlers/iface/BookInterface.kt b/Server/src/main/content/global/handlers/iface/BookInterface.kt index 070b83ebd..508b8b16d 100644 --- a/Server/src/main/content/global/handlers/iface/BookInterface.kt +++ b/Server/src/main/content/global/handlers/iface/BookInterface.kt @@ -1,5 +1,6 @@ package content.global.handlers.iface +import content.global.handlers.iface.BookInterface.Companion.FANCY_BOOK_2_27 import core.api.closeInterface import core.api.getAttribute import core.api.openInterface @@ -158,9 +159,22 @@ class BookInterface : InterfaceListener { if (pageSet == getAttribute(player, CURRENT_PAGE_ATTRIBUTE, 0)) { player.packetDispatch.sendInterfaceConfig(componentId, enableLineId, false) player.packetDispatch.sendModelOnInterface(modelId, componentId, drawLineId, 0) - player.packetDispatch.sendAngleOnInterface(componentId, drawLineId, zoom, pitch, yaw) - } else { - player.packetDispatch.sendInterfaceConfig(componentId, enableLineId, true) + player.packetDispatch.sendAngleOnInterface(componentId, drawLineId, zoom, pitch, yaw) + } + } + + /** Sets item on lineId of pageSet (0 index). Call this in the display function after pageSetup. */ + fun setItemOnPage(player: Player, pageSet: Int, itemId: Int, componentId: Int, enableLineId: Int, drawLineId: Int, zoom: Int, pitch: Int, yaw: Int) { + if (pageSet == getAttribute(player, CURRENT_PAGE_ATTRIBUTE, 0)) { + player.packetDispatch.sendInterfaceConfig(componentId, enableLineId, false) + player.packetDispatch.sendItemOnInterface(itemId, 1, componentId, drawLineId) + } + } + + /** Clears models(pictures) on lineId of pageSet (0 index). Call this in the display function after pageSetup. */ + fun clearModelsOnPage(player: Player, componentId: Int) { + BookInterface.FANCY_BOOK_2_27_IMAGE_ENABLE_DRAW_IDS.forEach { drawId -> + player.packetDispatch.sendInterfaceConfig(componentId, drawId, true); } } diff --git a/Server/src/main/content/minigame/castlewars/CastlewarsBook.kt b/Server/src/main/content/minigame/castlewars/CastlewarsBook.kt new file mode 100644 index 000000000..8395ece6a --- /dev/null +++ b/Server/src/main/content/minigame/castlewars/CastlewarsBook.kt @@ -0,0 +1,146 @@ +package content.minigame.castlewars + +import content.data.Quests +import content.global.handlers.iface.BookInterface +import content.global.handlers.iface.BookLine +import content.global.handlers.iface.Page +import content.global.handlers.iface.PageSet +import core.api.getAttribute +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.player.Player +import core.game.system.command.sets.ModelViewerCommandSet.Companion.ATTRIBUTE_MODEL_NUMBER +import core.game.system.command.sets.ModelViewerCommandSet.Companion.ATTRIBUTE_PITCH +import core.game.system.command.sets.ModelViewerCommandSet.Companion.ATTRIBUTE_YAW +import core.game.system.command.sets.ModelViewerCommandSet.Companion.ATTRIBUTE_ZOOM +import core.game.system.command.sets.ModelViewerCommandSet.Companion.DEF_BOOK +import core.game.world.GameWorld +import org.rs09.consts.Items + +class CastlewarsBook : InteractionListener { + + companion object { + private val TITLE = "Castle Wars Manual" + private val CONTENTS = arrayOf( + PageSet( + Page( + BookLine("Objective:", 38), + BookLine("The aim is to get into your", 39), + BookLine("opponents castle and take", 40), + BookLine("their team standard. Then", 41), + BookLine("bring that back and capture", 42), + BookLine("it on your teams standard. ", 43), + ), + Page( + BookLine("Toolkit:", 58), + BookLine("This useful item allows you", 59), + BookLine("to repair broken doors and", 60), + BookLine("catapults. Simply use it on", 61), + BookLine("the item to be repaired, or", 62), + BookLine("have one in your inventory", 63), + BookLine("when you select the option,", 64), + BookLine("and you'll rebuild it!", 65) + ) + ), + PageSet( + Page( + BookLine("Bandages:", 43), + BookLine("These can be used to heal", 44), + BookLine("some health and restore some", 45), + BookLine("of your running energy.", 46), + BookLine("You can also use them to", 47), + BookLine("heal fellow players.", 48), + ), + Page( + BookLine("Explosive Potion:", 58), + BookLine("A simple but effective item,", 59), + BookLine("use it to blow up your", 60), + BookLine("opponents catapult and", 61), + BookLine("barricades! It can also be", 62), + BookLine("used to clear the tunnels", 63), + BookLine("under the arena for some,", 64), + BookLine("sneak attacks into your", 65), + BookLine("opponents castle!", 66) + ) + ), + PageSet( + Page( + BookLine("Barricade:", 43), + BookLine("Use these constructs to block", 44), + BookLine("your opponents movement", 45), + BookLine("and prevent them accessing", 46), + BookLine("your castle. Each team can", 47), + BookLine("only have 10 built at any one", 48), + BookLine("time.", 49), + ), + Page( + BookLine("Bucket:", 58), + BookLine("Fill a bucket with water and", 59), + BookLine("you can use it to put out a", 60), + BookLine("burning catapult or barricade,", 61), + BookLine("but be quick or it'll be", 62), + BookLine("destroyed.", 63), + ) + ), + PageSet( + Page( + BookLine("Tinderbox:", 43), + BookLine("Logs aren't all that's", 44), + BookLine("flammable, use a tinderbox to", 45), + BookLine("set light to your opponents", 46), + BookLine("You can also use them to", 47), + BookLine("catapult and barricades.", 48), + ), + Page( + BookLine("Pickaxe:", 58), + BookLine("Use a pickaxe to mine your", 59), + BookLine("way through the tunnels", 60), + BookLine("under the arena for a sneak", 61), + BookLine("attack into your opponents", 62), + BookLine("castle. Don't forget to", 63), + BookLine("collapse the tunnels into your", 64), + BookLine(" own castle though! ", 65), + ) + ), + PageSet( + Page( + BookLine("Catapult:", 43), + BookLine("Use this war machine to", 44), + BookLine("launch rocks at your", 45), + BookLine("opponents. Just give it rough", 46), + BookLine("coordinates and let the rock", 47), + BookLine("fly, just be careful not to hit", 48), + BookLine("your team with it!", 49), + ), + Page( + BookLine("Rock:", 58), + BookLine("Used as ammo for the", 59), + BookLine("catapult, and not much else.", 60), + BookLine("Brings new meaning to the", 61), + BookLine("phrase 'flies like a rock'.", 62), + ) + ), + ) + private fun display(player: Player, pageNum: Int, buttonID: Int) : Boolean { + BookInterface.pageSetup(player, BookInterface.FANCY_BOOK_2_27, TITLE, CONTENTS) + BookInterface.clearModelsOnPage(player, BookInterface.FANCY_BOOK_2_27); + BookInterface.setItemOnPage(player, 0, Items.TOOLKIT_4051, BookInterface.FANCY_BOOK_2_27, BookInterface.FANCY_BOOK_2_27_IMAGE_ENABLE_DRAW_IDS[17], BookInterface.FANCY_BOOK_2_27_IMAGE_DRAW_IDS[17], 768, 192, 1792) + BookInterface.setItemOnPage(player, 1, Items.BANDAGES_4049, BookInterface.FANCY_BOOK_2_27, BookInterface.FANCY_BOOK_2_27_IMAGE_ENABLE_DRAW_IDS[2], BookInterface.FANCY_BOOK_2_27_IMAGE_DRAW_IDS[2], 768, 192, 1792) + BookInterface.setItemOnPage(player, 1, Items.EXPLOSIVE_POTION_4045, BookInterface.FANCY_BOOK_2_27, BookInterface.FANCY_BOOK_2_27_IMAGE_ENABLE_DRAW_IDS[17], BookInterface.FANCY_BOOK_2_27_IMAGE_DRAW_IDS[17], 768, 192, 1792) + BookInterface.setItemOnPage(player, 2, Items.BARRICADE_4053, BookInterface.FANCY_BOOK_2_27, BookInterface.FANCY_BOOK_2_27_IMAGE_ENABLE_DRAW_IDS[2], BookInterface.FANCY_BOOK_2_27_IMAGE_DRAW_IDS[2], 768, 192, 1792) + BookInterface.setItemOnPage(player, 2, Items.BUCKET_OF_WATER_1929, BookInterface.FANCY_BOOK_2_27, BookInterface.FANCY_BOOK_2_27_IMAGE_ENABLE_DRAW_IDS[17], BookInterface.FANCY_BOOK_2_27_IMAGE_DRAW_IDS[17], 768, 192, 1792) + BookInterface.setItemOnPage(player, 3, Items.TINDERBOX_590, BookInterface.FANCY_BOOK_2_27, BookInterface.FANCY_BOOK_2_27_IMAGE_ENABLE_DRAW_IDS[2], BookInterface.FANCY_BOOK_2_27_IMAGE_DRAW_IDS[2], 768, 192, 1792) + BookInterface.setItemOnPage(player, 3, Items.BRONZE_PICKAXE_1265, BookInterface.FANCY_BOOK_2_27, BookInterface.FANCY_BOOK_2_27_IMAGE_ENABLE_DRAW_IDS[17], BookInterface.FANCY_BOOK_2_27_IMAGE_DRAW_IDS[17], 768, 192, 1792) + BookInterface.setModelOnPage(player, 4, 38202, BookInterface.FANCY_BOOK_2_27, BookInterface.FANCY_BOOK_2_27_IMAGE_ENABLE_DRAW_IDS[4], BookInterface.FANCY_BOOK_2_27_IMAGE_DRAW_IDS[4], 4048, 192, 768) + BookInterface.setItemOnPage(player, 4, Items.ROCK_4043, BookInterface.FANCY_BOOK_2_27, BookInterface.FANCY_BOOK_2_27_IMAGE_ENABLE_DRAW_IDS[17], BookInterface.FANCY_BOOK_2_27_IMAGE_DRAW_IDS[17], 768, 192, 1792) + return true + } + } + + override fun defineListeners() { + on(Items.CASTLEWARS_MANUAL_4055, IntType.ITEM, "read") { player, _ -> + BookInterface.openBook(player, BookInterface.FANCY_BOOK_2_27, ::display) + return@on true + } + } +} \ No newline at end of file From 789466e4abe47bdc86c01487b7c016474f9f69d8 Mon Sep 17 00:00:00 2001 From: Player Name Date: Mon, 10 Nov 2025 12:10:44 +0000 Subject: [PATCH 086/117] Fixed incorrect ordering of operations when opening the bank Somewhat improved sending of tab configurations and free space --- .../game/container/impl/BankContainer.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/Server/src/main/core/game/container/impl/BankContainer.java b/Server/src/main/core/game/container/impl/BankContainer.java index 2b1640293..5f8a51215 100644 --- a/Server/src/main/core/game/container/impl/BankContainer.java +++ b/Server/src/main/core/game/container/impl/BankContainer.java @@ -153,17 +153,16 @@ public final class BankContainer extends Container { BankContainer.this.close(); return true; }); - refresh(listener); player.getInterfaceManager().openSingleTab(new Component(763)); - player.getInventory().getListeners().add(player.getBank().listener); + refresh(listener); player.getInventory().refresh(); - setVarp(player, 1249, lastAmountX); + player.getInventory().getListeners().add(player.getBank().listener); + setVarp(player, 1249, lastAmountX); player.getPacketDispatch().sendIfaceSettings(1278, 73, 762, 0, SIZE); int settings = new IfaceSettingsBuilder().enableOptions(new IntRange(0,5)).enableExamine().enableSlotSwitch().build(); player.getPacketDispatch().sendIfaceSettings(settings, 0, 763, 0, 27); player.getPacketDispatch().sendRunScript(1451, ""); open = true; - } /** @@ -281,7 +280,7 @@ public final class BankContainer extends Container { */ public void updateLastAmountX(int amount) { this.lastAmountX = amount; - setVarp(player, 1249, amount); + setVarp(player, 1249, amount); } /** @@ -414,7 +413,7 @@ public final class BankContainer extends Container { * @return If items have to be noted {@code true}. */ public boolean isNoteItems() { - return getVarbit(player, Vars.VARBIT_IFACE_BANK_NOTE_MODE) == 1; + return getVarbit(player, Vars.VARBIT_IFACE_BANK_NOTE_MODE) == 1; } /** @@ -422,7 +421,7 @@ public final class BankContainer extends Container { * @param noteItems If items have to be noted {@code true}. */ public void setNoteItems(boolean noteItems) { - setVarbit(player, Vars.VARBIT_IFACE_BANK_NOTE_MODE, noteItems ? 1 : 0, true); + setVarbit(player, Vars.VARBIT_IFACE_BANK_NOTE_MODE, noteItems ? 1 : 0, true); } /** @@ -456,7 +455,7 @@ public final class BankContainer extends Container { * @param insertItems The insert items value. */ public void setInsertItems(boolean insertItems) { - setVarbit(player, Vars.VARBIT_IFACE_BANK_INSERT_MODE, insertItems ? 1 : 0, true); + setVarbit(player, Vars.VARBIT_IFACE_BANK_INSERT_MODE, insertItems ? 1 : 0, true); } /** @@ -464,7 +463,7 @@ public final class BankContainer extends Container { * @return {@code True} if inserting items mode is enabled. */ public boolean isInsertItems() { - return getVarbit(player, Vars.VARBIT_IFACE_BANK_INSERT_MODE) == 1; + return getVarbit(player, Vars.VARBIT_IFACE_BANK_INSERT_MODE) == 1; } /** @@ -498,22 +497,22 @@ public final class BankContainer extends Container { public void update(Container c, ContainerEvent event) { if (c instanceof BankContainer) { PacketRepository.send(ContainerPacket.class, new ContainerContext(player, 762, 64000, 95, event.getItems(), false, event.getSlots())); + player.getBank().setTabConfigurations(); + player.getBank().sendBankSpace(); } else { PacketRepository.send(ContainerPacket.class, new ContainerContext(player, 763, 64000, 93, event.getItems(), false, event.getSlots())); } - player.getBank().setTabConfigurations(); - player.getBank().sendBankSpace(); } @Override public void refresh(Container c) { if (c instanceof BankContainer) { PacketRepository.send(ContainerPacket.class, new ContainerContext(player, 762, 64000, 95, c.toArray(), c.capacity(), false)); + player.getBank().setTabConfigurations(); + player.getBank().sendBankSpace(); } else { PacketRepository.send(ContainerPacket.class, new ContainerContext(player, 763, 64000, 93, c.toArray(), 28, false)); } - player.getBank().setTabConfigurations(); - player.getBank().sendBankSpace(); } } } From 991d4aa364b283a0100ef3b83867809ac445060d Mon Sep 17 00:00:00 2001 From: Oven Bread Date: Mon, 10 Nov 2025 12:24:45 +0000 Subject: [PATCH 087/117] Added some newly found examine texts --- Server/data/configs/npc_configs.json | 2 +- Server/data/configs/object_configs.json | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Server/data/configs/npc_configs.json b/Server/data/configs/npc_configs.json index 87b666074..053f9ffe4 100644 --- a/Server/data/configs/npc_configs.json +++ b/Server/data/configs/npc_configs.json @@ -9414,7 +9414,7 @@ "attack_level": "15" }, { - "examine": "Children are just like real people...just smaller.", + "examine": "A sad looking child.", "melee_animation": "0", "range_animation": "0", "defence_animation": "0", diff --git a/Server/data/configs/object_configs.json b/Server/data/configs/object_configs.json index 54f50f614..ae364fd37 100644 --- a/Server/data/configs/object_configs.json +++ b/Server/data/configs/object_configs.json @@ -20055,6 +20055,10 @@ "examine": "Home sweet home?", "ids": "15480" }, + { + "examine": "Baby bread.", + "ids": "15506,15507" + }, { "examine": "Home sweet home?", "ids": "15748" @@ -20183,6 +20187,18 @@ "examine": "A short longboat!", "ids": "21834" }, + { + "examine": "A place to sit and watch furniture grow.", + "ids": "28627" + }, + { + "examine": "It's not rolling, but it seems to have gathered some moss.", + "ids": "28635" + }, + { + "examine": "A thick metal gate.", + "ids": "28690,28691,28692,28693" + }, { "examine": "Contains traces of summoning energy.", "ids": "29939,29943,29944,29945,29947,29951,29952,29953,29954" From f1f5a97859117cbc693b42d70cc8f551bbcb5755 Mon Sep 17 00:00:00 2001 From: Kennynes Date: Mon, 10 Nov 2025 12:28:13 +0000 Subject: [PATCH 088/117] Made the Black Demon cutscene more authentic and fixed Glough's chathead in the cutscene --- .../quest/grandtree/BlackDemonCutscene.kt | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Server/src/main/content/region/kandarin/quest/grandtree/BlackDemonCutscene.kt b/Server/src/main/content/region/kandarin/quest/grandtree/BlackDemonCutscene.kt index 2afe9f1cf..186d85616 100644 --- a/Server/src/main/content/region/kandarin/quest/grandtree/BlackDemonCutscene.kt +++ b/Server/src/main/content/region/kandarin/quest/grandtree/BlackDemonCutscene.kt @@ -1,20 +1,16 @@ package content.region.kandarin.quest.grandtree -import content.region.misthalin.dorgeshuun.quest.thelosttribe.LostTribeCutscene import core.ServerConstants -import core.api.openDialogue import core.api.sendChat -import core.api.sendDialogue -import core.api.sendMessage import core.game.activity.Cutscene import core.game.dialogue.FacialExpression import core.game.node.entity.player.Player import core.game.world.map.Direction import core.game.world.map.Location -import core.game.global.action.DoorActionHandler -import core.game.node.entity.npc.NPC +import core.game.world.update.flag.context.Animation import org.rs09.consts.NPCs +// Source video https://www.youtube.com/watch?v=LS1Xtuz0sLA class BlackDemonCutscene(player: Player) : Cutscene(player) { override fun setup() { setExit(Location.create(2491, 9864, 0)) @@ -23,7 +19,6 @@ class BlackDemonCutscene(player: Player) : Cutscene(player) { } loadRegion(9882) addNPC(NPCs.GLOUGH_671, 48, 8, Direction.WEST) - addNPC(NPCs.BLACK_DEMON_677, 43, 9, Direction.EAST) } override fun runStage(stage: Int) { @@ -40,6 +35,8 @@ class BlackDemonCutscene(player: Player) : Cutscene(player) { rotateCamera(0, 0) sendChat(player, "Hello?") player.face(getNPC(NPCs.GLOUGH_671)!!) + addNPC(NPCs.BLACK_DEMON_677, 39, 8, Direction.EAST) + move(getNPC(NPCs.BLACK_DEMON_677)!!, 41, 8) timedUpdate(3) } 2 -> { @@ -53,7 +50,7 @@ class BlackDemonCutscene(player: Player) : Cutscene(player) { timedUpdate(10) } 4 -> { - moveCamera(55, 4,2000) + moveCamera(55, 4, 2000) rotateCamera(55, 6) timedUpdate(4) } @@ -62,24 +59,27 @@ class BlackDemonCutscene(player: Player) : Cutscene(player) { playerDialogueUpdate(FacialExpression.SCARED, "Glough?") } 6 -> { - dialogueUpdate(NPCs.GLOUGH_671, FacialExpression.ANGRY, "You really are becoming a headache! Well, at least now you can die knowing you were right, it will save me having to hunt you down like all the other human filth of " + ServerConstants.SERVER_NAME + "!") + dialogueUpdate(NPCs.GLOUGH_671, FacialExpression.OLD_ANGRY1, "You really are becoming a headache! Well, at least now you can die knowing you were right, it will save me having to hunt you down like all the other human filth of " + ServerConstants.SERVER_NAME + "!") } 7 -> { playerDialogueUpdate(FacialExpression.SCARED, "You're crazy, Glough!") } 8 -> { - dialogueUpdate(NPCs.GLOUGH_671, FacialExpression.ANGRY, "Bah! Well, soon you'll see, the gnomes are ready to fight. In three weeks this tree will be dead wood, in ten weeks it will be 30 battleships! Finally we will rid the world of the disease called humanity!") + dialogueUpdate(NPCs.GLOUGH_671, FacialExpression.OLD_ANGRY1, "Bah! Well, soon you'll see, the gnomes are ready to fight. In three weeks this tree will be dead wood, in ten weeks it will be 30 battleships! Finally we will rid the world of the disease called humanity!") } 9 -> { playerDialogueUpdate(FacialExpression.SCARED, "What makes you think I'll let you get away with it?") } 10 -> { - moveCamera(47,9) - rotateCamera(40, 9) - dialogueUpdate(NPCs.GLOUGH_671, FacialExpression.ANGRY, "Fool...meet my little friend!") + moveCamera(55, 8, 700) + rotateCamera(48, 8) + val demon = getNPC(NPCs.BLACK_DEMON_677)!! + move(demon, 46, 8) + demon.animate(Animation(64, 140)) + dialogueUpdate(NPCs.GLOUGH_671, FacialExpression.OLD_ANGRY1, "Fool...meet my little friend!") } 11 -> { - end{ + end(fade = false){ BlackDemonNPC(NPCs.BLACK_DEMON_677, Location.create(2485, 9864, 0)).init() } } From 618baf0662eb47d3748c4b98b232d3dd94e7b3e5 Mon Sep 17 00:00:00 2001 From: Kennynes Date: Mon, 10 Nov 2025 12:30:03 +0000 Subject: [PATCH 089/117] Rewrote nettle tea handlers, additionally allowing emptying of cup of tea --- .../handlers/item/EmptyOptionListener.kt | 1 + .../global/skill/cooking/NettleTeaListener.kt | 18 ++++++ .../global/skill/cooking/NettleTeaPlugin.java | 61 ------------------- .../skill/cooking/NettleWaterListener.kt | 18 ++++++ .../skill/cooking/NettleWaterPlugin.java | 35 ----------- 5 files changed, 37 insertions(+), 96 deletions(-) create mode 100644 Server/src/main/content/global/skill/cooking/NettleTeaListener.kt delete mode 100644 Server/src/main/content/global/skill/cooking/NettleTeaPlugin.java create mode 100644 Server/src/main/content/global/skill/cooking/NettleWaterListener.kt delete mode 100644 Server/src/main/content/global/skill/cooking/NettleWaterPlugin.java diff --git a/Server/src/main/content/global/handlers/item/EmptyOptionListener.kt b/Server/src/main/content/global/handlers/item/EmptyOptionListener.kt index b4ae28212..89dd5caae 100644 --- a/Server/src/main/content/global/handlers/item/EmptyOptionListener.kt +++ b/Server/src/main/content/global/handlers/item/EmptyOptionListener.kt @@ -48,6 +48,7 @@ class EmptyOptionListener : InteractionListener { POTION(Items.POTION_195, Items.VIAL_229, "You empty the vial.", Sounds.LIQUID_2401), BURNT_STEW(Items.BURNT_STEW_2005, Items.BOWL_1923, "You empty the contents of the bowl onto the floor.", Sounds.LIQUID_2401), NETTLE_TEA(Items.NETTLE_TEA_4239, Items.BOWL_1923, "You empty the contents of the bowl onto the floor.", Sounds.LIQUID_2401), + CUP_OF_TEA(Items.CUP_OF_TEA_4242, Items.EMPTY_CUP_1980, "You empty the cup of tea.", Sounds.LIQUID_2401), NETTLE_WATER(Items.NETTLE_WATER_4237, Items.BOWL_1923, "You empty the contents of the bowl onto the floor.", Sounds.LIQUID_2401), NETTLE_TEA_MILKY(Items.NETTLE_TEA_4240, Items.BOWL_1923, "You empty the contents of the bowl onto the floor.", Sounds.LIQUID_2401), BURNT_CURRY(Items.BURNT_CURRY_2013, Items.BOWL_1923, "You empty the contents of the bowl onto the floor.", Sounds.LIQUID_2401), diff --git a/Server/src/main/content/global/skill/cooking/NettleTeaListener.kt b/Server/src/main/content/global/skill/cooking/NettleTeaListener.kt new file mode 100644 index 000000000..ac1765428 --- /dev/null +++ b/Server/src/main/content/global/skill/cooking/NettleTeaListener.kt @@ -0,0 +1,18 @@ +package content.global.skill.cooking + +import org.rs09.consts.Items +import core.api.replaceSlot +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.item.Item + +class NettleTeaListener : InteractionListener { + + override fun defineListeners() { + onUseWith(IntType.ITEM, Items.EMPTY_CUP_1980, Items.NETTLE_TEA_4239) { player, used, with -> + replaceSlot(player, with.asItem().slot, Item(Items.BOWL_1923), with.asItem()) + replaceSlot(player, used.asItem().slot, Item(Items.CUP_OF_TEA_4242), used.asItem()) + return@onUseWith true + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/cooking/NettleTeaPlugin.java b/Server/src/main/content/global/skill/cooking/NettleTeaPlugin.java deleted file mode 100644 index 161c99991..000000000 --- a/Server/src/main/content/global/skill/cooking/NettleTeaPlugin.java +++ /dev/null @@ -1,61 +0,0 @@ -package content.global.skill.cooking; - -import core.game.interaction.NodeUsageEvent; -import core.game.interaction.UseWithHandler; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; -import core.plugin.Initializable; -import core.plugin.Plugin; - -/** - * Represents the plugin used to create nettle tea in a cup. - * @author 'Vexia - * @version 1.0 - */ -@Initializable -public final class NettleTeaPlugin extends UseWithHandler { - - /** - * Represents the empty cup item. - */ - private static final Item EMPTY_CUP = new Item(1980, 1); - - /** - * Represents the nettle tea item. - */ - private static final Item NETTLE_TEA = new Item(4239, 1); - - /** - * Represents the bowl item. - */ - private static final Item BOWL = new Item(1923); - - /** - * Represents the cup of tea item. - */ - private static final Item CUP_OF_TEA = new Item(4242, 1); - - /** - * Constructs a new {@code NettleTeaPlugin} {@code Object}. - */ - public NettleTeaPlugin() { - super(1980); - } - - @Override - public Plugin newInstance(Object arg) throws Throwable { - addHandler(4239, ITEM_TYPE, this); - return this; - } - - @Override - public boolean handle(NodeUsageEvent event) { - final Player player = event.getPlayer(); - if (player.getInventory().remove(EMPTY_CUP) && player.getInventory().remove(NETTLE_TEA)) { - player.getInventory().add(BOWL); - player.getInventory().add(CUP_OF_TEA); - } - return true; - } - -} diff --git a/Server/src/main/content/global/skill/cooking/NettleWaterListener.kt b/Server/src/main/content/global/skill/cooking/NettleWaterListener.kt new file mode 100644 index 000000000..bf52e8067 --- /dev/null +++ b/Server/src/main/content/global/skill/cooking/NettleWaterListener.kt @@ -0,0 +1,18 @@ +package content.global.skill.cooking + +import org.rs09.consts.Items +import core.api.replaceSlot +import core.api.removeItem +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.item.Item + +class NettleWaterListener : InteractionListener { + override fun defineListeners() { + onUseWith(IntType.ITEM, Items.BOWL_OF_WATER_1921, Items.NETTLES_4241) { player, used, with -> + replaceSlot(player, used.asItem().slot, Item(Items.NETTLE_WATER_4237), used.asItem()) + removeItem(player, with.asItem()) + return@onUseWith true + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/cooking/NettleWaterPlugin.java b/Server/src/main/content/global/skill/cooking/NettleWaterPlugin.java deleted file mode 100644 index e8dbeb385..000000000 --- a/Server/src/main/content/global/skill/cooking/NettleWaterPlugin.java +++ /dev/null @@ -1,35 +0,0 @@ -package content.global.skill.cooking; - -import core.game.interaction.NodeUsageEvent; -import core.game.interaction.UseWithHandler; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; -import core.plugin.Initializable; -import core.plugin.Plugin; - -/** - * @author Adam - */ -@Initializable -public class NettleWaterPlugin extends UseWithHandler { - - public NettleWaterPlugin() { - super(1921); - } - - @Override - public boolean handle(NodeUsageEvent event) { - final Player player = event.getPlayer(); - player.getInventory().remove(new Item(1921, 1)); - player.getInventory().remove(new Item(4241, 1)); - player.getInventory().add(new Item(4237, 1)); - return true; - } - - @Override - public Plugin newInstance(Object arg) throws Throwable { - addHandler(4241, ITEM_TYPE, this); - return this; - } - -} From 8694d36ee8b7999c7a885ca3befd8489854bcdf4 Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Mon, 10 Nov 2025 12:33:06 +0000 Subject: [PATCH 090/117] Another round of Entrana allowed item auditing, now much more authentic --- .../core/cache/def/impl/ItemDefinition.java | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/Server/src/main/core/cache/def/impl/ItemDefinition.java b/Server/src/main/core/cache/def/impl/ItemDefinition.java index 13eb0940e..b7c17e5ec 100644 --- a/Server/src/main/core/cache/def/impl/ItemDefinition.java +++ b/Server/src/main/core/cache/def/impl/ItemDefinition.java @@ -1,6 +1,5 @@ package core.cache.def.impl; -import content.global.skill.summoning.familiar.BurdenBeast; import core.api.EquipmentSlot; import core.cache.Cache; import core.cache.def.Definition; @@ -605,12 +604,11 @@ public class ItemDefinition extends Definition { * @return {@code True} if so. */ public static boolean canEnterEntrana(Player player) { - Container[] containers; - if (player.getFamiliarManager().hasFamiliar() && player.getFamiliarManager().getFamiliar().isBurdenBeast()) { - containers = new Container[] { player.getInventory(), player.getEquipment(), ((BurdenBeast) player.getFamiliarManager().getFamiliar()).getContainer() }; - } else { - containers = new Container[] { player.getInventory(), player.getEquipment() }; + Container[] containers = new Container[] { player.getInventory(), player.getEquipment() }; + if (player.getFamiliarManager().hasFamiliar() && !player.getFamiliarManager().hasPet()) { + return false; } + for (Container c : containers) { for (Item i : c.toArray()) { if (i == null) { @@ -650,6 +648,7 @@ public class ItemDefinition extends Definition { Items.BOOK_OF_BALANCE_3844, Items.DAMAGED_BOOK_3843, Items.WIZARD_BOOTS_2579, + Items.COMBAT_BRACELET_11126, Items.COMBAT_BRACELET1_11124, Items.COMBAT_BRACELET2_11122, Items.COMBAT_BRACELET3_11120, @@ -665,10 +664,28 @@ public class ItemDefinition extends Definition { Items.HAM_HOOD_4302, Items.HAM_CLOAK_4304, Items.HAM_LOGO_4306, - Items.GLOVES_4308, - Items.BOOTS_4310, Items.ZAMORAK_ROBE_1033, - Items.ZAMORAK_ROBE_1035 + Items.ZAMORAK_ROBE_1035, + Items.PRIEST_GOWN_426, + Items.PRIEST_GOWN_428, + Items.DRUIDS_ROBE_538, + Items.DRUIDS_ROBE_540, + Items.FLOWERS_2460, + Items.FLOWERS_2462, + Items.FLOWERS_2464, + Items.FLOWERS_2466, + Items.FLOWERS_2468, + Items.FLOWERS_2470, + Items.FLOWERS_2472, + Items.FLOWERS_2474, + Items.FLOWERS_2476, + Items.FIXED_DEVICE_6082, + Items.GHOSTLY_BOOTS_6106, + Items.GHOSTLY_ROBE_6107, + Items.GHOSTLY_ROBE_6108, + Items.GHOSTLY_HOOD_6109, + Items.GHOSTLY_GLOVES_6110, + Items.GHOSTLY_CLOAK_6111 )); private static final HashSet entranaBannedItems = new HashSet(Arrays.asList( /**Items.BUTTERFLY_NET_10010, easing the restriction until barehanded implementation**/ @@ -677,15 +694,10 @@ public class ItemDefinition extends Definition { Items.CANNON_BASE_6, Items.CANNON_STAND_8, Items.CANNON_FURNACE_12, - Items.COOKING_GAUNTLETS_775, - Items.CHAOS_GAUNTLETS_777, - Items.GOLDSMITH_GAUNTLETS_776, - Items.KARAMJA_GLOVES_1_11136, - Items.KARAMJA_GLOVES_2_11138, - Items.KARAMJA_GLOVES_3_11140, - Items.VYREWATCH_TOP_9634, - Items.VYREWATCH_LEGS_9636, - Items.VYREWATCH_SHOES_9638 + Items.ZAMORAK_STOLE_10474, + Items.EXPLORERS_RING_1_13560, + Items.EXPLORERS_RING_2_13561, + Items.EXPLORERS_RING_3_13562 )); @@ -700,11 +712,11 @@ public class ItemDefinition extends Definition { if (entranaBannedItems.contains(getId())) { return false; } - if (equipSlot(getId()) == EquipmentSlot.AMMO) { + if (equipSlot(getId()) == EquipmentSlot.AMMO || equipSlot(getId()) == EquipmentSlot.NECK || equipSlot(getId()) == EquipmentSlot.RING) { return true; } - if (getName().toLowerCase().startsWith("ring") || getName().toLowerCase().startsWith("amulet")) { - return true; + if (hasAction("summon")) { + return false; } int[] bonuses = getConfiguration(ItemConfigParser.BONUS); return bonuses == null || Arrays.stream(bonuses).allMatch(x -> x == 0); From e6b1203be4d31eceba61b074a7d2f31bfea4c834 Mon Sep 17 00:00:00 2001 From: Ryan <2804894-ryannathans@users.noreply.gitlab.com> Date: Tue, 25 Nov 2025 18:10:02 +1100 Subject: [PATCH 091/117] Fixed IP address not getting written to DB on login --- .../main/core/game/node/entity/player/info/login/LoginParser.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/Server/src/main/core/game/node/entity/player/info/login/LoginParser.kt b/Server/src/main/core/game/node/entity/player/info/login/LoginParser.kt index 75eb6e4c6..649db34a5 100644 --- a/Server/src/main/core/game/node/entity/player/info/login/LoginParser.kt +++ b/Server/src/main/core/game/node/entity/player/info/login/LoginParser.kt @@ -41,6 +41,7 @@ class LoginParser(val details: PlayerDetails) { loginListeners.forEach(Consumer { listener: LoginListener -> listener.login(player) }) //Run our login hooks parser.runContentHooks() //Run our saved-content-parsing hooks player.details.session.setObject(player) + player.getDetails().accountInfo.lastUsedIp = player.getDetails().getIpAddress() if (reconnect) { reconnect(player) } else { From 4b3cfae309cb57a10b1e4fec67cc8368afffd34f Mon Sep 17 00:00:00 2001 From: oftheshire Date: Tue, 25 Nov 2025 07:28:09 +0000 Subject: [PATCH 092/117] Fixed typo in Larxus's dialogue (champion's challenge) --- .../cchallange/ChampionChallengeListener.kt | 25 +++++++++--------- .../activity/cchallange/LarxusDialogue.kt | 26 +++++++++---------- .../activity/cchallange/npc/ImpChampionNPC.kt | 20 +++----------- 3 files changed, 30 insertions(+), 41 deletions(-) diff --git a/Server/src/main/content/global/activity/cchallange/ChampionChallengeListener.kt b/Server/src/main/content/global/activity/cchallange/ChampionChallengeListener.kt index e1e2cd6c8..1b9147172 100644 --- a/Server/src/main/content/global/activity/cchallange/ChampionChallengeListener.kt +++ b/Server/src/main/content/global/activity/cchallange/ChampionChallengeListener.kt @@ -45,35 +45,35 @@ class ChampionChallengeListener : InteractionListener, MapArea { private val IMP_SCROLL_TEXT = arrayOf( "How about picking on someone your own size? I'll", - "see you at the Champion's Guild.", + "see you at the Champions' Guild.", "", "Champion of Imps" ) private val GOBLIN_SCROLL_TEXT = arrayOf( "Fight me if you think you can human, I'll wait", - "for you in the Champion's Guild.", + "for you in the Champions' Guild.", "", "Champion of Goblins" ) private val SKELETON_SCROLL_TEXT = arrayOf( - "I'll be waiting at the Champions' Guild to", - "collect your bones.", + "I'll be waiting at the Champions' Guild to collect", + "your bones.", "", "Champion of Skeletons" ) private val ZOMBIE_SCROLL_TEXT = arrayOf( - "You come to Champions' Guild, you fight me,", - "I squish you, I get brains!", + "You come to Champions' Guild, you fight me, I", + "squish you, I get brains!", "", "Champion of Zombies" ) private val GIANT_SCROLL_TEXT = arrayOf( - "Get yourself to the Champions' Guild, if you", - "dare to face me puny human.", + "Get yourself to the Champions' Guild, if you dare", + "to face me puny human.", "", "Champion of Giants" ) @@ -93,21 +93,22 @@ class ChampionChallengeListener : InteractionListener, MapArea { ) private val EARTH_WARRIOR_TEXT = arrayOf( - "I challenge you to a duel, come to the arena beneath", - "the Champion's Guild and fight me if you dare.", + "I challenge you to a duel, come to the arena", + "beneath the Champions' Guild and fight me if you", + "dare.", "", "Champion of Earth Warriors" ) private val JOGRE_SCROLL_TEXT = arrayOf( "You think you can defeat me? Come to the", - "Champion's Guild and prove it!", + "Champions' Guild and prove it!", "", "Champion of Jogres" ) private val LESSER_DEMON_SCROLL_TEXT = arrayOf( - "Come to the Champion's Guild so I can banish", + "Come to the Champions' Guild so I can banish", "you mortal!", "", "Champion of Lesser Demons" diff --git a/Server/src/main/content/global/activity/cchallange/LarxusDialogue.kt b/Server/src/main/content/global/activity/cchallange/LarxusDialogue.kt index 6ec919bd7..3157b6b50 100644 --- a/Server/src/main/content/global/activity/cchallange/LarxusDialogue.kt +++ b/Server/src/main/content/global/activity/cchallange/LarxusDialogue.kt @@ -27,24 +27,24 @@ class LarxusDialogue(val ChallengeStart: Boolean = false) : DialogueFile() { 0 -> { face(findNPC(NPCs.LARXUS_3050)!!, player!!, 1) for (i in scrolls)when{ - inInventory(player!!,Items.CHAMPION_SCROLL_6798) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're not allowed to use prayer's. Do you still want to proceed?").also { stage = 1 } - inInventory(player!!,Items.CHAMPION_SCROLL_6799) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're allowed to use only weapons. Do you still want to proceed?").also { stage = 1 } - inInventory(player!!,Items.CHAMPION_SCROLL_6800) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're allowed to use only melee combat skill. Do you still want to proceed?").also { stage = 1 } - inInventory(player!!,Items.CHAMPION_SCROLL_6801) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're allowed to use only magic skill. Do you still want to proceed?").also { stage = 1 } - inInventory(player!!,Items.CHAMPION_SCROLL_6802) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're not allowed to use melee combat skills. Do you still want to proceed?").also { stage = 1 } - inInventory(player!!,Items.CHAMPION_SCROLL_6803) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're not allowed to use weapons with special attack. Do you still want to proceed?").also { stage = 1 } - inInventory(player!!,Items.CHAMPION_SCROLL_6804) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're not allowed to use ranged skill. Do you still want to proceed?").also { stage = 1 } - inInventory(player!!,Items.CHAMPION_SCROLL_6805) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're allowed to use only equipment. Do you still want to proceed?").also { stage = 1 } - inInventory(player!!,Items.CHAMPION_SCROLL_6806) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're allowed to use only ranged skill. Do you still want to proceed?").also { stage = 1 } - inInventory(player!!,Items.CHAMPION_SCROLL_6807) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're not allowed to use magic skill. Do you still want to proceed?").also { stage = 1 } + inInventory(player!!,Items.CHAMPION_SCROLL_6798) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're not allowed to use any Prayers. Do you still want to proceed?").also { stage = 1 } + inInventory(player!!,Items.CHAMPION_SCROLL_6799) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're only allowed to take Weapons, no other items are allowed. Do you still want to proceed?").also { stage = 1 } + inInventory(player!!,Items.CHAMPION_SCROLL_6800) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're only allowed to use Melee attacks, no Ranged or Magic. Do you still want to proceed?").also { stage = 1 } + inInventory(player!!,Items.CHAMPION_SCROLL_6801) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're only allowed to use Magic attacks, no Melee or Ranged. Do you still want to proceed?").also { stage = 1 } + inInventory(player!!,Items.CHAMPION_SCROLL_6802) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're not allowed to use any Melee attacks. Do you still want to proceed?").also { stage = 1 } + inInventory(player!!,Items.CHAMPION_SCROLL_6803) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're not allowed to use any Special Attacks. Do you still want to proceed?").also { stage = 1 } + inInventory(player!!,Items.CHAMPION_SCROLL_6804) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're not allowed to use any Ranged attacks. Do you still want to proceed?").also { stage = 1 } + inInventory(player!!,Items.CHAMPION_SCROLL_6805) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're not allowed any Weapons or Armour. Do you still want to proceed?").also { stage = 1 } + inInventory(player!!,Items.CHAMPION_SCROLL_6806) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're only allowed to use Ranged attacks, no Melee or Magic. Do you still want to proceed?").also { stage = 1 } + inInventory(player!!,Items.CHAMPION_SCROLL_6807) -> npcl("So you want to accept the challenge huh? Well there are some specific rules for these Champion fights. For this fight you're not allowed to use any Magic attacks. Do you still want to proceed?").also { stage = 1 } else -> { sendMessage(player!!, "Nothing interesting happens.").also { stage = END_DIALOGUE } } } } - 1 -> options("Yes, let me at him!", "No, thanks I'll pass.").also { stage = 2 } + 1 -> options("Yes, let me at him!", "No thanks, I'll pass.").also { stage = 2 } 2 -> when (buttonID) { 1 -> playerl("Yes, let me at him!").also { stage = 3 } - 2 -> playerl("No, thanks I'll pass.").also { stage = END_DIALOGUE } + 2 -> playerl("No thanks, I'll pass.").also { stage = END_DIALOGUE } } 3 -> npcl("Your challenger is ready, please go down through the trapdoor when you're ready.").also { stage = 4 } 4 -> { @@ -65,7 +65,7 @@ class LarxusDialogue(val ChallengeStart: Boolean = false) : DialogueFile() { 3 -> playerl("Nothing thanks.").also { stage = END_DIALOGUE } } 3 -> npcl("Well pass it here and we'll get you started.").also { stage = END_DIALOGUE } - 4 -> npcl("This is the champions' arena, the champions of various, races use it to duel those they deem worthy of the, honour.").also { stage = END_DIALOGUE } + 4 -> npcl("This is the champions' arena. The champions of various races use it to duel those they deem worthy of the honour. If you find a challenge scroll in your travels, bring it here to face your challenger.").also { stage = END_DIALOGUE } } } } diff --git a/Server/src/main/content/global/activity/cchallange/npc/ImpChampionNPC.kt b/Server/src/main/content/global/activity/cchallange/npc/ImpChampionNPC.kt index 4a81fc066..1c131efc9 100644 --- a/Server/src/main/content/global/activity/cchallange/npc/ImpChampionNPC.kt +++ b/Server/src/main/content/global/activity/cchallange/npc/ImpChampionNPC.kt @@ -3,8 +3,6 @@ package content.global.activity.cchallange.npc import core.api.* import core.game.node.entity.Entity import core.game.node.entity.combat.BattleState -import core.game.node.entity.combat.CombatStyle -import core.game.node.entity.combat.equipment.WeaponInterface import core.game.node.entity.npc.AbstractNPC import core.game.node.entity.player.Player import core.game.node.entity.skill.Skills @@ -63,21 +61,11 @@ class ImpChampionNPC(id: Int = 0, location: Location? = null) : AbstractNPC(id, super.checkImpact(state) val player = state.attacker if (player is Player) { - val w = player.getExtension(WeaponInterface::class.java) - if (state.style == CombatStyle.MELEE || state.style == CombatStyle.MAGIC || state.style == CombatStyle.RANGE) { + + //somehow the maximumHit is determined to be zero by this point if you're using a melee special attack. + if (state.maximumHit == 0) { state.neutralizeHits() - state.estimatedHit = state.maximumHit - } - if (w.weaponInterface.interfaceId == 10) { - sendMessage(player, "You cannot use special attack in this challenge.") - if (state.estimatedHit > -1) { - state.estimatedHit = 0 - return - } - if (state.secondaryHit > -1) { - state.secondaryHit = 0 - return - } + sendMessage(player, "Larxus said you couldn't use special attacks in this duel.") } } } From f25f439bdf095a4fb2fc2d624843d5ee1abc18d4 Mon Sep 17 00:00:00 2001 From: dam <27978131-real_damighty@users.noreply.gitlab.com> Date: Tue, 25 Nov 2025 09:36:01 +0200 Subject: [PATCH 093/117] Fixed female characters not being able to pay Falador Hairdresser hairstyle change fee --- .../main/content/global/handlers/iface/HairDresserInterface.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/src/main/content/global/handlers/iface/HairDresserInterface.kt b/Server/src/main/content/global/handlers/iface/HairDresserInterface.kt index b831471f2..d9058006e 100644 --- a/Server/src/main/content/global/handlers/iface/HairDresserInterface.kt +++ b/Server/src/main/content/global/handlers/iface/HairDresserInterface.kt @@ -174,7 +174,7 @@ class HairDresserInterface : ComponentPlugin(){ when(button){ 199 -> player.setAttribute("beard-setting",false) 200 -> player.setAttribute("beard-setting",true) - 196,274 -> pay(player) + 196,274,68,100 -> pay(player) else -> when(component?.id){ 592 -> { //Female if(femaleColorButtonRange.contains(button)){ From 8d182f25052a197c3304158f2b154ce109195092 Mon Sep 17 00:00:00 2001 From: Player Name Date: Tue, 25 Nov 2025 08:04:03 +0000 Subject: [PATCH 094/117] Corrected the Ape Atoll teleport location Added Solihib npc spawn Implemented Solihib's store --- Server/data/configs/npc_configs.json | 1 + Server/data/configs/npc_spawns.json | 4 ++ .../skill/magic/modern/ModernListeners.kt | 2 +- .../dialogue/marim/SolihibDialogue.kt | 42 +++++++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 Server/src/main/content/region/misc/apeatoll/dialogue/marim/SolihibDialogue.kt diff --git a/Server/data/configs/npc_configs.json b/Server/data/configs/npc_configs.json index 053f9ffe4..8bbc43f55 100644 --- a/Server/data/configs/npc_configs.json +++ b/Server/data/configs/npc_configs.json @@ -74752,6 +74752,7 @@ }, { "name": "Solihib", + "movement_radius": "1", "id": "1433" }, { diff --git a/Server/data/configs/npc_spawns.json b/Server/data/configs/npc_spawns.json index cee858845..fd15f374c 100644 --- a/Server/data/configs/npc_spawns.json +++ b/Server/data/configs/npc_spawns.json @@ -3771,6 +3771,10 @@ "npc_id": "1427", "loc_data": "{2957,3025,0,0,0}-" }, + { + "npc_id": "1433", + "loc_data": "{2770,2789,0,1,6}-" + }, { "npc_id": "1434", "loc_data": "{2753,2770,0,1,6}-" diff --git a/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt b/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt index e36eaa577..d070123a3 100644 --- a/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt +++ b/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt @@ -96,7 +96,7 @@ class ModernListeners : SpellListener("modern"){ if (!hasRequirement(player, Quests.MONKEY_MADNESS)) return@onCast requires(player,64, arrayOf(Item(Items.FIRE_RUNE_554,2),Item(Items.WATER_RUNE_555,2),Item(Items.LAW_RUNE_563,2),Item(Items.BANANA_1963))) - sendTeleport(player,74.0, Location.create(2754, 2784, 0)) + sendTeleport(player,74.0, Location.create(2795, 2798, 1)) } onCast(Modern.TELEPORT_TO_HOUSE, NONE){ player, _ -> diff --git a/Server/src/main/content/region/misc/apeatoll/dialogue/marim/SolihibDialogue.kt b/Server/src/main/content/region/misc/apeatoll/dialogue/marim/SolihibDialogue.kt new file mode 100644 index 000000000..381e3e856 --- /dev/null +++ b/Server/src/main/content/region/misc/apeatoll/dialogue/marim/SolihibDialogue.kt @@ -0,0 +1,42 @@ +package content.region.misc.apeatoll.dialogue.marim + +import content.data.Quests +import core.api.hasRequirement +import core.api.openNpcShop +import core.game.dialogue.ChatAnim +import core.game.dialogue.DialogueLabeller +import core.game.dialogue.DialogueOption +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.npc.NPC +import org.rs09.consts.NPCs + +class SolihibDialogue : InteractionListener { + override fun defineListeners() { + on(NPCs.SOLIHIB_1433, IntType.NPC, "talk-to") { player, node -> + if (!hasRequirement(player, Quests.MONKEY_MADNESS)) return@on true + DialogueLabeller.open(player, SolihibDialogueLabellerFile(), node as NPC) + return@on true + } + on(NPCs.SOLIHIB_1433, IntType.NPC, "trade") { player, _ -> + if (!hasRequirement(player, Quests.MONKEY_MADNESS)) return@on true + openNpcShop(player, NPCs.SOLIHIB_1433) + return@on true + } + } + + class SolihibDialogueLabellerFile : DialogueLabeller() { + override fun addConversation() { + assignToIds(NPCs.SOLIHIB_1433) + + npc(ChatAnim.FRIENDLY, "Would you like to buy or sell some food?") + options( + DialogueOption("trade", "Yes, please."), + DialogueOption("nowhere", "No, thanks.") + ) + label("trade") + exec { player, _ -> openNpcShop(player, NPCs.SOLIHIB_1433) } + goto("nowhere") + } + } +} \ No newline at end of file From 9a7a8858181b7916d1d4d6cbd120439ce622b7d6 Mon Sep 17 00:00:00 2001 From: dam <27978131-real_damighty@users.noreply.gitlab.com> Date: Tue, 25 Nov 2025 10:12:24 +0200 Subject: [PATCH 095/117] Barrows equipment now degrades by 20% on grave death (fully repaired equipment is excluded from this) Barrows equipment now breaks and drops on non-grave deaths (instead of converting to coins) Rewrote Bob handlers --- Server/src/main/content/data/RepairItem.java | 87 --- Server/src/main/content/data/RepairItem.kt | 43 ++ .../item/equipment/BarrowsEquipment.kt | 244 ++++++++ .../equipment/BarrowsEquipmentRegister.kt | 67 +-- .../decoration/workshop/ArmourStand.kt | 101 ++-- .../lumbridge/dialogue/BobDialogue.java | 569 ------------------ .../lumbridge/dialogue/BobDialogue.kt | 266 ++++++++ .../lumbridge/handlers/BobRepairItem.java | 48 -- .../lumbridge/handlers/BobRepairItem.kt | 47 ++ .../game/node/entity/combat/graves/Grave.kt | 7 +- .../core/game/node/entity/player/Player.java | 14 +- 11 files changed, 681 insertions(+), 812 deletions(-) delete mode 100644 Server/src/main/content/data/RepairItem.java create mode 100644 Server/src/main/content/data/RepairItem.kt create mode 100644 Server/src/main/content/global/handlers/item/equipment/BarrowsEquipment.kt delete mode 100644 Server/src/main/content/region/misthalin/lumbridge/dialogue/BobDialogue.java create mode 100644 Server/src/main/content/region/misthalin/lumbridge/dialogue/BobDialogue.kt delete mode 100644 Server/src/main/content/region/misthalin/lumbridge/handlers/BobRepairItem.java create mode 100644 Server/src/main/content/region/misthalin/lumbridge/handlers/BobRepairItem.kt diff --git a/Server/src/main/content/data/RepairItem.java b/Server/src/main/content/data/RepairItem.java deleted file mode 100644 index 3ab25a4d5..000000000 --- a/Server/src/main/content/data/RepairItem.java +++ /dev/null @@ -1,87 +0,0 @@ -package content.data; - -import core.game.node.item.Item; - -/** - * Represents the repair item type. - * @author Vexia - */ -public enum RepairItem { - BRONZE_HATCHET(new Item(494, 1), new Item(1351, 1), 0), - BRONZE_PICKAXE(new Item(468, 1), new Item(1265, 1), 0), - IRON_HATCHET(new Item(496, 1), new Item(1349, 1), 0), - IRON_PICKAXE(new Item(470, 1), new Item(1267, 1), 0), - STEEL_HATCHET(new Item(498, 1), new Item(1353, 1), 0), - STEEL_PICKAXE(new Item(472, 1), new Item(1269, 1), 14), - BLACK_HATCHET(new Item(500, 1), new Item(1361, 1), 10), - MITHRIL_HATCHET(new Item(502, 1), new Item(1355, 1), 18), - MITHRIL_PICKAXE(new Item(474, 1), new Item(1273, 1), 43), - ADAMANT_HATCHET(new Item(504, 1), new Item(1357, 1), 43), - ADAMANT_PICKAXE(new Item(476, 1), new Item(1271, 1), 107), - RUNE_HATCHET(new Item(506, 1), new Item(1359, 1), 427), - RUNE_PICKAXE(new Item(478, 1), new Item(1275, 1), 1100), - DRAGON_HATCHET(new Item(6741, 1), new Item(6739, 1), 1800); - - /** - * The item id. - */ - private final Item item; - - /** - * The product item. - */ - private final Item product; - - /** - * The cost of the money to repair. - */ - private final int cost; - - /** - * Constructs a new {@code BobRepairItem} {@code Object}. - * @param item the item. - * @param product the product. - * @param cost the cost. - */ - RepairItem(Item item, Item product, int cost) { - this.item = item; - this.product = product; - this.cost = cost; - } - - /** - * Gets the item. - * @return The item. - */ - public Item getItem() { - return item; - } - - /** - * Gets the product. - * @return The product. - */ - public Item getProduct() { - return product; - } - - /** - * Gets the cost. - * @return The cost. - */ - public int getCost() { - return cost; - } - - /** - * Gets the reapir item by the id. - * @param id the id. - * @return the repair item. - */ - public static RepairItem forId(int id) { - for (RepairItem item : RepairItem.values()) - if (item.item.getId() == id) - return item; - return null; - } -} \ No newline at end of file diff --git a/Server/src/main/content/data/RepairItem.kt b/Server/src/main/content/data/RepairItem.kt new file mode 100644 index 000000000..4d89a7194 --- /dev/null +++ b/Server/src/main/content/data/RepairItem.kt @@ -0,0 +1,43 @@ +package content.data + +import core.game.node.item.Item + +/** + * Represents the repair item type. + * @author Vexia + * @author Damighty - Kotlin conversion + */ +enum class RepairItem( + val item: Item, + val product: Item, + val cost: Int +) { + BRONZE_HATCHET(Item(494, 1), Item(1351, 1), 0), + BRONZE_PICKAXE(Item(468, 1), Item(1265, 1), 0), + IRON_HATCHET(Item(496, 1), Item(1349, 1), 0), + IRON_PICKAXE(Item(470, 1), Item(1267, 1), 0), + STEEL_HATCHET(Item(498, 1), Item(1353, 1), 0), + STEEL_PICKAXE(Item(472, 1), Item(1269, 1), 14), + BLACK_HATCHET(Item(500, 1), Item(1361, 1), 10), + MITHRIL_HATCHET(Item(502, 1), Item(1355, 1), 18), + MITHRIL_PICKAXE(Item(474, 1), Item(1273, 1), 43), + ADAMANT_HATCHET(Item(504, 1), Item(1357, 1), 43), + ADAMANT_PICKAXE(Item(476, 1), Item(1271, 1), 107), + RUNE_HATCHET(Item(506, 1), Item(1359, 1), 427), + RUNE_PICKAXE(Item(478, 1), Item(1275, 1), 1100), + DRAGON_HATCHET(Item(6741, 1), Item(6739, 1), 1800); + + companion object { + /** + * List of all repairable item IDs. + */ + @JvmStatic + val repairableItemIds: List = values().map { it.item.id } + + /** + * Gets the repair item by the broken items ID. + */ + @JvmStatic + fun forId(id: Int): RepairItem? = values().firstOrNull { it.item.id == id } + } +} diff --git a/Server/src/main/content/global/handlers/item/equipment/BarrowsEquipment.kt b/Server/src/main/content/global/handlers/item/equipment/BarrowsEquipment.kt new file mode 100644 index 000000000..5cdbb874a --- /dev/null +++ b/Server/src/main/content/global/handlers/item/equipment/BarrowsEquipment.kt @@ -0,0 +1,244 @@ +package content.global.handlers.item.equipment + +import core.game.node.item.Item +import org.rs09.consts.Items + +/** + * Barrows equipment information and utilities. + * @author 'Vexia - original code + * @author Damighty - Kotlin conversion and refactor + */ +object BarrowsEquipment { + // Barrows equipment lasts for 15 hours of combat. Each piece has 4 degradation tiers (100, 75, 50, 25). + const val DEGRADATION_TICKS_PER_TIER = (15 * 6000) / 4 // 22500 ticks per tier + private const val MAX_DURABILITY = DEGRADATION_TICKS_PER_TIER * 4 + + /** + * A data class for each Barrows piece. Holds all information related to a specific piece of Barrows gear. + * + * @param brother The Barrows brother this item belongs to. + * @param equipmentType The slot type ("weapon", "body", "legs", "helm"). + * @param itemName The formatted name of the item. + * @param baseRepairCost The full repair cost in GP. + * @param degradationStates A list of item IDs, from fully repaired (index 0) to broken (index 5). + */ + data class BarrowsItemDefinition( + val brother: String, + val equipmentType: String, + val itemName: String, + val baseRepairCost: Int, + val degradationStates: List + ) { + val repairedId: Int = degradationStates.first() + val brokenId: Int = degradationStates.last() + + /** Checks if a given item ID belongs to this specific equipment set. */ + fun contains(itemId: Int): Boolean = itemId in degradationStates + + /** Gets the degradation index for a given item ID (0=repaired, 1=100, ..., 5=broken). */ + fun getDegradationIndex(itemId: Int): Int = degradationStates.indexOf(itemId) + } + + /** All Barrows equipment data. */ + private val barrowsItemDefinitions = listOf( + // Dharok + BarrowsItemDefinition("dharok", "helm", "Dharok's helm", 60_000, + listOf(Items.DHAROKS_HELM_4716, Items.DHAROKS_HELM_100_4880, + Items.DHAROKS_HELM_75_4881, Items.DHAROKS_HELM_50_4882, + Items.DHAROKS_HELM_25_4883, Items.DHAROKS_HELM_0_4884)), + BarrowsItemDefinition("dharok", "weapon", "Dharok's greataxe", 100_000, + listOf(Items.DHAROKS_GREATAXE_4718, Items.DHAROKS_AXE_100_4886, + Items.DHAROKS_AXE_75_4887, Items.DHAROKS_AXE_50_4888, + Items.DHAROKS_AXE_25_4889, Items.DHAROKS_AXE_0_4890)), + BarrowsItemDefinition("dharok", "body", "Dharok's platebody", 90_000, + listOf(Items.DHAROKS_PLATEBODY_4720, Items.DHAROKS_BODY_100_4892, + Items.DHAROKS_BODY_75_4893, Items.DHAROKS_BODY_50_4894, + Items.DHAROKS_BODY_25_4895, Items.DHAROKS_BODY_0_4896)), + BarrowsItemDefinition("dharok", "legs", "Dharok's platelegs", 80_000, + listOf(Items.DHAROKS_PLATELEGS_4722, Items.DHAROKS_LEGS_100_4898, + Items.DHAROKS_LEGS_75_4899, Items.DHAROKS_LEGS_50_4900, + Items.DHAROKS_LEGS_25_4901, Items.DHAROKS_LEGS_0_4902)), + // Guthan + BarrowsItemDefinition("guthan", "helm", "Guthan's helm", 60_000, + listOf(Items.GUTHANS_HELM_4724, Items.GUTHANS_HELM_100_4904, + Items.GUTHANS_HELM_75_4905, Items.GUTHANS_HELM_50_4906, + Items.GUTHANS_HELM_25_4907, Items.GUTHANS_HELM_0_4908)), + BarrowsItemDefinition("guthan", "weapon", "Guthan's warspear", 100_000, + listOf(Items.GUTHANS_WARSPEAR_4726, Items.GUTHANS_SPEAR_100_4910, + Items.GUTHANS_SPEAR_75_4911, Items.GUTHANS_SPEAR_50_4912, + Items.GUTHANS_SPEAR_25_4913, Items.GUTHANS_SPEAR_0_4914)), + BarrowsItemDefinition("guthan", "body", "Guthan's platebody", 90_000, + listOf(Items.GUTHANS_PLATEBODY_4728, Items.GUTHANS_BODY_100_4916, + Items.GUTHANS_BODY_75_4917, Items.GUTHANS_BODY_50_4918, + Items.GUTHANS_BODY_25_4919, Items.GUTHANS_BODY_0_4920)), + BarrowsItemDefinition("guthan", "legs", "Guthan's chainskirt", 80_000, + listOf(Items.GUTHANS_CHAINSKIRT_4730, Items.GUTHANS_SKIRT_100_4922, + Items.GUTHANS_SKIRT_75_4923, Items.GUTHANS_SKIRT_50_4924, + Items.GUTHANS_SKIRT_25_4925, Items.GUTHANS_SKIRT_0_4926)), + // Torag + BarrowsItemDefinition("torag", "helm", "Torag's helm", 60_000, + listOf(Items.TORAGS_HELM_4745, Items.TORAGS_HELM_100_4952, + Items.TORAGS_HELM_75_4953, Items.TORAGS_HELM_50_4954, + Items.TORAGS_HELM_25_4955, Items.TORAGS_HELM_0_4956)), + BarrowsItemDefinition("torag", "weapon", "Torag's hammers", 100_000, + listOf(Items.TORAGS_HAMMERS_4747, Items.TORAGS_HAMMER_100_4958, + Items.TORAGS_HAMMER_75_4959, Items.TORAGS_HAMMER_50_4960, + Items.TORAGS_HAMMER_25_4961, Items.TORAGS_HAMMER_0_4962)), + BarrowsItemDefinition("torag", "body", "Torag's platebody", 90_000, + listOf(Items.TORAGS_PLATEBODY_4749, Items.TORAGS_BODY_100_4964, + Items.TORAGS_BODY_75_4965, Items.TORAGS_BODY_50_4966, + Items.TORAGS_BODY_25_4967, Items.TORAGS_BODY_0_4968)), + BarrowsItemDefinition("torag", "legs", "Torag's platelegs", 80_000, + listOf(Items.TORAGS_PLATELEGS_4751, Items.TORAGS_LEGS_100_4970, + Items.TORAGS_LEGS_75_4971, Items.TORAGS_LEGS_50_4972, + Items.TORAGS_LEGS_25_4973, Items.TORAGS_LEGS_0_4974)), + // Verac + BarrowsItemDefinition("verac", "helm", "Verac's helm", 60_000, + listOf(Items.VERACS_HELM_4753, Items.VERACS_HELM_100_4976, + Items.VERACS_HELM_75_4977, Items.VERACS_HELM_50_4978, + Items.VERACS_HELM_25_4979, Items.VERACS_HELM_0_4980)), + BarrowsItemDefinition("verac", "weapon", "Verac's flail", 100_000, + listOf(Items.VERACS_FLAIL_4755, Items.VERACS_FLAIL_100_4982, + Items.VERACS_FLAIL_75_4983, Items.VERACS_FLAIL_50_4984, + Items.VERACS_FLAIL_25_4985, Items.VERACS_FLAIL_0_4986)), + BarrowsItemDefinition("verac", "body", "Verac's brassard", 90_000, + listOf(Items.VERACS_BRASSARD_4757, Items.VERACS_TOP_100_4988, + Items.VERACS_TOP_75_4989, Items.VERACS_TOP_50_4990, + Items.VERACS_TOP_25_4991, Items.VERACS_TOP_0_4992)), + BarrowsItemDefinition("verac", "legs", "Verac's plateskirt", 80_000, + listOf(Items.VERACS_PLATESKIRT_4759, Items.VERACS_SKIRT_100_4994, + Items.VERACS_SKIRT_75_4995, Items.VERACS_SKIRT_50_4996, + Items.VERACS_SKIRT_25_4997, Items.VERACS_SKIRT_0_4998)), + // Ahrim + BarrowsItemDefinition("ahrim", "helm", "Ahrim's hood", 60_000, + listOf(Items.AHRIMS_HOOD_4708, Items.AHRIMS_HOOD_100_4856, + Items.AHRIMS_HOOD_75_4857, Items.AHRIMS_HOOD_50_4858, + Items.AHRIMS_HOOD_25_4859, Items.AHRIMS_HOOD_0_4860)), + BarrowsItemDefinition("ahrim", "weapon", "Ahrim's staff", 100_000, + listOf(Items.AHRIMS_STAFF_4710, Items.AHRIMS_STAFF_100_4862, + Items.AHRIMS_STAFF_75_4863, Items.AHRIMS_STAFF_50_4864, + Items.AHRIMS_STAFF_25_4865, Items.AHRIMS_STAFF_0_4866)), + BarrowsItemDefinition("ahrim", "body", "Ahrim's robetop", 90_000, + listOf(Items.AHRIMS_ROBETOP_4712, Items.AHRIMS_TOP_100_4868, + Items.AHRIMS_TOP_75_4869, Items.AHRIMS_TOP_50_4870, + Items.AHRIMS_TOP_25_4871, Items.AHRIMS_TOP_0_4872)), + BarrowsItemDefinition("ahrim", "legs", "Ahrim's robeskirt", 80_000, + listOf(Items.AHRIMS_ROBESKIRT_4714, Items.AHRIMS_SKIRT_100_4874, + Items.AHRIMS_SKIRT_75_4875, Items.AHRIMS_SKIRT_50_4876, + Items.AHRIMS_SKIRT_25_4877, Items.AHRIMS_SKIRT_0_4878)), + // Karil + BarrowsItemDefinition("karil", "helm", "Karil's coif", 60_000, + listOf(Items.KARILS_COIF_4732, Items.KARILS_COIF_100_4928, + Items.KARILS_COIF_75_4929, Items.KARILS_COIF_50_4930, + Items.KARILS_COIF_25_4931, Items.KARILS_COIF_0_4932)), + BarrowsItemDefinition("karil", "weapon", "Karil's crossbow", 100_000, + listOf(Items.KARILS_CROSSBOW_4734, Items.KARILS_X_BOW_100_4934, + Items.KARILS_X_BOW_75_4935, Items.KARILS_X_BOW_50_4936, + Items.KARILS_X_BOW_25_4937, Items.KARILS_X_BOW_0_4938)), + BarrowsItemDefinition("karil", "body", "Karil's leathertop", 90_000, + listOf(Items.KARILS_LEATHERTOP_4736, Items.KARILS_TOP_100_4940, + Items.KARILS_TOP_75_4941, Items.KARILS_TOP_50_4942, + Items.KARILS_TOP_25_4943, Items.KARILS_TOP_0_4944)), + BarrowsItemDefinition("karil", "legs", "Karil's leatherskirt", 80_000, + listOf(Items.KARILS_LEATHERSKIRT_4738, Items.KARILS_SKIRT_100_4946, + Items.KARILS_SKIRT_75_4947, Items.KARILS_SKIRT_50_4948, + Items.KARILS_SKIRT_25_4949, Items.KARILS_SKIRT_0_4950)) + ) + + /** Cached access to an item's full definition from its ID */ + private val itemIdToDefinitionMap: Map by lazy { + barrowsItemDefinitions.flatMap { def -> + def.degradationStates.map { id -> id to def } + }.toMap() + } + + /** Gets all degradation state arrays for degradation registration */ + fun getAllEquipmentSets(): Collection = barrowsItemDefinitions.map { it.degradationStates.toIntArray() } + + /** Gets all repairable Barrows item IDs (anything not fully repaired) */ + fun getAllRepairableBarrowsIds(): List = itemIdToDefinitionMap.filter { !isFullyRepaired(it.key) }.keys.toList() + + /** Gets the full definition for a Barrows item */ + @JvmStatic + fun getDefinition(itemId: Int): BarrowsItemDefinition? = itemIdToDefinitionMap[itemId] + + /** Checks if an item ID is any Barrows item */ + @JvmStatic + fun isBarrowsItem(itemId: Int): Boolean = itemId in itemIdToDefinitionMap + + /** Checks if a Barrows item is fully repaired */ + @JvmStatic + fun isFullyRepaired(itemId: Int): Boolean = getDefinition(itemId)?.repairedId == itemId + + /** Checks if a Barrows item is broken */ + @JvmStatic + fun isBroken(itemId: Int): Boolean = getDefinition(itemId)?.brokenId == itemId + + /** + * Calculates the repair cost for a degraded Barrows item + * @param item The degraded Barrows item + * @return The repair cost in GP, or -1 if the item cannot be repaired + */ + fun getRepairCost(item: Item): Int { + val def = getDefinition(item.id) ?: return -1 + if (isFullyRepaired(item.id)) return -1 + + val totalRemainingDurability = calculateTotalRemainingDurability(item, def) + val durabilityLost = MAX_DURABILITY - totalRemainingDurability + val cost = ((durabilityLost.toDouble() / MAX_DURABILITY) * def.baseRepairCost).toInt() + + return cost + } + + /** + * Reduces the durability of a Barrows item by 20% of its total remaining durability + * @param item The Barrows item + * @return The item with reduced durability, or null if the item is not a valid Barrows piece + */ + @JvmStatic + fun graveDeathDurabilityReduction(item: Item): Item? { + val def = getDefinition(item.id) ?: return null + if (isBroken(item.id)) return item + + val totalRemainingDurability = calculateTotalRemainingDurability(item, def) + val durabilityReduction = (totalRemainingDurability * 0.2).toInt() + val newRemainingDurability = totalRemainingDurability - durabilityReduction + + return createItemFromDurability(def, newRemainingDurability, item.amount) + } + + /** + * Calculates the total remaining durability ticks (charge) for a Barrows item + */ + private fun calculateTotalRemainingDurability(item: Item, def: BarrowsItemDefinition): Int { + return when (val index = def.getDegradationIndex(item.id)) { + -1 -> 0 // Invalid + 0 -> MAX_DURABILITY // Fully repaired + 5 -> 0 // Broken + else -> { + // For degraded items (100, 75, 50, 25), durability is the number of fully remaining tiers below it, plus the charge of the current tier. + // Index 1 (100) has 3 tiers below it. Index 4 (25) has 0 tiers below it. + val remainingTiers = 4 - index + (remainingTiers * DEGRADATION_TICKS_PER_TIER) + item.charge + } + } + } + + /** + * Creates a new Item instance based on a total durability value + */ + private fun createItemFromDurability(def: BarrowsItemDefinition, durability: Int, amount: Int): Item { + if (durability <= 0) { + return Item(def.brokenId, amount) + } + // Tier is 1-indexed (1=25, 2=50, 3=75, 4=100) + val tier = ((durability - 1) / DEGRADATION_TICKS_PER_TIER) + 1 + // Degradation index is 4-indexed (4=25, 3=50, 2=75, 1=100) + val degradationIndex = 5 - tier + + val newId = def.degradationStates[degradationIndex] + val newCharge = (durability - 1) % DEGRADATION_TICKS_PER_TIER + 1 + + return Item(newId, amount, newCharge) + } +} diff --git a/Server/src/main/content/global/handlers/item/equipment/BarrowsEquipmentRegister.kt b/Server/src/main/content/global/handlers/item/equipment/BarrowsEquipmentRegister.kt index d7e57ab9e..1a0960711 100644 --- a/Server/src/main/content/global/handlers/item/equipment/BarrowsEquipmentRegister.kt +++ b/Server/src/main/content/global/handlers/item/equipment/BarrowsEquipmentRegister.kt @@ -4,73 +4,16 @@ import core.plugin.Initializable import core.plugin.Plugin @Initializable -class BarrowsEquipmentRegister : Plugin{ - public companion object { - // Barrows equipment lasts for 15 hours of combat, and each piece has 4 stages of degredation - @JvmField - public val TICKS = (15 * 6000) / 4 - } - - val DHAROK_HELM = arrayOf(4716,4880,4881,4882,4883,4884) - val DHAROK_AXE = arrayOf(4718,4886,4887,4888,4889,4890) - val DHAROK_BODY = arrayOf(4720,4892,4893,4894,4895,4896) - val DHAROK_LEGS = arrayOf(4722,4898,4899,4900,4901,4902) - - val GUTHAN_HELM = arrayOf(4724,4904,4905,4906,4907,4908) - val GUTHAN_SPEAR = arrayOf(4726,4910,4911,4912,4913,4914) - val GUTHAN_BODY = arrayOf(4728,4916,4917,4918,4919,4920) - val GUTHAN_SKIRT = arrayOf(4730,4922,4923,4924,4925,4926) - - val TORAG_HELM = arrayOf(4745,4952,4953,4954,4955,4956) - val TORAG_HAMMER = arrayOf(4747,4958,4959,4960,4961,4962) - val TORAG_BODY = arrayOf(4749,4964,4965,4966,4967,4968) - val TORAG_LEGS = arrayOf(4751,4970,4971,4972,4973,4974) - - val VERAC_HELM = arrayOf(4753,4976,4977,4978,4979,4980) - val VERAC_FLAIL = arrayOf(4755,4982,4983,4984,4985,4986) - val VERAC_BRASS = arrayOf(4757,4988,4989,4990,4991,4992) - val VERAC_SKIRT = arrayOf(4759,4994,4995,4996,4997,4998) - - val AHRIM_HOOD = arrayOf(4708,4856,4857,4858,4859,4860) - val AHRIM_STAFF = arrayOf(4710,4862,4863,4864,4865,4866) - val AHRIM_TOP = arrayOf(4712,4868,4869,4870,4871,4872) - val AHRIM_SKIRT = arrayOf(4714,4874,4875,4876,4877,4878) - - val KARIL_COIF = arrayOf(4732,4928,4929,4930,4931,4932) - val KARIL_CBOW = arrayOf(4734,4934,4935,4936,4937,4938) - val KARIL_TOP = arrayOf(4736,4940,4941,4942,4943,4944) - val KARIL_SKIRT = arrayOf(4738,4946,4947,4948,4949,4950) - +class BarrowsEquipmentRegister : Plugin { override fun newInstance(arg: Any?): Plugin { - EquipmentDegrader.registerSet(TICKS, AHRIM_HOOD) - EquipmentDegrader.registerSet(TICKS, AHRIM_STAFF) - EquipmentDegrader.registerSet(TICKS, AHRIM_TOP) - EquipmentDegrader.registerSet(TICKS, AHRIM_SKIRT) - EquipmentDegrader.registerSet(TICKS, KARIL_COIF) - EquipmentDegrader.registerSet(TICKS, KARIL_CBOW) - EquipmentDegrader.registerSet(TICKS, KARIL_TOP) - EquipmentDegrader.registerSet(TICKS, KARIL_SKIRT) - EquipmentDegrader.registerSet(TICKS, DHAROK_HELM) - EquipmentDegrader.registerSet(TICKS, DHAROK_AXE) - EquipmentDegrader.registerSet(TICKS, DHAROK_BODY) - EquipmentDegrader.registerSet(TICKS, DHAROK_LEGS) - EquipmentDegrader.registerSet(TICKS, GUTHAN_HELM) - EquipmentDegrader.registerSet(TICKS, GUTHAN_SPEAR) - EquipmentDegrader.registerSet(TICKS, GUTHAN_BODY) - EquipmentDegrader.registerSet(TICKS, GUTHAN_SKIRT) - EquipmentDegrader.registerSet(TICKS, TORAG_HELM) - EquipmentDegrader.registerSet(TICKS, TORAG_HAMMER) - EquipmentDegrader.registerSet(TICKS, TORAG_BODY) - EquipmentDegrader.registerSet(TICKS, TORAG_LEGS) - EquipmentDegrader.registerSet(TICKS, VERAC_HELM) - EquipmentDegrader.registerSet(TICKS, VERAC_FLAIL) - EquipmentDegrader.registerSet(TICKS, VERAC_BRASS) - EquipmentDegrader.registerSet(TICKS, VERAC_SKIRT) + BarrowsEquipment.getAllEquipmentSets().forEach { + degradationSet -> + EquipmentDegrader.registerSet(BarrowsEquipment.DEGRADATION_TICKS_PER_TIER, degradationSet.toTypedArray()) + } return this } override fun fireEvent(identifier: String?, vararg args: Any?): Any { return Unit } - } diff --git a/Server/src/main/content/global/skill/construction/decoration/workshop/ArmourStand.kt b/Server/src/main/content/global/skill/construction/decoration/workshop/ArmourStand.kt index b30f862d1..44a050754 100644 --- a/Server/src/main/content/global/skill/construction/decoration/workshop/ArmourStand.kt +++ b/Server/src/main/content/global/skill/construction/decoration/workshop/ArmourStand.kt @@ -4,8 +4,7 @@ import core.game.dialogue.DialoguePlugin import content.data.RepairItem import core.game.interaction.NodeUsageEvent import core.game.interaction.UseWithHandler -import content.region.misthalin.lumbridge.dialogue.BobDialogue.BarrowsEquipment -import content.region.misthalin.lumbridge.dialogue.BobDialogue.BarrowsEquipment.BarrowsFullEquipment +import content.global.handlers.item.equipment.BarrowsEquipment import core.game.node.entity.player.Player import core.game.node.entity.skill.Skills import core.game.node.item.Item @@ -14,8 +13,10 @@ import core.plugin.Plugin import kotlin.math.ceil import org.rs09.consts.Items +private val ALL_REPAIRABLE_ITEM_IDS = (RepairItem.repairableItemIds + BarrowsEquipment.getAllRepairableBarrowsIds()).toIntArray() + @Initializable -class ArmourStand : UseWithHandler(494, 468, 496, 470, 498, 472, 500, 502, 474, 504, 476, 506, 478, 6741, 4856, 4857, 4858, 4859, 4860, 4862, 4863, 4864, 4865, 4866, 4868, 4869, 4870, 4871, 4872, 4874, 4875, 4876, 4877, 4878, 4880, 4881, 4882, 4883, 4884, 4886, 4887, 4888, 4889, 4890, 4892, 4893, 4894, 4895, 4896, 4898, 4899, 4900, 4901, 4902, 4904, 4905, 4906, 4907, 4908, 4910, 4911, 4912, 4913, 4914, 4916, 4917, 4918, 4919, 4920, 4922, 4923, 4924, 4925, 4926, 4928, 4929, 4930, 4931, 4932, 4934, 4935, 4936, 4937, 4938, 4940, 4941, 4942, 4943, 4944, 4946, 4947, 4948, 4949, 4950, 4952, 4953, 4954, 4955, 4956, 4958, 4959, 4960, 4961, 4962, 4964, 4965, 4966, 4967, 4968, 4970, 4971, 4972, 4973, 4974, 4976, 4977, 4978, 4979, 4980, 4982, 4983, 4984, 4985, 4986, 4988, 4989, 4990, 4991, 4992, 4994, 4995, 4996, 4997, 4998){ +class ArmourStand : UseWithHandler(*ALL_REPAIRABLE_ITEM_IDS) { override fun newInstance(arg: Any?): Plugin { addHandler(13715, OBJECT_TYPE, this) return this @@ -24,76 +25,90 @@ class ArmourStand : UseWithHandler(494, 468, 496, 470, 498, 472, 500, 502, 474, override fun handle(event: NodeUsageEvent?): Boolean { event ?: return false val player = event.player - val repairItem = RepairItem.forId(event.used.id) + val usedItem = event.used.asItem() - var baseCost = 0.0 + var baseCost = 0 var product: Item? = null - if(repairItem != null){ - baseCost = repairItem.cost * 1.0 - product = repairItem.product - } else if(BarrowsEquipment.isBarrowsItem(event.used.id)){ - //Begin terrible code thanks to Vexia - val type = BarrowsEquipment.formatedName(event.used.id) - val single = BarrowsEquipment.getSingleName(type) - val equipment = BarrowsEquipment.getEquipmentType(type) - val newString = type.toLowerCase().replace(single, "").trim { it <= ' ' }.replace("'s", "") - val newewString = StringBuilder() - newewString.append(newString).append(" $equipment") - val fullequip = BarrowsFullEquipment.forName(newewString.toString()) - baseCost = BarrowsEquipment.getFormatedCost(equipment,event.used.asItem()) * 1.0 - product = fullequip.full - //End terrible code thanks to Vexia - } + val repairItem = RepairItem.forId(usedItem.id) + val barrowsDef = BarrowsEquipment.getDefinition(usedItem.id) - if((repairItem == null && baseCost == 0.0)){ + if (repairItem != null) { + baseCost = repairItem.cost + product = repairItem.product + } else if (barrowsDef != null) { + if (BarrowsEquipment.isFullyRepaired(event.used.id)) { + player.sendMessage("That item can't be repaired.") + return true + } + baseCost = BarrowsEquipment.getRepairCost(usedItem) + product = Item(barrowsDef.repairedId) + } else { player.sendMessage("That item can't be repaired.") return true } - val cost: Int = ceil(((100.0 - (player.skills.getLevel(Skills.SMITHING) / 2.0) ) / 100.0) * baseCost).toInt() - - player.dialogueInterpreter.open(58824213,event.used,cost,product) + val discountMultiplier = (100.0 - (player.skills.getLevel(Skills.SMITHING) / 2.0)) / 100.0 + val cost = ceil(discountMultiplier * baseCost).toInt() + player.dialogueInterpreter.open(58824213,usedItem, cost, product) return true } @Initializable - class RepairDialogue(player: Player? = null) : DialoguePlugin(player){ - override fun newInstance(player: Player?): DialoguePlugin { - return RepairDialogue(player) - } - var item: Item? = null - var cost: Int = 0 - var product: Item? = null + class RepairDialogue(player: Player? = null) : DialoguePlugin(player) { + + private var item: Item? = null + private var cost: Int = 0 + private var product: Item? = null + + override fun newInstance(player: Player?): DialoguePlugin = RepairDialogue(player) override fun open(vararg args: Any?): Boolean { item = args[0] as Item cost = args[1] as Int product = args[2] as Item - player.dialogueInterpreter.sendDialogue("Would you like to repair your ${(item as Item).name.toLowerCase()}","for $cost gp?") + + val itemName = item?.name?.lowercase() ?: "item" + player.dialogueInterpreter.sendDialogue( + "Would you like to repair your $itemName", + "for $cost gp?" + ) stage = 0 return true } override fun handle(interfaceId: Int, buttonId: Int): Boolean { - item ?: return false - product ?: return false - when(stage){ - 0 -> options("Yes, please","No, thanks").also{stage++} - 1 -> when(buttonId){ - 1 -> exchangeItems(item as Item,cost,product as Item).also { end() } + val currentItem = item ?: return false + val currentProduct = product ?: return false + + when (stage) { + 0 -> { + options("Yes, please", "No, thanks") + stage++ + } + 1 -> when (buttonId) { + 1 -> { + exchangeItems(currentItem, cost, currentProduct) + end() + } 2 -> end() } } return true } - fun exchangeItems(item: Item, cost: Int, product: Item) { + private fun exchangeItems(item: Item, cost: Int, product: Item) { val coins = Item(Items.COINS_995, cost) + if (player.inventory.containsItem(coins) && player.inventory.containsItem(item)) { - player.inventory.remove(item, coins) - player.inventory.add(product) - player.sendMessage("You repair your ${product.name.toLowerCase()} for $cost.") + if (player.inventory.remove(item, coins)) { + if (player.inventory.add(product)) { + val costText = if (cost > 0) "${cost}gp" else "free" + player.sendMessage("You repair your ${product.name.lowercase()} for $costText.") + return + } + } + player.sendMessage("Report this to an administrator!") } else { player.sendMessage("You can't afford that.") } diff --git a/Server/src/main/content/region/misthalin/lumbridge/dialogue/BobDialogue.java b/Server/src/main/content/region/misthalin/lumbridge/dialogue/BobDialogue.java deleted file mode 100644 index e98e13693..000000000 --- a/Server/src/main/content/region/misthalin/lumbridge/dialogue/BobDialogue.java +++ /dev/null @@ -1,569 +0,0 @@ -package content.region.misthalin.lumbridge.dialogue; - -import core.cache.def.impl.ItemDefinition; -import core.game.dialogue.DialoguePlugin; -import core.game.dialogue.FacialExpression; -import content.data.RepairItem; -import core.game.node.entity.npc.NPC; -import core.game.node.entity.player.Player; -import core.game.node.entity.player.link.diary.AchievementDiary; -import core.game.node.entity.player.link.diary.DiaryType; -import core.game.node.item.Item; -import content.global.handlers.item.equipment.BarrowsEquipmentRegister; - -/** - * Represents the dialogue plugin used for the bob npc who repairs items. - * @author 'Vexia - * @version 1.0 - */ -public final class BobDialogue extends DialoguePlugin { - - /** - * Represents the item id being repaired. - */ - private int itemId = 0; - - /** - * Represents the item being repaired. - */ - private Item item; - - /** - * Represents the item repairing. - */ - private static RepairItem repairitem = null; - - /** - * The achievement diary. - */ - private final int level = 1; - - /** - * Constructs a new {@code BobDialogue} {@code Object}. - */ - public BobDialogue() { - /** - * empty. - */ - } - - /** - * Constructs a new {@code BobDialogue} {@code Object}. - * @param player the player. - */ - public BobDialogue(Player player) { - super(player); - } - - @Override - public boolean handle(int interfaceId, int buttonId) { - switch (stage) { - case 754: - options("Yes, please.", "No, thank you."); - stage = 755; - break; - case 755: - switch (buttonId) { - case 1: - player("Yes, please."); - stage = 757; - break; - case 2: - player("No, thank you."); - stage = 756; - break; - } - break; - case 756: - end(); - break; - case 757: - end(); - if (repairitem != null) { - if (!player.getInventory().contains(995, repairitem.getCost())) { - end(); - player.getPacketDispatch().sendMessage("You don't have enough to pay him."); - break; - } - if (!player.getInventory().contains(itemId, 1)) { - end(); - return true; - } - player.getInventory().remove(new Item(995, repairitem.getCost())); - if (player.getInventory().remove(new Item(itemId, 1))) { - player.getInventory().add(repairitem.getProduct()); - } - String cost = "free"; - if (repairitem.getCost() != 0) { - cost = repairitem.getCost() + "gp"; - } - player.getPacketDispatch().sendMessage("Bob fixes your " + ItemDefinition.forId(itemId).getName().toLowerCase().replace("broken", "").trim() + " for " + cost + "."); - } - if (repairitem == null) { - String cost = "free"; - String type = BarrowsEquipment.formatedName(itemId); - String single = BarrowsEquipment.getSingleName(type); - String equipment = BarrowsEquipment.getEquipmentType(type); - String newString = type.toLowerCase().replace(single, "").trim().replace("'s", ""); - StringBuilder newewString = new StringBuilder(); - newewString.append(newString).append(" " + equipment); - final BarrowsEquipment.BarrowsFullEquipment fullequip = BarrowsEquipment.BarrowsFullEquipment.forName(newewString.toString()); - if (BarrowsEquipment.getFormatedCost(equipment, item) != 0) { - cost = String.valueOf(BarrowsEquipment.getFormatedCost(equipment, item) + "gp"); - } - if (!player.getInventory().contains(995, BarrowsEquipment.getFormatedCost(equipment, item))) { - end(); - player.getPacketDispatch().sendMessage("You don't have enough to pay him."); - break; - } - if (fullequip == null || fullequip.getFull() == null) { - player.getPacketDispatch().sendMessage("Report this to an administrator!"); - return true; - } - if (!player.getInventory().contains(itemId, 1)) { - end(); - return true; - } - player.getInventory().remove(new Item(995, BarrowsEquipment.getFormatedCost(equipment, item))); - if (player.getInventory().remove(new Item(itemId, 1))) { - player.getInventory().add(fullequip.getFull()); - } - player.getPacketDispatch().sendMessage("Bob fixes your " + equipment + " for " + cost + "."); - } - break; - case 678: - end(); - break; - case 0: - switch (buttonId) { - case 1: - player("Give me a quest!"); - stage = -5; - break; - case 2: - player("Have you anything to sell?"); - stage = 10; - break; - case 3: - player("Can you repair my items for me?"); - stage = 20; - break; - case 4: - player("I'd like to talk about Achievement Diaries."); - stage = 30; - break; - } - break; - case -5: - interpreter.sendDialogues(npc, FacialExpression.FURIOUS, "Get yer own!"); - stage = -4; - break; - case -4: - end(); - break; - case 10: - npc("Yes! I buy and sell axes! Take your pick (or axe)!"); - stage = 11; - break; - case 11: - end(); - npc.openShop(player); - break; - case 20: - npc("Of course I'll repair it, though the materials may cost", "you. Just hand me the item and I'll have a look."); - stage = 21; - break; - case 21: - end(); - break; - case 30: - if (AchievementDiary.canClaimLevelRewards(player, DiaryType.LUMBRIDGE, level)) { - player("I've done all the medium tasks in my Lumbridge", "Achievement Diary."); - stage = 150; - break; - } - if (AchievementDiary.canReplaceReward(player, DiaryType.LUMBRIDGE, level)) { - player("I've seemed to have lost my explorer's ring..."); - stage = 160; - break; - } - options("What is the Achievement Diary?", "What are the rewards?", "How do I claim the rewards?", "See you later."); - stage = 31; - break; - case 31: - switch (buttonId) { - case 1: - player("What is the Achievement Diary?"); - stage = 110; - break; - case 2: - player("What are the rewards?"); - stage = 120; - break; - case 3: - player("How do I claim the rewards?"); - stage = 130; - break; - case 4: - player("See you later!"); - stage = 140; - break; - } - break; - case 110: - npc("Ah, well, it's a diary that helps you keep track of", "particular achievements you've made in the world of", "Gielinor. In Lumbridge and Draynor I can help you", "discover some very useful things indeed."); - stage++; - break; - case 111: - npc("Eventually with enough exploration you will be", "rewarded for your explorative efforts."); - stage++; - break; - case 112: - npc("You can access your Achievement Diary by going to", "the Quest Journal. When you've opened the Quest", "Journal click on the green star icon on the top right", "hand corner. This will open the diary."); - stage = 30; - break; - case 120: - npc("Ah, well there are different rewards for each", "Achievement Diary. For completing the Lumbridge and", "Draynor diary you are presented with an explorer's", "ring."); - stage++; - break; - case 121: - npc("This ring will become increasingly useful with each", "section of the diary that you complete."); - stage = 30; - break; - case 130: - npc("You need to complete the tasks so that they're all ticked", "off, then you can claim your reward. Most of them are", "straightforward although you might find some required", "quests to be started, if not finished."); - stage++; - break; - case 131: - npc("To claim the explorer's ring speak to Explorer Jack", " in Lumbridge, Ned in Draynor Village or myself."); - stage = 30; - break; - case 140: - end(); - break; - case 150: - npc("Yes I see that, you'll be wanting your", "reward then I assume?"); - stage++; - break; - case 151: - player("Yes please."); - stage++; - break; - case 152: - AchievementDiary.flagRewarded(player, DiaryType.LUMBRIDGE, level); - npc("This ring is a representation of the adventures you", "went on to complete your tasks."); - stage ++; - break; - case 153: - player("Wow, thanks!"); - stage = 30; - break; - case 160: - AchievementDiary.grantReplacement(player, DiaryType.LUMBRIDGE, level); - npc("You better be more careful this time."); - stage = -1; - break; - } - - return true; - } - - @Override - public DialoguePlugin newInstance(Player player) { - return new BobDialogue(player); - } - - @Override - public boolean open(Object... args) { - npc = (NPC) args[0]; - boolean repair = false; - boolean wrong = false; - if (npc.getId() == 3797 && args.length == 1) { - player("Can you repair my items for me?"); - stage = 20; - return true; - } - if (args.length == 1) { - options("Give me a quest!", "Have you anything to sell?", "Can you repair my items for me?", "Talk about Achievement Diaries"); - stage = 0; - return true; - } - if (args[1] != null) { - repair = (boolean) args[1]; - } - if (args[2] != null) { - wrong = (boolean) args[2]; - } - if (args[3] != null) { - repairitem = RepairItem.forId((int) args[3]); - itemId = (int) args[3]; - } - if (args[4] != null) { - item = (Item) args[4]; - } - if (repair && !wrong) { - String cost = "free"; - if (repairitem != null) { - if (repairitem.getCost() != 0) { - cost = String.valueOf(repairitem.getCost() + "gp"); - } - } else { - String type = BarrowsEquipment.formatedName(itemId); - String single = BarrowsEquipment.getSingleName(type); - String equipment = BarrowsEquipment.getEquipmentType(type); - String newString = type.toLowerCase().replace(single, "").trim().replace("'s", ""); - StringBuilder newewString = new StringBuilder(); - newewString.append(newString).append(" " + equipment); - if (BarrowsEquipment.getFormatedCost(equipment, item) != 0) { - cost = String.valueOf(BarrowsEquipment.getFormatedCost(equipment, item) + "gp"); - } - } - npc("Quite badly damaged, but easy to repair. Would you", "like me to repair it for " + cost + "?"); - stage = 754; - return true; - } - if (repair == true && wrong == true) { - npc("Sorry friend, but I can't do anything with that."); - stage = 678; - return true; - } - return true; - } - - @Override - public int[] getIds() { - return new int[] { 519, 3797 }; - } - - /** - * Barrows equipment information. - * @author 'Vexia - */ - public static class BarrowsEquipment { - - /** - * Represents the base names. - */ - private String[] base = new String[] { "dharok", "verac", "ahrim", "torag", "guthan" }; - - /** - * The weapon names. - */ - private static final String[] weapon_names = new String[] { "flail", "greataxe", "spear", "x-bow", "hammer", "hammers", "staff" }; - - /** - * The weapon body names. - */ - private static final String[] body_names = new String[] { "top", "platebody", "body" }; - - /** - * The helm names. - */ - private static final String[] helm_names = new String[] { "hood", "helm", "coif" }; - - /** - * The leg names. - */ - private static final String[] leg_names = new String[] { "skirt", "legs", "plateskirt", "platelegs" }; - - /** - * The prices. - */ - private static final Object[][] prices = new Object[][] { { "weapon", 100 }, { "body", 90 }, { "legs", 80 }, { "helm", 60 } }; - - /** - * Represents the items. - */ - private static final Object[][] ITEMS = { { 4856, "Ahrim's hood" }, { 4857, "Ahrim's hood" }, { 4858, "Ahrim's hood" }, { 4859, "Ahrim's hood" }, { 4860, "Ahrim's hood" }, { 4862, "Ahrim's staff" }, { 4863, "Ahrim's staff" }, { 4864, "Ahrim's staff" }, { 4865, "Ahrim's staff" }, { 4866, "Ahrim's staff" }, { 4868, "Ahrim's top" }, { 4869, "Ahrim's top" }, { 4870, "Ahrim's top" }, { 4871, "Ahrim's top" }, { 4872, "Ahrim's top" }, { 4874, "Ahrim's skirt" }, { 4875, "Ahrim's skirt" }, { 4876, "Ahrim's skirt" }, { 4877, "Ahrim's skirt" }, { 4878, "Ahrim's skirt" }, { 4880, "Dharok's helm" }, { 4881, "Dharok's helm" }, { 4882, "Dharok's helm" }, { 4883, "Dharok's helm" }, { 4884, "Dharok's helm" }, { 4886, "Dharok's greataxe" }, { 4887, "Dharok's greataxe" }, { 4888, "Dharok's greataxe" }, { 4889, "Dharok's greataxe" }, { 4890, "Dharok's greataxe" }, { 4892, "Dharok's platebody" }, { 4893, "Dharok's platebody" }, { 4894, "Dharok's platebody" }, { 4895, "Dharok's platebody" }, { 4896, "Dharok's platebody" }, { 4898, "Dharok's platelegs" }, { 4899, "Dharok's platelegs" }, { 4900, "Dharok's platelegs" }, { 4901, "Dharok's platelegs" }, { 4902, "Dharok's platelegs" }, { 4904, "Guthan's helm" }, { 4905, "Guthan's helm" }, { 4906, "Guthan's helm" }, { 4907, "Guthan's helm" }, { 4908, "Guthan's helm" }, { 4910, "Guthan's spear" }, { 4911, "Guthan's spear" }, { 4912, "Guthan's spear" }, { 4913, "Guthan's spear" }, { 4914, "Guthan's spear" }, { 4916, "Guthan's body" }, { 4917, "Guthan's body" }, { 4918, "Guthan's body" }, { 4919, "Guthan's body" }, { 4920, "Guthan's body" }, { 4922, "Guthan's skirt" }, { 4923, "Guthan's skirt" }, { 4924, "Guthan's skirt" }, { 4925, "Guthan's skirt" }, { 4926, "Guthan's skirt" }, { 4928, "Karil's coif" }, { 4929, "Karil's coif" }, { 4930, "Karil's coif" }, { 4931, "Karil's coif" }, { 4932, "Karil's coif" }, { 4934, "Karil's x-bow" }, { 4935, "Karil's x-bow" }, { 4936, "Karil's x-bow" }, { 4937, "Karil's x-bow" }, { 4938, "Karil's x-bow" }, { 4940, "Karil's top" }, { 4941, "Karil's top" }, { 4942, "Karil's top" }, { 4943, "Karil's top" }, { 4944, "Karil's top" }, { 4946, "Karil's skirt" }, { 4947, "Karil's skirt" }, { 4948, "Karil's skirt" }, { 4949, "Karil's skirt" }, { 4950, "Karil's skirt" }, { 4952, "Torag's helm" }, { 4953, "Torag's helm" }, { 4954, "Torag's helm" }, { 4955, "Torag's helm" }, { 4956, "Torag's helm" }, { 4958, "Torag's hammers" }, { 4959, "Torag's hammers" }, { 4960, "Torag's hammers" }, { 4961, "Torag's hammers" }, { 4962, "Torag's hammers" }, { 4964, "Torag's body" }, { 4965, "Torag's body" }, { 4966, "Torag's body" }, { 4967, "Torag's body" }, { 4968, "Torag's body" }, { 4970, "Torag's legs" }, { 4971, "Torag's legs" }, { 4972, "Torag's legs" }, { 4973, "Torag's legs" }, { 4974, "Torag's legs" }, { 4976, "Verac's helm" }, { 4977, "Verac's helm" }, { 4978, "Verac's helm" }, { 4979, "Verac's helm" }, { 4980, "Verac's helm" }, { 4982, "Verac's flail" }, { 4983, "Verac's flail" }, { 4984, "Verac's flail" }, { 4985, "Verac's flail" }, { 4986, "Verac's flail" }, { 4988, "Verac's top" }, { 4989, "Verac's top" }, { 4990, "Verac's top" }, { 4991, "Verac's top" }, { 4992, "Verac's top" }, { 4994, "Verac's skirt" }, { 4995, "Verac's skirt" }, { 4996, "Verac's skirt" }, { 4997, "Verac's skirt" }, { 4998, "Verac's skirt" } }; - - /** - * Gets the cost. - * @param name the name. - * @return the price. - */ - public static int getFormatedCost(String name, Item item) { - int ticks = BarrowsEquipmentRegister.TICKS; - int[] degrades = new int[] { 100, 75, 50, 25, 0 }; - for (int i = 0; i < prices.length; i++) { - String check = (String) prices[i][0]; - if (check.equals(name)) { - int degrade = 0; - for (int d : degrades) { - if (item.getName().contains(String.valueOf(d))) { - degrade = d; - break; - } - } - degrade -= 25 - (25 * ((double)item.getCharge() / (double)ticks)); - int max = (int) prices[i][1] * 1000; - return (int) (max - (max * (degrade * 0.01))); - } - } - return 0; - } - - /** - * Gets the cost of the item type. - * @param name the name. - * @return the return type. - */ - public static int getCost(String name) { - for (int i = 0; i < prices.length; i++) { - String check = (String) prices[i][0]; - if (check.equals(name)) { - return (int) prices[i][1]; - } - } - return 0; - } - - /** - * Represents if an item is a barrows item. - * @param id the id. - * @return {@code True} if so. - */ - public static boolean isBarrowsItem(int id) { - for (int i = 0; i < ITEMS.length; i++) { - if ((int) ITEMS[i][0] == id) { - return true; - } - } - return false; - } - - /** - * Gets the formatted name. - * @param id the id. - * @return the name. - */ - public static String formatedName(int id) { - for (int i = 0; i < ITEMS.length; i++) { - if ((int) ITEMS[i][0] == id) { - return (String) ITEMS[i][1]; - } - } - return null; - } - - /** - * Gets the equipment type. - * @param name the name. - * @return the type. - */ - public static String getEquipmentType(String name) { - name = name.toLowerCase().replace("verac's", "").replace("karil's", "").replace("dharok's", "").replace("torag's", "").replace("guthan's", "").replace("ahrim's", "").trim(); - for (int i = 0; i < weapon_names.length; i++) { - if (weapon_names[i].contains(name)) { - return "weapon"; - } - } - for (int k = 0; k < body_names.length; k++) { - if (body_names[k].contains(name)) { - return "body"; - } - } - for (int z = 0; z < leg_names.length; z++) { - if (leg_names[z].contains(name)) { - return "legs"; - } - } - for (int q = 0; q < helm_names.length; q++) { - if (helm_names[q].contains(name)) { - return "helm"; - } - } - return null; - } - - /** - * Method used t get its single name. - * @param name the name. - * @return the name. - */ - public static String getSingleName(String name) { - name = name.toLowerCase().replace("verac's", "").replace("karil's", "").replace("dharok's", "").replace("torag's", "").replace("guthan's", "").replace("ahrim's", "").trim(); - for (int i = 0; i < weapon_names.length; i++) { - if (weapon_names[i].contains(name)) { - return weapon_names[i]; - } - } - for (int k = 0; k < body_names.length; k++) { - if (body_names[k].contains(name)) { - return body_names[k]; - } - } - for (int z = 0; z < leg_names.length; z++) { - if (leg_names[z].contains(name)) { - return leg_names[z]; - } - } - for (int q = 0; q < helm_names.length; q++) { - if (helm_names[q].contains(name)) { - return helm_names[q]; - } - } - return null; - } - - /** - * Gets the bases. - * @return the base. - */ - public String[] getBase() { - return base; - } - - /** - * Represents the multiple full barrows equipment items. - * @author 'Vexia - * @version 1.0 - */ - public enum BarrowsFullEquipment { - VERAC_LEGS(new Item(4759, 1)), VERAC_TOP(new Item(4757, 1)), VERAC_WEAPON(new Item(4755, 1)), VERAC_HELM(new Item(4753, 1)), TORAG_LEGS(new Item(4751, 1)), TORAG_BODY(new Item(4749, 1)), TORAG_HELM(new Item(4745, 1)), TORAG_WEAPON(new Item(4747, 1)), KARIL_HELM(new Item(4732, 1)), KARIL_WEAPON(new Item(4734, 1)), KARIL_BODY(new Item(4736, 1)), KARIL_LEGS(new Item(4738, 1)), GUTHAN_HELM(new Item(4724, 1)), GUTHAN_BODY(new Item(4728, 1)), GUTHAN_LEGS(new Item(4730, 1)), GUTHAN_WEAPON(new Item(4726, 1)), DHAROK_HELM(new Item(4716, 1)), DHAROK_BODY(new Item(4720, 1)), DHAROK_LEGS(new Item(4722, 1)), DHAROK_WEAPON(new Item(4718, 1)), AHRIM_HELM(new Item(4708, 1)), AHRIM_BODY(new Item(4712, 1)), AHRIM_LEGS(new Item(4714, 1)), AHRIM_WEAPON(new Item(4710, 1)); - - /** - * Constructs a new {@code BarrowsEquipment} {@code Object}. - * @param full the full item. - */ - BarrowsFullEquipment(Item full) { - this.full = full; - } - - /** - * Represents the full item. - */ - private final Item full; - - /** - * for name - * @param name thename. - * @return the equipment. - */ - public static BarrowsFullEquipment forName(String name) { - if (name.equals("guthan body body")) { - name = "guthan body"; - } else if (name.equals("torag body body")) { - name = "torag body"; - } else if (name.equals("verac body")) { - name = "verac top"; - } - for (BarrowsFullEquipment barrow : BarrowsFullEquipment.values()) { - if (barrow.name().toLowerCase().replace("_", " ").trim().equalsIgnoreCase(name)) { - return barrow; - } - } - return null; - } - - /** - * Gets the full. - * @return The full. - */ - public Item getFull() { - return full; - } - } - - } - -} diff --git a/Server/src/main/content/region/misthalin/lumbridge/dialogue/BobDialogue.kt b/Server/src/main/content/region/misthalin/lumbridge/dialogue/BobDialogue.kt new file mode 100644 index 000000000..c759ef0b2 --- /dev/null +++ b/Server/src/main/content/region/misthalin/lumbridge/dialogue/BobDialogue.kt @@ -0,0 +1,266 @@ +package content.region.misthalin.lumbridge.dialogue + +import content.data.RepairItem +import content.global.handlers.item.equipment.BarrowsEquipment +import core.cache.def.impl.ItemDefinition +import core.game.dialogue.DialoguePlugin +import core.game.dialogue.FacialExpression +import core.game.node.entity.npc.NPC +import core.game.node.entity.player.Player +import core.game.node.entity.player.link.diary.AchievementDiary +import core.game.node.entity.player.link.diary.DiaryType +import core.game.node.item.Item +import core.plugin.Initializable + +/** + * Represents the dialogue plugin used for the bob npc who repairs items. + * @author 'Vexia + * @author Damighty - Kotlin conversion + * @version 1.0 + */ +@Initializable +class BobDialogue(player: Player? = null) : DialoguePlugin(player) { + + /** + * Represents the item id being repaired. + */ + private var itemId = 0 + + /** + * Represents the item being repaired. + */ + private lateinit var item: Item + + /** + * Represents the item repairing. + */ + private var repairItem: RepairItem? = null + + /** + * The achievement diary. + */ + private val level = 1 + + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + when (stage) { + 754 -> { + options("Yes, please.", "No, thank you.") + stage = 755 + } + 755 -> when (buttonId) { + 1 -> { + player("Yes, please.") + stage = 757 + } + 2 -> { + player("No, thank you.") + stage = 756 + } + } + 756 -> end() + 757 -> { + end() + val repairItemDef = RepairItem.forId(itemId) + + if (repairItemDef != null) { + // Standard repairable items + if (!player.inventory.contains(995, repairItemDef.cost)) { + player.packetDispatch.sendMessage("You don't have enough to pay him.") + return true + } + if (player.inventory.remove(Item(itemId, 1))) { + player.inventory.remove(Item(995, repairItemDef.cost)) + player.inventory.add(repairItemDef.product) + val costText = if (repairItemDef.cost > 0) "${repairItemDef.cost}gp" else "free" + player.packetDispatch.sendMessage("Bob fixes your ${item.name.lowercase().replace("broken", "").trim()} for $costText.") + } + } else { + // Barrows items + val barrowsDef = BarrowsEquipment.getDefinition(itemId) ?: return true + val repairCost = BarrowsEquipment.getRepairCost(item) + + if (!player.inventory.contains(995, repairCost)) { + player.packetDispatch.sendMessage("You don't have enough to pay him.") + return true + } + if (player.inventory.remove(Item(itemId, 1))) { + player.inventory.remove(Item(995, repairCost)) + player.inventory.add(Item(barrowsDef.repairedId)) + val costText = if (repairCost > 0) "${repairCost}gp" else "free" + player.packetDispatch.sendMessage("Bob fixes your ${barrowsDef.itemName.lowercase()} for $costText.") + } + } + return true + } + 678 -> end() + 0 -> when (buttonId) { + 1 -> { + player("Give me a quest!") + stage = -5 + } + 2 -> { + player("Have you anything to sell?") + stage = 10 + } + 3 -> { + player("Can you repair my items for me?") + stage = 20 + } + 4 -> { + player("I'd like to talk about Achievement Diaries.") + stage = 30 + } + } + -5 -> { + interpreter.sendDialogues(npc, FacialExpression.FURIOUS, "Get yer own!") + stage = -4 + } + -4 -> end() + 10 -> { + npc("Yes! I buy and sell axes! Take your pick (or axe)!") + stage = 11 + } + 11 -> { + end() + npc.openShop(player) + } + 20 -> { + npc("Of course I'll repair it, though the materials may cost", "you. Just hand me the item and I'll have a look.") + stage = 21 + } + 21 -> end() + 30 -> { + if (AchievementDiary.canClaimLevelRewards(player, DiaryType.LUMBRIDGE, level)) { + player("I've done all the medium tasks in my Lumbridge", "Achievement Diary.") + stage = 150 + } else if (AchievementDiary.canReplaceReward(player, DiaryType.LUMBRIDGE, level)) { + player("I've seemed to have lost my explorer's ring...") + stage = 160 + } else { + options("What is the Achievement Diary?", "What are the rewards?", "How do I claim the rewards?", "See you later.") + stage = 31 + } + } + 31 -> when (buttonId) { + 1 -> { + player("What is the Achievement Diary?") + stage = 110 + } + 2 -> { + player("What are the rewards?") + stage = 120 + } + 3 -> { + player("How do I claim the rewards?") + stage = 130 + } + 4 -> { + player("See you later!") + stage = 140 + } + } + 110 -> { + npc("Ah, well, it's a diary that helps you keep track of", "particular achievements you've made in the world of", "Gielinor. In Lumbridge and Draynor I can help you", "discover some very useful things indeed.") + stage++ + } + 111 -> { + npc("Eventually with enough exploration you will be", "rewarded for your explorative efforts.") + stage++ + } + 112 -> { + npc("You can access your Achievement Diary by going to", "the Quest Journal. When you've opened the Quest", "Journal click on the green star icon on the top right", "hand corner. This will open the diary.") + stage = 30 + } + 120 -> { + npc("Ah, well there are different rewards for each", "Achievement Diary. For completing the Lumbridge and", "Draynor diary you are presented with an explorer's", "ring.") + stage++ + } + 121 -> { + npc("This ring will become increasingly useful with each", "section of the diary that you complete.") + stage = 30 + } + 130 -> { + npc("You need to complete the tasks so that they're all ticked", "off, then you can claim your reward. Most of them are", "straightforward although you might find some required", "quests to be started, if not finished.") + stage++ + } + 131 -> { + npc("To claim the explorer's ring speak to Explorer Jack", " in Lumbridge, Ned in Draynor Village or myself.") + stage = 30 + } + 140 -> end() + 150 -> { + npc("Yes I see that, you'll be wanting your", "reward then I assume?") + stage++ + } + 151 -> { + player("Yes please.") + stage++ + } + 152 -> { + AchievementDiary.flagRewarded(player, DiaryType.LUMBRIDGE, level) + npc("This ring is a representation of the adventures you", "went on to complete your tasks.") + stage++ + } + 153 -> { + player("Wow, thanks!") + stage = 30 + } + 160 -> { + AchievementDiary.grantReplacement(player, DiaryType.LUMBRIDGE, level) + npc("You better be more careful this time.") + stage = -1 + } + } + return true + } + + override fun newInstance(player: Player): DialoguePlugin { + return BobDialogue(player) + } + + override fun open(vararg args: Any): Boolean { + npc = args[0] as NPC + var repair = false + var wrong = false + + if (npc.id == 3797 && args.size == 1) { + player("Can you repair my items for me?") + stage = 20 + return true + } + + if (args.size == 1) { + options("Give me a quest!", "Have you anything to sell?", "Can you repair my items for me?", "Talk about Achievement Diaries") + stage = 0 + return true + } + + if (args.size > 1) repair = args[1] as Boolean + if (args.size > 2) wrong = args[2] as Boolean + if (args.size > 3) { + repairItem = RepairItem.forId(args[3] as Int) + itemId = args[3] as Int + } + if (args.size > 4) item = args[4] as Item + + if (repair && !wrong) { + val cost = RepairItem.forId(itemId)?.cost ?: BarrowsEquipment.getRepairCost(item) + val costText = if (cost > 0) "${cost}gp" else "free" + npc("Quite badly damaged, but easy to repair. Would you", "like me to repair it for $costText?") + stage = 754 + return true + } + + if (repair && wrong) { + npc("Sorry friend, but I can't do anything with that.") + stage = 678 + return true + } + + return true + } + + override fun getIds(): IntArray { + return intArrayOf(519, 3797) + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/misthalin/lumbridge/handlers/BobRepairItem.java b/Server/src/main/content/region/misthalin/lumbridge/handlers/BobRepairItem.java deleted file mode 100644 index 12025ef46..000000000 --- a/Server/src/main/content/region/misthalin/lumbridge/handlers/BobRepairItem.java +++ /dev/null @@ -1,48 +0,0 @@ -package content.region.misthalin.lumbridge.handlers; - -import content.region.misthalin.lumbridge.dialogue.BobDialogue; -import content.data.RepairItem; -import core.game.interaction.NodeUsageEvent; -import core.game.interaction.UseWithHandler; -import core.game.node.entity.npc.NPC; -import core.game.node.entity.player.Player; -import core.plugin.Plugin; -import core.plugin.Initializable; -import core.plugin.ClassScanner; - -/** - * Represents the plugin used to handle an item being used on bob. - * @author 'Vexia - * @version 1.0 - */ -@Initializable -public final class BobRepairItem extends UseWithHandler { - - /** - * Constructs a new {@code BobRepairItem} {@code Object}. - */ - public BobRepairItem() { - super(494, 468, 496, 470, 498, 472, 500, 502, 474, 504, 476, 506, 478, 6741, 4856, 4857, 4858, 4859, 4860, 4862, 4863, 4864, 4865, 4866, 4868, 4869, 4870, 4871, 4872, 4874, 4875, 4876, 4877, 4878, 4880, 4881, 4882, 4883, 4884, 4886, 4887, 4888, 4889, 4890, 4892, 4893, 4894, 4895, 4896, 4898, 4899, 4900, 4901, 4902, 4904, 4905, 4906, 4907, 4908, 4910, 4911, 4912, 4913, 4914, 4916, 4917, 4918, 4919, 4920, 4922, 4923, 4924, 4925, 4926, 4928, 4929, 4930, 4931, 4932, 4934, 4935, 4936, 4937, 4938, 4940, 4941, 4942, 4943, 4944, 4946, 4947, 4948, 4949, 4950, 4952, 4953, 4954, 4955, 4956, 4958, 4959, 4960, 4961, 4962, 4964, 4965, 4966, 4967, 4968, 4970, 4971, 4972, 4973, 4974, 4976, 4977, 4978, 4979, 4980, 4982, 4983, 4984, 4985, 4986, 4988, 4989, 4990, 4991, 4992, 4994, 4995, 4996, 4997, 4998); - } - - @Override - public Plugin newInstance(Object arg) throws Throwable { - addHandler(519, NPC_TYPE, this); - addHandler(3797, NPC_TYPE, this); - ClassScanner.definePlugin(new BobDialogue()); - return this; - } - - @Override - public boolean handle(NodeUsageEvent event) { - final Player player = event.getPlayer(); - final RepairItem repair = RepairItem.forId(event.getUsedItem().getId()); - if (repair == null && !BobDialogue.BarrowsEquipment.isBarrowsItem(event.getUsedItem().getId())) { - player.getDialogueInterpreter().open(519, ((NPC) event.getUsedWith()), true, true, null); - return true; - } - player.getDialogueInterpreter().open(519, ((NPC) event.getUsedWith()), true, false, event.getUsedItem().getId(), event.getUsedItem()); - return true; - } - -} diff --git a/Server/src/main/content/region/misthalin/lumbridge/handlers/BobRepairItem.kt b/Server/src/main/content/region/misthalin/lumbridge/handlers/BobRepairItem.kt new file mode 100644 index 000000000..d939865f1 --- /dev/null +++ b/Server/src/main/content/region/misthalin/lumbridge/handlers/BobRepairItem.kt @@ -0,0 +1,47 @@ +package content.region.misthalin.lumbridge.handlers + +import content.global.handlers.item.equipment.BarrowsEquipment +import content.data.RepairItem +import core.game.interaction.NodeUsageEvent +import core.game.interaction.UseWithHandler +import core.game.node.entity.npc.NPC +import core.plugin.Plugin +import core.plugin.Initializable + +private val ALL_REPAIRABLE_ITEM_IDS = (RepairItem.repairableItemIds + BarrowsEquipment.getAllRepairableBarrowsIds()).toIntArray() + +/** + * Handles items being used on Bob for repair services. + * @author 'Vexia + * @author Damighty - Kotlin conversion + */ +@Initializable +class BobRepairItem : UseWithHandler(*ALL_REPAIRABLE_ITEM_IDS) { + + override fun newInstance(arg: Any?): Plugin { + addHandler(519, NPC_TYPE, this) + addHandler(3797, NPC_TYPE, this) + return this + } + + override fun handle(event: NodeUsageEvent): Boolean { + val player = event.player + val item = event.usedItem + val npc = event.usedWith as NPC + + val isBarrowsItem = BarrowsEquipment.isBarrowsItem(item.id) + + if (isBarrowsItem) { + if (BarrowsEquipment.isFullyRepaired(item.id)) { + player.dialogueInterpreter.open(519, npc, true, true, item.id) + return true + } + } else if (RepairItem.forId(item.id) == null) { + player.dialogueInterpreter.open(519, npc, true, true) + return true + } + + player.dialogueInterpreter.open(519, npc, true, false, item.id, item) + return true + } +} \ No newline at end of file diff --git a/Server/src/main/core/game/node/entity/combat/graves/Grave.kt b/Server/src/main/core/game/node/entity/combat/graves/Grave.kt index 2118ab7dc..e330489a9 100644 --- a/Server/src/main/core/game/node/entity/combat/graves/Grave.kt +++ b/Server/src/main/core/game/node/entity/combat/graves/Grave.kt @@ -1,5 +1,6 @@ package core.game.node.entity.combat.graves +import content.global.handlers.item.equipment.BarrowsEquipment import core.api.clearHintIcon import core.api.registerHintIcon import core.api.sendMessage @@ -65,7 +66,11 @@ class Grave : AbstractNPC { continue } - val finalItem = GraveController.checkTransform(item) + val finalItem = if (BarrowsEquipment.isBarrowsItem(item.id) && !BarrowsEquipment.isFullyRepaired(item.id)) { + BarrowsEquipment.graveDeathDurabilityReduction(item) ?: item + } else { + GraveController.checkTransform(item) + } val gi = GroundItemManager.create(finalItem, this.location, player) gi.isRemainPrivate = true diff --git a/Server/src/main/core/game/node/entity/player/Player.java b/Server/src/main/core/game/node/entity/player/Player.java index 1592d0b0e..05867a01d 100644 --- a/Server/src/main/core/game/node/entity/player/Player.java +++ b/Server/src/main/core/game/node/entity/player/Player.java @@ -1,5 +1,6 @@ package core.game.node.entity.player; +import content.global.handlers.item.equipment.BarrowsEquipment; import content.global.handlers.item.equipment.special.SalamanderSwingHandler; import content.global.skill.runecrafting.PouchManager; import core.api.ContentAPIKt; @@ -661,11 +662,13 @@ public class Player extends Entity { if (item == null) continue; if (killer instanceof Player) itemsLost.append(getItemName(item.getId())).append("(").append(item.getAmount()).append("), "); + if (GraveController.shouldCrumble(item.getId())) continue; if (GraveController.shouldRelease(item.getId())) continue; - if (!item.getDefinition().isTradeable()) { + + if (!BarrowsEquipment.isBarrowsItem(item.getId()) && !item.getDefinition().isTradeable()) { if (killer instanceof Player) { int value = item.getDefinition().getAlchemyValue(true); if (getStatLevel(killer, Skills.MAGIC) < 55) value /= 2; @@ -673,7 +676,14 @@ public class Player extends Entity { continue; } else stayPrivate = true; } - item = GraveController.checkTransform(item); + if (BarrowsEquipment.isBarrowsItem(item.getId())) { + if (!BarrowsEquipment.isBroken(item.getId())) { + int brokenItemId = Objects.requireNonNull(BarrowsEquipment.getDefinition(item.getId())).getBrokenId(); + item = new Item(brokenItemId, item.getAmount()); + } + } else { + item = GraveController.checkTransform(item); + } GroundItem gi = GroundItemManager.create(item, location, killer instanceof Player ? (Player) killer : this); gi.setRemainPrivate(stayPrivate); } From 78f1c58b4e45532b62d08d082178f5085680cdab Mon Sep 17 00:00:00 2001 From: Player Name Date: Tue, 25 Nov 2025 08:36:36 +0000 Subject: [PATCH 096/117] Fixed the remaining dramen tree inauthenticity, now provides reward after a single tick of cutting --- .../gather/woodcutting/WoodcuttingListener.kt | 20 ++++++- .../woodcutting/WoodcuttingSkillPulse.java | 9 +-- .../quest/lostcity/DramenTreeListener.kt | 47 ---------------- .../lumbridge/quest/lostcity/LostCity.kt | 55 +++++++++++++++++-- 4 files changed, 68 insertions(+), 63 deletions(-) delete mode 100644 Server/src/main/content/region/misthalin/lumbridge/quest/lostcity/DramenTreeListener.kt diff --git a/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt b/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt index 87efe8614..6b1be0ab0 100644 --- a/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt +++ b/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt @@ -1,5 +1,6 @@ package content.global.skill.gather.woodcutting +import content.data.Quests import content.data.skill.SkillingTool import content.data.tables.BirdNest import content.global.skill.farming.FarmingPatch.Companion.forObject @@ -72,6 +73,17 @@ class WoodcuttingListener : InteractionListener { if (clockReady(player, Clocks.SKILLING)) { animateWoodcutting(player) + + if (resource == WoodcuttingNode.DRAMEN_TREE) { + // Reward after one chop and then abort chopping (this is authentic) + queueScript(player, 1, QueueStrength.STRONG) { + sendMessage(player, "You cut a branch from the Dramen tree.") + addItem(player, resource.getReward()) + return@queueScript clearScripts(player) + } + return delayClock(player, Clocks.SKILLING, 1) + } + if (!checkReward(player, resource, tool) && !getAttribute(player, "instachop", false)) return delayClock(player, Clocks.SKILLING, 3) @@ -115,9 +127,7 @@ class WoodcuttingListener : InteractionListener { player.getSkills().addExperience(Skills.WOODCUTTING, experience, true) //send the message for the resource reward - if (resource == WoodcuttingNode.DRAMEN_TREE) { - player.packetDispatch.sendMessage("You cut a branch from the Dramen tree.") - } else if (reward == Items.BARK_3239 && rewardAmount == 0) { + if (reward == Items.BARK_3239 && rewardAmount == 0) { player.packetDispatch.sendMessage("You chop away some bark, but it falls to pieces before you can pick it up.") } else { player.packetDispatch.sendMessage("You get some " + ItemDefinition.forId(reward).name.lowercase(Locale.getDefault()) + ".") @@ -229,6 +239,10 @@ class WoodcuttingListener : InteractionListener { player.packetDispatch.sendMessage("You do not have an axe to use.") return false } + if (node.id == org.rs09.consts.Scenery.DRAMEN_TREE_1292 && getQuestStage(player, Quests.LOST_CITY) <= 20) { + //TODO: find out if there is any authentic message to be shown + return false + } if (player.inventory.freeSlots() < 1 && node.isActive) { player.sendMessage("Your inventory is too full to hold any more " + ItemDefinition.forId(resource.getReward()).name.lowercase(Locale.getDefault()) + ".") return false diff --git a/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingSkillPulse.java b/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingSkillPulse.java index 1fe03050c..d69c7fea1 100644 --- a/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingSkillPulse.java +++ b/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingSkillPulse.java @@ -162,13 +162,8 @@ public class WoodcuttingSkillPulse extends Pulse { player.getSkills().addExperience(Skills.WOODCUTTING, experience, true); - //send the message for the resource reward, and in the case of the dramen tree, authentically abort the chopping action - if (resource == WoodcuttingNode.DRAMEN_TREE) { - player.getPacketDispatch().sendMessage("You cut a branch from the Dramen tree."); - stop(); - } else { - player.getPacketDispatch().sendMessage("You get some " + ItemDefinition.forId(reward).getName().toLowerCase() + "."); - } + //send the message for the resource reward + player.getPacketDispatch().sendMessage("You get some " + ItemDefinition.forId(reward).getName().toLowerCase() + "."); //give the reward player.getInventory().add(new Item(reward, rewardAmount)); player.dispatch(new ResourceProducedEvent(reward, rewardAmount, node, -1)); diff --git a/Server/src/main/content/region/misthalin/lumbridge/quest/lostcity/DramenTreeListener.kt b/Server/src/main/content/region/misthalin/lumbridge/quest/lostcity/DramenTreeListener.kt deleted file mode 100644 index 679e28bde..000000000 --- a/Server/src/main/content/region/misthalin/lumbridge/quest/lostcity/DramenTreeListener.kt +++ /dev/null @@ -1,47 +0,0 @@ -package content.region.misthalin.lumbridge.quest.lostcity - -import core.game.node.scenery.Scenery -import content.data.skill.SkillingTool -import content.global.skill.gather.woodcutting.WoodcuttingSkillPulse -import core.game.world.map.Location -import org.rs09.consts.NPCs -import org.rs09.consts.Scenery as Sceneries -import core.game.interaction.InteractionListener -import core.api.getQuestStage -import core.api.sendMessage -import core.game.interaction.IntType -import content.data.Quests - -class DramenTreeListener : InteractionListener { - - override fun defineListeners() { - - on(Sceneries.DRAMEN_TREE_1292, IntType.SCENERY, "chop down"){ player, node -> - val questStage = getQuestStage(player,Quests.LOST_CITY) - if (SkillingTool.getHatchet(player) == null) { - sendMessage(player,"You do not have an axe which you have the level to use.") - return@on true - } - if (questStage < 20) { - return@on true - } - if (questStage == 20) { - if (player.getAttribute("treeSpawned", false)) { - return@on true - } - val spirit = TreeSpiritNPC(NPCs.TREE_SPIRIT_655, Location(2862, 9734, 0)) - spirit.target = player - spirit.init() - spirit.attack(player) - player.setAttribute("treeSpawned", true) - spirit.sendChat("You must defeat me before touching the tree!") - return@on true - } - - player.pulseManager.run(WoodcuttingSkillPulse(player, node as Scenery)) - return@on true - } - - } - -} diff --git a/Server/src/main/content/region/misthalin/lumbridge/quest/lostcity/LostCity.kt b/Server/src/main/content/region/misthalin/lumbridge/quest/lostcity/LostCity.kt index 744ef3ca1..89f48fe42 100644 --- a/Server/src/main/content/region/misthalin/lumbridge/quest/lostcity/LostCity.kt +++ b/Server/src/main/content/region/misthalin/lumbridge/quest/lostcity/LostCity.kt @@ -1,22 +1,29 @@ package content.region.misthalin.lumbridge.quest.lostcity +import core.api.Event +import core.api.LoginListener +import core.api.clearScripts +import core.api.getQuestStage +import core.game.event.EventHook +import core.game.event.InteractionEvent +import core.game.node.entity.Entity import core.game.node.entity.player.Player import core.game.node.entity.player.link.quest.Quest import core.game.node.entity.skill.Skills import core.game.node.item.Item +import core.game.world.map.Location import core.plugin.Initializable import org.rs09.consts.Items import content.data.Quests +import org.rs09.consts.NPCs +import org.rs09.consts.Scenery /** * LostCity class for the Lost City quest - * @author lila - * @author Vexia - * @author Aero + * @author lila, Vexia, Aero, Player Name */ @Initializable -class LostCity : Quest(Quests.LOST_CITY, 83, 82, 3, 147, 0, 1, 6) { - +class LostCity : Quest(Quests.LOST_CITY, 83, 82, 3, 147, 0, 1, 6), LoginListener { class SkillRequirement(val skill: Int?, val level: Int?) val requirements = arrayListOf() @@ -67,9 +74,45 @@ class LostCity : Quest(Quests.LOST_CITY, 83, 82, 3, 147, 0, 1, 6) { line(player, BLUE + "and be able to defeat a " + RED + "Level 101 Spirit without weapons", line++) } + private val DramenTreeHook = object : EventHook { + override fun process(entity: Entity, event: InteractionEvent) { + if (event.target.id == Scenery.DRAMEN_TREE_1292 && event.option == "chop down") { + val player = entity as Player + val questStage = getQuestStage(player, Quests.LOST_CITY) + if (questStage == 20) { + if (player.getAttribute("treeSpawned", false)) { + return + } + val spirit = TreeSpiritNPC(NPCs.TREE_SPIRIT_655, Location(2862, 9734, 0)) + spirit.target = player + spirit.init() + spirit.attack(player) + player.setAttribute("treeSpawned", true) + spirit.sendChat("You must defeat me before touching the tree!") + } + } + } + } + + override fun setStage(player: Player, stage: Int) { + super.setStage(player, stage) + if (stage <= 20) { + player.hook(Event.Interacted, DramenTreeHook) + } + if (stage == 21) { + player.unhook(DramenTreeHook) + } + } + + override fun login(player: Player) { + if (getQuestStage(player, Quests.LOST_CITY) <= 20) { + player.hook(Event.Interacted, DramenTreeHook) + } + } + override fun newInstance(`object`: Any?): Quest { requirements.add(SkillRequirement(Skills.WOODCUTTING, 36)) requirements.add(SkillRequirement(Skills.CRAFTING, 31)) return this } -} \ No newline at end of file +} From 1fa2d3460fb342a13799542c22035516f40e54df Mon Sep 17 00:00:00 2001 From: Player Name Date: Tue, 25 Nov 2025 12:56:41 +0000 Subject: [PATCH 097/117] Unified and reimplemented various banker dialogues Accessibility to second bank is now locked behind server config toggle second_bank Eniola, Arnold Lydspor, and Emerald Benedict now offer access to second bank Locked the backported ability to charge our ring of wealth with GE teleports behind server config toggle ring_of_wealth_teleport Corrected enchant spells to always yield uncharged dragonstone items, rather than fully charged ones Fixed inauthentic jewellery UI Locked the availability of our inauthentic player commands behind server config toggle player_commands. Admins are immune to this and still always have all commands available. Note that this includes ::confirmrules, so be sure not to set your server to show rules and info, but not allow commands --- .../main/content/data/EnchantedJewellery.kt | 2 +- .../content/global/dialogue/BankerDialogue.kt | 181 ------------- .../item/EnchantJewelleryTabListener.kt | 121 +++++---- .../content/global/handlers/npc/BankerNPC.kt | 240 ++++++++++++++---- .../MaximillianSackvilleDialogue.kt | 120 --------- .../main/content/minigame/mta/EnchantSpell.kt | 25 +- .../dialogue/EmeraldBenedictDialogue.kt | 27 +- .../burthorpe/dialogue/JadeDialogue.kt | 106 -------- .../burthorpe/handlers/HeroGuildPlugin.java | 41 +-- .../dialogue/SirsalBankerDialogue.kt | 209 --------------- .../kandarin/dialogue/EniolaDialogue.kt | 18 +- .../pisc/dialogue/ArnoldLydsporDialogue.kt | 198 +++++++-------- .../dialogue/BankerTutorDialogue.java | 155 ----------- .../StrongHoldOfPlayerSafetyListener.kt | 2 +- .../BookcaseDialogueFile.kt | 4 +- Server/src/main/core/ServerConstants.kt | 18 ++ Server/src/main/core/api/ContentAPI.kt | 2 +- .../core/game/node/entity/impl/Animator.java | 1 - .../main/core/game/system/command/Command.kt | 6 +- .../game/system/config/ServerConfigParser.kt | 6 + 20 files changed, 468 insertions(+), 1014 deletions(-) delete mode 100644 Server/src/main/content/global/dialogue/BankerDialogue.kt delete mode 100644 Server/src/main/content/minigame/bountyhunter/MaximillianSackvilleDialogue.kt delete mode 100644 Server/src/main/content/region/asgarnia/burthorpe/dialogue/JadeDialogue.kt delete mode 100644 Server/src/main/content/region/fremennik/lunarisle/dialogue/SirsalBankerDialogue.kt delete mode 100644 Server/src/main/content/region/misc/tutisland/dialogue/BankerTutorDialogue.java diff --git a/Server/src/main/content/data/EnchantedJewellery.kt b/Server/src/main/content/data/EnchantedJewellery.kt index c34a0ffb6..cb94545e5 100644 --- a/Server/src/main/content/data/EnchantedJewellery.kt +++ b/Server/src/main/content/data/EnchantedJewellery.kt @@ -20,7 +20,7 @@ import org.rs09.consts.Sounds import java.util.* /** - * Represents an enchanted jewellery. + * Represents a piece of enchanted jewellery. * @author Vexia, downthecrop, Player Name */ enum class EnchantedJewellery( diff --git a/Server/src/main/content/global/dialogue/BankerDialogue.kt b/Server/src/main/content/global/dialogue/BankerDialogue.kt deleted file mode 100644 index e913ce4f0..000000000 --- a/Server/src/main/content/global/dialogue/BankerDialogue.kt +++ /dev/null @@ -1,181 +0,0 @@ -package content.global.dialogue - -import core.api.* -import core.game.node.entity.player.Player -import core.game.node.entity.player.link.IronmanMode -import core.plugin.Initializable -import core.game.dialogue.IfTopic -import core.game.dialogue.Topic -import content.global.handlers.npc.BankerNPC -import core.tools.END_DIALOGUE -import core.tools.START_DIALOGUE - -@Initializable -class BankerDialogue(player: Player? = null) : core.game.dialogue.DialoguePlugin(player) { - override fun handle(interfaceId: Int, buttonId: Int): Boolean { - when (stage) { - START_DIALOGUE -> when { - hasIronmanRestriction(player, IronmanMode.ULTIMATE) -> { - npcl( - core.game.dialogue.FacialExpression.ANNOYED, - "My apologies, dear ${if (player.isMale) "sir" else "madam"}, " + - "our services are not available for Ultimate ${if (player.isMale) "Ironmen" else "Ironwomen"}" - ).also { stage = END_DIALOGUE } - } - - else -> { - npcl( - core.game.dialogue.FacialExpression.FRIENDLY, - "Good day, how may I help you?" - ).also { - if (hasAwaitingGrandExchangeCollections(player)) { - stage++ - } else { - stage += 2 - } - } - } - } - - 1 -> npcl( - core.game.dialogue.FacialExpression.FRIENDLY, - "Before we go any further, I should inform you that you " + - "have items ready for collection from the Grand Exchange." - ).also { stage++ } - - 2 -> showTopics( - Topic(core.game.dialogue.FacialExpression.FRIENDLY, "I'd like to access my bank account, please.", 10), - IfTopic( - core.game.dialogue.FacialExpression.FRIENDLY, - "I'd like to switch to my ${getBankAccountName(player, true)} bank account.", - 13, - hasActivatedSecondaryBankAccount(player) - ), - IfTopic( - core.game.dialogue.FacialExpression.FRIENDLY, - "I'd like to open a secondary bank account.", - 20, - !hasActivatedSecondaryBankAccount(player) - ), - Topic(core.game.dialogue.FacialExpression.FRIENDLY, "I'd like to check my PIN settings.", 11), - Topic(core.game.dialogue.FacialExpression.FRIENDLY, "I'd like to collect items.", 12), - Topic(core.game.dialogue.FacialExpression.ASKING, "What is this place?", 3), - ) - - 3 -> npcl( - core.game.dialogue.FacialExpression.FRIENDLY, - "This is a branch of the Bank of Gielinor. We have branches in many towns." - ).also { stage++ } - - 4 -> playerl( - core.game.dialogue.FacialExpression.ASKING, - "And what do you do?" - ).also { stage++ } - - 5 -> npcl( - core.game.dialogue.FacialExpression.FRIENDLY, - "We will look after your items and money for you. " + - "Leave your valuables with us if you want to keep them safe." - ).also { stage = END_DIALOGUE } - - 10 -> { - openBankAccount(player) - end() - } - - 11 -> { - openBankPinSettings(player) - end() - } - - 12 -> { - openGrandExchangeCollectionBox(player) - end() - } - - 13 -> { - toggleBankAccount(player) - - npcl( - core.game.dialogue.FacialExpression.FRIENDLY, - "Your active bank account has been switched. " + - "You can now access your ${getBankAccountName(player)} account." - ).also { stage = 2 } - } - - 20 -> npcl( - core.game.dialogue.FacialExpression.FRIENDLY, - "Certainly. We offer secondary accounts to all our customers." - ).also { stage++ } - - 21 -> npcl( - core.game.dialogue.FacialExpression.FRIENDLY, - "The secondary account comes with a standard fee of 5,000,000 coins. The fee is non-refundable " + - "and account activation is permanent." - ).also { stage++ } - - 22 -> npcl( - core.game.dialogue.FacialExpression.FRIENDLY, - "If your inventory does not contain enough money to cover the costs, we will complement " + - "the amount with the money inside your primary bank account." - ).also { stage++ } - - 23 -> npcl( - core.game.dialogue.FacialExpression.ASKING, - "Knowing all this, would you like to proceed with opening your secondary bank account?" - ).also { stage++ } - - 24 -> showTopics( - Topic(core.game.dialogue.FacialExpression.HAPPY, "Yes, I am still interested.", 25), - Topic(core.game.dialogue.FacialExpression.ANNOYED, "Actually, I've changed my mind.", 26) - ) - - 25 -> { - when (activateSecondaryBankAccount(player)) { - SecondaryBankAccountActivationResult.ALREADY_ACTIVE -> { - npcl( - core.game.dialogue.FacialExpression.FRIENDLY, - "Your bank account was already activated, there is no need to pay twice." - ).also { stage = END_DIALOGUE } - } - - SecondaryBankAccountActivationResult.INTERNAL_FAILURE -> { - npcl( - core.game.dialogue.FacialExpression.ANNOYED, - "I must apologize, the transaction was not successful. Please check your " + - "primary bank account and your inventory - if there's money missing, please " + - "screenshot your chat box and contact the game developers." - ).also { stage = END_DIALOGUE } - } - - SecondaryBankAccountActivationResult.NOT_ENOUGH_MONEY -> { - npcl( - core.game.dialogue.FacialExpression.ANNOYED, - "It appears that you do not have the money necessary to cover the costs " + - "associated with opening a secondary bank account. I will be waiting here " + - "until you do." - ).also { stage = END_DIALOGUE } - } - - SecondaryBankAccountActivationResult.SUCCESS -> { - npcl( - core.game.dialogue.FacialExpression.FRIENDLY, - "Your secondary bank account has been opened and can be accessed through any " + - "of the Bank of Gielinor's employees. Thank you for choosing our services." - ).also { stage = END_DIALOGUE } - } - } - } - - 26 -> npcl( - core.game.dialogue.FacialExpression.FRIENDLY, - "Very well. Should you decide a secondary bank account is needed, do not hesitate to " + - "contact any of the Bank of Gielinor's stationary employees. We will be happy to help." - ).also { stage = END_DIALOGUE } - } - - return true - } - - override fun getIds(): IntArray = BankerNPC.NPC_IDS -} diff --git a/Server/src/main/content/global/handlers/item/EnchantJewelleryTabListener.kt b/Server/src/main/content/global/handlers/item/EnchantJewelleryTabListener.kt index c2d65fc08..c642372ff 100644 --- a/Server/src/main/content/global/handlers/item/EnchantJewelleryTabListener.kt +++ b/Server/src/main/content/global/handlers/item/EnchantJewelleryTabListener.kt @@ -1,85 +1,94 @@ package content.global.handlers.item +import core.ServerConstants import core.api.* import core.game.interaction.IntType import core.game.interaction.InteractionListener import core.game.interaction.QueueStrength +import core.game.node.entity.player.info.LogType +import core.game.node.entity.player.info.PlayerMonitor +import core.game.node.item.Item +import core.game.world.update.flag.context.Animation +import org.rs09.consts.Animations import org.rs09.consts.Items import org.rs09.consts.Sounds class EnchantJewelleryTabListener : InteractionListener { - private val LVL_1_ENCHANT = mapOf( - Items.SAPPHIRE_RING_1637 to Items.RING_OF_RECOIL_2550, - Items.SAPPHIRE_NECKLACE_1656 to Items.GAMES_NECKLACE8_3853, - Items.SAPPHIRE_AMULET_1694 to Items.AMULET_OF_MAGIC_1727, - Items.SAPPHIRE_BRACELET_11072 to Items.BRACELET_OF_CLAY_11074 + Items.SAPPHIRE_RING_1637 to Items.RING_OF_RECOIL_2550, + Items.SAPPHIRE_NECKLACE_1656 to Items.GAMES_NECKLACE8_3853, + Items.SAPPHIRE_AMULET_1694 to Items.AMULET_OF_MAGIC_1727, + Items.SAPPHIRE_BRACELET_11072 to Items.BRACELET_OF_CLAY_11074 ) private val LVL_2_ENCHANT = mapOf( - Items.EMERALD_RING_1639 to Items.RING_OF_DUELLING8_2552, - Items.EMERALD_NECKLACE_1658 to Items.BINDING_NECKLACE_5521, - Items.EMERALD_AMULET_1696 to Items.AMULET_OF_DEFENCE_1729, - Items.EMERALD_BRACELET_11076 to Items.CASTLEWAR_BRACE3_11079 + Items.EMERALD_RING_1639 to Items.RING_OF_DUELLING8_2552, + Items.EMERALD_NECKLACE_1658 to Items.BINDING_NECKLACE_5521, + Items.EMERALD_AMULET_1696 to Items.AMULET_OF_DEFENCE_1729, + Items.EMERALD_BRACELET_11076 to Items.CASTLEWAR_BRACE3_11079 ) - private val LVL_3_ENCHANT = mapOf( - Items.RUBY_RING_1641 to Items.RING_OF_FORGING_2568, - Items.RUBY_NECKLACE_1660 to Items.DIGSITE_PENDANT_5_11194, - Items.RUBY_AMULET_1698 to Items.AMULET_OF_STRENGTH_1725, - Items.RUBY_BRACELET_11085 to Items.INOCULATION_BRACE_11088 + Items.RUBY_RING_1641 to Items.RING_OF_FORGING_2568, + Items.RUBY_NECKLACE_1660 to Items.DIGSITE_PENDANT_5_11194, + Items.RUBY_AMULET_1698 to Items.AMULET_OF_STRENGTH_1725, + Items.RUBY_BRACELET_11085 to Items.INOCULATION_BRACE_11088 ) - private val LVL_4_ENCHANT = mapOf( - Items.DIAMOND_RING_1643 to Items.RING_OF_LIFE_2570, - Items.DIAMOND_NECKLACE_1662 to Items.PHOENIX_NECKLACE_11090, - Items.DIAMOND_AMULET_1700 to Items.AMULET_OF_POWER_1731, - Items.DIAMOND_BRACELET_11092 to Items.FORINTHRY_BRACE5_11095 + Items.DIAMOND_RING_1643 to Items.RING_OF_LIFE_2570, + Items.DIAMOND_NECKLACE_1662 to Items.PHOENIX_NECKLACE_11090, + Items.DIAMOND_AMULET_1700 to Items.AMULET_OF_POWER_1731, + Items.DIAMOND_BRACELET_11092 to Items.FORINTHRY_BRACE5_11095 ) - private val LVL_5_ENCHANT = mapOf( - Items.DRAGONSTONE_RING_1645 to Items.RING_OF_WEALTH4_14646, - Items.DRAGON_NECKLACE_1664 to Items.SKILLS_NECKLACE4_11105, - Items.DRAGONSTONE_AMMY_1702 to Items.AMULET_OF_GLORY4_1712, - Items.DRAGON_BRACELET_11115 to Items.COMBAT_BRACELET4_11118 + Items.DRAGONSTONE_RING_1645 to Items.RING_OF_WEALTH_2572, + Items.DRAGON_NECKLACE_1664 to Items.SKILLS_NECKLACE_11113, + Items.DRAGONSTONE_AMMY_1702 to Items.AMULET_OF_GLORY_1704, + Items.DRAGON_BRACELET_11115 to Items.COMBAT_BRACELET_11126 ) - private val LVL_6_ENCHANT = mapOf( - Items.ONYX_RING_6575 to Items.RING_OF_STONE_6583, - Items.ONYX_NECKLACE_6577 to Items.BERSERKER_NECKLACE_11128, - Items.ONYX_AMULET_6581 to Items.AMULET_OF_FURY_6585, - Items.ONYX_BRACELET_11130 to Items.REGEN_BRACELET_11133 + Items.ONYX_RING_6575 to Items.RING_OF_STONE_6583, + Items.ONYX_NECKLACE_6577 to Items.BERSERKER_NECKLACE_11128, + Items.ONYX_AMULET_6581 to Items.AMULET_OF_FURY_6585, + Items.ONYX_BRACELET_11130 to Items.REGEN_BRACELET_11133 + ) + private val TAB_MAPPING = arrayOf( + Pair(Items.ENCHANT_SAPPHIRE_8016, LVL_1_ENCHANT), + Pair(Items.ENCHANT_EMERALD_8017, LVL_2_ENCHANT), + Pair(Items.ENCHANT_RUBY_8018, LVL_3_ENCHANT), + Pair(Items.ENCHANT_DIAMOND_8019, LVL_4_ENCHANT), + Pair(Items.ENCHANT_DRAGONSTN_8020, LVL_5_ENCHANT), + Pair(Items.ENCHANT_ONYX_8021, LVL_6_ENCHANT) ) override fun defineListeners() { - on(IntType.ITEM, "break") {player, node -> - closeAllInterfaces(player) - delayEntity(player, 1) - queueScript(player, strength = QueueStrength.SOFT) { - - val items = when (node.id) { - 8016 -> LVL_1_ENCHANT //Sapphire - 8017 -> LVL_2_ENCHANT - 8018 -> LVL_3_ENCHANT - 8019 -> LVL_4_ENCHANT - 8020 -> LVL_5_ENCHANT - 8021 -> LVL_6_ENCHANT - else -> return@queueScript stopExecuting(player) - } - - if (inInventory(player, node.id)) { - for (item in player.inventory.toArray()) { - if (item == null) continue - val product = items[item.id] ?: continue - if (removeItem(player, node.id) && (removeItem(player, item.id)) && addItem(player, product)) { - playAudio(player, Sounds.POH_TABLET_BREAK_979) - animate(player, 4069, true) - break + for ((tablet, mapping) in TAB_MAPPING) { + on(tablet, IntType.ITEM, "break") { player, _ -> + sendMessage(player, "Try using the tablet on the item instead.") //TODO authentic message + return@on true + } + for ((unenchanted, enchanted) in mapping) { + onUseWith(IntType.ITEM, tablet, unenchanted) { player, tabItem, node -> + var product = enchanted + if (product == Items.RING_OF_WEALTH_2572 && ServerConstants.RING_OF_WEALTH_TELEPORT) { + product = Items.RING_OF_WEALTH_14638 + } + if (removeItem(player, Item(tabItem.id))) { + closeAllInterfaces(player) + playAudio(player, Sounds.POH_TABLET_BREAK_979) + val anim = Animation(Animations.POH_TABLET_BREAK_4069) + animate(player, anim, true) + delayEntity(player, anim.duration) + queueScript(player, anim.duration, QueueStrength.SOFT) { + val item = node.asItem() + val ret = replaceSlot(player, item.slot, Item(product), item) + if (ret != item) { + PlayerMonitor.log(player, LogType.DUPE_ALERT, "Unknown slot-replacement problem when enchanting jewellery (adding $product replaced $ret rather than $item)") + } + return@queueScript stopExecuting(player) } } + return@onUseWith true } - return@queueScript stopExecuting(player) } - return@on true - } - } + } + } } diff --git a/Server/src/main/content/global/handlers/npc/BankerNPC.kt b/Server/src/main/content/global/handlers/npc/BankerNPC.kt index e59ac6b69..7dff2f0d7 100644 --- a/Server/src/main/content/global/handlers/npc/BankerNPC.kt +++ b/Server/src/main/content/global/handlers/npc/BankerNPC.kt @@ -1,62 +1,54 @@ package content.global.handlers.npc +import content.global.handlers.scenery.BankBoothListener +import core.ServerConstants import core.api.* +import core.game.dialogue.ChatAnim +import core.game.dialogue.DialogueLabeller +import core.game.dialogue.DialogueOption +import core.game.interaction.IntType +import core.game.interaction.InteractionListener import core.game.node.Node import core.game.node.entity.Entity import core.game.node.entity.npc.AbstractNPC import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player +import core.game.node.entity.player.link.IronmanMode import core.game.world.map.Direction import core.game.world.map.Location import core.plugin.Initializable import org.rs09.consts.NPCs -import core.game.interaction.InteractionListener -import core.game.interaction.IntType -import content.global.handlers.scenery.BankBoothListener /** * Provides dialogue tree for all generic banker NPCs as well as * handles all the common interactions like 'bank' and 'collect'. * * @author vddCore + * @author Player Name */ @Initializable class BankerNPC : AbstractNPC, InteractionListener { + val NPC_IDS = intArrayOf( + NPCs.BANKER_44, NPCs.BANKER_45, NPCs.BANKER_494, NPCs.BANKER_495, NPCs.BANKER_496, NPCs.BANKER_497, + NPCs.BANKER_498, NPCs.BANKER_499, NPCs.BANKER_1036, NPCs.BANKER_1360, NPCs.BANKER_2163, NPCs.BANKER_2164, + NPCs.BANKER_2354, NPCs.BANKER_2355, NPCs.BANKER_2568, NPCs.BANKER_2569, NPCs.BANKER_2570, NPCs.BANKER_3198, + NPCs.BANKER_3199, NPCs.BANKER_5258, NPCs.BANKER_5259, NPCs.BANKER_5260, NPCs.BANKER_5261, NPCs.BANKER_5776, + NPCs.BANKER_5777, NPCs.BANKER_5912, NPCs.BANKER_5913, NPCs.BANKER_6200, NPCs.BANKER_6532, NPCs.BANKER_6533, + NPCs.BANKER_6534, NPCs.BANKER_6535, NPCs.BANKER_7445, NPCs.BANKER_7446, NPCs.BANKER_7605, + NPCs.GUNDAI_902, + NPCs.GHOST_BANKER_1702, NPCs.GNOME_BANKER_166, NPCs.NARDAH_BANKER_3046, NPCs.MAGNUS_GRAM_5488, NPCs.TZHAAR_KET_ZUH_2619, + NPCs.SIRSAL_BANKER_4519, NPCs.FADLI_958, NPCs.BANK_TUTOR_4907, NPCs.JADE_4296, + NPCs.OGRESS_BANKER_7049, NPCs.OGRESS_BANKER_7050, + NPCs.BANKER_6538 + ) + companion object { private const val LUNAR_ISLE_BANK_REGION = 8253 - val SPECIAL_NPC_IDS = intArrayOf( - NPCs.SIRSAL_BANKER_4519, NPCs.FADLI_958, NPCs.BANK_TUTOR_4907, NPCs.JADE_4296, - NPCs.OGRESS_BANKER_7049, NPCs.OGRESS_BANKER_7050, NPCs.ARNOLD_LYDSPOR_3824, - - /* Maximillian Sackville - Near Wilderness bounty-hunter area. */ - NPCs.BANKER_6538 - ) - - val NPC_IDS = intArrayOf( - NPCs.BANKER_44, NPCs.BANKER_45, NPCs.BANKER_494, NPCs.BANKER_495, NPCs.BANKER_496, NPCs.BANKER_497, - NPCs.BANKER_498, NPCs.BANKER_499, NPCs.BANKER_1036, NPCs.BANKER_1360, NPCs.BANKER_2163, NPCs.BANKER_2164, - NPCs.BANKER_2354, NPCs.BANKER_2355, NPCs.BANKER_2568, NPCs.BANKER_2569, NPCs.BANKER_2570, NPCs.BANKER_3198, - NPCs.BANKER_3199, NPCs.BANKER_5258, NPCs.BANKER_5259, NPCs.BANKER_5260, NPCs.BANKER_5261, NPCs.BANKER_5776, - NPCs.BANKER_5777, NPCs.BANKER_5912, NPCs.BANKER_5913, NPCs.BANKER_6200, NPCs.BANKER_6532, NPCs.BANKER_6533, - NPCs.BANKER_6534, NPCs.BANKER_6535, NPCs.BANKER_7445, NPCs.BANKER_7446, NPCs.BANKER_7605, - NPCs.GUNDAI_902, - - NPCs.GHOST_BANKER_1702, NPCs.GNOME_BANKER_166, NPCs.NARDAH_BANKER_3046, NPCs.MAGNUS_GRAM_5488, NPCs.TZHAAR_KET_ZUH_2619, - ) - - private val ALL_BANKER_NPC_IDS = intArrayOf( - *SPECIAL_NPC_IDS, - *NPC_IDS - ) - /** * This is poorly named, but performs a few checks to see if the player * is trying to access the bank on the Lunar Isle and returns a boolean * controlling whether or not to pass the quick bank or collection use. - * - * TODO - * The location of this method is shit too. Find a better place for it? */ fun checkLunarIsleRestriction(player: Player, node: Node): Boolean { if (node.location.regionId != LUNAR_ISLE_BANK_REGION) @@ -121,37 +113,35 @@ class BankerNPC : AbstractNPC, InteractionListener { private fun provideDestinationOverride(entity: Entity, node: Node): Location { val npc = node as NPC - return when(npc.id) { /* Ogress bankers are 2x2 with their spawn being offset to south-western tile. */ - NPCs.OGRESS_BANKER_7049, - NPCs.OGRESS_BANKER_7050 -> npc.location.transform(3, 1, 0) - - NPCs.BANKER_6532, NPCs.BANKER_6533, - NPCs.BANKER_6534, NPCs.BANKER_6535 -> npc.location.transform(npc.direction, 1) - + NPCs.OGRESS_BANKER_7049, NPCs.OGRESS_BANKER_7050 -> npc.location.transform(3, 1, 0) /* Magnus has no bank booth nearby so we need to handle that edge case here. */ NPCs.MAGNUS_GRAM_5488 -> npc.location.transform(Direction.NORTH, 2) - + /* Special-cased NPCs, idk why */ + NPCs.BANKER_6532, NPCs.BANKER_6533, NPCs.BANKER_6534, NPCs.BANKER_6535 -> npc.location.transform(npc.direction, 1) else -> { if (npc is BankerNPC) { npc.findAdjacentBankBoothLocation()?.let { return it.second } } - return npc.location } } } override fun defineListeners() { - on(ALL_BANKER_NPC_IDS, IntType.NPC, "bank", handler = Companion::attemptBank) - on(ALL_BANKER_NPC_IDS, IntType.NPC, "collect", handler = Companion::attemptCollect) + on(NPC_IDS, IntType.NPC, "bank", handler = Companion::attemptBank) + on(NPC_IDS, IntType.NPC, "collect", handler = Companion::attemptCollect) + on(NPC_IDS, IntType.NPC, "talk-to") { player, node -> + DialogueLabeller.open(player, BankerDialogueLabellerFile(), node as NPC) + return@on true + } } override fun defineDestinationOverrides() { - setDest(IntType.NPC, ALL_BANKER_NPC_IDS, "bank", "collect", "talk-to", handler = ::provideDestinationOverride) + setDest(IntType.NPC, NPC_IDS, "bank", "collect", "talk-to", handler = ::provideDestinationOverride) } override fun init() { @@ -167,5 +157,167 @@ class BankerNPC : AbstractNPC, InteractionListener { } } - override fun getIds(): IntArray = ALL_BANKER_NPC_IDS + class BankerDialogueLabellerFile : DialogueLabeller() { + val BANKERS_WITH_EXTRA_OPTION = intArrayOf(NPCs.BANK_TUTOR_4907, NPCs.JADE_4296, NPCs.BANKER_6538) + + override fun addConversation() { + exec { player, npc -> + if (npc.id == NPCs.SIRSAL_BANKER_4519 && !hasSealOfPassage(player)) { + loadLabel(player, "no seal of passage") + } + if (hasIronmanRestriction(player, IronmanMode.ULTIMATE)) { + if (npc.id == NPCs.JADE_4296) loadLabel(player, "uim for jade") + loadLabel(player, "uim") + } + if (npc.id == NPCs.JADE_4296) loadLabel(player, "hello for jade") + loadLabel(player, "hello") + } + label("no seal of passage") + npc(ChatAnim.ANNOYED, "What are you doing here, Fremennik?!") + player(ChatAnim.WORRIED, "I have a Seal of Pass...") + npc(ChatAnim.ANGRY, "No you don't! Begone!") + // todo: kick them out, but only one of the two banker types should do so (see historical wiki) + goto("nowhere") + label("uim for jade") + npc(ChatAnim.ANNOYED, "Greetings, warrior. I wish I could help you, but our services are not available for Ultimate Iron@g[men,women].") + goto("nowhere") + label("uim") + npc(ChatAnim.ANNOYED, "My apologies, dear @g[sir,madam], our services are not available for Ultimate Iron@g[men,women]") + goto("nowhere") + label("hello for jade") + npc("Greetings warrior, how may I help you?") + exec { player, _ -> loadLabel(player, if (hasAwaitingGrandExchangeCollections(player)) "ge collect" else "main options") } + label("hello") + npc("Good day, would you like to access your bank account?") + exec { player, _ -> loadLabel(player, if (hasAwaitingGrandExchangeCollections(player)) "ge collect" else "main options") } + label("ge collect") + npc("Before we go any further, I should inform you that you have items ready for collection from the Grand Exchange.") + goto("main options") + + label("main options") + options( + DialogueOption("how to use", "How do I use the bank?") { _, npc -> return@DialogueOption npc.id == NPCs.BANK_TUTOR_4907 }, + DialogueOption("who is the bounty hunter banker", "Who are you?") { _, npc -> return@DialogueOption npc.id == NPCs.BANKER_6538 }, + DialogueOption("access", "I'd like to access my bank account please.", expression = ChatAnim.ASKING), + DialogueOption("buy second bank", "I'd like to open a secondary bank account.") { player, _ -> return@DialogueOption ServerConstants.SECOND_BANK && !hasActivatedSecondaryBankAccount(player) }, + DialogueOption("switch second bank", "I'd like to switch to my primary bank account.") { player, _ -> return@DialogueOption hasActivatedSecondaryBankAccount(player) && isUsingSecondaryBankAccount(player) }, + DialogueOption("switch second bank", "I'd like to switch to my secondary bank account.") { player, _ -> return@DialogueOption hasActivatedSecondaryBankAccount(player) && !isUsingSecondaryBankAccount(player) }, + DialogueOption("pin", "I'd like to check my PIN settings."), + DialogueOption("collect", "I'd like to collect items."), + DialogueOption("what is this place", "What is this place?") { _, npc -> return@DialogueOption npc.id !in BANKERS_WITH_EXTRA_OPTION }, + DialogueOption("jade's employment history", "How long have you worked here?") { _, npc -> return@DialogueOption npc.id == NPCs.JADE_4296 }, + DialogueOption("is the bounty hunter banker afraid", "Aren't you afraid of working in the Wilderness?") { _, npc -> return@DialogueOption npc.id == NPCs.BANKER_6538 && !ServerConstants.SECOND_BANK } + ) + + label("how to use") + options( + DialogueOption("using bank", "Using the bank itself.", "Using the bank itself. I'm not sure how....?"), + DialogueOption("using deposit", "Using bank deposit boxes.", "Using Bank deposit boxes.... what are they?"), + DialogueOption("using PIN", "What's this PIN thing that people keep talking about?", "What's this PIN thing that people keep talking about?"), + DialogueOption("nowhere", "Goodbye.") + ) + + label("using bank") + npc("Speak to any banker and ask to see your bank", "account. If you have set a PIN you will be asked for", "it, then all the belongings you have placed in the bank", "will appear in the window. To withdraw one item, left-") + npc("click on it once.") + npc("To withdraw many, right-click on the item and select", "from the menu. The same for depositing, left-click on", "the item in your inventory to deposit it in the bank.", "Right-click on it to deposit many of the same items.") + npc("To move things around in your bank: firstly select", "Swap or Insert as your default moving mode, you can", "find these buttons on the bank window itself. Then click", "and drag an item to where you want it to appear.") + npc("You may withdraw 'notes' or 'certificates' when the", "items you are trying to withdraw do not stack in your", "inventory. This will only work for items which are", "tradeable.") + npc("For instance, if you wanted to sell 100 logs to another", "player, they would not fit in one inventory and you", "would need to do multiple trades. Instead, click the", "Note button to withdraw the logs as 'certs' or 'notes'") + npc("then withdraw the items you need.") + goto("how to use") + + label("using deposit") + npc("They look like grey pillars, there's one just over there,", "near the desk. Bank deposit boxes save so much time.", "If you're simply wanting to deposit a single item, 'Use'", "it on the deposit box.") + npc("Otherwise, simply click once on the box and it will give", "you a choice of what to deposit in an interface very", "similar to the bank itself. Very quick for when you're", "simply fishing or mining etc.") + goto("how to use") + + label("using PIN") + npc("The PIN - Personal Identification Number - can be", "set on your bank account to protect the items there in", "case someone finds out your account password. It", "consists of four numbers that you remember and tell") + npc("no one.") + npc("So if someone did manage to get your password they", "couldn't steal your items if they were in the bank.") + goto("how to use") + + label("who is the bounty hunter banker") + npc(ChatAnim.NEUTRAL, "How inconsiderate of me, dear @g[sir,madam]. My name is Maximillian Sackville and I conduct operations here on behalf of The Bank of Gielinor.") + goto("main options") + + label("access") + exec { player, _ -> openBankAccount(player) } + goto("nowhere") + + label("buy second bank") + npc("Certainly. We offer secondary accounts to all our customers.") + npc("The secondary account comes with a standard fee of 5,000,000 coins. The fee is non-refundable and account activation is permanent.") + npc("If your inventory does not contain enough money to cover the costs, we will complement the amount with the money inside your primary bank account.") + npc("Knowing all this, would you like to proceed with opening your secondary bank account?") + options( + DialogueOption("buy second bank yes", "Yes, I am still interested.", expression = ChatAnim.HAPPY), + DialogueOption("buy second bank no", "Actually, I've changed my mind.", expression = ChatAnim.ANNOYED) + ) + + label("buy second bank yes") + exec { player, _ -> loadLabel(player, when (activateSecondaryBankAccount(player)) { + SecondaryBankAccountActivationResult.ALREADY_ACTIVE -> "already" + SecondaryBankAccountActivationResult.INTERNAL_FAILURE -> "failure" + SecondaryBankAccountActivationResult.NOT_ENOUGH_MONEY -> "not enough cash" + SecondaryBankAccountActivationResult.SUCCESS -> "success" + }) } + label("already") + npc(ChatAnim.FRIENDLY, "Your bank account was already activated, there is no need to pay twice.") + goto("main options") + label("failure") + npc(ChatAnim.ANNOYED, "I must apologize, the transaction was not successful. Please check your primary bank account and your inventory - if there's money missing, please screenshot your chatbox and contact the game developers.") + goto("main options") + label("not enough cash") + npc(ChatAnim.ANNOYED, "It appears that you do not have the money necessary to cover the costs associated with opening a secondary bank account. I will be waiting here until you do.") + goto("main options") + label("success") + npc(ChatAnim.FRIENDLY, "Your secondary bank account has been opened and can be accessed through any of the Bank of Gielinor's employees. Thank you for choosing our services.") + goto("main options") + + label("buy second bank no") + npc("Very well. Should you decide a secondary bank account is needed, do not hesitate to contact any of the Bank of Gielinor's stationary employees. We will be happy to help.") + goto("main options") + + label("switch second bank") + exec { player, _ -> + toggleBankAccount(player) + loadLabel(player, if (isUsingSecondaryBankAccount(player)) "now secondary" else "now primary") + } + label("now primary") + npc("Your active bank account has been switched. You can now access your primary account.") + goto("main options") + label("now secondary") + npc("Your active bank account has been switched. You can now access your secondary account.") + goto("main options") + + label("pin") + exec { player, _ -> openBankPinSettings(player) } + goto("nowhere") + + label("collect") + exec { player, _ -> openGrandExchangeCollectionBox(player) } + goto("nowhere") + + label("what is this place") + npc("This is a branch of the Bank of Gielinor. We have branches in many towns.") + player("And what do you do?") + npc("We will look after your items and money for you. Leave your valuables with us if you want to keep them safe.") + goto("main options") + + label("jade's employment history") + npc(ChatAnim.FRIENDLY, "Oh, ever since the Guild opened. I like it here.") + player(ChatAnim.ASKING, "Why's that?") + npc(ChatAnim.FRIENDLY, "Well... what with all these warriors around, there's not much chance of my bank being robbed, is there?") + goto("nowhere") + + label("is the bounty hunter banker afraid") + npc(ChatAnim.NEUTRAL, "While the Wilderness is quite a dangerous place, The Bank of Gielinor offers us - roving bankers - extraordinary benefits for our hard work in hazardous environments.") + npc(ChatAnim.NEUTRAL, "This allows us to provide our services to customers regardless of their current whereabouts. Our desire to serve is stronger than our fear of the Wilderness.") + goto("nowhere") + } + } + + override fun getIds(): IntArray = NPC_IDS } diff --git a/Server/src/main/content/minigame/bountyhunter/MaximillianSackvilleDialogue.kt b/Server/src/main/content/minigame/bountyhunter/MaximillianSackvilleDialogue.kt deleted file mode 100644 index a5b047041..000000000 --- a/Server/src/main/content/minigame/bountyhunter/MaximillianSackvilleDialogue.kt +++ /dev/null @@ -1,120 +0,0 @@ -package content.minigame.bountyhunter - -import core.api.* -import core.game.dialogue.DialoguePlugin -import core.game.dialogue.FacialExpression -import core.game.node.entity.player.Player -import core.game.node.entity.player.link.IronmanMode -import core.plugin.Initializable -import org.rs09.consts.NPCs -import core.game.dialogue.IfTopic -import core.game.dialogue.Topic -import core.tools.END_DIALOGUE -import core.tools.START_DIALOGUE - -/** - * Provides dialogue tree for Maximillian Sackville, - * the Bounty Hounter roving banker. - * - * @author vddCore - */ -@Initializable -class MaximillianSackvilleDialogue(player: Player? = null) : DialoguePlugin(player) { - override fun handle(interfaceId: Int, buttonId: Int): Boolean { - when (stage) { - START_DIALOGUE -> when { - hasIronmanRestriction(player, IronmanMode.ULTIMATE) -> { - npcl( - FacialExpression.NEUTRAL, - "My apologies, dear ${if (player.isMale) "sir" else "madam"}, " + - "our services are not available for Ultimate ${if (player.isMale) "Ironmen" else "Ironwomen"}." - ).also { stage = END_DIALOGUE } - } - - else -> { - npcl( - FacialExpression.NEUTRAL, - "Good day, how may I help you?" - ).also { - if (hasAwaitingGrandExchangeCollections(player)) { - stage++ - } else { - stage += 2 - } - } - } - } - - 1 -> npcl( - FacialExpression.NEUTRAL, - "Before we go any further, I should inform you that you " + - "have items ready for collection from the Grand Exchange." - ).also { stage++ } - - 2 -> playerl( - FacialExpression.ASKING, - "Who are you?" - ).also { stage++ } - - 3 -> npcl( - FacialExpression.NEUTRAL, - "How inconsiderate of me, dear ${if (player.isMale) "sir" else "madam"}. " + - "My name is Maximillian Sackville and I conduct operations here on behalf " + - "of The Bank of Gielinor." - ).also { stage++ } - - 4 -> showTopics( - Topic(FacialExpression.NEUTRAL, "I'd like to access my bank account.", 10), - IfTopic( - FacialExpression.NEUTRAL, - "I'd like to switch to my ${getBankAccountName(player, true)} bank account.", - 11, - hasActivatedSecondaryBankAccount(player) - ), - Topic(FacialExpression.NEUTRAL, "I'd like to check my PIN settings.", 12), - Topic(FacialExpression.NEUTRAL, "I'd like to collect items.", 13), - Topic(FacialExpression.ASKING, "Aren't you afraid of working in the Wilderness?", 5) - ) - - 5 -> npcl( - FacialExpression.NEUTRAL, - "While the Wilderness is quite a dangerous place, The Bank of Gielinor offers " + - "us - roving bankers - extraordinary benefits for our hard work in hazardous environments." - ).also { stage++ } - - 6 -> npcl( - FacialExpression.NEUTRAL, - "This allows us to provide our services to customers regardless of their current " + - "whereabouts. Our desire to serve is stronger than our fear of the Wilderness." - ).also { stage = END_DIALOGUE } - - 10 -> { - openBankAccount(player) - end() - } - - 11 -> { - toggleBankAccount(player) - - npcl( - FacialExpression.NEUTRAL, - "Naturally. You can now access your ${getBankAccountName(player)} bank account." - ).also { stage = END_DIALOGUE } - } - - 12 -> { - openBankPinSettings(player) - end() - } - - 13 -> { - openGrandExchangeCollectionBox(player) - end() - } - } - - return true - } - - override fun getIds() = intArrayOf(NPCs.BANKER_6538) -} \ No newline at end of file diff --git a/Server/src/main/content/minigame/mta/EnchantSpell.kt b/Server/src/main/content/minigame/mta/EnchantSpell.kt index d6df04fe8..53f05bc13 100644 --- a/Server/src/main/content/minigame/mta/EnchantSpell.kt +++ b/Server/src/main/content/minigame/mta/EnchantSpell.kt @@ -1,6 +1,8 @@ package content.minigame.mta import content.minigame.mta.impl.EnchantingZone.Shapes +import core.ServerConstants +import core.api.replaceSlot import core.game.node.Node import core.game.node.entity.Entity import core.game.node.entity.combat.spell.SpellType @@ -9,6 +11,8 @@ import core.game.node.entity.player.link.SpellBookManager.SpellBook import core.game.node.entity.player.link.audio.Audio import core.game.node.entity.combat.spell.MagicSpell import core.game.node.entity.combat.spell.Runes +import core.game.node.entity.player.info.LogType +import core.game.node.entity.player.info.PlayerMonitor import core.game.node.item.Item import core.game.world.update.flag.context.Animation import core.game.world.update.flag.context.Graphics @@ -47,7 +51,7 @@ class EnchantSpell : MagicSpell { return false } entity.interfaceManager.setViewedTab(6) - val enchanted = jewellery?.getOrDefault(target.id,null) + var enchanted = jewellery?.getOrDefault(target.id,null) if (enchanted == null) { entity.packetDispatch.sendMessage("You can't use this spell on this item.") @@ -56,12 +60,15 @@ class EnchantSpell : MagicSpell { if (!meetsRequirements(entity, true, true)) { return false } - - if (entity.inventory.remove(target)) { - visualize(entity, target) - entity.inventory.add(enchanted) + if (enchanted.id == Items.RING_OF_WEALTH_2572 && ServerConstants.RING_OF_WEALTH_TELEPORT) { + enchanted = Item(Items.RING_OF_WEALTH_14638) } + visualize(entity, target) + val ret = replaceSlot(entity, target.slot, enchanted) + if (ret != target) { + PlayerMonitor.log(entity, LogType.DUPE_ALERT, "Unknown slot-replacement problem when enchanting jewellery (adding $enchanted replaced $ret rather than $target)") + } //MTA-Specific Code if (entity.zoneMonitor.isInZone("Enchantment Chamber")) { @@ -184,10 +191,10 @@ class EnchantSpell : MagicSpell { SpellBook.MODERN.register(51, EnchantSpell(68, 78.0, mapOf( //Begin Jewelry Enchantment - Items.DRAGONSTONE_RING_1645 to Item(14646), - Items.DRAGON_NECKLACE_1664 to Item(Items.SKILLS_NECKLACE4_11105), - Items.DRAGONSTONE_AMMY_1702 to Item(Items.AMULET_OF_GLORY4_1712), - Items.DRAGON_BRACELET_11115 to Item(Items.COMBAT_BRACELET4_11118), + Items.DRAGONSTONE_RING_1645 to Item(Items.RING_OF_WEALTH_2572), + Items.DRAGON_NECKLACE_1664 to Item(Items.SKILLS_NECKLACE_11113), + Items.DRAGONSTONE_AMMY_1702 to Item(Items.AMULET_OF_GLORY_1704), + Items.DRAGON_BRACELET_11115 to Item(Items.COMBAT_BRACELET_11126), //Begin MTA-Specific Enchantments Items.CUBE_6899 to Item(Items.ORB_6902), Items.CYLINDER_6898 to Item(Items.ORB_6902), diff --git a/Server/src/main/content/region/asgarnia/burthorpe/dialogue/EmeraldBenedictDialogue.kt b/Server/src/main/content/region/asgarnia/burthorpe/dialogue/EmeraldBenedictDialogue.kt index 9a200f9e5..e59b3d907 100644 --- a/Server/src/main/content/region/asgarnia/burthorpe/dialogue/EmeraldBenedictDialogue.kt +++ b/Server/src/main/content/region/asgarnia/burthorpe/dialogue/EmeraldBenedictDialogue.kt @@ -1,5 +1,6 @@ package content.region.asgarnia.burthorpe.dialogue +import core.ServerConstants import core.api.* import core.game.dialogue.DialoguePlugin import core.game.dialogue.FacialExpression @@ -21,7 +22,7 @@ import core.tools.START_DIALOGUE class EmeraldBenedictDialogue(player: Player? = null) : DialoguePlugin(player) { override fun handle(interfaceId: Int, buttonId: Int): Boolean { when (stage) { - START_DIALOGUE -> if(hasIronmanRestriction(player, IronmanMode.ULTIMATE)) { + START_DIALOGUE -> if (hasIronmanRestriction(player, IronmanMode.ULTIMATE)) { npcl( FacialExpression.ANNOYED, "Get lost, tin can." @@ -53,6 +54,12 @@ class EmeraldBenedictDialogue(player: Player? = null) : DialoguePlugin(player) { 4, hasActivatedSecondaryBankAccount(player) ), + IfTopic( + FacialExpression.ASKING, + "Yes, but can you open a secondary bank account for me?", + 7, + ServerConstants.SECOND_BANK && !hasActivatedSecondaryBankAccount(player) + ), Topic(FacialExpression.ASKING, "Yes, but can you show me my PIN settings?", 5), Topic(FacialExpression.ASKING, "Yes, but can you show me my collection box?", 6), Topic(FacialExpression.ANNOYED, "Yes, thanks. And I'll keep hold of it too.", END_DIALOGUE) @@ -68,7 +75,7 @@ class EmeraldBenedictDialogue(player: Player? = null) : DialoguePlugin(player) { npcl( FacialExpression.SUSPICIOUS, "Sure thing. Feel free to rummage through whatever's in your ${getBankAccountName(player)} now." - ).also { stage = END_DIALOGUE } + ).also { stage = 2 } } 5 -> { @@ -80,10 +87,24 @@ class EmeraldBenedictDialogue(player: Player? = null) : DialoguePlugin(player) { openGrandExchangeCollectionBox(player) end() } + + 7 -> { + npcl( + FacialExpression.SUSPICIOUS, + "Sure, just give me five million in gold and I'll take care of it." + ).also { stage++ } + } + + 8 -> { + playerl( + FacialExpression.SUSPICIOUS, + "On second thought, I think I'll ask somebody more reputable..." + ).also { stage = END_DIALOGUE } + } } return true } override fun getIds(): IntArray = intArrayOf(NPCs.EMERALD_BENEDICT_2271) -} \ No newline at end of file +} diff --git a/Server/src/main/content/region/asgarnia/burthorpe/dialogue/JadeDialogue.kt b/Server/src/main/content/region/asgarnia/burthorpe/dialogue/JadeDialogue.kt deleted file mode 100644 index 85ebda973..000000000 --- a/Server/src/main/content/region/asgarnia/burthorpe/dialogue/JadeDialogue.kt +++ /dev/null @@ -1,106 +0,0 @@ -package content.region.asgarnia.burthorpe.dialogue - -import core.api.* -import core.game.dialogue.DialoguePlugin -import core.game.dialogue.FacialExpression -import core.game.node.entity.player.Player -import core.game.node.entity.player.link.IronmanMode -import core.plugin.Initializable -import org.rs09.consts.NPCs -import core.game.dialogue.IfTopic -import core.game.dialogue.Topic -import core.tools.END_DIALOGUE -import core.tools.START_DIALOGUE - -/** - * Provides a dialogue tree for Jade inside Warriors' Guild. - * - * @author vddCore - */ -@Initializable -class JadeDialogue(player: Player? = null) : DialoguePlugin(player) { - override fun handle(interfaceId: Int, buttonId: Int): Boolean { - when (stage) { - START_DIALOGUE -> if (hasIronmanRestriction(player, IronmanMode.ULTIMATE)) { - npcl( - FacialExpression.NEUTRAL, - "Greetings, warrior. I wish I could help you, but " + - "our services are not available for Ultimate ${if (player.isMale) "Ironmen" else "Ironwomen"}." - ).also { stage = END_DIALOGUE } - } - else { - npcl( - FacialExpression.NEUTRAL, - "Greetings warrior, how may I help you?" - ).also { - if (hasAwaitingGrandExchangeCollections(player)) { - stage++ - } else { - stage += 2 - } - } - } - - 1 -> npcl( - FacialExpression.NEUTRAL, - "Before we go any further, I should inform you that you " + - "have items ready for collection from the Grand Exchange." - ).also { stage++ } - - 2 -> showTopics( - Topic(FacialExpression.NEUTRAL, "I'd like to access my bank account, please.", 10), - IfTopic( - FacialExpression.NEUTRAL, - "I'd like to switch to my ${getBankAccountName(player, true)} bank account.", - 13, - hasActivatedSecondaryBankAccount(player) - ), - Topic(FacialExpression.NEUTRAL, "I'd like to check my PIN settings.", 11), - Topic(FacialExpression.NEUTRAL, "I'd like to see my collection box.", 12), - Topic(FacialExpression.ASKING, "How long have you worked here?", 3) - ) - - 3 -> npcl( - FacialExpression.FRIENDLY, - "Oh, ever since the Guild opened. I like it here." - ).also { stage++ } - - 4 -> playerl( - FacialExpression.ASKING, - "Why's that?" - ).also { stage++ } - - 5 -> npcl( - FacialExpression.FRIENDLY, - "Well... What with all these warriors around, there's not much chance of my bank being robbed, is there?" - ).also { stage = 2 } - - 10 -> { - openBankAccount(player) - end() - } - - 11 -> { - openBankPinSettings(player) - end() - } - - 12 -> { - openGrandExchangeCollectionBox(player) - end() - } - - 13 -> { - toggleBankAccount(player) - npcl( - FacialExpression.FRIENDLY, - "Of course! Your ${getBankAccountName(player)} account is now active!" - ).also { stage = END_DIALOGUE } - } - } - - return true - } - - override fun getIds(): IntArray = intArrayOf(NPCs.JADE_4296) -} \ No newline at end of file diff --git a/Server/src/main/content/region/asgarnia/burthorpe/handlers/HeroGuildPlugin.java b/Server/src/main/content/region/asgarnia/burthorpe/handlers/HeroGuildPlugin.java index cc4e8fb62..60e6c86fd 100644 --- a/Server/src/main/content/region/asgarnia/burthorpe/handlers/HeroGuildPlugin.java +++ b/Server/src/main/content/region/asgarnia/burthorpe/handlers/HeroGuildPlugin.java @@ -1,5 +1,6 @@ package content.region.asgarnia.burthorpe.handlers; +import core.ServerConstants; import core.cache.def.impl.SceneryDefinition; import content.data.EnchantedJewellery; import core.game.global.action.DoorActionHandler; @@ -18,11 +19,15 @@ import core.plugin.Initializable; import core.plugin.ClassScanner; import static core.api.ContentAPIKt.hasRequirement; +import static core.api.ContentAPIKt.sendMessage; + import content.data.Quests; +import org.rs09.consts.Items; /** * Represents the hero guild. * @author Vexia + * @author Player Name */ @Initializable public final class HeroGuildPlugin extends OptionHandler { @@ -43,8 +48,8 @@ public final class HeroGuildPlugin extends OptionHandler { switch (id) { case 2624: case 2625: - if (!hasRequirement(player, Quests.HEROES_QUEST)) - return true; + if (!hasRequirement(player, Quests.HEROES_QUEST)) + return true; DoorActionHandler.handleAutowalkDoor(player, (Scenery) node); break; } @@ -56,6 +61,7 @@ public final class HeroGuildPlugin extends OptionHandler { /** * Handles the recharging of dragonstone jewellery. * @author Vexia + * @author Player Name */ public static final class JewelleryRechargePlugin extends UseWithHandler { @@ -65,8 +71,7 @@ public final class HeroGuildPlugin extends OptionHandler { private static final int[] IDS = new int[] { 1710, 1708, 1706, 1704, 11107, 11109, 11111, 11113, 11120, 11122, 11124, 11126, 10354, 10356, 10358, 10360, 10362, 14644,14642,14640,14638, 2572 }; /** - * Constructs a new {@Code JewelleryRechargePlugin} {@Code - * Object} + * Constructs a new JewelleryRechargePlugin object */ public JewelleryRechargePlugin() { super(IDS); @@ -83,18 +88,24 @@ public final class HeroGuildPlugin extends OptionHandler { @Override public boolean handle(NodeUsageEvent event) { final Player player = event.getPlayer(); - if (!hasRequirement(player, Quests.HEROES_QUEST)) - return true; + if (!hasRequirement(player, Quests.HEROES_QUEST)) { + return true; //hasRequirement shows the message + } final EnchantedJewellery jewellery; assert event.getUsedItem() != null; jewellery = EnchantedJewellery.Companion.getIdMap().get(event.getUsedItem().getId()); - if (!hasRequirement(player, Quests.HEROES_QUEST)) - return true; - if (jewellery == EnchantedJewellery.COMBAT_BRACELET || jewellery == EnchantedJewellery.SKILLS_NECKLACE) - if (!hasRequirement(player, Quests.LEGENDS_QUEST)) - return true; - if (jewellery == null && event.getUsedItem().getId() != 2572) { - return true; + if (jewellery == null) { + return false; //nothing interesting happens + } + if (jewellery == EnchantedJewellery.RING_OF_WEALTH) { + if (!ServerConstants.RING_OF_WEALTH_TELEPORT) { + return false; + } + } + if (jewellery == EnchantedJewellery.COMBAT_BRACELET || jewellery == EnchantedJewellery.SKILLS_NECKLACE) { + if (!hasRequirement(player, Quests.LEGENDS_QUEST)) { + return true; + } } boolean fam = event.getUsedWith() instanceof NPC; if (fam && jewellery != EnchantedJewellery.AMULET_OF_GLORY & jewellery != EnchantedJewellery.AMULET_OF_GLORY_T) { @@ -113,9 +124,9 @@ public final class HeroGuildPlugin extends OptionHandler { player.getInventory().replace(rechargedItem, event.getUsedItem().getSlot()); String name = jewellery.getJewelleryName(rechargedItem); if (!fam) { - player.sendMessage("You dip the " + name + " in the fountain..."); + sendMessage(player, "You dip the " + name.toLowerCase() + " in the fountain..."); } else { - player.sendMessage("Your titan recharges the glory."); + sendMessage(player, "Your titan recharges the " + name.toLowerCase() + "."); } return true; } diff --git a/Server/src/main/content/region/fremennik/lunarisle/dialogue/SirsalBankerDialogue.kt b/Server/src/main/content/region/fremennik/lunarisle/dialogue/SirsalBankerDialogue.kt deleted file mode 100644 index ebe225e11..000000000 --- a/Server/src/main/content/region/fremennik/lunarisle/dialogue/SirsalBankerDialogue.kt +++ /dev/null @@ -1,209 +0,0 @@ -package content.region.fremennik.lunarisle.dialogue - -import core.api.* -import core.game.dialogue.DialoguePlugin -import core.game.dialogue.FacialExpression -import core.game.node.entity.player.Player -import core.game.node.entity.player.link.IronmanMode -import core.plugin.Initializable -import org.rs09.consts.NPCs -import core.game.dialogue.IfTopic -import core.game.dialogue.Topic -import core.tools.END_DIALOGUE -import core.tools.START_DIALOGUE - -/** - * Handles Sirsal banker dialogue tree. - * - * @author vddCore - */ -@Initializable -class SirsalBankerDialogue(player: Player? = null) : DialoguePlugin(player) { - override fun handle(interfaceId: Int, buttonId: Int): Boolean { - when (stage) { - START_DIALOGUE -> if (hasSealOfPassage(player)) { - if (hasIronmanRestriction(player, IronmanMode.ULTIMATE)) { - npcl( - FacialExpression.NEUTRAL, - "My apologies, dear ${if (player.isMale) "sir" else "madam"}, " + - "our services are not available for Ultimate ${if (player.isMale) "Ironmen" else "Ironwomen"}" - ).also { stage = END_DIALOGUE } - } else { - - npcl( - FacialExpression.NEUTRAL, - "Good day, how may I help you?" - ).also { - if (hasAwaitingGrandExchangeCollections(player)) { - stage++ - } else { - stage += 2 - } - } - } - } else { - playerl(FacialExpression.HALF_WORRIED, "Hi, I...") - stage = 30 - } - - 1 -> npcl( - FacialExpression.NEUTRAL, - "Before we go any further, I should inform you that you " + - "have items ready for collection from the Grand Exchange." - ).also { stage++ } - - 2 -> showTopics( - Topic(FacialExpression.NEUTRAL, "I'd like to access my bank account, please.", 10), - IfTopic( - FacialExpression.NEUTRAL, - "I'd like to switch to my ${getBankAccountName(player, true)} bank account.", - 13, - hasActivatedSecondaryBankAccount(player) - ), - IfTopic( - FacialExpression.NEUTRAL, - "I'd like to open a secondary bank account.", - 20, - !hasActivatedSecondaryBankAccount(player) - ), - Topic(FacialExpression.NEUTRAL, "I'd like to check my PIN settings.", 11), - Topic(FacialExpression.NEUTRAL, "I'd like to collect items.", 12), - Topic(FacialExpression.ASKING, "What is this place?", 3), - ) - - 3 -> npcl( - FacialExpression.NEUTRAL, - "This is a branch of the Bank of Gielinor. We have branches in many towns." - ).also { stage++ } - - 4 -> playerl( - FacialExpression.ASKING, - "And what do you do?" - ).also { stage++ } - - 5 -> npcl( - FacialExpression.NEUTRAL, - "We will look after your items and money for you. " + - "Leave your valuables with us if you want to keep them safe." - ).also { stage = END_DIALOGUE } - - 10 -> { - openBankAccount(player) - end() - } - - 11 -> { - openBankPinSettings(player) - end() - } - - 12 -> { - openGrandExchangeCollectionBox(player) - end() - } - - 13 -> { - toggleBankAccount(player) - - npcl( - FacialExpression.NEUTRAL, - "Your active bank account has been switched. " + - "You can now access your ${getBankAccountName(player)} account." - ).also { stage = END_DIALOGUE } - } - - 20 -> npcl( - FacialExpression.NEUTRAL, - "Certainly. We offer secondary accounts to all our customers." - ).also { stage++ } - - 21 -> npcl( - FacialExpression.NEUTRAL, - "The secondary account comes with a standard fee of 5,000,000 coins. The fee is non-refundable " + - "and account activation is permanent." - ).also { stage++ } - - 22 -> npcl( - FacialExpression.NEUTRAL, - "If your inventory does not contain enough money to cover the costs, we will complement " + - "the amount with the money inside your primary bank account." - ).also { stage++ } - - 23 -> npcl( - FacialExpression.ASKING, - "Knowing all this, would you like to proceed with opening your secondary bank account?" - ).also { stage++ } - - 24 -> showTopics( - Topic(FacialExpression.NEUTRAL, "Yes, I am still interested.", 25), - Topic(FacialExpression.NEUTRAL, "Actually, I've changed my mind.", 26) - ) - - 25 -> { - when (activateSecondaryBankAccount(player)) { - SecondaryBankAccountActivationResult.ALREADY_ACTIVE -> { - npcl( - FacialExpression.NEUTRAL, - "Your bank account was already activated, there is no need to pay twice." - ).also { stage = END_DIALOGUE } - } - - SecondaryBankAccountActivationResult.INTERNAL_FAILURE -> { - npcl( - FacialExpression.NEUTRAL, - "I must apologize, the transaction was not successful. Please check your " + - "primary bank account and your inventory - if there's money missing, please " + - "screenshot your chat box and contact the game developers." - ).also { stage = END_DIALOGUE } - } - - SecondaryBankAccountActivationResult.NOT_ENOUGH_MONEY -> { - npcl( - FacialExpression.NEUTRAL, - "It appears that you do not have the money necessary to cover the costs " + - "associated with opening a secondary bank account. I will be waiting here " + - "until you do." - ).also { stage = END_DIALOGUE } - } - - SecondaryBankAccountActivationResult.SUCCESS -> { - npcl( - FacialExpression.NEUTRAL, - "Your secondary bank account has been opened and can be accessed through any " + - "of the Bank of Gielinor's employees. Thank you for choosing our services." - ).also { stage = END_DIALOGUE } - } - } - } - - 26 -> npcl( - FacialExpression.NEUTRAL, - "Very well. Should you decide a secondary bank account is needed, do not hesitate to " + - "contact any of the Bank of Gielinor's stationary employees. We will be happy to help." - ).also { stage = END_DIALOGUE } - - 30 -> npcl( - FacialExpression.ANNOYED, - "What are you doing here, Fremennik?!" - ).also { stage++ } - - 31 -> playerl( - FacialExpression.WORRIED, - "I have a Seal of Pass..." - ).also { stage++ } - - 32 -> npcl( - FacialExpression.ANGRY, - "No you don't! Begone!" - ).also { stage = END_DIALOGUE } - - /* TODO: Is the above related to Lunar Diplomacy? */ - } - - return true - } - - override fun getIds(): IntArray { - return intArrayOf(NPCs.SIRSAL_BANKER_4519) - } -} diff --git a/Server/src/main/content/region/kandarin/dialogue/EniolaDialogue.kt b/Server/src/main/content/region/kandarin/dialogue/EniolaDialogue.kt index de5a3e3c8..fd294e309 100644 --- a/Server/src/main/content/region/kandarin/dialogue/EniolaDialogue.kt +++ b/Server/src/main/content/region/kandarin/dialogue/EniolaDialogue.kt @@ -1,8 +1,10 @@ package content.region.kandarin.dialogue +import core.ServerConstants import core.api.* import core.game.dialogue.DialoguePlugin import core.game.dialogue.FacialExpression +import core.game.dialogue.IfTopic import core.game.node.entity.player.Player import core.game.node.entity.player.link.IronmanMode import core.plugin.Initializable @@ -54,11 +56,16 @@ class EniolaDialogue(player: Player? = null) : DialoguePlugin(player) { 4 -> showTopics( Topic(FacialExpression.HALF_THINKING, "If you work for the bank, what are you doing here?", 10), Topic(FacialExpression.NEUTRAL, "I'd like to access my bank account, please.", 30), + IfTopic(FacialExpression.NEUTRAL, "I'd like to open a secondary bank account.", 5, ServerConstants.SECOND_BANK && !hasActivatedSecondaryBankAccount(player)), + IfTopic(FacialExpression.NEUTRAL, "I'd like to switch to my ${getBankAccountName(player, true)} bank account.", 40, hasActivatedSecondaryBankAccount(player)), Topic(FacialExpression.NEUTRAL, "I'd like to check my PIN settings.", 31), Topic(FacialExpression.NEUTRAL, "I'd like to see my collection box.", 32), - Topic(FacialExpression.NEUTRAL, "Never mind.", END_DIALOGUE) + IfTopic(FacialExpression.NEUTRAL, "Never mind.", END_DIALOGUE, !ServerConstants.SECOND_BANK) ) + 5 -> npcl(FacialExpression.ASKING, "Sorry, ${if (player.isMale) "sir" else "ma'am"}, the bank didn't license me for that.").also { stage++ } + 6 -> playerl(FacialExpression.HALF_GUILTY, "Oh, okay, I'll ask a banker who is stationed in an actual bank.").also { stage = END_DIALOGUE } + 10 -> npcl( FacialExpression.NEUTRAL, "My presence here is the start of a new enterprise of travelling banks. " + @@ -133,7 +140,7 @@ class EniolaDialogue(player: Player? = null) : DialoguePlugin(player) { 22 -> npcl( FacialExpression.NEUTRAL, - "I'm sorry to hear that, dear ${if (player.isMale) "sir" else "madam"}. " + "I'm sorry to hear that, dear ${if (player.isMale) "sir" else "madam"}." ).also { stage++ } 23 -> npcl( @@ -158,10 +165,15 @@ class EniolaDialogue(player: Player? = null) : DialoguePlugin(player) { openInterface(player, Components.BANK_CHARGE_ZMI_619) end() } + + 40 -> { + toggleBankAccount(player) + npcl( FacialExpression.NEUTRAL, "Your active bank account has been switched. You can now access your ${getBankAccountName(player)} account.").also { stage = END_DIALOGUE } + } } return true } override fun getIds(): IntArray = intArrayOf(NPCs.ENIOLA_6362) -} \ No newline at end of file +} diff --git a/Server/src/main/content/region/kandarin/pisc/dialogue/ArnoldLydsporDialogue.kt b/Server/src/main/content/region/kandarin/pisc/dialogue/ArnoldLydsporDialogue.kt index 1fd390535..b1233b698 100644 --- a/Server/src/main/content/region/kandarin/pisc/dialogue/ArnoldLydsporDialogue.kt +++ b/Server/src/main/content/region/kandarin/pisc/dialogue/ArnoldLydsporDialogue.kt @@ -1,115 +1,101 @@ package content.region.kandarin.pisc.dialogue -import core.api.* -import core.game.dialogue.DialoguePlugin -import core.game.dialogue.FacialExpression -import core.game.node.entity.player.Player +import core.ServerConstants +import core.api.addItemOrDrop +import core.api.hasActivatedSecondaryBankAccount +import core.api.hasIronmanRestriction +import core.api.isUsingSecondaryBankAccount +import core.api.openBankAccount +import core.api.openBankPinSettings +import core.api.openGrandExchangeCollectionBox +import core.api.openNpcShop +import core.api.toggleBankAccount +import core.game.dialogue.ChatAnim +import core.game.dialogue.DialogueLabeller +import core.game.dialogue.DialogueOption +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.npc.NPC import core.game.node.entity.player.link.IronmanMode -import core.plugin.Initializable +import core.game.node.item.Item import org.rs09.consts.Items import org.rs09.consts.NPCs -import core.game.dialogue.IfTopic -import core.game.dialogue.Topic -import core.tools.END_DIALOGUE -import core.tools.START_DIALOGUE -/** - * Provides the regular dialogue for Arnold Lydspor. - * TODO: Swan Song quest will need a special case handling. - * - * @author vddCore - */ -@Initializable -class ArnoldLydsporDialogue(player: Player? = null) : DialoguePlugin(player) { - override fun handle(interfaceId: Int, buttonId: Int): Boolean { - when (stage) { - START_DIALOGUE -> npcl( - FacialExpression.FRIENDLY, - "Ah, you come back! What you want from Arnold, heh?" - ).also { stage++ } - - 1 -> showTopics( - IfTopic( - FacialExpression.ASKING, - "Can you open my bank account, please?", - 2, - !hasIronmanRestriction(player, IronmanMode.ULTIMATE) - ), - - IfTopic( - FacialExpression.NEUTRAL, - "I'd like to check my bank PIN settings.", - 3, - !hasIronmanRestriction(player, IronmanMode.ULTIMATE) - ), - IfTopic( - FacialExpression.NEUTRAL, - "I'd like to collect items.", - 4, - !hasIronmanRestriction(player, IronmanMode.ULTIMATE) - ), - Topic(FacialExpression.ASKING, "Would you like to trade?", 5), - Topic(FacialExpression.FRIENDLY, "Nothing, I just came to chat.", 7) - ) - - 2 -> { - openBankAccount(player) - end() - } - - 3 -> { - openBankPinSettings(player) - end() - } - - 4 -> { - openGrandExchangeCollectionBox(player) - end() - } - - 5 -> npcl( - FacialExpression.FRIENDLY, - "Ja, I have wide range of stock..." - ).also { stage++ } - - 6 -> { - openNpcShop(player, NPCs.ARNOLD_LYDSPOR_3824) - end() - } - - 7 -> npcl(FacialExpression.FRIENDLY, - "Ah, that is nice - always I like to chat, but " + - "Herr Caranos tell me to get back to work! " + - "Here, you been nice, so have a present." - ).also { stage++ } - - 8 -> sendItemDialogue( - player, - Items.CABBAGE_1965, - "Arnold gives you a cabbage." - ).also { - addItemOrDrop(player, Items.CABBAGE_1965) - stage++ - } - - 9 -> playerl( - FacialExpression.HALF_THINKING, - "A cabbage?" - ).also { stage++ } - - 10 -> npcl( - FacialExpression.HAPPY, - "Ja, cabbage is good for you!" - ).also { stage++ } - - 11 -> playerl( - FacialExpression.NEUTRAL, - "Um... Thanks!" - ).also { stage = END_DIALOGUE } +class ArnoldLydsporDialogue : InteractionListener { + override fun defineListeners() { + on(NPCs.ARNOLD_LYDSPOR_3824, IntType.NPC, "talk-to") { player, node -> + DialogueLabeller.open(player, ArnoldLydsporLabellerFile(), node as NPC) + return@on true + } + on(NPCs.ARNOLD_LYDSPOR_3824, IntType.NPC, "bank") { player, _ -> + openBankAccount(player) + return@on true + } + on(NPCs.ARNOLD_LYDSPOR_3824, IntType.NPC, "collect") { player, _ -> + openGrandExchangeCollectionBox(player) + return@on true } - - return true } - override fun getIds(): IntArray = intArrayOf(NPCs.ARNOLD_LYDSPOR_3824) -} \ No newline at end of file + class ArnoldLydsporLabellerFile : DialogueLabeller() { + override fun addConversation() { + npc(ChatAnim.FRIENDLY, "Ah, you come back! What you want from Arnold, heh?") + goto("main options") + + label("main options") + options( + DialogueOption("access", "Can you open my bank account please?", expression = ChatAnim.ASKING) { player, _ -> !hasIronmanRestriction(player, IronmanMode.ULTIMATE) }, + DialogueOption("buy second bank", "I'd like to open a secondary bank account.") { player, _ -> return@DialogueOption !hasIronmanRestriction(player, IronmanMode.ULTIMATE) && ServerConstants.SECOND_BANK && !hasActivatedSecondaryBankAccount(player) }, + DialogueOption("switch second bank", "I'd like to switch to my primary bank account.") { player, _ -> return@DialogueOption hasActivatedSecondaryBankAccount(player) && isUsingSecondaryBankAccount(player) }, + DialogueOption("switch second bank", "I'd like to switch to my secondary bank account.") { player, _ -> return@DialogueOption hasActivatedSecondaryBankAccount(player) && !isUsingSecondaryBankAccount(player) }, + DialogueOption("pin", "I'd like to check my PIN settings.", expression = ChatAnim.NEUTRAL) { player, _ -> !hasIronmanRestriction(player, IronmanMode.ULTIMATE) }, + DialogueOption("collect", "I'd like to collect items.", expression = ChatAnim.NEUTRAL) { player, _ -> !hasIronmanRestriction(player, IronmanMode.STANDARD) }, + DialogueOption("trade", "Would you like to trade?", expression = ChatAnim.ASKING), + DialogueOption("chat", "Nothing, I just came to chat.", expression = ChatAnim.FRIENDLY) { player, _ -> return@DialogueOption hasIronmanRestriction(player, IronmanMode.STANDARD) || !ServerConstants.SECOND_BANK } + ) + + label("access") + exec { player, _ -> openBankAccount(player) } + goto("nowhere") + + label("buy second bank") + npc(ChatAnim.GUILTY, "I'm so sorry! My little shop does not have the fibre-optic connections to be able to open a second account. Try asking again in a brick-and-mortar building in a big city!") + player(ChatAnim.FRIENDLY, "That's okay, I understand. I will ask again in a big bank like Varrock's.") + goto("nowhere") + + label("switch second bank") + exec { + player, _ -> toggleBankAccount(player) + loadLabel(player, if (isUsingSecondaryBankAccount(player)) "now secondary" else "now primary") + } + label("now primary") + npc("Your active bank account has been switched. You can now access your primary account.") + goto("main options") + label("now secondary") + npc("Your active bank account has been switched. You can now access your secondary account.") + goto("main options") + + label("pin") + exec { player, _ -> openBankPinSettings(player) } + goto("nowhere") + + label("collect") + exec { player, _ -> openGrandExchangeCollectionBox(player) } + goto("nowhere") + + label("trade") + npc(ChatAnim.FRIENDLY, "Ja, I have wide range of stock...") + exec { player, _ -> openNpcShop(player, NPCs.ARNOLD_LYDSPOR_3824) } + goto("nowhere") + + label("chat") + npc("Ah, that is nice - always I like to chat, but Herr Caranos tell me to get back to work! Here, you been nice, so have a present.") + exec { player, _ -> addItemOrDrop(player, Items.CABBAGE_1965) } + item(Item(Items.CABBAGE_1965), "Arnold gives you a cabbage.") + player(ChatAnim.HALF_THINKING, "A cabbage?") + npc(ChatAnim.HAPPY, "Ja, cabbage is good for you!") + player(ChatAnim.NEUTRAL, "Um... Thanks!") + goto("nowhere") + } + } +} diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/BankerTutorDialogue.java b/Server/src/main/content/region/misc/tutisland/dialogue/BankerTutorDialogue.java deleted file mode 100644 index c6a121500..000000000 --- a/Server/src/main/content/region/misc/tutisland/dialogue/BankerTutorDialogue.java +++ /dev/null @@ -1,155 +0,0 @@ -package content.region.misc.tutisland.dialogue; - -import core.game.dialogue.DialoguePlugin; -import core.game.node.entity.npc.NPC; -import core.plugin.Initializable; -import core.game.node.entity.player.Player; - -/** - * Represents the banker tutor dialogue. - * @author 'Vexia - * @version 1.0 - */ -@Initializable -public final class BankerTutorDialogue extends DialoguePlugin { - - /** - * Constructs a new {@code BankerTutorDialogue} {@code Object}. - * @param player the player. - */ - public BankerTutorDialogue(final Player player) { - super(player); - } - - /** - * Constructs a new {@code BankerTutorDialogue} {@code Object}. - */ - public BankerTutorDialogue() { - /** - * empty. - */ - } - - @Override - public DialoguePlugin newInstance(Player player) { - return new BankerTutorDialogue(player); - } - - @Override - public boolean open(Object... args) { - npc = args[0] instanceof NPC ? (NPC) args[0] : null; - npc("Good day, would you like to access your bank account?"); - stage = 0; - return true; - } - - @Override - public boolean handle(int interfaceId, int buttonId) { - switch (stage) { - case 0: - options("How do I use the bank?", "I'd like to access my bank account please.", "I'd like to check my PIN settings."); - stage = 1; - break; - case 1: - switch (buttonId) { - case 1: - options("Using the bank itself.", "Using Bank deposit boxes.", "What's this PIN thing that people keep talking about?", "Goodbye."); - stage = 9; - break; - case 2: - end(); - player.getBank().open(); - break; - case 3: - end(); - player.getBankPinManager().openSettings(); - break; - } - break; - case 9: - switch (buttonId) { - case 1: - player("Using the bank itself. I'm not sure how....?"); - stage = 10; - break; - case 2: - player("Using Bank deposit boxes.... what are they?"); - stage = 20; - break; - case 3: - player("What's this PIN thing that people keep talking about?"); - stage = 30; - break; - case 4: - player("Goodbye."); - stage = 99; - break; - } - break; - case 10: - npc("Speak to any banker and ask to see your bank", "account. If you have set a PIN you will be asked for", "it, then all the belongings you have placed in the bank", "will appear in the window. To withdraw one item, left-"); - stage = 11; - break; - case 11: - npc("click on it once."); - stage = 12; - break; - case 12: - npc("To withdraw many, right-click on the item and select", "from the menu. The same for depositing, left-click on", "the item in your inventory to deposit it in the bank.", "Right-click on it to deposit many of the same items."); - stage = 13; - break; - case 13: - npc("To move things around in your bank: firstly select", "Swap or Insert as your default moving mode, you can", "find these buttons on the bank window itself. Then click", "and drag an item to where you want it to appear."); - stage = 14; - break; - case 14: - npc("You may withdraw 'notes' or 'certificates' when the", "items you are trying to withdraw do not stack in your", "inventory. This will only work for items which are", "tradeable."); - stage = 15; - break; - case 15: - npc("For instance, if you wanted to sell 100 logs to another", "player, they would not fit in one inventory and you", "would need to do multiple trades. Instead, click the", "Note button to do withdraw the logs as 'certs' or 'notes',"); - stage = 16; - break; - case 16: - npc("then withdraw the items you need."); - stage = 99; - break; - case 20: - npc("They look like grey pillars, there's one just over there,", "near the desk. Bank deposit boxes save so much time.", "If you're simply wanting to deposit a single item, 'Use'", "it on the deposit box."); - stage = 21; - break; - case 21: - npc("Otherwise, simply click once on the box and it will give", "you a choice of what to deposit in an interface very", "similar to the bank itself. Very quick for when you're", "simply fishing or mining etc."); - stage = 22; - break; - case 22: - end(); - break; - case 30: - npc("The PIN - Personal Identification Number - can be", "set on your bank account to protect the items there in", "case someone finds out your account password. It", "consists of four numbers that you remember and tell"); - stage = 31; - break; - case 31: - npc("no one."); - stage = 32; - break; - case 32: - npc("So if someone did manage to get your password they", "couldn't steal your items if they were in the bank."); - stage = 33; - break; - case 33: - end(); - break; - case 99: - end(); - break; - } - return true; - } - - @Override - public int[] getIds() { - return new int[] { 4907 }; - } - -} diff --git a/Server/src/main/content/region/misthalin/barbvillage/stronghold/playersafety/StrongHoldOfPlayerSafetyListener.kt b/Server/src/main/content/region/misthalin/barbvillage/stronghold/playersafety/StrongHoldOfPlayerSafetyListener.kt index 0b4134a0e..8c6a1ca73 100644 --- a/Server/src/main/content/region/misthalin/barbvillage/stronghold/playersafety/StrongHoldOfPlayerSafetyListener.kt +++ b/Server/src/main/content/region/misthalin/barbvillage/stronghold/playersafety/StrongHoldOfPlayerSafetyListener.kt @@ -246,7 +246,7 @@ package content.region.misthalin.barbvillage.stronghold.playersafety addItem(player, Items.SAFETY_GLOVES_12629) sendItemDialogue( player, Items.SAFETY_GLOVES_12629, - "You open the chest to find a pair of safety gloves. " + "You open the chest to find a pair of safety gloves." ) } } diff --git a/Server/src/main/content/region/morytania/quest/creatureoffenkenstrain/BookcaseDialogueFile.kt b/Server/src/main/content/region/morytania/quest/creatureoffenkenstrain/BookcaseDialogueFile.kt index f188633d9..7a07c5a44 100644 --- a/Server/src/main/content/region/morytania/quest/creatureoffenkenstrain/BookcaseDialogueFile.kt +++ b/Server/src/main/content/region/morytania/quest/creatureoffenkenstrain/BookcaseDialogueFile.kt @@ -68,7 +68,7 @@ class BookcaseEastDialogueFile : DialogueFile() { class ChimneySweepingOnABudgetBook { companion object { - private val TITLE = "Chimney Sweeping on a Budget " + private val TITLE = "Chimney Sweeping on a Budget" val CONTENTS = arrayOf( PageSet( Page( @@ -103,4 +103,4 @@ class ChimneySweepingOnABudgetBook { return true } } -} \ No newline at end of file +} diff --git a/Server/src/main/core/ServerConstants.kt b/Server/src/main/core/ServerConstants.kt index d67997daf..721174ec8 100644 --- a/Server/src/main/core/ServerConstants.kt +++ b/Server/src/main/core/ServerConstants.kt @@ -339,6 +339,24 @@ class ServerConstants { @JvmField var ENHANCED_DEEP_WILDERNESS = false + @JvmField + var WILDERNESS_EXCLUSIVE_LOOT = false + + @JvmField + var SHOOTING_STAR_RING = false + + @JvmField + var RING_OF_WEALTH_TELEPORT = false + + @JvmField + var SECOND_BANK = false + + @JvmField + var PLAYER_COMMANDS = false + + @JvmField + var BOOSTED_TRAWLER_REWARDS = false + @JvmField var STARTUP_MOMENT = Calendar.getInstance() diff --git a/Server/src/main/core/api/ContentAPI.kt b/Server/src/main/core/api/ContentAPI.kt index 615e51996..7ec533eb5 100644 --- a/Server/src/main/core/api/ContentAPI.kt +++ b/Server/src/main/core/api/ContentAPI.kt @@ -2083,7 +2083,7 @@ fun runcs2 (player: Player, scriptId: Int, vararg arguments: Any) { * @param callback a callback to handle the selection. The parameters passed to the callback are the slot in the inventory of the selected item, and the 0-9 index of the option clicked. **/ @JvmOverloads -fun sendItemSelect (player: Player, vararg options: String, keepAlive: Boolean = false, callback: (slot: Int, optionIndex: Int) -> Unit) { +fun sendItemSelect(player: Player, vararg options: String, keepAlive: Boolean = false, callback: (slot: Int, optionIndex: Int) -> Unit) { player.interfaceManager.openSingleTab(Component(12)) val scriptArgs = arrayOf ((12 shl 16) + 18, 93, 4, 7, 0, -1, "", "", "", "", "", "", "", "", "") for (i in 0 until min(9, options.size)) diff --git a/Server/src/main/core/game/node/entity/impl/Animator.java b/Server/src/main/core/game/node/entity/impl/Animator.java index 3afafb41b..430c4d551 100644 --- a/Server/src/main/core/game/node/entity/impl/Animator.java +++ b/Server/src/main/core/game/node/entity/impl/Animator.java @@ -2,7 +2,6 @@ package core.game.node.entity.impl; import core.game.interaction.Clocks; import core.game.node.entity.Entity; -import core.game.node.entity.npc.NPC; import core.game.world.GameWorld; import core.game.world.update.flag.context.Animation; import core.game.world.update.flag.context.Graphics; diff --git a/Server/src/main/core/game/system/command/Command.kt b/Server/src/main/core/game/system/command/Command.kt index fdbc56d90..8fc40e222 100644 --- a/Server/src/main/core/game/system/command/Command.kt +++ b/Server/src/main/core/game/system/command/Command.kt @@ -2,6 +2,7 @@ package core.game.system.command import core.game.node.entity.player.Player import core.ServerConstants +import core.game.node.entity.player.info.Rights import core.game.world.GameWorld import kotlin.collections.ArrayList @@ -12,7 +13,10 @@ import kotlin.collections.ArrayList class Command(val name: String, val privilege: Privilege, val usage: String = "UNDOCUMENTED", val description: String = "UNDOCUMENTED", val handle: (Player, Array) -> Unit) { fun attemptHandling(player: Player, args: Array?){ args ?: return - if(player.rights.ordinal >= privilege.ordinal || GameWorld.settings?.isDevMode == true || ServerConstants.I_AM_A_CHEATER){ + val hasRights = player.rights == Rights.ADMINISTRATOR || (ServerConstants.PLAYER_COMMANDS && player.rights.ordinal >= privilege.ordinal) + val isDev = GameWorld.settings?.isDevMode == true + val isCheater = ServerConstants.I_AM_A_CHEATER + if (hasRights || isDev || isCheater) { handle(player,args) } } diff --git a/Server/src/main/core/game/system/config/ServerConfigParser.kt b/Server/src/main/core/game/system/config/ServerConfigParser.kt index 72df173ff..d0ececce1 100644 --- a/Server/src/main/core/game/system/config/ServerConfigParser.kt +++ b/Server/src/main/core/game/system/config/ServerConfigParser.kt @@ -167,6 +167,12 @@ object ServerConfigParser { ServerConstants.FORCE_EASTER_EVENTS = data.getBoolean("world.force_easter_randoms", false) ServerConstants.RUNECRAFTING_FORMULA_REVISION = data.getLong("world.runecrafting_formula_revision", 581).toInt() ServerConstants.ENHANCED_DEEP_WILDERNESS = data.getBoolean("world.enhanced_deep_wilderness", false) + ServerConstants.WILDERNESS_EXCLUSIVE_LOOT = data.getBoolean("world.wilderness_exclusive_loot", false) + ServerConstants.SHOOTING_STAR_RING = data.getBoolean("world.shooting_star_ring", false) + ServerConstants.RING_OF_WEALTH_TELEPORT = data.getBoolean("world.ring_of_wealth_teleport", false) + ServerConstants.SECOND_BANK = data.getBoolean("world.second_bank", false) + ServerConstants.PLAYER_COMMANDS = data.getBoolean("world.player_commands", false) + ServerConstants.BOOSTED_TRAWLER_REWARDS = data.getBoolean("world.boosted_trawler_rewards", false) ServerConstants.CONNECTIVITY_CHECK_URL = data.getString("server.connectivity_check_url", "https://google.com,https://2009scape.org") ServerConstants.CONNECTIVITY_TIMEOUT = data.getLong("server.connectivity_timeout", 500L).toInt() From 8dd82dd3568e8b7e23ca989175e3528f943d5ff7 Mon Sep 17 00:00:00 2001 From: oftheshire Date: Tue, 25 Nov 2025 13:00:31 +0000 Subject: [PATCH 098/117] Light source is now required to get boots of lightness Boots of lightness now work as expected --- .../lightsources/LightSourceExtinguisher.kt | 9 +++++ .../lightsources/LightSourceLighter.kt | 12 +++++++ .../SkillcapeEquipmentPlugin.kt | 9 +++++ .../skill/skillcapeperks/SkillcapePerks.kt | 9 ++++- .../global/skill/slayer/SlayerPlugin.java | 17 +++------ .../templeofikov/TempleOfIkovListeners.kt | 36 +++++++++++++++++++ 6 files changed, 79 insertions(+), 13 deletions(-) diff --git a/Server/src/main/content/global/skill/crafting/lightsources/LightSourceExtinguisher.kt b/Server/src/main/content/global/skill/crafting/lightsources/LightSourceExtinguisher.kt index 357b69ca8..0df72d1f2 100644 --- a/Server/src/main/content/global/skill/crafting/lightsources/LightSourceExtinguisher.kt +++ b/Server/src/main/content/global/skill/crafting/lightsources/LightSourceExtinguisher.kt @@ -1,12 +1,15 @@ package content.global.skill.crafting.lightsources +import content.data.LightSource import core.api.log +import core.api.* import core.cache.def.impl.ItemDefinition import core.game.container.Container import core.game.interaction.OptionHandler import core.game.node.Node import core.game.node.entity.player.Player import core.game.node.item.Item +import core.game.world.map.Location import core.tools.SystemLogger import core.plugin.Initializable import core.plugin.Plugin @@ -32,6 +35,12 @@ class LightSourceExtinguisher : OptionHandler(){ lightSource ?: return false.also { log(this::class.java, Log.WARN, "UNHANDLED EXTINGUISH OPTION: ID = ${node.id}") } + // For Temple of Ikov - if you are in the dark basement, do not let light source extinguish. + if(player.location.isInRegion(10648)) { + sendMessage(player, "Extinguishing the " + LightSource.getActiveLightSource(player).product.name.lowercase() + " would leave you without a light source.") + return true + } + player.inventory.replace(node.asItem(), Item(lightSource.fullID)) return true } diff --git a/Server/src/main/content/global/skill/crafting/lightsources/LightSourceLighter.kt b/Server/src/main/content/global/skill/crafting/lightsources/LightSourceLighter.kt index 81a1a98cc..d636ee588 100644 --- a/Server/src/main/content/global/skill/crafting/lightsources/LightSourceLighter.kt +++ b/Server/src/main/content/global/skill/crafting/lightsources/LightSourceLighter.kt @@ -1,12 +1,16 @@ package content.global.skill.crafting.lightsources +import core.api.* +import core.api.teleport import core.game.container.Container import core.game.event.LitLightSourceEvent import core.game.interaction.NodeUsageEvent import core.game.interaction.UseWithHandler +import core.game.node.entity.Entity import core.game.node.entity.player.Player import core.game.node.entity.skill.Skills import core.game.node.item.Item +import core.game.world.map.Location import core.plugin.Initializable import core.plugin.Plugin @@ -72,6 +76,14 @@ class LightSourceLighter : UseWithHandler(590,36,38){ return true } + // For Temple of Ikov - if you are in the dark basement and light a light source, switch to the light basement. + // For the listener that covers the firemaking cape perk, see content.global.skill.skillcapeperks.SkillcapePerks.kt + if(event.player.location.isInRegion(10648) && event.player.location.withinDistance(Location(2639,9738,0), 8)) { + teleport(event.player, Location.create(event.player.getLocation().getX(), event.player.getLocation().getY() + 23, event.player.getLocation().getZ())) + closeDialogue(event.player) + // Dark basement is region 10648, min 2639 9738 0, max 2643 9744 0. Add 23 to the Y loc to tele to light basement + } + if(!light(event.player,used,lightSource)){ event.player.sendMessage("You need a Firemaking level of at least ${lightSource.levelRequired} to light this.") } diff --git a/Server/src/main/content/global/skill/skillcapeperks/SkillcapeEquipmentPlugin.kt b/Server/src/main/content/global/skill/skillcapeperks/SkillcapeEquipmentPlugin.kt index 0ad090fcd..36a76e8cd 100644 --- a/Server/src/main/content/global/skill/skillcapeperks/SkillcapeEquipmentPlugin.kt +++ b/Server/src/main/content/global/skill/skillcapeperks/SkillcapeEquipmentPlugin.kt @@ -1,6 +1,8 @@ package content.global.skill.skillcapeperks +import core.api.sendMessage import core.game.interaction.InteractionListener +import core.game.world.GameWorld class SkillcapeEquipmentPlugin : InteractionListener { override fun defineListeners() { @@ -19,6 +21,13 @@ class SkillcapeEquipmentPlugin : InteractionListener { onUnequip(capes){player, node -> val skillcape = Skillcape.forId(node.id) + + // For Temple of Ikov. Do not let player unequip firemaking skillcape in the dark basement (need to keep an active light source). + if(player.location.isInRegion(10648) && (node.id == 9804 || node.id == 9805) && GameWorld.settings?.skillcape_perks == true) { + sendMessage(player, "Unequipping that skillcape would leave you without a light source.") + return@onUnequip false + } + val perk = SkillcapePerks.forSkillcape(skillcape) perk.deactivate(player) return@onUnequip true diff --git a/Server/src/main/content/global/skill/skillcapeperks/SkillcapePerks.kt b/Server/src/main/content/global/skill/skillcapeperks/SkillcapePerks.kt index 7dbc1992b..eae541c70 100644 --- a/Server/src/main/content/global/skill/skillcapeperks/SkillcapePerks.kt +++ b/Server/src/main/content/global/skill/skillcapeperks/SkillcapePerks.kt @@ -131,8 +131,15 @@ enum class SkillcapePerks(val attribute: String, val effect: ((Player) -> Unit)? player.setAttribute("/save:$attribute",true) } player.debug("Activated ${this.name}") - if(this == CONSTANT_GLOW) + if(this == CONSTANT_GLOW) { DarkZone.checkDarkArea(player) + // For Temple of Ikov - if you are in the dark basement and put on the firemaking cape with its 2009Scape light source perk, switch to the light basement. + // For the listener that teleports if you light a normal light source, see content.global.skill.crafting.lightsources.LightSourceLighter.kt + if(player.location.isInRegion(10648) && player.location.withinDistance(Location(2639,9738,0), 8)) { + teleport(player, Location.create(player.getLocation().getX(), player.getLocation().getY() + 23, player.getLocation().getZ())) + closeDialogue(player) + } + } } fun operate(player: Player){ diff --git a/Server/src/main/content/global/skill/slayer/SlayerPlugin.java b/Server/src/main/content/global/skill/slayer/SlayerPlugin.java index 53e426aa3..eb02797d6 100644 --- a/Server/src/main/content/global/skill/slayer/SlayerPlugin.java +++ b/Server/src/main/content/global/skill/slayer/SlayerPlugin.java @@ -1,7 +1,6 @@ package content.global.skill.slayer; import core.cache.def.impl.SceneryDefinition; -import core.game.global.action.ClimbActionHandler; import core.game.global.action.DigAction; import core.game.global.action.DigSpadeHandler; import core.game.interaction.OptionHandler; @@ -34,8 +33,6 @@ public class SlayerPlugin extends OptionHandler { SceneryDefinition.forId(15767).getHandlers().put("option:enter", this); SceneryDefinition.forId(15811).getHandlers().put("option:exit", this); SceneryDefinition.forId(15812).getHandlers().put("option:exit", this); - SceneryDefinition.forId(96).getHandlers().put("option:climb-up", this); - SceneryDefinition.forId(35121).getHandlers().put("option:climb-down", this); for (Location loc : BRYNE_DIGS) { DigSpadeHandler.register(loc, new DigAction() { @Override @@ -53,27 +50,25 @@ public class SlayerPlugin extends OptionHandler { public boolean handle(Player player, Node node, String option) { switch (node.getId()) { case 8785: + // Outside of trap door in East Ardy player.teleport(new Location(2543, 3327, 0)); break; case 23158: case 23157: + // Outside brine rat cave player.teleport(new Location(2729, 3733, 0)); break; case 15767: if (!hasRequirement(player, Quests.CABIN_FEVER)) return true; player.teleport(new Location(3748, 9373, 0)); + // Cave horrors - inside of cave break; case 15811: case 15812: + // Cave horrors - outside of cave player.teleport(new Location(3749, 2973, 0)); break; - case 96: - ClimbActionHandler.climb(player, null, new Location(2649, 9804, 0)); - break; - case 35121: - ClimbActionHandler.climb(player, null, new Location(2641, 9763, 0)); - break; } return true; } @@ -81,11 +76,9 @@ public class SlayerPlugin extends OptionHandler { @Override public Location getDestination(Node node, Node n) { if (n.getId() == 23158 || n.getId() == 23157) { + // Inside brine rate cave return new Location(2690, 10124, 0); } - if (n.getId() == 96) { - return new Location(2641, 9763, 0); - } return null; } diff --git a/Server/src/main/content/region/kandarin/quest/templeofikov/TempleOfIkovListeners.kt b/Server/src/main/content/region/kandarin/quest/templeofikov/TempleOfIkovListeners.kt index f513997e3..135e3dc92 100644 --- a/Server/src/main/content/region/kandarin/quest/templeofikov/TempleOfIkovListeners.kt +++ b/Server/src/main/content/region/kandarin/quest/templeofikov/TempleOfIkovListeners.kt @@ -1,7 +1,9 @@ package content.region.kandarin.quest.templeofikov +import content.data.LightSource import content.data.Quests import content.global.skill.agility.AgilityHandler +import content.global.skill.skillcapeperks.SkillcapePerks import core.api.* import core.game.global.action.DoorActionHandler import core.game.global.action.PickupHandler @@ -114,6 +116,40 @@ class TempleOfIkovListeners : InteractionListener { // https://www.youtube.com/watch?v=6ZGpJNeGLJ0 // sendDialogue("Hmm...bit dark down here! I'm not going to venture far!") + // Ikov dungeon top stairs to either boots of lightness or the dark room + on(Scenery.STAIRS_35121, SCENERY, "Climb-down") { player, node -> + if (LightSource.hasActiveLightSource(player) || SkillcapePerks.isActive(SkillcapePerks.CONSTANT_GLOW, player)) { // has light source + teleport(player, Location.create(2641, 9763, 0)) + } else { // doesn't have light source + teleport(player, Location.create(2641, 9740, 0)) + sendDialogueLines(player, "Hmm...bit dark down here! I'm not going to venture far!") + } + true + } + + // Ikov dungeon bottom stairs (lit) to top stairs + on(Scenery.STAIRS_96, SCENERY, "Climb-up") { player, node -> + teleport(player, Location.create(2649, 9804, 0)) + } + + // Ikov dungeon bottom stairs (dark) to top stairs + on(Scenery.STAIRS_33232, SCENERY, "Climb-up") { player, node -> + teleport(player, Location.create(2649, 9804, 0)) + } + + // don't let light extinguish or cape take off + val lightSourceProducts = LightSource.values().map { it.product.id }.toIntArray() + + on(lightSourceProducts, ITEM, "drop") { player, light -> + val active = LightSource.getActiveLightSource(player).product.id + if (player.location.isInRegion(10648) && light.id == active) { + sendMessage(player, "Dropping the " + LightSource.getActiveLightSource(player).product.name.lowercase() + " would leave you without a light source.") + return@on false + } + val removed = removeItem(player, light.id) + if (removed) produceGroundItem(player, light.id) + return@on true + } // B: Attach lever, authentic if you log out, the lever is lost, and you have to do that bridge again onUseWith(SCENERY, Items.LEVER_83, Scenery.LEVER_BRACKET_86) { player, used, with -> From 66030f36d61335ad7eb9bb557304d76aaa436069 Mon Sep 17 00:00:00 2001 From: Kennynes Date: Tue, 25 Nov 2025 13:01:34 +0000 Subject: [PATCH 099/117] Evil Bob's fishing event cutscene now much more authentic --- .../ame/events/evilbob/ServantCutscene.kt | 134 +++++++++++------- 1 file changed, 83 insertions(+), 51 deletions(-) diff --git a/Server/src/main/content/global/ame/events/evilbob/ServantCutscene.kt b/Server/src/main/content/global/ame/events/evilbob/ServantCutscene.kt index db54e0db9..4dfc72698 100644 --- a/Server/src/main/content/global/ame/events/evilbob/ServantCutscene.kt +++ b/Server/src/main/content/global/ame/events/evilbob/ServantCutscene.kt @@ -3,38 +3,47 @@ package content.global.ame.events.evilbob import core.api.* import core.game.activity.Cutscene import core.game.node.entity.player.Player +import core.game.world.map.Direction class ServantCutsceneN(player: Player) : Cutscene(player) { override fun setup() { setExit(player.location.transform(0, 0, 0)) loadRegion(13642) + addNPC(2479, 28, 41, Direction.SOUTH) // Evil Bob + addNPC(2481, 31, 41, Direction.SOUTH_WEST) // Servant } override fun runStage(stage: Int) { when (stage) { 0 -> { - fadeToBlack() - timedUpdate(4) + teleport(player, 30, 41) + moveCamera(30, 37, 400, 255) + rotateCamera(30, 50, 400, 255) + openInterface(player, 186) + timedUpdate(0) } 1 -> { - teleport(player, 29, 41) - moveCamera(30, 43) // +7 from statue - openInterface(player, 186) + timedUpdate(0) + } + 2 -> { // Slow start + moveCamera(30, 49, 400, 2) timedUpdate(2) } - 2 -> { + 3 -> { // Fast middle + moveCamera(30, 44, 380, 4) + timedUpdate(6) + } + 4 -> { // Slow end + moveCamera(30, 45, 340, 2) timedUpdate(2) - rotateCamera(30, 51, 300, 100) // the statue loc - fadeFromBlack() } - 3 -> { - timedUpdate(9) - moveCamera(30, 46, 300, 2) // +4 from statue + 5 -> { + timedUpdate(2) } - 4 -> { - end{ player.locks.lockTeleport(1000000) } + 6 -> { closeInterface(player) + end(fade = false) { player.locks.lockTeleport(1000000) } } } } @@ -45,32 +54,40 @@ class ServantCutsceneS(player: Player) : Cutscene(player) { override fun setup() { setExit(player.location.transform(0, 0, 0)) loadRegion(13642) + addNPC(2479, 28, 41, Direction.SOUTH) // Evil Bob + addNPC(2481, 31, 41, Direction.SOUTH_WEST) // Servant } override fun runStage(stage: Int) { when (stage) { 0 -> { - fadeToBlack() - timedUpdate(4) + teleport(player, 30, 41) + moveCamera(31, 46, 365, 255) + rotateCamera(29, 30, 365, 255) + openInterface(player, 186) + timedUpdate(0) } 1 -> { - teleport(player, 29, 41) - moveCamera(29, 38) // +7 from statue - openInterface(player, 186) - timedUpdate(2) + timedUpdate(0) } 2 -> { + moveCamera(31, 43, 480, 2) timedUpdate(2) - rotateCamera(29, 30, 300, 100) // the statue loc - fadeFromBlack() } 3 -> { - timedUpdate(9) - moveCamera(29, 35, 300, 2) // +4 from statue + moveCamera(31, 37, 455, 4) + timedUpdate(8) } 4 -> { - end{ player.locks.lockTeleport(1000000) } + moveCamera(31, 36, 395, 2) + timedUpdate(2) + } + 5 -> { + timedUpdate(3) + } + 6 -> { closeInterface(player) + end(fade = false) { player.locks.lockTeleport(1000000) } } } } @@ -81,32 +98,40 @@ class ServantCutsceneE(player: Player) : Cutscene(player) { override fun setup() { setExit(player.location.transform(0, 0, 0)) loadRegion(13642) + addNPC(2479, 28, 41, Direction.SOUTH) // Evil Bob + addNPC(2481, 31, 41, Direction.SOUTH_WEST) // Servant } override fun runStage(stage: Int) { when (stage) { 0 -> { - fadeToBlack() - timedUpdate(4) + teleport(player, 30, 41) + moveCamera(25, 41, 440, 255) + rotateCamera(42, 41, 440, 255) + openInterface(player, 186) + timedUpdate(0) } 1 -> { - teleport(player, 29, 41) - moveCamera(35, 41) // +7 from statue - openInterface(player, 186) + timedUpdate(0) + } + 2 -> { // Slow start + moveCamera(28, 41, 500, 3) timedUpdate(2) } - 2 -> { + 3 -> { // Fast middle + moveCamera(34, 41, 390, 5) + timedUpdate(6) + } + 4 -> { // Slow end + moveCamera(36, 41, 340, 2) timedUpdate(2) - rotateCamera(43, 41, 300, 100) // the statue loc - fadeFromBlack() } - 3 -> { - timedUpdate(9) - moveCamera(38, 41, 300, 2) // +4 from statue + 5 -> { + timedUpdate(4) } - 4 -> { - end{ player.locks.lockTeleport(1000000) } + 6 -> { closeInterface(player) + end(fade = false) { player.locks.lockTeleport(1000000) } } } } @@ -117,34 +142,41 @@ class ServantCutsceneW(player: Player) : Cutscene(player) { override fun setup() { setExit(player.location.transform(0, 0, 0)) loadRegion(13642) + addNPC(2479, 28, 41, Direction.SOUTH) // Evil Bob + addNPC(2481, 31, 41, Direction.SOUTH_WEST) // Servant } override fun runStage(stage: Int) { when (stage) { 0 -> { - fadeToBlack() - timedUpdate(4) + teleport(player, 30, 41) + moveCamera(34, 41, 325, 255) + rotateCamera(16, 40, 300, 255) + openInterface(player, 186) + timedUpdate(0) } 1 -> { - teleport(player, 29, 41) - moveCamera(25, 40) // +7 from statue - openInterface(player, 186) + timedUpdate(0) + } + 2 -> { // Slow start + moveCamera(31, 41, 440, 3) timedUpdate(2) } - 2 -> { + 3 -> { // Fast middle + moveCamera(24, 41, 330, 5) + timedUpdate(7) + } + 4 -> { // Slow end + moveCamera(23, 41, 300, 2) timedUpdate(2) - rotateCamera(18, 40, 300, 100) // the statue loc - fadeFromBlack() } - 3 -> { - timedUpdate(9) - moveCamera(22, 40, 300, 2) // +4 from statue + 5 -> { + timedUpdate(3) } - 4 -> { - end{ player.locks.lockTeleport(1000000) } + 6 -> { closeInterface(player) + end(fade = false) { player.locks.lockTeleport(1000000) } } - } } } \ No newline at end of file From dfb53cfa03fff99374e60875e01a16f10d995749 Mon Sep 17 00:00:00 2001 From: Player Name Date: Tue, 25 Nov 2025 13:12:56 +0000 Subject: [PATCH 100/117] Made XP rates and ironman optional, locked behind new server config settings xp_rates and ironman Removed the ironman_icons server config setting, locking it behind ironman instead Removed the default_xp_rate server config setting and hardcoded the default xp rate to 1x Changed a bunch of server config defaults for inauthentic features to disabled Added enable_global_chat to the worldprops default config. Note the change from enable_globalchat to enable_global_chat Removed the allow_slayer_rerolls worldprops config option Large number of tutorial island improvements and authenticity enhancements --- .../global/skill/cooking/CookingRewrite.kt | 39 +- .../skill/cooking/DoughMakingListener.kt | 14 +- .../skill/slayer/SlayerMasterDialogue.java | 135 +-- .../skill/smithing/FurnaceOptionPlugin.java | 20 - .../misc/tutisland/dialogue/RatPenDialogue.kt | 15 + .../dialogue/SkipTutorialDialogue.kt | 7 +- .../dialogue/SurvivalExpertDialogue.kt | 187 ++-- .../TutorialCombatInstructorDialogue.kt | 181 ++-- .../TutorialFinanceAdvisorDialogue.kt | 27 +- .../dialogue/TutorialMagicTutorDialogue.kt | 366 +++---- .../dialogue/TutorialMasterChefDialogue.kt | 80 +- .../TutorialMiningInstructorDialogue.kt | 163 ++- .../dialogue/TutorialPrayerDialogue.kt | 15 +- .../dialogue/TutorialQuestGuideDialogue.kt | 14 +- .../dialogue/TutorialRSGuideDialogue.kt | 61 +- .../tutisland/handlers/TutorialDialogs.kt | 554 ++++++++++ .../handlers/TutorialEventReceivers.kt | 51 +- .../handlers/TutorialFurnaceListener.kt | 51 - .../tutisland/handlers/TutorialListeners.kt | 116 ++- .../misc/tutisland/handlers/TutorialStage.kt | 964 +++--------------- Server/src/main/core/ServerConstants.kt | 19 +- Server/src/main/core/api/ContentAPI.kt | 4 +- Server/src/main/core/api/utils/Permadeath.kt | 4 + .../core/game/dialogue/DialogueLabeller.kt | 93 +- .../core/game/dialogue/DialoguePlugin.java | 4 +- .../game/node/entity/player/info/Rights.java | 10 +- .../player/info/login/PlayerSaveParser.kt | 3 - .../entity/player/info/login/PlayerSaver.kt | 1 - .../node/entity/player/link/GlobalData.java | 23 - .../node/entity/player/link/IronmanMode.java | 2 +- .../entity/player/link/TeleportManager.java | 2 +- .../core/game/node/entity/skill/Skills.java | 3 +- .../game/system/config/ServerConfigParser.kt | 17 +- .../src/main/core/game/world/GameSettings.kt | 6 - .../main/core/net/packet/PacketProcessor.kt | 2 +- Server/src/test/resources/test.conf | 3 +- Server/worldprops/default.conf | 47 +- 37 files changed, 1526 insertions(+), 1777 deletions(-) create mode 100644 Server/src/main/content/region/misc/tutisland/dialogue/RatPenDialogue.kt create mode 100644 Server/src/main/content/region/misc/tutisland/handlers/TutorialDialogs.kt delete mode 100644 Server/src/main/content/region/misc/tutisland/handlers/TutorialFurnaceListener.kt diff --git a/Server/src/main/content/global/skill/cooking/CookingRewrite.kt b/Server/src/main/content/global/skill/cooking/CookingRewrite.kt index 7bc993a8b..e65406487 100644 --- a/Server/src/main/content/global/skill/cooking/CookingRewrite.kt +++ b/Server/src/main/content/global/skill/cooking/CookingRewrite.kt @@ -1,17 +1,22 @@ package content.global.skill.cooking -import core.api.amountInInventory +import content.region.misc.tutisland.handlers.TutorialStage +import core.api.* import core.game.interaction.IntType import core.game.interaction.InteractionListener +import core.game.interaction.QueueStrength +import core.game.node.entity.impl.Animator import core.game.node.entity.player.Player import core.game.node.item.Item import core.game.node.scenery.Scenery +import core.game.world.update.flag.context.Animation import org.rs09.consts.Items import org.rs09.consts.Items.BREAD_DOUGH_2307 import org.rs09.consts.Items.RAW_BEAR_MEAT_2136 import org.rs09.consts.Items.RAW_BEEF_2132 import org.rs09.consts.Items.SEAWEED_401 import org.rs09.consts.Items.UNCOOKED_CAKE_1889 +import org.rs09.consts.Sounds /** * @author Ceikry @@ -32,8 +37,36 @@ class CookingRewrite : InteractionListener { } override fun defineListeners() { - onUseWith(IntType.SCENERY,RAW_FOODS, *COOKING_OBJs){ player, used, with -> + if (!getAttribute(player, "/save:tutorial:complete", false)) { + // On tutorial island, we don't want to show the cook-x menu and we always want to burn our first shrimp + // This requirement of a simplified and predictable cooking system means that, for the second time, I have to reinvent the wheel of cooking + // (We only need to care about shrimp here, the tutorial island range is special and has its own separate listener) + queueScript(player, 0, QueueStrength.WEAK) { stage -> + if (stage == 0) { + val FIRE_ANIMATION = Animation(897, Animator.Priority.HIGH) + lock(player, FIRE_ANIMATION.duration) + lockInteractions(player, FIRE_ANIMATION.duration) + animate(player, FIRE_ANIMATION) + playAudio(player, Sounds.FRY_2577) + return@queueScript delayScript(player, FIRE_ANIMATION.duration) + } + val tutStage = getAttribute(player, "/save:tutorial:stage", 0) + if (tutStage < 15) { + replaceSlot(player, used.asItem().slot, Item(Items.BURNT_SHRIMP_7954), used.asItem()) + setAttribute(player, "tutorial:stage", 15) + TutorialStage.load(player, 15) + } else { + replaceSlot(player, used.asItem().slot, Item(Items.SHRIMPS_315), used.asItem()) + if (tutStage == 15) { + setAttribute(player, "tutorial:stage", 16) + TutorialStage.load(player, 16) + } + } + return@queueScript stopExecuting(player) + } + return@onUseWith true + } val item = used.asItem() val obj = with.asScenery() val range = obj.name.toLowerCase().contains("range") @@ -66,7 +99,7 @@ class CookingRewrite : InteractionListener { } companion object { - val COOKING_OBJs = intArrayOf(24313,21302, 13528, 13529, 13533, 13531, 13536, 13539, 13542, 2728, 2729, 2730, 2731, 2732, 2859, 3038, 3039, 3769, 3775, 4265, 4266, 5249, 5499, 5631, 5632, 5981, 9682, 10433, 11404, 11405, 11406, 12102, 12796, 13337, 13881, 14169, 14919, 15156, 20000, 20001, 21620, 21792, 22713, 22714, 23046, 24283, 24284, 25155, 25156, 25465, 25730, 27297, 29139, 30017, 32099, 33500, 34495, 34546, 36973, 37597, 37629, 37726, 114, 4172, 5275, 8750, 16893, 22154, 34410, 34565, 114, 9085, 9086, 9087, 12269, 15398, 25440, 25441, 2724, 2725, 2726, 4618, 4650, 5165, 6093, 6094, 6095, 6096, 8712, 9374, 9439, 9440, 9441, 10824, 17640, 17641, 17642, 17643, 18039, 18170, 21795, 24285, 24329, 27251, 33498, 35449, 36815, 36816, 37426, 40110, 10377) + val COOKING_OBJs = intArrayOf(24313,21302, 13528, 13529, 13533, 13531, 13536, 13539, 13542, 2728, 2729, 2730, 2731, 2732, 2859, 3038, 3769, 3775, 4265, 4266, 5249, 5499, 5631, 5632, 5981, 9682, 10433, 11404, 11405, 11406, 12102, 12796, 13337, 13881, 14169, 14919, 15156, 20000, 20001, 21620, 21792, 22713, 22714, 23046, 24283, 24284, 25155, 25156, 25465, 25730, 27297, 29139, 30017, 32099, 33500, 34495, 34546, 36973, 37597, 37629, 37726, 114, 4172, 5275, 8750, 16893, 22154, 34410, 34565, 114, 9085, 9086, 9087, 12269, 15398, 25440, 25441, 2724, 2725, 2726, 4618, 4650, 5165, 6093, 6094, 6095, 6096, 8712, 9374, 9439, 9440, 9441, 10824, 17640, 17641, 17642, 17643, 18039, 18170, 21795, 24285, 24329, 27251, 33498, 35449, 36815, 36816, 37426, 40110, 10377) @JvmStatic fun cook(player: Player, `object`: Scenery?, initial: Int, product: Int, amount: Int) { diff --git a/Server/src/main/content/global/skill/cooking/DoughMakingListener.kt b/Server/src/main/content/global/skill/cooking/DoughMakingListener.kt index a60f89ff7..267d9ccfe 100644 --- a/Server/src/main/content/global/skill/cooking/DoughMakingListener.kt +++ b/Server/src/main/content/global/skill/cooking/DoughMakingListener.kt @@ -1,5 +1,6 @@ package content.global.skill.cooking +import content.region.misc.tutisland.handlers.TutorialStage import core.api.* import core.game.event.ResourceProducedEvent import core.game.node.entity.skill.Skills @@ -24,7 +25,16 @@ class DoughMakingListener : InteractionListener { FULL_WATER_CONTAINERS_TO_EMPTY_CONTAINERS.keys.toIntArray(), Items.POT_OF_FLOUR_1933 ) { player, waterContainer, flourContainer -> - openDialogue(player, DoughMakeDialogue(waterContainer.asItem(), flourContainer.asItem())) + if (getAttribute(player, "/save:tutorial:complete", false)) { + openDialogue(player, DoughMakeDialogue(waterContainer.asItem(), flourContainer.asItem())) + return@onUseWith true + } + // Continue the tutorial + replaceSlot(player, waterContainer.asItem().slot, Item(Items.BUCKET_1925), waterContainer.asItem()) + replaceSlot(player, flourContainer.asItem().slot, Item(Items.EMPTY_POT_1931), flourContainer.asItem()) + addItemOrDrop(player, Items.BREAD_DOUGH_2307) + setAttribute(player, "tutorial:stage", 20) + TutorialStage.load(player, 20) return@onUseWith true } } @@ -73,7 +83,7 @@ class DoughMakingListener : InteractionListener { sendMessage( player!!, - "You mix the flower and the water to make some ${selectedDoughProduct.itemName.toLowerCase()}." + "You mix the flour and the water to make some ${selectedDoughProduct.itemName.toLowerCase()}." ) } } else { diff --git a/Server/src/main/content/global/skill/slayer/SlayerMasterDialogue.java b/Server/src/main/content/global/skill/slayer/SlayerMasterDialogue.java index cb6aa6570..c8a57c44a 100644 --- a/Server/src/main/content/global/skill/slayer/SlayerMasterDialogue.java +++ b/Server/src/main/content/global/skill/slayer/SlayerMasterDialogue.java @@ -42,11 +42,6 @@ public final class SlayerMasterDialogue extends DialoguePlugin { */ private static final Item HOLY_SYMBOL = new Item(1718); - /** - * Represents the items to use. - */ - private static final Item[] ITEMS = new Item[]{new Item(9813), new Item(9814)}; - /** * Represents the coins item. */ @@ -69,8 +64,6 @@ public final class SlayerMasterDialogue extends DialoguePlugin { private final int level = 2; - private int rerolls = 0; - /** * Constructs a new {@code SlayerMasterDialogue} {@code Object}. */ @@ -120,7 +113,6 @@ public final class SlayerMasterDialogue extends DialoguePlugin { @Override public boolean handle(int interfaceId, int buttonId) { - rerolls = ServerStore.getInt(getStoreFile(), player.getUsername().toLowerCase(), 0); if (isDiary) { switch (stage) { case 999: @@ -482,36 +474,19 @@ public final class SlayerMasterDialogue extends DialoguePlugin { stage = 999; break; } - if (!SlayerManager.getInstance(player).hasTask()) { + boolean hasTask = SlayerManager.getInstance(player).hasTask(); + boolean reroll = hasTask && master == Master.TURAEL && !Master.hasSameTask(master, player); + if (!hasTask || reroll) { SlayerManager.getInstance(player).generate(master); - if (SlayerManager.getInstance(player).getTask() == Tasks.JAD) { - interpreter.sendDialogues(master.getNpc(), getExpression(master), "Excellent, you're doing great. Your new task is to", "defeat the almighty TzTok-Jad."); - } else { - interpreter.sendDialogues(master.getNpc(), getExpression(master), "Excellent, you're doing great. Your new task is to kill", "" + SlayerManager.getInstance(player).getAmount() + " " + SlayerUtils.pluralise(SlayerManager.getInstance(player).getTaskName()) + "."); - } + interpreter.sendDialogues(master.getNpc(), getExpression(master), "Excellent, you're doing great. Your new task is to kill", "" + SlayerManager.getInstance(player).getAmount() + " " + SlayerUtils.pluralise(SlayerManager.getInstance(player).getTaskName()) + "."); stage = 844; break; } - if (Master.hasSameTask(master, player)) { - interpreter.sendDialogues(master.getNpc(), getExpression(master), "You're still hunting something. But let me check something..."); - stage = 847; - } else { - SlayerManager.getInstance(player).flags.setTaskStreak(0); - SlayerManager.getInstance(player).generate(master); - if (SlayerManager.getInstance(player).getTask() == Tasks.JAD) { - interpreter.sendDialogues(master.getNpc(), getExpression(master), "Excellent, you're doing great. Your new task is to", "defeat the almighty TzTok-Jad."); - } else { - interpreter.sendDialogues(master.getNpc(), getExpression(master), "Excellent, you're doing great. Your new task is to kill", "" + SlayerManager.getInstance(player).getAmount() + " " + SlayerUtils.pluralise(SlayerManager.getInstance(player).getTaskName()) + "."); - } - stage = 844; - } + interpreter.sendDialogues(master.getNpc(), getExpression(master), "You're still hunting something. Come back when you've","finished your task."); + stage = END_DIALOGUE; break; case 844: - if (GameWorld.getSettings().getAllow_slayer_reroll()) { - options("Got any tips for me?", "Okay, great!", "I'd like to re-roll that task."); - } else { - options("Got any tips for me?", "Okay, great!"); - } + options("Got any tips for me?", "Okay, great!"); stage++; break; case 845: @@ -524,47 +499,6 @@ public final class SlayerMasterDialogue extends DialoguePlugin { player("Okay, great!"); stage = 999; break; - case 3: - player("I'd like to re-roll this task."); - if(rerolls == 10){ - stage++; - } else { - SlayerManager.getInstance(player).clear(); - getStoreFile().put(player.getUsername().toLowerCase(), rerolls + 1); - stage = 701; - } - } - break; - case 846: - npcl(FacialExpression.NEUTRAL, "Actually, you're out of free rerolls. You can buy a reroll from my reward store, though."); - stage = END_DIALOGUE; - break; - case 847: - if(rerolls < 10){ - npcl(FacialExpression.NEUTRAL, "You do have " + (10 - rerolls) + " rerolls left today, would you like to use one?"); - stage++; - } - else { - npcl(FacialExpression.NEUTRAL, "And it also seems you're out of rerolls for today. That's unfortunate."); - stage = END_DIALOGUE; - } - break; - case 848: - options("Yes, please.", "No, thanks."); - stage++; - break; - case 849: - switch(buttonId){ - case 1: - playerl(FacialExpression.FRIENDLY, "Yes, please."); - SlayerManager.getInstance(player).clear(); - getStoreFile().put(player.getUsername().toLowerCase(), rerolls + 1); - stage = 701; - break; - case 2: - playerl(FacialExpression.NEUTRAL, "No, thanks."); - stage = END_DIALOGUE; - break; } break; case 860: @@ -608,56 +542,6 @@ public final class SlayerMasterDialogue extends DialoguePlugin { } stage = 999; break; - case 906: - switch (buttonId) { - case 1: - player("May I buy a Quest Point cape?"); - stage = 907; - break; - case 2: - interpreter.sendDialogues(master.getNpc(), FacialExpression.HALF_GUILTY, "'Ello, and what are you after, then?"); - stage = 0; - break; - } - break; - case 907: - npc("You bet, " + player.getUsername() + "! Right when you give me 99000 coins."); - stage = 908; - break; - case 908: - options("Okay, here you go.", "No, thanks."); - stage = 909; - break; - case 909: - switch (buttonId) { - case 1: - player("Okay, here you go."); - stage = 910; - break; - case 2: - end(); - break; - } - break; - case 910: - if (player.getInventory().freeSlots() < 2) { - player("I don't seem to have enough inventory space."); - stage = 999; - return true; - } - if (!player.getInventory().containsItem(COINS)) { - player("I don't seem to have enough coins with", "me at this time."); - stage = 999; - return true; - } - if (player.getInventory().remove(COINS) && player.getInventory().add(ITEMS)) { - npc("Have fun with it."); - stage = 999; - } else { - player("I don't seem to have enough coins with", "me at this time."); - stage = 999; - } - break; } return true; } @@ -698,9 +582,4 @@ public final class SlayerMasterDialogue extends DialoguePlugin { public int[] getIds() { return new int[]{70, 1598, 1596, 1597, 1599, 7780, 8275, 8273, 8274, 8649}; } - - private JSONObject getStoreFile() { - return ServerStore.getArchive("daily-slayer-rerolls"); - } - } diff --git a/Server/src/main/content/global/skill/smithing/FurnaceOptionPlugin.java b/Server/src/main/content/global/skill/smithing/FurnaceOptionPlugin.java index e34e71cc7..be0190327 100644 --- a/Server/src/main/content/global/skill/smithing/FurnaceOptionPlugin.java +++ b/Server/src/main/content/global/skill/smithing/FurnaceOptionPlugin.java @@ -78,26 +78,6 @@ public final class FurnaceOptionPlugin extends OptionHandler { player.getPacketDispatch().sendItemZoomOnInterface(2363, 150, 311, 12); } - /** - * Method used to handle the tutorial island interaction. - * @param player the player. - */ - private final void handleTutorialIsland(final Player player) { - if (player.getInventory().containItems(438, 436)) { - player.animate(ANIMATION); - GameWorld.getPulser().submit(new Pulse(2, player) { - @Override - public boolean pulse() { - player.getInventory().remove(ITEMS); - player.getInventory().add(Bar.BRONZE.getProduct()); - player.getSkills().addExperience(Skills.SMITHING, Bar.BRONZE.getExperience()); - return true; - } - - }); - } - } - /** * Represents the plugin used to handle the ore on the furance. * @author 'Vexia diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/RatPenDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/RatPenDialogue.kt new file mode 100644 index 000000000..3728d6d9c --- /dev/null +++ b/Server/src/main/content/region/misc/tutisland/dialogue/RatPenDialogue.kt @@ -0,0 +1,15 @@ +package content.region.misc.tutisland.dialogue + +import content.region.misc.tutisland.handlers.sendStageDialog +import core.game.dialogue.ChatAnim +import core.game.dialogue.DialogueLabeller + +/** + * Vannaka's angry dialogue when you try to enter his rat pen before you're supposed to + */ +class RatPenDialogue : DialogueLabeller() { + override fun addConversation() { + npc(ChatAnim.ANGRY, "Oi, get away from there!", "Don't enter my rat pen unless I say so!") + exec { player, _ -> sendStageDialog(player) } + } +} diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/SkipTutorialDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/SkipTutorialDialogue.kt index 3a16f44c9..f19f67a64 100644 --- a/Server/src/main/content/region/misc/tutisland/dialogue/SkipTutorialDialogue.kt +++ b/Server/src/main/content/region/misc/tutisland/dialogue/SkipTutorialDialogue.kt @@ -9,10 +9,11 @@ import core.game.world.map.Location import core.plugin.Initializable import org.rs09.consts.NPCs import content.region.misc.tutisland.handlers.TutorialStage +import core.game.component.Component.setUnclosable /** * Handles Skippy's skip tutorial dialogue - * @author Ceikry + * @author Ceikry, Player Name */ @Initializable class SkipTutorialDialogue(player: Player? = null) : DialoguePlugin(player) { @@ -21,13 +22,13 @@ class SkipTutorialDialogue(player: Player? = null) : DialoguePlugin(player) { } override fun open(vararg args: Any?): Boolean { - npcl(FacialExpression.FRIENDLY, "Hey, would you like to skip to the end? Choose wisely! This is the only time you get this choice.") + setUnclosable(player, npcl(FacialExpression.FRIENDLY, "Hey, would you like to skip to the end? Choose wisely! This is the only time you get this choice.")) return true } override fun handle(interfaceId: Int, buttonId: Int): Boolean { when(stage){ - 0 -> options("Yes, I'd like to skip the tutorial.", "No thanks.").also { stage++ } + 0 -> options("Yes, I'd like to skip the tutorial.", "No, thanks.").also { stage++ } 1 -> when(buttonId) { 1 -> { diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/SurvivalExpertDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/SurvivalExpertDialogue.kt index 21c914b98..4e0385178 100644 --- a/Server/src/main/content/region/misc/tutisland/dialogue/SurvivalExpertDialogue.kt +++ b/Server/src/main/content/region/misc/tutisland/dialogue/SurvivalExpertDialogue.kt @@ -1,119 +1,108 @@ package content.region.misc.tutisland.dialogue -import core.api.addItem +import core.api.addItemOrDrop import core.api.inInventory import core.api.setAttribute -import core.game.component.Component -import core.game.dialogue.DialoguePlugin -import core.game.dialogue.FacialExpression -import core.game.node.entity.npc.NPC -import core.game.node.entity.player.Player -import core.plugin.Initializable import org.rs09.consts.Items import org.rs09.consts.NPCs import content.region.misc.tutisland.handlers.TutorialStage +import content.region.misc.tutisland.handlers.sendStageDialog +import core.api.getAttribute +import core.api.inEquipment +import core.game.dialogue.ChatAnim +import core.game.dialogue.DialogueLabeller +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.npc.NPC +import core.game.node.item.Item -/** - * Handles the survival expert's dialogue - * @author Ceikry - */ -@Initializable -class SurvivalExpertDialogue(player: Player? = null) : DialoguePlugin(player) { - override fun newInstance(player: Player?): DialoguePlugin { - return SurvivalExpertDialogue(player) +class SurvivalExpertDialogue : InteractionListener { + override fun defineListeners() { + on(NPCs.SURVIVAL_EXPERT_943, IntType.NPC, "talk-to") { player, node -> + DialogueLabeller.open(player, SurvivalExpertDialogueFile(), node as NPC) + return@on true + } } +} - override fun open(vararg args: Any?): Boolean { - npc = args[0] as NPC - val tutStage = player?.getAttribute("tutorial:stage", 0) ?: 0 - when(tutStage) - { - 4 -> Component.setUnclosable( - player, - interpreter.sendDialogues( - npc, - FacialExpression.FRIENDLY, - "Hello there, newcomer. My name is Brynna. My job is", - "to teach you a few survival tips and tricks. First off", - "we're going to start with the most basic survival skill of", - "all: making a fire." - ) - ) +class SurvivalExpertDialogueFile : DialogueLabeller() { + override fun addConversation() { + assignToIds(NPCs.SURVIVAL_EXPERT_943) - 11 -> Component.setUnclosable( - player, - interpreter.sendDialogues( - npc, - FacialExpression.FRIENDLY, - "Well done! Next we need to get some food in our", - "bellies. We'll need something to cook. There are shrimp", - "in the pond there, so let's catch and cook some." - ) - ) - - 5, 14, 15 -> { - if(!inInventory(player, Items.BRONZE_AXE_1351)) - { - player.dialogueInterpreter.sendItemMessage(Items.BRONZE_AXE_1351, "The Survival Expert gives you a spare bronze axe.") - addItem(player, Items.BRONZE_AXE_1351) + exec { player, _ -> + when (val stage = getAttribute(player, "/save:tutorial:stage", 0)) { + 4 -> loadLabel(player, "hello") + 5, 6, 7, 8, 9, 10, 12, 13, 14 -> { + if (!inInventory(player, Items.BRONZE_AXE_1351) && !inEquipment(player, Items.BRONZE_AXE_1351)) { + loadLabel(player, "spare axe") + } + if (!inInventory(player, Items.TINDERBOX_590)) { + loadLabel(player, "spare tinderbox") + } + if (stage >= 11 && !inInventory(player, Items.SMALL_FISHING_NET_303)) { + loadLabel(player, "spare net") + } + loadLabel(player, "nowhere") } - if(!inInventory(player, Items.TINDERBOX_590)) - { - player.dialogueInterpreter.sendItemMessage(Items.TINDERBOX_590, "The Survival Expert gives you a spare tinderbox.") - addItem(player, Items.TINDERBOX_590) - } - return false + 11 -> loadLabel(player, "fishing") + else -> loadLabel(player, "nowhere") } } - return true - } - override fun handle(interfaceId: Int, buttonId: Int): Boolean { - when(player?.getAttribute("tutorial:stage", 0)) - { - 4 -> when(stage) - { - 0 -> { - Component.setUnclosable( - player, - interpreter.sendDoubleItemMessage( - Items.TINDERBOX_590, - Items.BRONZE_AXE_1351, - "The Survival Guide gives you a tinderbox and a bronze axe!" - ) - ) - addItem(player, Items.TINDERBOX_590) - addItem(player, Items.BRONZE_AXE_1351) - stage++ - } - 1 -> { - end() - setAttribute(player, "tutorial:stage", 5) - TutorialStage.load(player, 5) - } + label("hello") + npc(ChatAnim.FRIENDLY, "Hello there, newcomer. My name is Brynna. My job is to teach you a few survival tips and tricks. First off we're going to start with the most basic survival skill of all: making a fire.", unclosable = true) + exec { player, _ -> + addItemOrDrop(player, Items.TINDERBOX_590) + addItemOrDrop(player, Items.BRONZE_AXE_1351) + } + item(Item(Items.TINDERBOX_590), Item(Items.BRONZE_AXE_1351), "The Survival Guide gives you a tinderbox and a bronze", "axe!", unclosable = true) + exec { player, _ -> + setAttribute(player, "tutorial:stage", 5) + TutorialStage.load(player, 5) + } + goto("nowhere") + + label("fishing") + npc(ChatAnim.FRIENDLY, "Well done! Next we need to get some food in our bellies. We'll need something to cook. There are shrimp in the pond there, so let's catch and cook some.", unclosable = true) + exec { player, _ -> addItemOrDrop(player, Items.SMALL_FISHING_NET_303) } + item(Item(Items.SMALL_FISHING_NET_303), "The Survival Guide gives you a", "net!", unclosable = true) + exec { player, _ -> + setAttribute(player, "tutorial:stage", 12) + TutorialStage.load(player, 12) + } + goto("nowhere") + + label("spare axe") + exec { player, _ -> addItemOrDrop(player, Items.BRONZE_AXE_1351) } + item(Item(Items.BRONZE_AXE_1351), "The Survival Guide gives you a spare bronze axe.", unclosable = true) + exec { player, _ -> + if (!inInventory(player, Items.TINDERBOX_590)) { + loadLabel(player, "spare tinderbox") } - - 11 -> when(stage){ - 0 -> { - Component.setUnclosable( - player, - interpreter.sendItemMessage(303, "The Survival Guide gives you a net!") - ) - addItem(player, Items.SMALL_FISHING_NET_303) - stage++ - } - 1 -> { - end() - setAttribute(player, "tutorial:stage", 12) - TutorialStage.load(player, 12) - } + val stage = getAttribute(player, "/save:tutorial:stage", 0) + if (stage >= 11 && !inInventory(player, Items.SMALL_FISHING_NET_303)) { + loadLabel(player, "spare net") } } - return true - } + goto("nowhere") //closes the dialogue, letting the hook reopen the tutorial stage dialog as appropriate - override fun getIds(): IntArray { - return intArrayOf(NPCs.SURVIVAL_EXPERT_943) - } + label("spare tinderbox") + exec { player, _ -> addItemOrDrop(player, Items.TINDERBOX_590) } + item(Item(Items.TINDERBOX_590), "The Survival Guide gives you a spare tinderbox.", unclosable = true) + exec { player, _ -> + val stage = getAttribute(player, "/save:tutorial:stage", 0) + if (stage >= 11 && !inInventory(player, Items.SMALL_FISHING_NET_303)) { + loadLabel(player, "spare net") + } + } + goto("nowhere") -} \ No newline at end of file + label("spare net") + exec { player, _ -> addItemOrDrop(player, Items.SMALL_FISHING_NET_303) } + item(Item(Items.SMALL_FISHING_NET_303), "The Survival Guide gives you a spare net.", unclosable = true) + goto("nowhere") + + label("nowhere") + exec { player, _ -> sendStageDialog(player) } + } +} diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialCombatInstructorDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialCombatInstructorDialogue.kt index 3513aaeb8..1ab335e26 100644 --- a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialCombatInstructorDialogue.kt +++ b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialCombatInstructorDialogue.kt @@ -1,95 +1,106 @@ package content.region.misc.tutisland.dialogue -import core.api.* -import core.game.dialogue.DialoguePlugin -import core.game.dialogue.FacialExpression import core.game.node.entity.npc.NPC -import core.game.node.entity.player.Player -import core.plugin.Initializable import org.rs09.consts.Items import org.rs09.consts.NPCs import content.region.misc.tutisland.handlers.TutorialStage +import content.region.misc.tutisland.handlers.sendStageDialog +import core.api.* +import core.game.dialogue.ChatAnim +import core.game.dialogue.DialogueLabeller +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.player.Player +import core.game.node.item.Item -/** - * Handles the combat instructor's dialogue - * @author Ceikry - */ -@Initializable -class TutorialCombatInstructorDialogue(player: Player? = null) : DialoguePlugin(player) { - override fun newInstance(player: Player?): DialoguePlugin { - return TutorialCombatInstructorDialogue(player) - } - - override fun open(vararg args: Any?): Boolean { - npc = args[0] as NPC - - when(getAttribute(player, "tutorial:stage", 0)) - { - 44 -> playerl(FacialExpression.FRIENDLY, "Hi! My name's ${player.username}.") - 47 -> npcl(FacialExpression.FRIENDLY, "Very good, but that little butter knife isn't going to protect you much. Here, take these.") - 53 -> playerl(FacialExpression.FRIENDLY, "I did it! I killed a giant rat!") - 54 -> { - player.dialogueInterpreter.sendDoubleItemMessage(Items.SHORTBOW_841, Items.BRONZE_ARROW_882, "The Combat Guide gives you some bronze arrows and a shortbow!") - if(!inInventory(player, Items.SHORTBOW_841) && !inEquipment(player, Items.SHORTBOW_841)) - addItemOrDrop(player, Items.SHORTBOW_841) - if(!inInventory(player, Items.BRONZE_ARROW_882) && !inEquipment(player, Items.BRONZE_ARROW_882)) - addItemOrDrop(player, Items.BRONZE_ARROW_882, 30) - } +class CombatInstructorDialogue : InteractionListener { + override fun defineListeners() { + on(NPCs.COMBAT_INSTRUCTOR_944, IntType.NPC, "talk-to") { player, node -> + DialogueLabeller.open(player, CombatInstructorDialogueFile(), node as NPC) + return@on true } - return true } - - override fun handle(interfaceId: Int, buttonId: Int): Boolean { - when(getAttribute(player, "tutorial:stage", 0)) - { - 44 -> when(stage){ - 0 -> npcl(FacialExpression.ANGRY, "Do I look like I care? To me you're just another newcomer who thinks they're ready to fight.").also { stage++ } - 1 -> npcl(FacialExpression.FRIENDLY, "I'm Vannaka, the greatest swordsman alive.").also { stage++ } - 2 -> npcl(FacialExpression.FRIENDLY, "Let's get started by teaching you to wield a weapon.").also { stage++ } - 3 -> { - end() - setAttribute(player, "tutorial:stage", 45) - TutorialStage.load(player, 45) - } - } - - 47 -> when(stage){ - 0 -> { - addItemOrDrop(player, Items.BRONZE_SWORD_1277) - addItemOrDrop(player, Items.WOODEN_SHIELD_1171) - sendDoubleItemDialogue(player, Items.BRONZE_SWORD_1277, Items.WOODEN_SHIELD_1171, "The Combat Guide gives you a bronze sword and a wooden shield!") - stage++ - } - 1 -> { - end() - setAttribute(player, "tutorial:stage", 48) - TutorialStage.load(player, 48) - } - } - - 53 -> when(stage){ - 0 -> npcl(FacialExpression.FRIENDLY, "I saw, ${player.username}. You seem better at this than I thought. Now that you have grasped basic swordplay, let's move on.").also { stage++ } - 1 -> npcl(FacialExpression.FRIENDLY, "Let's try some ranged attacking, with this you can kill foes from a distance. Also, foes unable to reach you are as good as dead. You'll be able to attack the rats, without entering the pit.").also { stage++ } - 2 -> { - sendDoubleItemDialogue(player, Items.SHORTBOW_841, Items.BRONZE_ARROW_882, "The Combat Guide gives you some bronze arrows and a shortbow!") - if(!inInventory(player, Items.SHORTBOW_841) && !inEquipment(player, Items.SHORTBOW_841)) - addItem(player, Items.SHORTBOW_841) - if(!inInventory(player, Items.BRONZE_ARROW_882) && !inEquipment(player, Items.BRONZE_ARROW_882)) - addItem(player, Items.BRONZE_ARROW_882, 30) - stage++ - } - 3 -> { - end() - setAttribute(player, "tutorial:stage", 54) - TutorialStage.load(player, 54) - } - } - } - return true - } - - override fun getIds(): IntArray { - return intArrayOf(NPCs.COMBAT_INSTRUCTOR_944) - } - +} + +class CombatInstructorDialogueFile : DialogueLabeller() { + fun lostWeapon(player: Player, id: Int): Boolean { + return !inInventory(player, id) && !inEquipment(player, id) + } + + override fun addConversation() { + assignToIds(NPCs.COMBAT_INSTRUCTOR_944) + + exec { player, _ -> + when (getAttribute(player, "tutorial:stage", 0)) { + 44 -> loadLabel(player, "hello") + 47 -> loadLabel(player, "butter") + 48, 49, 50, 51, 52 -> loadLabel(player, if (lostWeapon(player, Items.BRONZE_SWORD_1277)) "lost sword" else if (lostWeapon(player, Items.WOODEN_SHIELD_1171)) "lost shield" else "nowhere") + 53 -> loadLabel(player, "killed rat") + 54 -> loadLabel(player, if (lostWeapon(player, Items.SHORTBOW_841)) "lost bow" else if (lostWeapon(player, Items.BRONZE_ARROW_882)) "lost arrows" else "nowhere") + else -> loadLabel(player, "nowhere") + } + } + + label("hello") + player(ChatAnim.FRIENDLY, "Hi! My name's ${player?.username}.", unclosable = true) + npc(ChatAnim.ANGRY, "Do I look like I care? To me you're just another newcomer who thinks they're ready to fight.", unclosable = true) + npc(ChatAnim.FRIENDLY, "I'm Vannaka, the greatest swordsman alive.", unclosable = true) + npc(ChatAnim.FRIENDLY, "Let's get started by teaching you to wield a weapon.", unclosable = true) + exec { player, _ -> + setAttribute(player, "tutorial:stage", 45) + TutorialStage.load(player, 45) + } + + label("butter") + npc(ChatAnim.FRIENDLY, "Very good, but that little butter knife isn't going to protect you much. Here, take these.", unclosable = true) + exec { player, _ -> + addItemOrDrop(player, Items.BRONZE_SWORD_1277) + addItemOrDrop(player, Items.WOODEN_SHIELD_1171) + } + item(Item(Items.BRONZE_SWORD_1277), Item(Items.WOODEN_SHIELD_1171), "The Combat Guide gives you a bronze sword and a", "wooden shield!", unclosable = true) + exec { player, _ -> + setAttribute(player, "tutorial:stage", 48) + TutorialStage.load(player, 48) + } + goto("nowhere") + + label("lost sword") + exec { player, _ -> addItemOrDrop(player, Items.BRONZE_SWORD_1277) } + item(Item(Items.BRONZE_SWORD_1277), "The Combat Guide gives you a spare sword.", unclosable = true) + exec { player, _ -> if (lostWeapon(player, Items.WOODEN_SHIELD_1171)) loadLabel(player, "lost shield") } + goto("nowhere") + + label("lost shield") + exec { player, _ -> addItemOrDrop(player, Items.WOODEN_SHIELD_1171) } + item(Item(Items.WOODEN_SHIELD_1171), "The Combat Guide gives you a spare shield.", unclosable = true) + goto("nowhere") + + label("killed rat") + player(ChatAnim.FRIENDLY, "I did it! I killed a giant rat!", unclosable = true) + npc(ChatAnim.FRIENDLY, "I saw, ${player?.username}. You seem better at this than I thought. Now that you have grasped basic swordplay, let's move on.", unclosable = true) + npc(ChatAnim.FRIENDLY, "Let's try some ranged attacking, with this you can kill foes from a distance. Also, foes unable to reach you are as good as dead. You'll be able to attack the rats, without entering the pit.", unclosable = true) + exec { player, _ -> + addItemOrDrop(player, Items.SHORTBOW_841) + addItemOrDrop(player, Items.BRONZE_ARROW_882, 30) + } + item(Item(Items.SHORTBOW_841), Item(Items.BRONZE_ARROW_882), "The Combat Guide gives you some bronze arrows and", "a shortbow!", unclosable = true) + exec { player, _ -> + setAttribute(player, "tutorial:stage", 54) + TutorialStage.load(player, 54) + } + goto("nowhere") + + label("lost bow") + exec { player, _ -> addItemOrDrop(player, Items.SHORTBOW_841) } + item(Item(Items.SHORTBOW_841), "The Combat Guide gives you a spare bow.", unclosable = true) + goto("nowhere") + + label("lost arrows") + exec { player, _ -> addItemOrDrop(player, Items.BRONZE_ARROW_882, 10) } + item(Item(Items.BRONZE_ARROW_882), "You receive some spare arrows.", unclosable = true) + goto("nowhere") + + label("nowhere") + exec { player, _ -> sendStageDialog(player) } + } } diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialFinanceAdvisorDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialFinanceAdvisorDialogue.kt index 305911ae5..d94bcd922 100644 --- a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialFinanceAdvisorDialogue.kt +++ b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialFinanceAdvisorDialogue.kt @@ -7,11 +7,12 @@ import core.game.node.entity.player.Player import core.plugin.Initializable import org.rs09.consts.NPCs import content.region.misc.tutisland.handlers.TutorialStage +import core.game.component.Component.setUnclosable import core.game.world.GameWorld.settings /** * Handles the finance tutor's dialogue - * @author Ceikry + * @author Ceikry, Player Name */ @Initializable class TutorialFinanceAdvisorDialogue(player: Player? = null) : core.game.dialogue.DialoguePlugin(player) { @@ -23,8 +24,8 @@ class TutorialFinanceAdvisorDialogue(player: Player? = null) : core.game.dialogu npc = args[0] as NPC when(getAttribute(player, "tutorial:stage", 0)) { - 58 -> playerl(core.game.dialogue.FacialExpression.FRIENDLY, "Hello, who are you?") - 59 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Move along, now.").also { return false } + 58 -> setUnclosable(player, playerl(core.game.dialogue.FacialExpression.FRIENDLY, "Hello, who are you?")) + 59 -> setUnclosable(player, npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Move along, now.")) } return true } @@ -32,21 +33,22 @@ class TutorialFinanceAdvisorDialogue(player: Player? = null) : core.game.dialogu override fun handle(interfaceId: Int, buttonId: Int): Boolean { when(getAttribute(player, "tutorial:stage", 0)){ 58 -> when(stage++){ - 0 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "I'm the Financial Advisor. I'm here to tell people how to make money.") - 1 -> playerl(core.game.dialogue.FacialExpression.FRIENDLY, "Okay. How can I make money then?") - 2 -> npcl(core.game.dialogue.FacialExpression.HALF_THINKING, "How you can make money? Quite.") - 3 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Well there are three basic ways of making money here: combat, quests, and trading. I will talk you through each of them very quickly.") - 4 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Let's start with combat as it is probably still fresh in your mind. Many enemies, both human and monster will drop items when they die.") - 5 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Now, the next way to earn money quickly is by quests. Many people on " + settings!!.name + " have things they need doing, which they will reward you for.") - 6 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "By getting a high level in skills such as Cooking, Mining, Smithing or Fishing, you can create or catch your own items and sell them for pure profit.") - 7 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Lastly, we have jobs you can get from tutors in Lumbridge. These pay very handsomely early on!").also { stage++ } - 8 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Well, that about covers it. Move along now.") + 0 -> setUnclosable(player, npcl(core.game.dialogue.FacialExpression.FRIENDLY, "I'm the Financial Advisor. I'm here to tell people how to make money.")) + 1 -> setUnclosable(player, playerl(core.game.dialogue.FacialExpression.FRIENDLY, "Okay. How can I make money then?")) + 2 -> setUnclosable(player, npcl(core.game.dialogue.FacialExpression.HALF_THINKING, "How you can make money? Quite.")) + 3 -> setUnclosable(player, npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Well there are three basic ways of making money here: combat, quests, and trading. I will talk you through each of them very quickly.")) + 4 -> setUnclosable(player, npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Let's start with combat as it is probably still fresh in your mind. Many enemies, both human and monster will drop items when they die.")) + 5 -> setUnclosable(player, npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Now, the next way to earn money quickly is by quests. Many people on " + settings!!.name + " have things they need doing, which they will reward you for.")) + 6 -> setUnclosable(player, npcl(core.game.dialogue.FacialExpression.FRIENDLY, "By getting a high level in skills such as Cooking, Mining, Smithing or Fishing, you can create or catch your own items and sell them for pure profit.")) + 7 -> setUnclosable(player, npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Lastly, we have jobs you can get from tutors in Lumbridge. These pay very handsomely early on!")).also { stage++ } + 8 -> setUnclosable(player, npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Well, that about covers it. Move along now.")) 9 -> { end() setAttribute(player, "tutorial:stage", 59) TutorialStage.load(player, 59) } } + 59 -> TutorialStage.load(player, 59) } return true } @@ -54,5 +56,4 @@ class TutorialFinanceAdvisorDialogue(player: Player? = null) : core.game.dialogu override fun getIds(): IntArray { return intArrayOf(NPCs.FINANCIAL_ADVISOR_947) } - } \ No newline at end of file diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMagicTutorDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMagicTutorDialogue.kt index 6c15f7449..68707766d 100644 --- a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMagicTutorDialogue.kt +++ b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMagicTutorDialogue.kt @@ -4,208 +4,226 @@ import content.global.handlers.iface.RulesAndInfo import content.region.misc.tutisland.handlers.* import core.ServerConstants import core.api.* +import core.game.dialogue.ChatAnim +import core.game.dialogue.DialogueLabeller +import core.game.dialogue.DialogueOption +import core.game.interaction.InteractionListener +import core.game.interaction.QueueStrength import core.game.node.entity.npc.NPC -import core.game.node.entity.player.Player import core.game.node.entity.player.link.IronmanMode import core.game.node.entity.player.link.TeleportManager import core.game.node.item.Item import core.game.world.GameWorld import core.game.world.map.Location import core.plugin.Initializable -import core.tools.END_DIALOGUE import core.worker.ManagementEvents import org.rs09.consts.Items import org.rs09.consts.NPCs import proto.management.JoinClanRequest -/** - * Handles the magic tutor's dialogue - * @author Ceikry - */ @Initializable -class TutorialMagicTutorDialogue(player: Player? = null) : core.game.dialogue.DialoguePlugin(player) { - private val STARTER_PACK = arrayOf( - Item(1351, 1), - Item(590, 1), - Item(303, 1), - Item(315, 1), - Item(1925, 1), - Item(1931, 1), - Item(2309, 1), - Item(1265, 1), - Item(1205, 1), - Item(1277, 1), - Item(1171, 1), - Item(841, 1), - Item(882, 25), - Item(556, 25), - Item(558, 15), - Item(555, 6), - Item(557, 4), - Item(559, 2) - ) - private val STARTER_BANK = arrayOf(Item(995, 25)) - - override fun newInstance(player: Player?): core.game.dialogue.DialoguePlugin { - return TutorialMagicTutorDialogue(player) +class TutorialMagicTutorDialogue : InteractionListener { + override fun defineListeners() { + on(NPCs.MAGIC_INSTRUCTOR_946, NPC, "talk-to") { player, _ -> + val stage = getAttribute(player, "tutorial:stage", 0) + if (stage == 70 && inInventory(player, Items.AIR_RUNE_556) && inInventory(player, Items.MIND_RUNE_558)) { + // Player should be killing chickens instead, and could be. Instead of opening the dialogue and doing nothing (which will make you lose the tutorial island dialog), do nothing at all + return@on true + } + openDialogue(player, TutorialMagicTutorDialogueFile(), NPC(NPCs.MAGIC_INSTRUCTOR_946)) + return@on true + } } +} - override fun open(vararg args: Any?): Boolean { - npc = args[0] as NPC - when(getAttribute(player, "tutorial:stage", 0)) - { - 67 -> playerl(core.game.dialogue.FacialExpression.FRIENDLY, "Hello.") - 69 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Good. This is a list of your spells. Currently you can only cast one offensive spell called Wind Strike. Let's try it out on one of those chickens.") - 70 -> if(!inInventory(player, Items.AIR_RUNE_556) && !inInventory(player, Items.MIND_RUNE_558)) - { - player.dialogueInterpreter.sendDoubleItemMessage(Items.AIR_RUNE_556, Items.MIND_RUNE_558, "You receive some spare runes.") +class TutorialMagicTutorDialogueFile : DialogueLabeller() { + override fun addConversation() { + val STARTER_PACK = arrayOf( + Item(Items.BRONZE_AXE_1351), + Item(Items.TINDERBOX_590), + Item(Items.SMALL_FISHING_NET_303), + Item(Items.SHRIMPS_315), + Item(Items.BUCKET_1925), + Item(Items.EMPTY_POT_1931), + Item(Items.BREAD_2309), + Item(Items.BRONZE_PICKAXE_1265), + Item(Items.BRONZE_DAGGER_1205), + Item(Items.BRONZE_SWORD_1277), + Item(Items.WOODEN_SHIELD_1171), + Item(Items.SHORTBOW_841), + Item(Items.BRONZE_ARROW_882, 25), + Item(Items.AIR_RUNE_556, 25), + Item(Items.MIND_RUNE_558, 15), + Item(Items.WATER_RUNE_555, 6), + Item(Items.EARTH_RUNE_557, 4), + Item(Items.BODY_RUNE_559, 2) + ) + val STARTER_BANK = arrayOf(Item(Items.COINS_995, 25)) + + exec { player, _ -> + when (getAttribute(player, "tutorial:stage", 0)) { + 67 -> loadLabel(player, "hello") + 69 -> loadLabel(player, "spelllist") + 70 -> { + if (!inInventory(player, Items.AIR_RUNE_556, 1) || !inInventory(player, Items.MIND_RUNE_558, 1)) { + loadLabel(player, "givemorerunes") + } else { + goto("nowhere") + } + } + 71 -> loadLabel(player, "finishedtutorial") + else -> goto("nowhere") + } + } + + label("hello") + player(ChatAnim.FRIENDLY, "Hello.", unclosable = true) + npc(ChatAnim.FRIENDLY, "Good day, newcomer. My name is Terrova. I'm here to tell you about Magic. Let's start by opening your spell list.", unclosable = true) + exec { player, _ -> + setAttribute(player, "tutorial:stage", 68) + TutorialStage.load(player, 68) + } + + label("spelllist") + npc(ChatAnim.FRIENDLY, "Good. This is a list of your spells. Currently you can only cast one offensive spell called Wind Strike. Let's try it out on one of those chickens.", unclosable = true) + exec { player, _ -> + setAttribute(player, "tutorial:stage", 70) addItemOrDrop(player, Items.AIR_RUNE_556, 15) addItemOrDrop(player, Items.MIND_RUNE_558, 15) - return false } - 71 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Alright, last thing. Are you interested in being an ironman or changing your experience rate?") - else -> return false - } - return true - } + item(Item(Items.AIR_RUNE_556), Item(Items.MIND_RUNE_558), "Terrova gives you 15 air runes and 15 mind runes!", unclosable = true) + exec { player, _ -> TutorialStage.load(player, 70) } - override fun handle(interfaceId: Int, buttonId: Int): Boolean { - when(getAttribute(player, "tutorial:stage", 0)) - { - 67 -> when(stage++){ - 0 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Good day, newcomer. My name is Terrova. I'm here to tell you about Magic. Let's start by opening your spell list.") - 1 -> { - end() - setAttribute(player, "tutorial:stage", 68) - TutorialStage.load(player, 68) + label("givemorerunes") + exec { player, _ -> + addItemOrDrop(player, Items.AIR_RUNE_556, 5) + addItemOrDrop(player, Items.MIND_RUNE_558, 5) + } + item(Item(Items.AIR_RUNE_556), Item(Items.MIND_RUNE_558), "You receive some spare runes.", unclosable = true) + exec { player, _ -> TutorialStage.load(player, 70) } + + label("finishedtutorial") + exec { player, _ -> + if (ServerConstants.XP_RATES || ServerConstants.IRONMAN) { + loadLabel(player, "talk about inauthentic") + } else { + loadLabel(player, "leave") } } - 69 -> when(stage++){ - 0 -> { - sendDoubleItemDialogue(player, Items.AIR_RUNE_556, Items.MIND_RUNE_558, "Terrova gives you 15 air runes and 15 mind runes!") - addItemOrDrop(player, Items.AIR_RUNE_556, 5) - addItemOrDrop(player, Items.MIND_RUNE_558, 5) - } - 1 -> { - end() - setAttribute(player, "tutorial:stage", 70) - TutorialStage.load(player, 70) - } + + label("talk about inauthentic") + npc(ChatAnim.FRIENDLY, "Alright, last thing. Are you interested in our inauthentic ${ServerConstants.SERVER_NAME} features?", unclosable = true) + goto("inauthentic") + + label("inauthentic") + options( + DialogueOption("xprate","Change XP rate (current: ${player?.skills?.experienceMultiplier}x)", skipPlayer = true) { _, _ -> + return@DialogueOption ServerConstants.XP_RATES + }, + DialogueOption("ironman","Set ironman mode (current: ${player?.ironmanManager?.mode?.name?.toLowerCase()})", skipPlayer = true) { _, _ -> + return@DialogueOption ServerConstants.IRONMAN + }, + DialogueOption("leave","I'm ready now."), + unclosable = true) + + label("xprate") + options( + DialogueOption("1.0x","1.0x (default)", skipPlayer = true), + DialogueOption("2.5x","2.5x", skipPlayer = true), + DialogueOption("5.0x","5.0x", skipPlayer = true), + title = "Change XP rate (current: ${player?.skills?.experienceMultiplier}x)", + unclosable = true + ) + for (rate in doubleArrayOf(1.0, 2.5, 5.0)) { + label("${rate}x") + exec { player, _ -> player.skills.experienceMultiplier = rate } + manual(unclosable = true) { player, _ -> player.dialogueInterpreter.sendDialogue("You set your XP rate to: ${rate}x.") } + goto("inauthentic") } - 71 -> when(stage){ - 0 -> options("Set Ironman Mode (current: ${player.ironmanManager.mode.name})", "Change XP Rate (current: ${player.skills.experienceMultiplier}x)", "I'm ready now.").also { stage++ } - 1 -> when(buttonId){ - 1 -> options("None","Standard","Ultimate","Nevermind.").also { stage = 10 } - 2 -> options("1.0x","2.5x","5.0x").also { stage = 20 } - 3 -> npcl(core.game.dialogue.FacialExpression.FRIENDLY, "Well, you're all finished here now. I'll give you a reasonable number of starting items when you leave.").also { stage = 30 } + + label("ironman") + options( + DialogueOption("NONE","None (default)", skipPlayer = true), + DialogueOption("STANDARD","Standard", skipPlayer = true), + DialogueOption("ULTIMATE","Ultimate (no bank)", skipPlayer = true), + title = "Change ironman mode (current: ${player?.ironmanManager?.mode?.name?.toLowerCase()}x)" + ) + for (mode in arrayOf(IronmanMode.NONE, IronmanMode.STANDARD, IronmanMode.ULTIMATE)) { + label(mode.name) + exec { player, _ -> player.ironmanManager.mode = mode } + manual(unclosable = true){ player, _ -> return@manual player.dialogueInterpreter.sendDialogue("You set your ironman mode to: ${mode.name.toLowerCase()}.") } + exec { player, _ -> loadLabel(player, if (player.ironmanManager.mode == IronmanMode.NONE) "inauthentic" else "ironwarning") } + } + + label("ironwarning") + manual(unclosable = true) { player, _ -> player.dialogueInterpreter.sendDialogue(*splitLines("WARNING: You have selected an ironman mode. This is an uncompromising mode that WILL completely restrict your ability to trade. This MAY leave you unable to complete certain content, including quests.")) } + goto("inauthentic") + + label("leave") + npc(ChatAnim.FRIENDLY, "Well, you're all finished here now. I'll give you a reasonable number of starting items when you leave.", unclosable = true) + options( + DialogueOption("leave:yes","Yes, I'm ready.","I'm ready to go now, thank you.", ChatAnim.FRIENDLY), + DialogueOption("nowhere","No, not yet.","I'm not quite ready to go yet, thank you.", ChatAnim.FRIENDLY), + title = "Leave Tutorial Island?", + unclosable = true + ) + + label("leave:yes") + manual { player, _ -> + setAttribute(player, "/save:tutorial:complete", true) + setVarbit(player, 3756, 0) + setVarp(player, 281, 1000, true) + teleport(player, Location.create(3233, 3230), TeleportManager.TeleportType.NORMAL) + closeOverlay(player) + + player.inventory.clear() + player.bank.clear() + player.equipment.clear() + player.interfaceManager.restoreTabs() + player.interfaceManager.setViewedTab(3) + player.inventory.add(*STARTER_PACK) + player.bank.add(*STARTER_BANK) + + TutorialStage.removeHintIcon(player) + player.unhook(TutorialKillReceiver) + player.unhook(TutorialFireReceiver) + player.unhook(TutorialResourceReceiver) + player.unhook(TutorialUseWithReceiver) + player.unhook(TutorialInteractionReceiver) + player.unhook(TutorialButtonReceiver) + player.unhook(TutorialDialogPreserver) + + if (GameWorld.settings!!.enable_default_clan) { + player.communication.currentClan = ServerConstants.SERVER_NAME.toLowerCase() + + val clanJoin = JoinClanRequest.newBuilder() + clanJoin.clanName = ServerConstants.SERVER_NAME.toLowerCase() + clanJoin.username = player.name + + ManagementEvents.publish(clanJoin.build()) } - 10 -> { - stage = 0 - if(buttonId < 5) - { - val mode = when (buttonId - 1) - { - 0 -> IronmanMode.NONE - 1 -> IronmanMode.STANDARD - 2 -> IronmanMode.ULTIMATE - else -> IronmanMode.NONE - } - if (mode != IronmanMode.NONE) stage = 11 - player.dialogueInterpreter.sendDialogue("You set your ironman mode to: ${mode.name}.") - player.ironmanManager.mode = mode - if (player.skills.experienceMultiplier == 10.0) player.skills.experienceMultiplier = 5.0 - } - else - { - handle(interfaceId, 0) - } - } - 11 -> player.dialogueInterpreter.sendPlainMessage(false, *splitLines("WARNING: You have selected an ironman mode. This is an uncompromising mode that WILL completely restrict your ability to trade. This MAY leave you unable to complete certain content, including quests.")).also { stage = 0 } - - 20 -> { - val rates = arrayOf(1.0,2.5,5.0) - val rate = rates[buttonId - 1] - if(rate == 10.0) { - player.dialogueInterpreter.sendDialogue("10.0x is no longer available!") - player.skills.experienceMultiplier = 5.0 - stage = 0 - return true - } - player.dialogueInterpreter.sendDialogue("You set your XP rate to: ${rate}x.") - player.skills.experienceMultiplier = rate - stage = 0 - } - - 30 -> player.dialogueInterpreter.sendOptions("Leave Tutorial Island?", "Yes, I'm ready.", "No, not yet.").also { stage++ } - 31 -> when(buttonId) - { - 1 -> playerl(core.game.dialogue.FacialExpression.FRIENDLY, "I'm ready to go now, thank you.").also { stage = 40 } - 2 -> playerl(core.game.dialogue.FacialExpression.FRIENDLY, "I'm not quite ready to go yet, thank you.").also { stage = END_DIALOGUE } - } - - 40 -> { - setAttribute(player, "/save:tutorial:complete", true) - setVarbit(player, 3756, 0) - setVarp(player, 281, 1000, true) - teleport(player, Location.create(3233, 3230), TeleportManager.TeleportType.NORMAL) - closeOverlay(player) - - player.inventory.clear() - player.bank.clear() - player.equipment.clear() - player.interfaceManager.restoreTabs() - player.interfaceManager.setViewedTab(3) - player.inventory.add(*STARTER_PACK) - player.bank.add(*STARTER_BANK) - - if(player.skills.experienceMultiplier == 10.0) - { - player.skills.experienceMultiplier = 5.0 - } - - //This overwrites the stuck dialogue after teleporting to Lumbridge for some reason - //Dialogue from 2007 or thereabouts - //Original is five lines, but if the same is done here it will break. Need to find another way of showing all this information. - interpreter.sendDialogue( - "Welcome to Lumbridge! To get more help, simply click on the", - "Lumbridge Guide or one of the Tutors - these can be found by looking", - "for the question mark icon on your mini-map. If you find you are lost", - "at any time, look for a signpost or use the Lumbridge Home Port Spell." - ) - stage = 12 - TutorialStage.removeHintIcon(player) - - player.unhook(TutorialKillReceiver) - player.unhook(TutorialFireReceiver) - player.unhook(TutorialResourceReceiver) - player.unhook(TutorialUseWithReceiver) - player.unhook(TutorialInteractionReceiver) - player.unhook(TutorialButtonReceiver) + // This shows the actual dialog, which is what this manual stage is for. + // Dialog is from 2007 or thereabouts. + // Original is five lines, but if the same is done here it will break. Need to find another way of showing all this information. + player.dialogueInterpreter.sendDialogue( + "Welcome to Lumbridge! To get more help, simply click on the", + "Lumbridge Guide or one of the Tutors - these can be found by looking", + "for the question mark icon on your mini-map. If you find you are lost", + "at any time, look for a signpost or use the Lumbridge Home Port Spell." + ) + if (ServerConstants.RULES_AND_INFO_ENABLED) { RulesAndInfo.openFor(player) - - if (GameWorld.settings!!.enable_default_clan) { - player.communication.currentClan = ServerConstants.SERVER_NAME.toLowerCase() - - val clanJoin = JoinClanRequest.newBuilder() - clanJoin.clanName = ServerConstants.SERVER_NAME.toLowerCase() - clanJoin.username = player.name - - ManagementEvents.publish(clanJoin.build()) + // The teleport finishing will release the player, so we need to relock them here + queueScript(player, 4, QueueStrength.SOFT) { _ -> + player.lock() + return@queueScript stopExecuting(player) } } - - 12 -> { - player.setAttribute("close_c_", true) - end() - } + return@manual null } - } - return true - } - override fun getIds(): IntArray { - return intArrayOf(NPCs.MAGIC_INSTRUCTOR_946) + label("nowhere") + exec { player, _ -> sendStageDialog(player) } } - } diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMasterChefDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMasterChefDialogue.kt index ad7d90bcb..89d985f29 100644 --- a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMasterChefDialogue.kt +++ b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMasterChefDialogue.kt @@ -1,7 +1,7 @@ package content.region.misc.tutisland.dialogue import core.api.* -import core.game.component.Component +import core.game.component.Component.setUnclosable import core.game.dialogue.DialoguePlugin import core.game.dialogue.FacialExpression import core.game.node.entity.npc.NPC @@ -25,32 +25,17 @@ class TutorialMasterChefDialogue(player: Player? = null) : DialoguePlugin(player npc = args[0] as NPC when(getAttribute(player, "tutorial:stage", 0)) { - 18 -> Component.setUnclosable( - player, - interpreter.sendDialogues( - npc, - FacialExpression.FRIENDLY, - "Ah! Welcome, newcomer. I am the Master Chef, Lev. It", - "is here I will teach you how to cook food truly fit for a", - "king." - ) - ) - - 19,20 -> { - if(!inInventory(player, Items.BREAD_DOUGH_2307)) - { - if(!inInventory(player, Items.BUCKET_OF_WATER_1929)) - { - sendItemDialogue(player, Items.BUCKET_OF_WATER_1929, "The Master Chef gives you another bucket of water.") + 18 -> setUnclosable(player, interpreter.sendDialogues(npc, FacialExpression.FRIENDLY, "Ah! Welcome, newcomer. I am the Master Chef, Lev. It", "is here I will teach you how to cook food truly fit for a", "king.")) + 19, 20 -> { + if (!inInventory(player, Items.BREAD_DOUGH_2307)) { + if (!inInventory(player, Items.BUCKET_OF_WATER_1929)) { + setUnclosable(player, player.dialogueInterpreter.sendItemMessage(Items.BUCKET_OF_WATER_1929, "The Master Chef gives you another bucket of water.")) addItemOrDrop(player, Items.BUCKET_OF_WATER_1929) - TutorialStage.load(player, 19) return false } - if(!inInventory(player, Items.POT_OF_FLOUR_1933)) - { - sendItemDialogue(player, Items.POT_OF_FLOUR_1933, "The Master Chef gives you another pot of flour.") + if (!inInventory(player, Items.POT_OF_FLOUR_1933)) { + setUnclosable(player, player.dialogueInterpreter.sendItemMessage(Items.POT_OF_FLOUR_1933, "The Master Chef gives you another pot of flour.")) addItemOrDrop(player, Items.POT_OF_FLOUR_1933) - TutorialStage.load(player, 19) return false } } @@ -61,54 +46,21 @@ class TutorialMasterChefDialogue(player: Player? = null) : DialoguePlugin(player } override fun handle(interfaceId: Int, buttonId: Int): Boolean { - when(getAttribute(player, "tutorial:stage", 0)) - { - 18 -> when(stage) - { - 0 -> Component.setUnclosable( - player, - interpreter.sendDialogues( - player, - FacialExpression.HALF_GUILTY, - "I already know how to cook. Brynna taught me just", - "now." - ) - ).also { stage++ } - 1 -> Component.setUnclosable( - player, - interpreter.sendDialogues( - npc, - FacialExpression.LAUGH, - "Hahahahahaha! You call THAT cooking? Some shrimp", - "on an open log fire? Oh, no, no no. I am going to", - "teach you the fine art of cooking bread." - ) - ).also { stage++ } - 2 -> Component.setUnclosable( - player, - interpreter.sendDialogues( - npc, - FacialExpression.FRIENDLY, - "And no fine meal is complete without good music, so", - "we'll cover that while you're here too." - ) - ).also { stage++ } + when (getAttribute(player, "tutorial:stage", 0)) { + 18 -> when(stage) { + 0 -> setUnclosable(player, interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "I already know how to cook. Brynna taught me just", "now.")).also { stage++ } + 1 -> setUnclosable(player, interpreter.sendDialogues(npc, FacialExpression.LAUGH, "Hahahahahaha! You call THAT cooking? Some shrimp", "on an open log fire? Oh, no, no no. I am going to", "teach you the fine art of cooking bread.")).also { stage++ } + 2 -> setUnclosable(player, interpreter.sendDialogues(npc, FacialExpression.FRIENDLY, "And no fine meal is complete without good music, so", "we'll cover that while you're here too.")).also { stage++ } 3 -> { - Component.setUnclosable( - player, - interpreter.sendDoubleItemMessage( - Items.BUCKET_OF_WATER_1929, - Items.POT_OF_FLOUR_1933, - "The Cooking Guide gives you a bucket of water and a pot of flour." - ) - ) + setUnclosable(player, interpreter.sendDoubleItemMessage(Items.BUCKET_OF_WATER_1929, Items.POT_OF_FLOUR_1933, "The Cooking Guide gives you a bucket of water and a","pot of flour.")) addItemOrDrop(player, Items.BUCKET_OF_WATER_1929) addItemOrDrop(player, Items.POT_OF_FLOUR_1933) stage++ + setAttribute(player, "tutorial:stage", 19) + TutorialStage.load(player, 19) } 4 -> { end() - setAttribute(player, "tutorial:stage", 19) TutorialStage.load(player, 19) } } diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMiningInstructorDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMiningInstructorDialogue.kt index 85a8577fa..fc1f96c81 100644 --- a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMiningInstructorDialogue.kt +++ b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialMiningInstructorDialogue.kt @@ -1,104 +1,95 @@ package content.region.misc.tutisland.dialogue -import core.game.dialogue.DialoguePlugin -import core.game.dialogue.FacialExpression import core.game.node.entity.npc.NPC -import core.game.node.entity.player.Player -import core.plugin.Initializable import org.rs09.consts.Items import org.rs09.consts.NPCs import content.region.misc.tutisland.handlers.TutorialStage +import content.region.misc.tutisland.handlers.sendStageDialog import core.api.* +import core.game.dialogue.ChatAnim +import core.game.dialogue.DialogueLabeller +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.player.Player +import core.game.node.item.Item -/** - * Handles the mining tutor's dialogue - * @author Ceikry - */ -@Initializable -class TutorialMiningInstructorDialogue(player: Player? = null) : DialoguePlugin(player) { - override fun newInstance(player: Player?): DialoguePlugin { - return TutorialMiningInstructorDialogue(player) +class MiningInstructorDialogue : InteractionListener { + override fun defineListeners() { + on(NPCs.MINING_INSTRUCTOR_948, IntType.NPC, "talk-to") { player, node -> + DialogueLabeller.open(player, MiningInstructorDialogueFile(), node as NPC) + return@on true + } + } +} + +class MiningInstructorDialogueFile : DialogueLabeller() { + fun lostPickaxe(player: Player): Boolean { + return !inInventory(player, Items.BRONZE_PICKAXE_1265) && !inEquipment(player, Items.BRONZE_PICKAXE_1265) } - override fun open(vararg args: Any?): Boolean { - npc = args[0] as NPC - when(getAttribute(player, "tutorial:stage", 0)) { - 30 -> npcl(FacialExpression.FRIENDLY, "Hi there. You must be new around here. So what do I call you? 'Newcomer' seems so impersonal, and if we're going to be working together, I'd rather tell you by name.") - 34 -> playerl(FacialExpression.FRIENDLY, "I prospected both types of rock! One set contains tin and the other has copper ore inside.") - 35 -> { - if(!inInventory(player, Items.BRONZE_PICKAXE_1265)) { - addItemOrDrop(player, Items.BRONZE_PICKAXE_1265) - player.dialogueInterpreter.sendItemMessage(Items.BRONZE_PICKAXE_1265, "Dezzick gives you a bronze pickaxe!") - stage = 3 - } - else { - TutorialStage.load(player, 35) - } - } - 40 -> playerl(FacialExpression.ASKING, "How do I make a weapon out of this?") - 41 -> { - if(!inInventory(player, Items.HAMMER_2347)) { - addItemOrDrop(player, Items.HAMMER_2347) - player.dialogueInterpreter.sendItemMessage(Items.HAMMER_2347, "Dezzick gives you a hammer!") - stage = 3 - } - else - { - end() - TutorialStage.load(player, 41) - } + override fun addConversation() { + assignToIds(NPCs.MINING_INSTRUCTOR_948) + + exec { player, _ -> + when (getAttribute(player, "tutorial:stage", 0)) { + 30 -> loadLabel(player, "hello") + 34 -> loadLabel(player, "prospected") + 35, 36, 37, 38, 39 -> loadLabel(player, if (lostPickaxe(player)) "lost pickaxe" else "nowhere") + 40 -> loadLabel(player, "make wep") + 41, 42, 43, 44, 45, 46 -> loadLabel(player, if (lostPickaxe(player)) "lost pickaxe" else if (!inInventory(player, Items.HAMMER_2347)) "lost hammer" else "nowhere") + else -> loadLabel(player, "nowhere") } } - return true - } - - override fun handle(interfaceId: Int, buttonId: Int): Boolean { - when(getAttribute(player, "tutorial:stage", 0)) { - 30 -> when(stage) { - 0 -> playerl(FacialExpression.FRIENDLY, "You can call me ${player.username}.").also { stage++ } - 1 -> npcl(FacialExpression.FRIENDLY, "Ok then, ${player.username}. My name is Dezzick and I'm a miner by trade. Let's prospect some of these rocks.").also { stage++ } - 2 -> { - end() - setAttribute(player, "tutorial:stage", 31) - TutorialStage.load(player, 31) - } - } - - 34,35 -> when(stage) { - 0 -> npcl(FacialExpression.FRIENDLY, "Absolutely right, ${player.username}. These two ore types can be smelted together to make bronze.").also { stage++ } - 1 -> npcl(FacialExpression.FRIENDLY, "So now you know what ore is in the rocks over there, why don't you have a go at mining some tin and copper? Here, you'll need this to start with.").also { stage++ } - 2 -> { - addItem(player, Items.BRONZE_PICKAXE_1265) - player.dialogueInterpreter.sendItemMessage(Items.BRONZE_PICKAXE_1265, "Dezzick gives you a bronze pickaxe!") - stage++ - } - 3 -> { - end() - setAttribute(player, "tutorial:stage", 35) - TutorialStage.load(player, 35) - } - } - - 40,41 -> when(stage){ - 0 -> npcl(FacialExpression.FRIENDLY, "Okay, I'll show you how to make a dagger out of it. You'll be needing this..").also { stage++ } - 1 -> { - addItem(player, Items.HAMMER_2347) - player.dialogueInterpreter.sendItemMessage(Items.HAMMER_2347, "Drezzick gives you a hammer!") - stage++ - } - 2 -> { - end() - setAttribute(player, "tutorial:stage", 41) - TutorialStage.load(player, 41) - } - } + label("hello") + npc(ChatAnim.FRIENDLY, "Hi there. You must be new around here. So what do I call you? 'Newcomer' seems so impersonal, and if we're going to be working together, I'd rather tell you by name.", unclosable = true) + player(ChatAnim.FRIENDLY, "You can call me ${player?.username}.", unclosable = true) + npc(ChatAnim.FRIENDLY, "Ok then, ${player?.username}. My name is Dezzick and I'm a miner by trade. Let's prospect some of these rocks.", unclosable = true) + exec { player, _ -> + setAttribute(player, "tutorial:stage", 31) + TutorialStage.load(player, 31) } - return true - } + label("prospected") + player(ChatAnim.FRIENDLY, "I prospected both types of rock! One set contains tin and the other has copper ore inside.", unclosable = true) + npc(ChatAnim.FRIENDLY, "Absolutely right, ${player?.username}. These two ore types can be smelted together to make bronze.", unclosable = true) + npc(ChatAnim.FRIENDLY, "So now you know what ore is in the rocks over there, why don't you have a go at mining some tin and copper? Here, you'll need this to start with.", unclosable = true) + exec { player, _ -> + addItem(player, Items.BRONZE_PICKAXE_1265) + setAttribute(player, "tutorial:stage", 35) + TutorialStage.load(player, 35) + } + item(Item(Items.BRONZE_PICKAXE_1265), "Dezzick gives you a bronze pickaxe!", unclosable = true) + goto("nowhere") - override fun getIds(): IntArray { - return intArrayOf(NPCs.MINING_INSTRUCTOR_948) + label("make wep") + player(ChatAnim.ASKING, "How do I make a weapon out of this?", unclosable = true) + npc(ChatAnim.FRIENDLY, "Okay, I'll show you how to make a dagger out of it. You'll be needing this.", unclosable = true) + exec { player, _ -> + addItem(player, Items.HAMMER_2347) + setAttribute(player, "tutorial:stage", 41) + TutorialStage.load(player, 41) + } + item(Item(Items.HAMMER_2347), "Dezzick gives you a hammer!", unclosable = true) + goto("nowhere") + + label("lost pickaxe") + exec { player, _ -> addItem(player, Items.BRONZE_PICKAXE_1265) } + item(Item(Items.BRONZE_PICKAXE_1265), "Dezzick gives you a spare pickaxe.", unclosable = true) + exec { player, _ -> + val stage = getAttribute(player, "/save:tutorial:stage", 0) + if (stage >= 41 && !inInventory(player, Items.HAMMER_2347)) { + loadLabel(player, "lost hammer") + } + } + goto("nowhere") + + label("lost hammer") + exec { player, _ -> addItem(player, Items.HAMMER_2347) } + item(Item(Items.HAMMER_2347), "Dezzick gives you a spare hammer.", unclosable = true) + goto("nowhere") + + label("nowhere") + exec { player, _ -> sendStageDialog(player) } } } \ No newline at end of file diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialPrayerDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialPrayerDialogue.kt index 5278dc65d..3fb71bd16 100644 --- a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialPrayerDialogue.kt +++ b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialPrayerDialogue.kt @@ -9,6 +9,7 @@ import core.game.node.entity.player.Player import core.plugin.Initializable import org.rs09.consts.NPCs import content.region.misc.tutisland.handlers.TutorialStage +import core.game.component.Component.setUnclosable /** * Handles the prayer guide's dialogue @@ -24,9 +25,9 @@ class TutorialPrayerDialogue(player: Player? = null) : DialoguePlugin(player) { npc = args[0] as NPC when(getAttribute(player, "tutorial:stage", 0)) { - 60 -> npcl(FacialExpression.FRIENDLY, "Greetings! I'd just like to briefly go over two topics with you: Prayer, and Friend's.") - 62 -> npcl(FacialExpression.FRIENDLY, "Prayers have all sorts of wonderful benefits! From boosting defence and damage, to protecting you from outside damage, to saving items on death!") - 65 -> npcl(FacialExpression.FRIENDLY, "For your friend and ignore lists, it's quite simple really! Use your friend list to keep track of players who you like, and ignore those you don't!") + 60 -> setUnclosable(player, npcl(FacialExpression.FRIENDLY, "Greetings! I'd just like to briefly go over two topics with you: Prayer, and Friend's.")) + 62 -> setUnclosable(player, npcl(FacialExpression.FRIENDLY, "Prayers have all sorts of wonderful benefits! From boosting defence and damage, to protecting you from outside damage, to saving items on death!")) + 65 -> setUnclosable(player, npcl(FacialExpression.FRIENDLY, "For your friend and ignore lists, it's quite simple really! Use your friend list to keep track of players who you like, and ignore those you don't!")) } return true } @@ -35,8 +36,8 @@ class TutorialPrayerDialogue(player: Player? = null) : DialoguePlugin(player) { when(getAttribute(player, "tutorial:stage", 0)) { 60 -> when(stage++){ - 0 -> playerl(FacialExpression.FRIENDLY, "Alright, sounds fun!") - 1 -> npcl(FacialExpression.FRIENDLY, "Right, so first thing: Prayer. Prayer is trained by offering bones to the gods, and can grant you many boons!") + 0 -> setUnclosable(player, playerl(FacialExpression.FRIENDLY, "Alright, sounds fun!")) + 1 -> setUnclosable(player, npcl(FacialExpression.FRIENDLY, "Right, so first thing: Prayer. Prayer is trained by offering bones to the gods, and can grant you many boons!")) 2 -> { end() setAttribute(player, "tutorial:stage", 61) @@ -45,8 +46,8 @@ class TutorialPrayerDialogue(player: Player? = null) : DialoguePlugin(player) { } 62 -> when(stage++){ - 0 -> playerl(FacialExpression.AMAZED, "Very cool!") - 1 -> npcl(FacialExpression.FRIENDLY, "Next up, let's talk about friends.") + 0 -> setUnclosable(player, playerl(FacialExpression.AMAZED, "Very cool!")) + 1 -> setUnclosable(player, npcl(FacialExpression.FRIENDLY, "Next up, let's talk about friends.")) 2 -> { end() setAttribute(player, "tutorial:stage", 63) diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialQuestGuideDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialQuestGuideDialogue.kt index 015928854..716bc2cf4 100644 --- a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialQuestGuideDialogue.kt +++ b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialQuestGuideDialogue.kt @@ -12,6 +12,7 @@ import core.plugin.Initializable import org.rs09.consts.Components import org.rs09.consts.NPCs import content.region.misc.tutisland.handlers.TutorialStage +import core.game.component.Component.setUnclosable /** * Handles the quest guide's dialogue @@ -27,7 +28,7 @@ class TutorialQuestGuideDialogue(player: Player? = null) : DialoguePlugin(player npc = args[0] as NPC when(getAttribute(player, "tutorial:stage", 0)) { - 27 -> Component.setUnclosable( + 27 -> setUnclosable( player, interpreter.sendDialogues( npc, @@ -37,7 +38,7 @@ class TutorialQuestGuideDialogue(player: Player? = null) : DialoguePlugin(player ) ) - 28 -> Component.setUnclosable( + 28 -> setUnclosable( player, interpreter.sendDialogues( npc, @@ -57,7 +58,7 @@ class TutorialQuestGuideDialogue(player: Player? = null) : DialoguePlugin(player when(getAttribute(player, "tutorial:stage", 0)) { 27 -> { - Component.setUnclosable( + setUnclosable( player, interpreter.sendPlaneMessageWithBlueTitle( "Open the Quest Journal.", @@ -72,7 +73,7 @@ class TutorialQuestGuideDialogue(player: Player? = null) : DialoguePlugin(player } 28 -> when(stage) { - 0 -> Component.setUnclosable( + 0 -> setUnclosable( player, interpreter.sendDialogues( npc, @@ -83,7 +84,7 @@ class TutorialQuestGuideDialogue(player: Player? = null) : DialoguePlugin(player "to begin." ) ).also { stage++ } - 1 -> Component.setUnclosable( + 1 -> setUnclosable( player, interpreter.sendDialogues( npc, @@ -93,7 +94,7 @@ class TutorialQuestGuideDialogue(player: Player? = null) : DialoguePlugin(player "see marking my house." ) ).also { stage++ } - 2 -> Component.setUnclosable( + 2 -> setUnclosable( player, interpreter.sendDialogues( npc, @@ -118,5 +119,4 @@ class TutorialQuestGuideDialogue(player: Player? = null) : DialoguePlugin(player override fun getIds(): IntArray { return intArrayOf(NPCs.QUEST_GUIDE_949) } - } diff --git a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialRSGuideDialogue.kt b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialRSGuideDialogue.kt index 4d7653695..b3611f89e 100644 --- a/Server/src/main/content/region/misc/tutisland/dialogue/TutorialRSGuideDialogue.kt +++ b/Server/src/main/content/region/misc/tutisland/dialogue/TutorialRSGuideDialogue.kt @@ -1,7 +1,6 @@ package content.region.misc.tutisland.dialogue import core.api.setAttribute -import core.game.component.Component import core.game.dialogue.DialoguePlugin import core.game.dialogue.FacialExpression import core.game.node.entity.npc.NPC @@ -9,10 +8,12 @@ import core.game.node.entity.player.Player import core.plugin.Initializable import org.rs09.consts.NPCs import content.region.misc.tutisland.handlers.TutorialStage +import core.game.component.Component.setUnclosable /** - * Handles the RuneSccape guide's dialogue + * Handles the 2009scape guide's dialogue * @author Ceikry + * @author Player Name */ @Initializable class TutorialRSGuideDialogue(player: Player? = null) : DialoguePlugin(player) { @@ -23,26 +24,12 @@ class TutorialRSGuideDialogue(player: Player? = null) : DialoguePlugin(player) { override fun open(vararg args: Any?): Boolean { npc = args[0] as NPC val tutStage = player?.getAttribute("tutorial:stage", 0) ?: 0 - if(tutStage < 2) { - end() - player.dialogueInterpreter.sendDialogues(npc,FacialExpression.HALF_GUILTY,"Greetings! Please follow the onscreen, instructions!") - return false - } else { - Component.setUnclosable( - player, - interpreter.sendDialogues( - npc, - FacialExpression.HALF_GUILTY, - "Greetings! Please follow the onscreen", - "instructions!" - ) - ) - } - - if(tutStage == 2) - { + if (tutStage < 2) { + setUnclosable(player, player.dialogueInterpreter.sendDialogues(npc,FacialExpression.HALF_GUILTY,"Greetings! Please follow the onscreen instructions!")) + stage = 99 + } else if (tutStage == 2) { player.lock() - Component.setUnclosable( + setUnclosable( player, interpreter.sendDialogues( npc, @@ -52,26 +39,17 @@ class TutorialRSGuideDialogue(player: Player? = null) : DialoguePlugin(player) { ) ) stage = 0 - return true - } - else - { - Component.setUnclosable( - player, - interpreter.sendDialogues( - npc, - FacialExpression.HALF_GUILTY, - "Please follow the onscreen instructions!" - ) - ) - return false + } else { + setUnclosable(player, player.dialogueInterpreter.sendDialogues(npc,FacialExpression.HALF_GUILTY,"Please follow the onscreen instructions!")) + stage = 99 } + return true } override fun handle(interfaceId: Int, buttonId: Int): Boolean { when(stage) { - 0 -> Component.setUnclosable( + 0 -> setUnclosable( player, interpreter.sendDialogues( npc, @@ -81,7 +59,7 @@ class TutorialRSGuideDialogue(player: Player? = null) : DialoguePlugin(player) { ) ).also { stage++ } - 1 -> Component.setUnclosable( + 1 -> setUnclosable( player, interpreter.sendDialogues( npc, @@ -92,7 +70,7 @@ class TutorialRSGuideDialogue(player: Player? = null) : DialoguePlugin(player) { ) ).also { stage++ } - 2 -> Component.setUnclosable( + 2 -> setUnclosable( player, interpreter.sendDialogues( npc, @@ -104,7 +82,7 @@ class TutorialRSGuideDialogue(player: Player? = null) : DialoguePlugin(player) { ) ).also { stage++ } - 3 -> Component.setUnclosable( + 3 -> setUnclosable( player, interpreter.sendDialogues( npc, @@ -114,7 +92,7 @@ class TutorialRSGuideDialogue(player: Player? = null) : DialoguePlugin(player) { ) ).also { stage++ } - 4 -> Component.setUnclosable( + 4 -> setUnclosable( player, interpreter.sendDialogues( npc, @@ -130,6 +108,11 @@ class TutorialRSGuideDialogue(player: Player? = null) : DialoguePlugin(player) { setAttribute(player, "tutorial:stage", 3) TutorialStage.load(player, 3) } + 99 -> { + end() + val tutStage = player?.getAttribute("tutorial:stage", 0) ?: 0 + TutorialStage.load(player, tutStage) + } } return true } diff --git a/Server/src/main/content/region/misc/tutisland/handlers/TutorialDialogs.kt b/Server/src/main/content/region/misc/tutisland/handlers/TutorialDialogs.kt new file mode 100644 index 000000000..78a493dcd --- /dev/null +++ b/Server/src/main/content/region/misc/tutisland/handlers/TutorialDialogs.kt @@ -0,0 +1,554 @@ +package content.region.misc.tutisland.handlers + +import core.api.getAttribute +import core.api.inInventory +import core.game.component.Component.setUnclosable +import core.game.event.DialogueCloseEvent +import core.game.event.EventHook +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import core.game.world.GameWorld.settings +import org.rs09.consts.Items +import kotlin.also + +fun sendStageDialog(player: Player, stage: Int) { + val message = when (stage) { + 0 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Getting started", + "To start the tutorial use your left mouse button to click on the", + "" + settings!!.name + " Guide in this room. He is indicated by a flashing", + "yellow arrow above his head. If you can't see him, use your", + "keyboard's arrow keys to rotate the view." + ) + 1 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "", + "", + "Game options", + "Please click on the flashing spanner icon found at the bottom", + "right of your screen. This will display your game options." + ) + 2 -> player.dialogueInterpreter.sendScrollMessageWithBlueTitle( + "Game Options", + "In the interface, you can now see a variety of options such as", + "screen brightness, sound and music volume and whether you", + "want to accept aid from other player's or not. Don't worry", + "about these too much for now; they will become easier as you", + "explore the game. Talk to the " + settings!!.name + " Guide to continue." + ) + 3 -> player.dialogueInterpreter.sendScrollMessageWithBlueTitle( + "Interacting with scenery", + "You can interact with many items of scenery by simply clicking", + "on them. Right clicking will also give more options. Feel free to", + "try it with the things in this room, then click on the door", + "indicated with the yellow arrow to go though to the next", + "instructor." + ) + 4 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Moving around", + "Follow the path to find the next instructor. Clicking on the", + "ground will walk you to that point. You can also navigate by", + "clicking on the minimap in the top-right corner of your screen.", + "Talk to Survival Expert by the pond to continue the tutorial." + ) + 5 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Viewing the items that you were given.", + "Click on the flashing backpack icon to the right-hand side of", + "the main window to view your inventory. Your inventory is a list", + "of everything you have in your backpack.", + "" + ) + 6 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Cut down a tree", + "You can click on the backpack icon at any time to view the", + "items that you currently have in your inventory. You will see", + "that you now have an axe in your inventory. Use this to get", + "some logs by clicking on one of the trees in the area." + ) + 7 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Please wait.", + "", + "Your character is now attempting to cut down the tree. Sit back", + "for a moment while " + (if (player.appearance.isMale) "he" else "she") + " does all the hard work.", + "" + ) + 8 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Making a fire", + "Well done! You managed to cut some logs from the tree! Next,", + "use the tinderbox in your inventory to light the logs.", + "First click on the tinderbox to 'use' it.", + "Then click on the logs in your inventory to light them." + ) + 9 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Please wait.", + "", + "Your character is now attempting to light the fire.", + "This should only take a few seconds.", + "" + ) + 10 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "You gained some experience.", + "", + "Click on the flashing bar graph icon near the inventory button", + "to see your skill state.", + "" + ) + 11 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Your skill stats", + "Here you will see how good your skills are. As you move your", + "mouse over any of the icons in this tab, the small yellow popup", + "box will show you the exact amount of experience you have", + "and how much is needed to get to the next level. Speak to the survival guide." + ) + 12 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Catch some shrimp", + "Click on the bubbling fishing spot, indicated by the flashing", + "arrow. Remember, you can check your inventory by clicking the", + "backpack icon.", + "" + ) + 13 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Please wait.", + "", + "This should only take a few seconds.", + "As you gain Fishing experience you'll find that there are many", + "types of fish and many ways to catch them." + ) + 14 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Cooking your shrimp", + "Now you have caught some shrimp, let's cook it. First light a", + "fire: chop down a tree and then use the tinderbox on the logs.", + "If you've lost your axe or tinderbox Brynna will give you", + "another." + ).also { + if (!inInventory(player, Items.RAW_SHRIMPS_317, 1)) { + setUnclosable( + player, + player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Cooking your shrimp", + "Now right click on the shrimp and select the use option. Next,", + "left click on the fire you just lit. If while doing this you look in", + "the top left of the screen, you will see the instruction that", + "you're giving your character." + ) + ) + } + } + 15 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Burning your shrimp", + "You have just burnt your first shrimp. This is normal. As you", + "get more experience in Cooking you will burn stuff less often.", + "Let's try cooking without burning it this time. First catch some", + "more shrimp, then use them on a fire." + ) + 16 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Well done, you've just cooked your first " + settings!!.name + " meal.", + "If you'd like a recap on anything you've learnt so far, speak to", + "the Survival Expert. You can now move on to the next", + "instructor. Click on the gate shown and follow the path.", + "Remember, you can move the camera with the arrow keys." + ) + 17 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Find your next instructor", + "Follow the path until you get to the door with the yellow arrow", + "above it. Click on the door to open it. Notice the mini map in the", + "top right; this shows a top down view of the area around you.", + "This can also be used for navigation." + ) + 18 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Find your next instructor", + "Talk to the chef indicated. He will teach you the more advanced", + "aspects of Cooking such as combining ingredients. He will also", + "teach you about your Music Player.", + "" + ) + 19 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Making dough", + "This is the base for many of the meals. To make dough we must", + "mix flour and water. First, right click the bucket of water and", + "select use, then left click on the pot of flour.", + "" + ) + 20 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Cooking dough", + "Now you have made dough, you can cook it. To cook the dough,", + "use it with the range shown by the arrow. If you lose your", + "dough, talk to Lev - he will give you more ingredients.", + "" + ) + 21 -> player.dialogueInterpreter.sendScrollMessageWithBlueTitle( + "Cooking dough", + "Well done! Your first loaf of bread. As you gain experience in", + "Cooking, you will be able to make other things like pies, cakes", + "and even kebabs. Now you've got the hang of cooking, let's", + "move on. Click on the flashing icon in the bottom right to see", + "the flashing icon in the bottom right to see the Music Player." + ) + 22 -> player.dialogueInterpreter.sendScrollMessageWithBlueTitle( + "The Music Player", + "From this interface you can control the music that is played.", + "As you explore the world and complete quests, more of the", + "tunes will become unlocked. Once you've examined this menu,", + "use the next door to continue. If you need a recap on anything", + "you've learnt so far, speak to the Master Chef." + ) + 23 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Emotes", + "", + "Now how about showing some feelings? You will see a flashing", + "icon in the shape of a person. Click on that to access your", + "emotes." + ) + 24 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Emotes", + "For those situations where words don't quite describe how you feel try", + "an emote. Go ahead try one out! You might notice that some of the", + "emotes are grey and cannot be used now. Don't worry! As you", + "progress further into the game you'll gain access to all sorts of things." + ) + 25 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Running", + "", + "It's only a short distance to the next guide.", + "Why not try running there? To do this, click on the run icon", + "next to the minimap." + ) + 26 -> player.dialogueInterpreter.sendScrollMessageWithBlueTitle( + "Run to the next guide", + "Now that you have the run button turned on, follow the path", + "until you come to the end. You may notice that the number on", + "the button goes down. This is your run energy. If your run", + "energy reaches zero, you'll stop running. Click on the door to", + "pass through it." + ) + 27 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Talk with the Quest Guide.", + "", + "He will tell you all about quests.", + "", + "" + ) + 28 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Your Quest Journal", + "", + "This is your Quest Journal, a list of all the quests in the game.", + "Talk to the Quest Guide again for an explanation.", + "" + ) + 29 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "", + "Moving on", + "It's time to enter some caves. Click on the ladder to go down to", + "the next area.", + "" + ) + 30 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Mining and Smithing", + "", + "Next let's get you a weapon, or more to the point, you can", + "make your first weapon yourself. Don't panic, the Mining", + "Instructor will help you. Talk to him and he'll tell you all about it." + ) + 31 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Prospecting", + "To prospect a mineable rock, just right click it and select the", + "'prospect rock' option. This will tell you the type of ore you can", + "mine from it. Try it now on one of the rocks indicated.", + "" + ) + 32 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Please wait.", + "", + "Your character is now attempting to prospect the rock. This", + "should only take a few seconds.", + "" + ) + 33 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "It's tin.", + "", + "So now you know there's tin in the grey rocks, try prospecting the", + "brown ones next.", + "" + ) + 34 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "It's copper.", + "", + "Talk to the Mining Instructor to find out about these types of", + "ore and how you can mine them.", + "He'll even give you the required tools.", + ) + 35 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Mining", + "", + "It's quite simple really. All you need to do is right click on the", + "rock and select 'mine' You can only mine when you have a", + "pickaxe. So give it a try: first mine one tin ore.", + ) + 36 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Please wait.", + "", + "Your character is now attempting to mine the rock.", + "This should only take a few seconds.", + "" + ) + 37 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Mining", + "", + "Now you have some tin ore you just need some copper ore,", + "then you'll have all you need to create a bronze bar. As you", + "did before right click on the copper rock and select 'mine'." + ) + 38 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Smelting", + "You should now have both some copper and tin ore. So let's", + "smelt them to make a bronze bar. To do this, right click on", + "either tin or copper ore and select use then left click on the", + "furnace. Try it now." + ) + 39, 40 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "You've made a bronze bar!", + "", + "Speak to the Mining Instructor and he'll show you how to make", + "it into a weapon.", + "" + ) + 41 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Smithing a dagger", + "To smith you'll need a hammer - like the one you were given by", + "Dezzick - access to an anvil like the one with the arrow over it", + "and enough metal bars to make what you are trying to smith.", + "To start the process, use the bar on one of the anvils." + ) + 42 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Smithing a dagger.", + "Now you have the Smithing menu open, you will see a list of all", + "the things you can make. Only the dagger can be made at your", + "skill level; this is shown by the white text under it. You'll need", + "to select the dagger to continue." + ) + 43 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "You've finished in this area.", + "", + "So let's move on. Go through the gates shown by the arrow.", + "Remember, you may need to move the camera to see your", + "surroundings. Speak to the guide for a recap at any time.", + ) + 44 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Combat", + "", + "In this area you will find out about combat with swords and", + "bows. Speak to the guide and he will tell you all about it.", + "" + ) + 45 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Wielding weapons", + "", + "You now have access to a new interface. Click on the flashing", + "icon of a man, the one to the right of your backpack icon.", + "" + ) + 46 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "This is your worn inventory.", + "From here you can see what items you have equipped. Let's", + "get one of those slots filled, go back to your inventory and", + "right click your dagger, select wield from the menu.", + "" + ) + 47 -> player.dialogueInterpreter.sendScrollMessageWithBlueTitle( + "You're now holding your dagger.", + "Clothes, armour, weapons and many other items are equipped", + "like this. You can unequip items by clicking on the item in the", + "worn equipment. You can close this window by clicking on the", + "small 'x' in the top-right hand corner. Speak to the Combat", + "Instructor." + ) + 48 -> player.dialogueInterpreter.sendScrollMessageWithBlueTitle( + "Unequipping items.", + "In your worn inventory panel, right click on the dagger and", + "select the remove option from the drop down list. After you've", + "unequipped the dagger, wield the sword and shield. As you", + "pass the mouse over an item you will see its name appear at", + "the top left of the screen." + ) + 49 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Combat interface.", + "", + "Click on the flashing crossed swords icon to see the combat", + "interface.", + "" + ) + 50 -> player.dialogueInterpreter.sendScrollMessageWithBlueTitle( + "This is your combat interface.", + "From this interface you can select the type of attack your", + "character will use. Different monsters have different", + "weaknesses. If you hover your mouse over the buttons, you", + "will see the type of XP you will receive when using each type of", + "attack. Now you have the tools needed for battle why not slay", + "some rats. Click on the gates indicated to continue." + ) + 51 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Attacking", + "To attack the rat, click it and select the attack option. You", + "will then walk over to it and start hitting it.", + "", + "" + ) + 52 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Sit back and watch.", + "While you are fighting you will see a bar over your head. The", + "bar shows how much health you have left. Your opponent will", + "have one too. You will continue to attack the rat until it's dead", + "or you do something else." + ) + 53 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Well done, you've made your first kill!", + "", + "Pass through the gate and talk to the Combat Instructor; he", + "will give you your next task.", + "" + ) + 54 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Rat ranging", + "Now you have a bow and some arrows. Before you can use", + "them you'll need to equip them. Once equipped with the", + "ranging gear try killing another rat. Remember: to attack, right", + "click on the monster and select attack." + ) + 55 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Moving on.", + "You have completed the tasks here. To move on, click on the", + "ladder shown. If you need to go over any of what you learnt", + "here, just talk to the Combat Instructor and he'll tell you what", + "he can." + ) + 56 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Banking.", + "Follow the path and you will come to the front of a building.", + "This is the 'Bank of " + settings!!.name + "' where you can store all your", + "most valued items. To open your bank box just right click on an", + "open booth indicated and select 'use'." + ) + 57 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "This is your bank box.", + "You can store stuff here for safekeeping. If you die, anything", + "in your bank will be saved. To deposit something, right click it", + "and select 'Deposit-1'. Once you've had a good look, close the", + "window and move on through the door indicated." + ) + 58 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Financial advice", + "", + "The guide here will tell you all about making cash. Just click on", + "him to hear what he's got to say.", + "" + ) + 59 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "", + "", + "Continue through the next door.", + "", + "" + ) + 60 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Prayer", + "Follow the path to the chapel and enter it.", + "Once inside talk to the monk. He'll tell you all about the Prayer", + "skill.", + "" + ) + 61 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Your Prayer List", + "", + "Click on the flashing icon to open the Prayer List.", + "", + "" + ) + 62 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "", + "Your Prayer List", + "", + "Talk with Brother Brace and he'll tell you all about prayers.", + "" + ) + 63 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "", + "Friends list", + "You should now see another new icon. Click on the flashing", + "smiling face to open your Friend List.", + "" + ) + 64 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "This is your Friends List.", + "", + "This will be explained by Brother Brace shortly, but first click", + "on the other flashing face in the interface.", + "" + ) + 65 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "This is your Ignore List.", + "The two lists, Friends and Ignore - can be very helpful for", + "keeping track of when your friends are online or for blocking", + "messages from people you simply don't like. Speak with", + "Brother Brace and he will tell you more." + ) + 66 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "", + "Your final instructor!", + "You're almost finished on tutorial island. Pass through the", + "door to find the path leading to your final instructor.", + "" + ) + 67 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Your final instructor!", + "Just follow the path to the Wizard's house, where you will be", + "shown how to cast spells. Just talk with the mage indicated to", + "find out more.", + "" + ) + 68 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "Open up your final tab.", + "", + "Open up the Magic Spellbook tab by clicking on the flashing", + "icon next to the Prayer List tab you just learned about.", + "" + ) + 69 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "", + "This is your spell list.", + "", + "Ask the mage about it.", + "" + ) + 70 -> player.dialogueInterpreter.sendScrollMessageWithBlueTitle( + "Cast Wind Strike at a chicken.", + "Now you have the runes you should see the Wind Strike icon at the", + "top-left of your spellbook, second in from the left. Walk over", + "to the caged chickens, click the Wind Strike icon and then", + "select one of the chickens to cast it on. It may take several", + "tries." + ) + 71 -> player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( + "You have almost completed the tutorial!", + "", + "All you need to do now is teleport to the mainland. Just speak", + "with Terrova and he'll tell you how to do that.", + "" + ) + else -> null + } + setUnclosable(player, message ?: return) +} + +fun sendStageDialog(entity: Entity) { + val player = entity as Player + val stage = getAttribute(player, "tutorial:stage", 0) + sendStageDialog(player, stage) +} + +// Because we don't have proper interface stacking, the above unclosable dialogs can still be closed, if a new dialog +// opens up over it and that dialog *is* closable. This is the case for npc dialogs and the smelting interface. Fake the +// authentic behavior by making sure we reopen our unclosable dialog when this happens. +object TutorialDialogPreserver : EventHook { + override fun process(entity: Entity, event: DialogueCloseEvent) { + sendStageDialog(entity) + } +} diff --git a/Server/src/main/content/region/misc/tutisland/handlers/TutorialEventReceivers.kt b/Server/src/main/content/region/misc/tutisland/handlers/TutorialEventReceivers.kt index 655c16d1c..c65191bb1 100644 --- a/Server/src/main/content/region/misc/tutisland/handlers/TutorialEventReceivers.kt +++ b/Server/src/main/content/region/misc/tutisland/handlers/TutorialEventReceivers.kt @@ -7,13 +7,30 @@ import core.game.node.entity.player.Player import content.global.skill.fishing.FishingSpot import content.global.skill.gather.mining.MiningNode import content.global.skill.gather.woodcutting.WoodcuttingNode +import core.api.animate +import core.api.delayScript +import core.api.forceWalk +import core.api.inInventory +import core.api.lock +import core.api.lockInteractions +import core.api.playAudio +import core.api.queueScript +import core.api.replaceSlot +import core.api.stopExecuting import core.game.event.* +import core.game.interaction.QueueStrength +import core.game.node.entity.impl.Animator +import core.game.node.item.Item +import core.game.world.map.Location +import core.game.world.update.flag.context.Animation import org.rs09.consts.Items import org.rs09.consts.NPCs +import org.rs09.consts.Scenery +import org.rs09.consts.Sounds /** * Event receivers for tutorial island - * @author Ceikry + * @author Ceikry, Player Name */ object TutorialButtonReceiver : EventHook { @@ -77,7 +94,7 @@ object TutorialButtonReceiver : EventHook } //Open equipment tab SD:548,42 HD:746,45 - 45 -> if((event.iface == 548 && event.buttonId == 42) || (event.iface == 746 && event.buttonId == 45)){ + 45, 46 -> if((event.iface == 548 && event.buttonId == 42) || (event.iface == 746 && event.buttonId == 45)){ setAttribute(entity, "tutorial:stage", 46) TutorialStage.load(entity, 46) } @@ -136,18 +153,6 @@ object TutorialInteractionReceiver : EventHook TutorialStage.load(entity, 13) } - //Prospect rock - Tin - 31 -> if(MiningNode.forId(event.target.id)?.identifier?.equals(2.toByte()) == true && event.option == "prospect"){ - setAttribute(entity, "tutorial:stage", 32) - TutorialStage.load(entity, 32) - } - - //Prospect rock- Copper - 33 -> if(MiningNode.forId(event.target.id)?.identifier?.equals(1.toByte()) == true && event.option == "prospect"){ - setAttribute(entity, "tutorial:stage", 34) - TutorialStage.load(entity, 34) - } - //Mine rock - Tin 35 -> if(MiningNode.forId(event.target.id)?.identifier?.equals(2.toByte()) == true && event.option == "mine"){ setAttribute(entity, "tutorial:stage", 36) @@ -211,30 +216,12 @@ object TutorialResourceReceiver : EventHook TutorialStage.load(entity, 14) } - //Cook a shrimp - 14,15 -> if(event.itemId == Items.BURNT_SHRIMP_7954) - { - setAttribute(entity, "tutorial:stage", 15) - TutorialStage.load(entity, 15) - } - else if(event.itemId == Items.SHRIMPS_315) - { - setAttribute(entity, "tutorial:stage", 16) - TutorialStage.load(entity, 16) - } - //Make some bread dough 19 -> if(event.itemId == Items.BREAD_DOUGH_2307) { setAttribute(entity, "tutorial:stage", 20) TutorialStage.load(entity, 20) } - //Bake some bread - 20 -> if(event.itemId == Items.BREAD_2309 || event.itemId == Items.BURNT_BREAD_2311) { - setAttribute(entity, "tutorial:stage", 21) - TutorialStage.load(entity, 21) - } - //Mine some tin ore 36 -> if(event.itemId == Items.TIN_ORE_438){ setAttribute(entity, "tutorial:stage", 37) diff --git a/Server/src/main/content/region/misc/tutisland/handlers/TutorialFurnaceListener.kt b/Server/src/main/content/region/misc/tutisland/handlers/TutorialFurnaceListener.kt deleted file mode 100644 index b53335a9d..000000000 --- a/Server/src/main/content/region/misc/tutisland/handlers/TutorialFurnaceListener.kt +++ /dev/null @@ -1,51 +0,0 @@ -package content.region.misc.tutisland.handlers - -import core.api.* -import core.game.event.ResourceProducedEvent -import core.game.node.entity.skill.Skills -import content.global.skill.smithing.smelting.Bar -import core.game.system.task.Pulse -import core.game.world.update.flag.context.Animation -import org.rs09.consts.Items -import org.rs09.consts.Scenery -import core.game.interaction.IntType -import core.game.interaction.InteractionListener - -/** - * Listener for tutorial island furnace - * @author Byte - */ -class TutorialFurnaceListener : InteractionListener { - - companion object { - private val ANIMATION = Animation(833) - - private val ORES = intArrayOf( - Items.TIN_ORE_438, - Items.COPPER_ORE_436 - ) - } - - override fun defineListeners() { - onUseWith(IntType.SCENERY, ORES, Scenery.FURNACE_3044) { player, _, _ -> - if (!inInventory(player, Items.TIN_ORE_438) || !inInventory(player, Items.COPPER_ORE_436)) { - return@onUseWith true - } - - animate(player, ANIMATION) - submitIndividualPulse(player, object: Pulse(2) { - override fun pulse(): Boolean { - if (removeItem(player, Items.TIN_ORE_438) && removeItem(player, Items.COPPER_ORE_436)) { - addItem(player, Items.BRONZE_BAR_2349) - rewardXP(player, Skills.SMITHING, Bar.BRONZE.experience) - player.dispatch(ResourceProducedEvent(Items.BRONZE_BAR_2349, 1, player, Items.COPPER_ORE_436)) - return true - } - return false - } - }) - - return@onUseWith true - } - } -} diff --git a/Server/src/main/content/region/misc/tutisland/handlers/TutorialListeners.kt b/Server/src/main/content/region/misc/tutisland/handlers/TutorialListeners.kt index c7dd26f50..5a143acfb 100644 --- a/Server/src/main/content/region/misc/tutisland/handlers/TutorialListeners.kt +++ b/Server/src/main/content/region/misc/tutisland/handlers/TutorialListeners.kt @@ -1,13 +1,25 @@ package content.region.misc.tutisland.handlers +import content.global.skill.smithing.smelting.Bar +import content.region.misc.tutisland.dialogue.RatPenDialogue import core.api.* +import core.game.event.ResourceProducedEvent import core.game.node.scenery.Scenery import core.game.system.task.Pulse import core.game.world.map.Location import org.rs09.consts.NPCs import core.game.interaction.InteractionListener import core.game.interaction.IntType +import core.game.interaction.QueueStrength +import core.game.node.entity.impl.Animator +import core.game.node.entity.npc.NPC +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item import core.game.world.repository.Repository +import core.game.world.update.flag.context.Animation +import org.rs09.consts.Items +import org.rs09.consts.Sounds /** * Handles tutorial-specific node interactions @@ -16,10 +28,13 @@ import core.game.world.repository.Repository class TutorialListeners : InteractionListener { val GUIDE_HOUSE_DOOR = 3014 val COOKS_DOOR = 3017 + val RANGE = 3039 val COOKS_EXIT = 3018 val QUEST_ENTER = 3019 val QUEST_LADDER = 3029 val QUEST_EXIT_LADDER = 3028 + val TIN_ROCK = 3043 + val COPPER_ROCK = 3042 val COMBAT_EXIT = 3030 val BANK_EXIT = 3024 val FINANCE_EXIT = 3025 @@ -27,6 +42,7 @@ class TutorialListeners : InteractionListener { val FIRST_GATE = intArrayOf(3015,3016) val COMBAT_GATES = intArrayOf(3020,3021) val RAT_GATES = intArrayOf(3022, 3023) + val FURNACE = 3044 override fun defineListeners() { on(GUIDE_HOUSE_DOOR, IntType.SCENERY, "open"){ player, door -> @@ -60,6 +76,36 @@ class TutorialListeners : InteractionListener { return@on true } + fun cookBread(player: Player, dough: Item): Boolean { + if (getAttribute(player, "tutorial:stage", 0) < 20) { + return true + } + // Need to reinvent the wheel of cooking. Yes, I do. On tutorial island, we don't want the default stuff like asking the player what dough they want to make. + queueScript(player, 0, QueueStrength.WEAK) { stage -> + if (stage == 0) { + val RANGE_ANIMATION = Animation(883, Animator.Priority.HIGH) + lock(player, RANGE_ANIMATION.duration) + lockInteractions(player, RANGE_ANIMATION.duration) + animate(player, RANGE_ANIMATION) + playAudio(player, Sounds.FRY_2577) + return@queueScript delayScript(player, RANGE_ANIMATION.duration) + } else { + replaceSlot(player, dough.slot, Item(Items.BREAD_2309), dough) + setAttribute(player, "tutorial:stage", 21) + TutorialStage.load(player, 21) + return@queueScript stopExecuting(player) + } + } + return true + } + on(RANGE, IntType.SCENERY, "use") { player, _ -> + val dough = player.inventory.get(Item(Items.BREAD_DOUGH_2307)) ?: return@on true + return@on cookBread(player, dough) + } + onUseWith(IntType.SCENERY, Items.BREAD_DOUGH_2307, RANGE) { player, dough, _ -> + return@onUseWith cookBread(player, dough as Item) + } + on(COOKS_EXIT, IntType.SCENERY, "open"){ player, door -> if(getAttribute(player, "tutorial:stage", 0) != 22) return@on true @@ -105,23 +151,41 @@ class TutorialListeners : InteractionListener { return@on true } - on(COMBAT_GATES, IntType.SCENERY, "open"){ player, gate -> - if(getAttribute(player, "tutorial:stage", 0) != 43) + on(TIN_ROCK, IntType.SCENERY, "prospect") { player, _ -> + if (getAttribute(player, "tutorial:stage", 0) != 31) { return@on true + } + setAttribute(player, "tutorial:stage", 32) + TutorialStage.load(player, 32) + return@on true + } + on(COPPER_ROCK, IntType.SCENERY, "prospect") { player, _ -> + if (getAttribute(player, "tutorial:stage", 0) != 33) { + return@on true + } + setAttribute(player, "tutorial:stage", 34) + TutorialStage.load(player, 34) + return@on true + } - setAttribute(player, "tutorial:stage", 44) - TutorialStage.load(player, 44) + on(COMBAT_GATES, IntType.SCENERY, "open"){ player, gate -> + if (getAttribute(player, "tutorial:stage", 0) < 43) { + return@on true + } + if (getAttribute(player, "tutorial:stage", 0) == 43) { + setAttribute(player, "tutorial:stage", 44) + TutorialStage.load(player, 44) + } core.game.global.action.DoorActionHandler.handleAutowalkDoor(player, gate as Scenery) } on(RAT_GATES, IntType.SCENERY, "open") { player, gate -> val stage = getAttribute(player, "tutorial:stage", 0) - if(stage !in 50..53){ - player.dialogueInterpreter.sendDialogues(NPCs.COMBAT_INSTRUCTOR_944, core.game.dialogue.FacialExpression.ANGRY, "Oi, get away from there!","Don't enter my rat pen unless I say so!") + if (stage !in 50..53) { + openDialogue(player, RatPenDialogue(), NPC(NPCs.COMBAT_INSTRUCTOR_944)) return@on true } - - if(stage == 50) { + if (stage == 50) { setAttribute(player, "tutorial:stage", 51) TutorialStage.load(player, 51) } @@ -130,11 +194,14 @@ class TutorialListeners : InteractionListener { } on(COMBAT_EXIT, IntType.SCENERY, "climb-up") { player, ladder -> - if(getAttribute(player, "tutorial:stage", 0) != 55) + val stage = getAttribute(player, "tutorial:stage", 0) + if (stage < 55) { return@on true - - setAttribute(player, "tutorial:stage", 56) - TutorialStage.load(player, 56) + } + if (stage == 55) { + setAttribute(player, "tutorial:stage", 56) + TutorialStage.load(player, 56) + } core.game.global.action.ClimbActionHandler.climbLadder(player, ladder.asScenery(), "climb-up") } @@ -165,5 +232,28 @@ class TutorialListeners : InteractionListener { core.game.global.action.DoorActionHandler.handleAutowalkDoor(player, door as Scenery) } + fun smeltBronzeBar(player: Player): Boolean { + if (getAttribute(player, "tutorial:stage", 0) < 38) { + return true + } + if (!inInventory(player, Items.COPPER_ORE_436) || !inInventory(player, Items.TIN_ORE_438)) { + return true + } + animate(player, 833) + queueScript(player, 2, QueueStrength.WEAK) { + if (removeItem(player, Items.COPPER_ORE_436) && removeItem(player, Items.TIN_ORE_438)) { + addItem(player, Items.BRONZE_BAR_2349) + rewardXP(player, Skills.SMITHING, Bar.BRONZE.experience) + player.dispatch(ResourceProducedEvent(Items.BRONZE_BAR_2349, 1, player, Items.COPPER_ORE_436)) + TutorialStage.load(player, 39) + } + return@queueScript stopExecuting(player) + } + return true + } + on(FURNACE, IntType.SCENERY, "use") { player, _ -> smeltBronzeBar(player) } + for (item in arrayOf(Items.COPPER_ORE_436, Items.TIN_ORE_438)) { + onUseWith(IntType.SCENERY, item, FURNACE) { player, _, _ -> smeltBronzeBar(player) } + } } -} \ No newline at end of file +} diff --git a/Server/src/main/content/region/misc/tutisland/handlers/TutorialStage.kt b/Server/src/main/content/region/misc/tutisland/handlers/TutorialStage.kt index 3ab07f9ac..50eeddb00 100644 --- a/Server/src/main/content/region/misc/tutisland/handlers/TutorialStage.kt +++ b/Server/src/main/content/region/misc/tutisland/handlers/TutorialStage.kt @@ -12,13 +12,13 @@ import core.game.world.map.Location import org.rs09.consts.Components import core.api.Event import core.game.world.GameWorld.Pulser -import core.game.world.GameWorld.settings import core.game.world.repository.Repository -import org.rs09.consts.Items +import org.rs09.consts.NPCs /** * Loads stage-relevant tutorial data * @author Ceikry + * @author Player Name */ object TutorialStage { /** @@ -26,28 +26,30 @@ object TutorialStage { * @param player the player to perform the actions on * @param stage the stage to load */ - fun load(player: Player, stage: Int, login: Boolean = false){ - if(login) - { + fun load(player: Player, stage: Int, login: Boolean = false) { + if (login) { player.hook(Event.ButtonClicked, TutorialButtonReceiver) player.hook(Event.Interacted, TutorialInteractionReceiver) player.hook(Event.ResourceProduced, TutorialResourceReceiver) player.hook(Event.UsedWith, TutorialUseWithReceiver) player.hook(Event.FireLit, TutorialFireReceiver) player.hook(Event.NPCKilled, TutorialKillReceiver) + player.hook(Event.DialogueClosed, TutorialDialogPreserver) openOverlay(player, Components.TUTORIAL_PROGRESS_371) player.packetDispatch.sendInterfaceConfig(371, 4, true) } - updateProgressBar(player) - when(stage) - { + when(stage) { 0 -> { lock(player, 10) teleport(player, Location.create(3094, 3107, 0)) hideTabs(player, login) CharacterDesign.open(player) + // We have two dialogs in this stage. This is awkward, but not a problem. + // The first dialog is impossible to close in any way, so we can send it here manually. + // The second dialog could be lost by e.g. talking to an npc, so this dialog gets implemented in + // TutorialDialogs.kt, which has the hook for restoring it if it does get lost. Component.setUnclosable( player, player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( @@ -59,380 +61,133 @@ object TutorialStage { ) ).also { runTask(player, 10) { - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Getting started", - "To start the tutorial use your left mouse button to click on the", - "" + settings!!.name + " Guide in this room. He is indicated by a flashing", - "yellow arrow above his head. If you can't see him, use your", - "keyboard's arrow keys to rotate the view." - ) - ) + sendStageDialog(player, stage) } } } - - 1 -> { hideTabs(player, login) player.interfaceManager.openTab(Component(Components.OPTIONS_261)) setVarbit(player, 3756, 12) removeHintIcon(player) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "", - "", - "Game options", - "Please click on the flashing spanner icon found at the bottom", - "right of your screen. This will display your game options." - ) - ) + sendStageDialog(player, stage) } - 2 -> { setVarbit(player, 3756, 0) hideTabs(player, login) - registerHintIcon(player, Repository.findNPC(945)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendScrollMessageWithBlueTitle( - "Game Options", - "In the interface, you can now see a variety of options such as", - "screen brightness, sound and music volume and whether you", - "want to accept aid from other player's or not. Don't worry", - "about these too much for now; they will become easier as you", - "explore the game. Talk to the " + settings!!.name + " Guide to continue." - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.RUNESCAPE_GUIDE_945)!!) + sendStageDialog(player, stage) } - 3 -> { hideTabs(player, login) removeHintIcon(player) registerHintIcon(player, Location.create(3098, 3107, 0), 125) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendScrollMessageWithBlueTitle( - "Interacting with scenery", - "You can interact with many items of scenery by simply clicking", - "on them. Right clicking will also give more options. Feel free to", - "try it with the things in this room, then click on the door", - "indicated with the yellow arrow to go though to the next", - "instructor." - ) - ) + sendStageDialog(player, stage) } - 4 -> { hideTabs(player, login) removeHintIcon(player) - registerHintIcon(player, Repository.findNPC(943)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Moving around", - "Follow the path to find the next instructor. Clicking on the", - "ground will walk you to that point. You can also navigate by", - "clicking on the minimap in the top-right corner of your screen.", - "Talk to Survival Expert by the pond to continue the tutorial." - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.SURVIVAL_EXPERT_943)!!) + sendStageDialog(player, stage) } - 5 -> { hideTabs(player, login) player.interfaceManager.openTab(Component(Components.INVENTORY_149)) setVarbit(player, 3756, 4) removeHintIcon(player) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Viewing the items that you were given.", - "Click on the flashing backpack icon to the right-hand side of", - "the main window to view your inventory. Your inventory is a list", - "of everything you have in your backpack.", - "" - ) - ) + sendStageDialog(player, stage) } - 6 -> { hideTabs(player, login) setVarbit(player, 3756, 4) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Cut down a tree", - "You can click on the backpack icon at any time to view the", - "items that you currently have in your inventory. You will see", - "that you now have an axe in your inventory. Use this to get", - "some logs by clicking on one of the trees in the area." - ) - ) + sendStageDialog(player, stage) } - 7 -> { hideTabs(player, login) removeHintIcon(player) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Please wait.", - "", - "Your character is now attempting to cut down the tree. Sit back", - "for a moment while " + (if (player.appearance.isMale) "he" else "she") + " does all the hard work.", - "" - ) - ) + sendStageDialog(player, stage) } - 8 -> { hideTabs(player, login) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Making a fire", - "Well done! You managed to cut some logs from the tree! Next,", - "use the tinderbox in your inventory to light the logs.", - "First click on the tinderbox to 'use' it.", - "Then click on the logs in your inventory to light them." - ) - ) + sendStageDialog(player, stage) } - 9 -> { hideTabs(player, login) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Please wait.", - "", - "Your character is now attempting to light the fire.", - "This should only take a few seconds.", - "" - ) - ) + sendStageDialog(player, stage) } - 10 -> { hideTabs(player, login) player.interfaceManager.openTab(Component(Components.STATS_320)) setVarbit(player, 3756, 2) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "You gained some experience.", - "", - "Click on the flashing bar graph icon near the inventory button", - "to see your skill state.", - "" - ) - ) + sendStageDialog(player, stage) } - 11 -> { hideTabs(player, login) setVarbit(player, 3756, 2) - registerHintIcon(player, Repository.findNPC(943)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Your skill stats", - "Here you will see how good your skills are. As you move your", - "mouse over any of the icons in this tab, the small yellow popup", - "box will show you the exact amount of experience you have", - "and how much is needed to get to the next level. Speak to the survival guide." - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.SURVIVAL_EXPERT_943)!!) + sendStageDialog(player, stage) } - 12 -> { hideTabs(player, login) setVarp(player, 406, 2) removeHintIcon(player) - registerHintIcon(player, Repository.findNPC(952)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Catch some shrimp", - "Click on the bubbling fishing spot, indicated by the flashing", - "arrow. Remember, you can check your inventory by clicking the", - "backpack icon.", - "" - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.FISHING_SPOT_952)!!) + sendStageDialog(player, stage) } - 13 -> { hideTabs(player, login) removeHintIcon(player) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Please wait.", - "", - "This should only take a few seconds.", - "As you gain Fishing experience you'll find that there are many", - "types of fish and many ways to catch them." - ) - ) + sendStageDialog(player, stage) } - 14 -> { hideTabs(player, login) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Cooking your shrimp", - "Now you have caught some shrimp, let's cook it. First light a", - "fire: chop down a tree and then use the tinderbox on the logs.", - "If you've lost your axe or tinderbox Brynna will give you", - "another." - ).also { - if (!inInventory(player, Items.RAW_SHRIMPS_317, 1)) { - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Cooking your shrimp", - "Now right click on the shrimp and select the use option. Next,", - "left click on the fire you just lit. If while doing this you look in", - "the top left of the screen, you will see the instruction that", - "you're giving your character." - ) - ) - } - } - ) + sendStageDialog(player, stage) } - 15 -> { hideTabs(player, login) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Burning your shrimp", - "You have just burnt your first shrimp. This is normal. As you", - "get more experience in Cooking you will burn stuff less often.", - "Let's try cooking without burning it this time. First catch some", - "more shrimp, then use them on a fire." - ) - ) + sendStageDialog(player, stage) } - 16 -> { hideTabs(player, login) + removeHintIcon(player) registerHintIcon(player, Location.create(3089, 3091, 0), 75) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Well done, you've just cooked your first " + settings!!.name + " meal.", - "If you'd like a recap on anything you've learnt so far, speak to", - "the Survival Expert. You can now move on to the next", - "instructor. Click on the gate shown and follow the path.", - "Remember, you can move the camera with the arrow keys." - ) - ) + sendStageDialog(player, stage) } - 17 -> { hideTabs(player, login) removeHintIcon(player) - registerHintIcon(player, Location.create(3078, 3084, 0), 125) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Find your next instructor", - "Follow the path until you get to the door with the yellow arrow", - "above it. Click on the door to open it. Notice the mini map in the", - "top right; this shows a top down view of the area around you.", - "This can also be used for navigation." - ) - ) + registerHintIcon(player, Location.create(3079, 3084, 0), 125) + sendStageDialog(player, stage) } - 18 -> { hideTabs(player, login) removeHintIcon(player) - registerHintIcon(player, Repository.findNPC(942)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Find your next instructor", - "Talk to the chef indicated. He will teach you the more advanced", - "aspects of Cooking such as combining ingredients. He will also", - "teach you about your Music Player.", - "" - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.MASTER_CHEF_942)!!) + sendStageDialog(player, stage) } - 19 -> { hideTabs(player, login) removeHintIcon(player) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Making dough", - "This is the base for many of the meals. To make dough we must", - "mix flour and water. First, right click the bucket of water and", - "select use, then left click on the pot of flour.", - "" - ) - ) + sendStageDialog(player, stage) } - 20 -> { hideTabs(player, login) registerHintIcon(player, Location.create(3076, 3081, 0), 75) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Cooking dough", - "Now you have made dough, you can cook it. To cook the dough,", - "use it with the range shown by the arrow. If you lose your", - "dough, talk to Lev - he will give you more ingredients.", - "" - ) - ) + sendStageDialog(player, stage) } - 21 -> { hideTabs(player, login) removeHintIcon(player) player.interfaceManager.openTab(Component(Components.MUSIC_V3_187)) setVarbit(player, 3756, 14) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendScrollMessageWithBlueTitle( - "Cooking dough", - "Well done! Your first loaf of bread. As you gain experience in", - "Cooking, you will be able to make other things like pies, cakes", - "and even kebabs. Now you've got the hang of cooking, let's", - "move on. Click on the flashing icon in the bottom right to see", - "the flashing icon in the bottom right to see the Music Player." - ) - ) + sendStageDialog(player, stage) } - 22 -> { hideTabs(player, login) setVarbit(player, 3756, 0) - registerHintIcon(player, Location.create(3072, 3090, 0), 125) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendScrollMessageWithBlueTitle( - "The Music Player", - "From this interface you can control the music that is played.", - "As you explore the world and complete quests, more of the", - "tunes will become unlocked. Once you've examined this menu,", - "use the next door to continue. If you need a recap on anything", - "you've learnt so far, speak to the Master Chef." - ) - ) + registerHintIcon(player, Location.create(3073, 3090, 0), 125) + sendStageDialog(player, stage) } - 23 -> { hideTabs(player, login) setVarbit(player, 3756, 13) @@ -440,159 +195,59 @@ object TutorialStage { player.interfaceManager.openTab(Component(Components.EMOTES_464)) stopWalk(player) player.locks.lockMovement(100000) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Emotes", - "", - "Now how about showing some feelings? You will see a flashing", - "icon in the shape of a person. Click on that to access your", - "emotes." - ) - ) + sendStageDialog(player, stage) } - 24 -> { hideTabs(player, login) setVarbit(player, 3756, 0) player.locks.lockMovement(100000) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Emotes", - "For those situations where words don't quite describe how you feel try", - "an emote. Go ahead try one out! You might notice that some of the", - "emotes are grey and cannot be used now. Don't worry! As you", - "progress further into the game you'll gain access to all sorts of things." - ) - ) + sendStageDialog(player, stage) } - 25 -> { hideTabs(player, login) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Running", - "", - "It's only a short distance to the next guide.", - "Why not try running there? To do this, click on the run icon", - "next to the minimap." - ) - ) + sendStageDialog(player, stage) } - 26 -> { hideTabs(player, login) - registerHintIcon(player, Repository.findNPC(949)!!) + registerHintIcon(player, Repository.findNPC(NPCs.QUEST_GUIDE_949)!!) player.locks.unlockMovement() - Component.setUnclosable( - player, - player.dialogueInterpreter.sendScrollMessageWithBlueTitle( - "Run to the next guide", - "Now that you have the run button turned on, follow the path", - "until you come to the end. You may notice that the number on", - "the button goes down. This is your run energy. If your run", - "energy reaches zero, you'll stop running. Click on the door to", - "pass through it." - ) - ) + sendStageDialog(player, stage) } - 27 -> { hideTabs(player, login) removeHintIcon(player) - registerHintIcon(player, Repository.findNPC(949)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Talk with the Quest Guide.", - "", - "He will tell you all about quests.", - "", - "" - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.QUEST_GUIDE_949)!!) + sendStageDialog(player, stage) } - 28 -> { hideTabs(player, login) setVarbit(player, 3756, 0) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Your Quest Journal", - "", - "This is your Quest Journal, a list of all the quests in the game.", - "Talk to the Quest Guide again for an explanation.", - "" - ) - ) + sendStageDialog(player, stage) } - 29 -> { hideTabs(player, login) removeHintIcon(player) setVarbit(player, 3756, 0) registerHintIcon(player, Location.create(3088, 3119, 0), 15) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "", - "Moving on", - "It's time to enter some caves. Click on the ladder to go down to", - "the next area.", - "" - ) - ) + sendStageDialog(player, stage) } - 30 -> { hideTabs(player, login) removeHintIcon(player) setVarbit(player, 3756, 0) - registerHintIcon(player, Repository.findNPC(948)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Mining and Smithing", - "", - "Next let's get you a weapon, or more to the point, you can", - "make your first weapon yourself. Don't panic, the Mining", - "Instructor will help you. Talk to him and he'll tell you all about it." - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.MINING_INSTRUCTOR_948)!!) + sendStageDialog(player, stage) } - 31 -> { hideTabs(player, login) removeHintIcon(player) registerHintIcon(player, Location.create(3076, 9504, 0), 50) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Prospecting", - "To prospect a mineable rock, just right click it and select the", - "'prospect rock' option. This will tell you the type of ore you can", - " mine from it. Try it now on one of the rocks indicated.", - "" - ) - ) + sendStageDialog(player, stage) } - 32 -> { hideTabs(player, login) removeHintIcon(player) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Please wait.", - "", - "Your character is now attempting to prospect the rock. This", - "should only take a few seconds.", - "" - ) - ) + sendStageDialog(player, stage) Pulser.submit(object : Pulse(3) { override fun pulse(): Boolean { setAttribute(player, "tutorial:stage", 33) @@ -601,196 +256,79 @@ object TutorialStage { } }) } - 33 -> { hideTabs(player, login) removeHintIcon(player) registerHintIcon(player, Location.create(3086, 9501, 0), 50) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "It's tin.", - "", - "So now you know there's tin in the grey rocks, try prospecting the", - "brown ones next.", - "" - ) - ) + sendStageDialog(player, stage) } - 34 -> { hideTabs(player, login) removeHintIcon(player) - registerHintIcon(player, Repository.findNPC(948)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "It's copper.", - "", - "Talk to the Mining Instructor to find out about these types of", - "ore and how you can mine them.", - "He'll even give you the required tools.", - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.MINING_INSTRUCTOR_948)!!) + sendStageDialog(player, stage) } - 35 -> { hideTabs(player, login) removeHintIcon(player) registerHintIcon(player, Location.create(3076, 9504), 50) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Mining", - "", - "It's quite simple really. All you need to do is right click on the", - "rock and select 'mine' You can only mine when you have a", - "pickaxe. So give it a try: first mine one tin ore.", - ) - ) + sendStageDialog(player, stage) } - 36 -> { hideTabs(player, login) removeHintIcon(player) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Please wait.", - "", - "Your character is now attempting to mine the rock.", - "This should only take a few seconds.", - "" - ) - ) + sendStageDialog(player, stage) } - 37 -> { hideTabs(player, login) registerHintIcon(player, Location.create(3086, 9501), 50) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Mining", - "", - "Now you have some tin ore you just need some copper ore,", - "then you'll have all you need to create a bronze bar. As you", - "did before right click on the copper rock and select 'mine'." - ) - ) + sendStageDialog(player, stage) } - 38 -> { hideTabs(player, login) removeHintIcon(player) registerHintIcon(player, Location.create(3079, 9496), 75) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Smelting", - "You should now have both some copper and tin ore. So let's", - "smelt them to make a bronze bar. To do this, right click on", - "either tin or copper ore and select use then left click on the", - "furnace. Try it now." - ) - ) + sendStageDialog(player, stage) + } + 39 -> { + sendStageDialog(player, stage) } - - //39 -> {} - 40 -> { hideTabs(player, login) removeHintIcon(player) - registerHintIcon(player, Repository.findNPC(948)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "You've made a bronze bar!", - "", - "Speak to the Mining Instructor and he'll show you how to make", - "it into a weapon.", - "" - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.MINING_INSTRUCTOR_948)!!) + sendStageDialog(player, stage) } - 41 -> { hideTabs(player, login) removeHintIcon(player) registerHintIcon(player, Location.create(3083, 9499), 50) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Smithing a dagger", - "To smith you'll need a hammer - like the one you were given by", - "Dezzick - access to an anvil like the one with the arrow over it", - "and enough metal bars to make what you are trying to smith.", - "To start the process, use the bar on one of the anvils." - ) - ) + sendStageDialog(player, stage) } - 42 -> { hideTabs(player, login) removeHintIcon(player) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Smithing a dagger.", - "Now you have the Smithing menu open, you will see a list of all", - "the things you can make. Only the dagger can be made at your", - "skill level; this is shown by the white text under it. You'll need", - "to select the dagger to continue." - ) - ) + sendStageDialog(player, stage) } - 43 -> { hideTabs(player, login) registerHintIcon(player, Location.create(3095, 9502), 75) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "You've finished in this area.", - "", - "So let's move on. Go through the gates shown by the arrow.", - "Remember, you may need to move the camera to see your", - "surroundings. Speak to the guide for a recap at any time.", - ) - ) + sendStageDialog(player, stage) } - 44 -> { hideTabs(player, login) removeHintIcon(player) - registerHintIcon(player, Repository.findNPC(944)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Combat", - "", - "In this area you will find out about combat with swords and", - "bows. Speak to the guide and he will tell you all about it.", - "" - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.COMBAT_INSTRUCTOR_944)!!) + sendStageDialog(player, stage) } - 45 -> { hideTabs(player, login) removeHintIcon(player) runTask(player, 10) { - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Wielding weapons", - "", - "You now have access to a new interface. Click on the flashing", - "icon of a man, the one to the right of your backpack icon.", - "" - ) - ) + // this part needs sendStageDialog because you could just be logging in here + sendStageDialog(player, stage) }.also { + // for this part, you are locked into the interface so we don't need sendStageDialog here hideTabs(player, login) removeHintIcon(player) player.interfaceManager.openTab(Component(Components.WORNITEMS_387)) @@ -807,429 +345,174 @@ object TutorialStage { ) } } - 46 -> { hideTabs(player, login) setVarbit(player, 3756, 0) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "This is your worn inventory.", - "From here you can see what items you have equipped. Let's", - "get one of those slots filled, go back to your inventory and", - "right click your dagger, select wield from the menu.", - "" - ) - ) + sendStageDialog(player, stage) } - 47 -> { hideTabs(player, login) removeHintIcon(player) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendScrollMessageWithBlueTitle( - "You're now holding your dagger.", - "Clothes, armour, weapons and many other items are equipped", - "like this. You can unequip items by clicking on the item in the", - "worn equipment. You can close this window by clicking on the", - "small 'x' in the top-right hand corner. Speak to the Combat", - "Instructor." - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.COMBAT_INSTRUCTOR_944)!!) + sendStageDialog(player, stage) } - 48 -> { hideTabs(player, login) removeHintIcon(player) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendScrollMessageWithBlueTitle( - "Unequipping items.", - "In your worn inventory panel, right click on the dagger and", - "select the remove option from the drop down list. After you've", - "unequipped the dagger, wield the sword and shield. As you", - "pass the mouse over an item you will see its name appear at", - "the top left of the screen." - ) - ) + sendStageDialog(player, stage) } - 49 -> { hideTabs(player, login) setVarbit(player, 3756, 1) var wepInter = player.getExtension(WeaponInterface::class.java) - if(wepInter == null) - { + if (wepInter == null) { wepInter = WeaponInterface(player) player.addExtension(WeaponInterface::class.java, wepInter) } player.interfaceManager.openTab(wepInter) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Combat interface.", - "", - "Click on the flashing crossed swords icon to see the combat", - "interface.", - "" - ) - ) + sendStageDialog(player, stage) } - 50 -> { hideTabs(player, login) setVarbit(player, 3756, 0) - registerHintIcon(player, Location.create(3110,9518,0), 75) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendScrollMessageWithBlueTitle( - "This is your combat interface.", - "From this interface you can select the type of attack your", - "character will use. Different monsters have different", - "weaknesses. If you hover your mouse over the buttons, you", - "will see the type of XP you will receive when using each type of", - "attack. Now you have the tools needed for battle why not slay", - "some rats. Click on the gates indicated to continue." - ) - ) + registerHintIcon(player, Location.create(3111,9518,0), 75) + sendStageDialog(player, stage) } - 51 -> { hideTabs(player, login) removeHintIcon(player) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Attacking", - "To attack the rat, click it and select the attack option. You", - "will then walk over to it and start hitting it.", - "", - "" - ) - ) + //FIXME: add a hint arrow over the rat closest to you that is not in combat with somebody else. https://www.youtube.com/watch?v=FGQ2BZrJIug. The below should work but doesn't. + registerHintIcon(player, Repository.findNPC(NPCs.GIANT_RAT_86)!!) + sendStageDialog(player, stage) } - 52 -> { hideTabs(player, login) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Sit back and watch.", - "While you are fighting you will see a bar over your head. The", - "bar shows how much health you have left. Your opponent will", - "have one too. You will continue to attack the rat until it's dead", - "or you do something else." - ) - ) + //FIXME: add a hint arrow over the rat you're in combat with (also in the ranging part btw). https://www.youtube.com/watch?v=FGQ2BZrJIug + sendStageDialog(player, stage) } - 53 -> { hideTabs(player, login) removeHintIcon(player) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Well done, you've made your first kill!", - "", - "Pass through the gate and talk to the Combat Instructor; he", - "will give you your next task.", - "" - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.COMBAT_INSTRUCTOR_944)!!) + sendStageDialog(player, stage) } - 54 -> { hideTabs(player, login) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Rat ranging", - "Now you have a bow and some arrows. Before you can use", - "them you'll need to equip them. Once equipped with the", - "ranging gear try killing another rat. Remember: to attack, right", - "click on the monster and select attack." - ) - ) + sendStageDialog(player, stage) } - 55 -> { hideTabs(player, login) removeHintIcon(player) registerHintIcon(player, Location.create(3111,9526), 50) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Moving on.", - "You have completed the tasks here. To move on, click on the", - "ladder shown. If you need to go over any of what you learnt", - "here, just talk to the Combat Instructor and he'll tell you what", - "he can." - ) - ) + sendStageDialog(player, stage) } - 56 -> { hideTabs(player, login) removeHintIcon(player) registerHintIcon(player, Location.create(3122,3124), 50) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Banking.", - "Follow the path and you will come to the front of a building.", - "This is the 'Bank of " + settings!!.name + "' where you can store all your", - "most valued items. To open your bank box just right click on an", - "open booth indicated and select 'use'." - ) - ) + sendStageDialog(player, stage) } - 57 -> { hideTabs(player, login) removeHintIcon(player) registerHintIcon(player, Location.create(3125, 3124), 75) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "This is your bank box.", - "You can store stuff here for safekeeping. If you die, anything", - "in your bank will be saved. To deposit something, right click it", - "and select 'Deposit-1'. Once you've had a good look, close the", - "window and move on through the door indicated." - ) - ) + sendStageDialog(player, stage) } - 58 -> { hideTabs(player, login) removeHintIcon(player) - registerHintIcon(player, Repository.findNPC(947)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Financial advice", - "", - "The guide here will tell you all about making cash. Just click on", - "him to hear what he's got to say.", - "" - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.FINANCIAL_ADVISOR_947)!!) + sendStageDialog(player, stage) } - 59 -> { hideTabs(player, login) removeHintIcon(player) registerHintIcon(player, Location.create(3130, 3124, 0), 75) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "", - "", - "Continue through the next door.", - "", - "" - ) - ) + sendStageDialog(player, stage) } - 60 -> { hideTabs(player, login) removeHintIcon(player) - registerHintIcon(player, Repository.findNPC(954)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Prayer", - "Follow the path to the chapel and enter it.", - "Once inside talk to the monk. He'll tell you all about the Prayer", - "skill.", - "" - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.BROTHER_BRACE_954)!!) + sendStageDialog(player, stage) } - 61 -> { hideTabs(player, login) removeHintIcon(player) player.interfaceManager.openTab(Component(Components.PRAYER_271)) setVarbit(player, 3756, 6) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Your Prayer List", - "", - "Click on the flashing icon to open the Prayer List.", - "", - "" - ) - ) + sendStageDialog(player, stage) } - 62 -> { hideTabs(player, login) - registerHintIcon(player, Repository.findNPC(954)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "", - "Your Prayer List", - "", - "Talk with Brother Brace and he'll tell you all about prayers.", - "" - ) - ) + removeHintIcon(player) + registerHintIcon(player, Repository.findNPC(NPCs.BROTHER_BRACE_954)!!) + sendStageDialog(player, stage) } - 63 -> { hideTabs(player, login) removeHintIcon(player) player.interfaceManager.openTab(Component(Components.FRIENDS2_550)) setVarbit(player, 3756, 9) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "", - "Friends list", - "You should now see another new icon. Click on the flashing", - "smiling face to open your Friend List.", - "" - ) - ) + sendStageDialog(player, stage) } - 64 -> { hideTabs(player, login) setVarbit(player, 3756, 10) player.interfaceManager.openTab(Component(Components.IGNORE2_551)) player.interfaceManager.openTab(Component(Components.CLANJOIN_589)) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "This is your Friends List.", - "", - "This will be explained by Brother Brace shortly, but first click", - "on the other flashing face in the interface.", - "" - ) - ) + sendStageDialog(player, stage) } - 65 -> { hideTabs(player, login) setVarbit(player, 3756, 0) - registerHintIcon(player, Repository.findNPC(945)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "This is your Ignore List.", - "The two lists, Friends and Ignore - can be very helpful for", - "keeping track of when your friends are online or for blocking", - "messages from people you simply don't like. Speak with", - "Brother Brace and he will tell you more." - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.BROTHER_BRACE_954)!!) + sendStageDialog(player, stage) } - 66 -> { hideTabs(player, login) removeHintIcon(player) registerHintIcon(player, Location.create(3122,3102), 75) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "", - "Your final instructor!", - "You're almost finished on tutorial island. Pass through the", - "door to find the path leading to your final instructor.", - "" - ) - ) + sendStageDialog(player, stage) } - 67 -> { hideTabs(player, login) removeHintIcon(player) - registerHintIcon(player, Repository.findNPC(946)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Your final instructor!", - "Just follow the path to the Wizard's house, where you will be", - "shown how to cast spells. Just talk with the mage indicated to", - "find out more.", - "" - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.MAGIC_INSTRUCTOR_946)!!) + sendStageDialog(player, stage) } - 68 -> { hideTabs(player, login) removeHintIcon(player) player.interfaceManager.openTab(Component(player.spellBookManager.spellBook)) setVarbit(player, 3756, 7) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "Open up your final tab.", - "", - "Open up the Magic Spellbook tab by clicking on the flashing", - "icon next to the Prayer List tab you just learned about.", - "" - ) - ) + sendStageDialog(player, stage) } - 69 -> { hideTabs(player, login) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "", - "This is your spell list.", - "", - "Ask the mage about it.", - "" - ) - ) + removeHintIcon(player) + setVarbit(player, 3756, 0) + registerHintIcon(player, Repository.findNPC(NPCs.MAGIC_INSTRUCTOR_946)!!) + sendStageDialog(player, stage) } - 70 -> { hideTabs(player, login) - registerHintIcon(player, Repository.findNPC(41)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendScrollMessageWithBlueTitle( - "Cast Wind Strike at a chicken.", - "Now you have the runes you should see the Wind Strike icon at the", - "top-left of your spellbook, second in from the left. Walk over", - "to the caged chickens, click the Wind Strike icon and then", - "select one of the chickens to cast it on. It may take several", - "tries." - ) - ) + removeHintIcon(player) + //FIXME: as with the rats, the below should work, but doesn't + registerHintIcon(player, Repository.findNPC(NPCs.CHICKEN_41)!!) + sendStageDialog(player, stage) } - 71 -> { removeHintIcon(player) player.interfaceManager.restoreTabs() - registerHintIcon(player, Repository.findNPC(946)!!) - Component.setUnclosable( - player, - player.dialogueInterpreter.sendPlaneMessageWithBlueTitle( - "You have almost completed the tutorial!", - "", - "All you need to do now is teleport to the mainland. Just speak", - "with Terrova and he'll tell you how to do that.", - "" - ) - ) + registerHintIcon(player, Repository.findNPC(NPCs.MAGIC_INSTRUCTOR_946)!!) + sendStageDialog(player, stage) } } } @JvmStatic - public fun hideTabs(player: Player, login: Boolean) + fun hideTabs(player: Player, login: Boolean) { val stage = getAttribute(player, "tutorial:stage", 0) if(login && player.interfaceManager.tabs.isNotEmpty()) @@ -1249,10 +532,9 @@ object TutorialStage { player.interfaceManager.openTab(Component(Components.QUESTJOURNAL_V2_274)) if(stage > 45) player.interfaceManager.openTab(Component(Components.WORNITEMS_387)) - if(stage > 49){ + if(stage > 46){ var wepInter = player.getExtension(WeaponInterface::class.java) - if(wepInter == null) - { + if (wepInter == null) { wepInter = WeaponInterface(player) player.addExtension(WeaponInterface::class.java, wepInter) } diff --git a/Server/src/main/core/ServerConstants.kt b/Server/src/main/core/ServerConstants.kt index 721174ec8..2959ccb0c 100644 --- a/Server/src/main/core/ServerConstants.kt +++ b/Server/src/main/core/ServerConstants.kt @@ -161,7 +161,7 @@ class ServerConstants { var LOG_CUTSCENE = true @JvmField - var RULES_AND_INFO_ENABLED = true + var RULES_AND_INFO_ENABLED = false @JvmField var WATCHDOG_ENABLED = true @@ -292,13 +292,16 @@ class ServerConstants { var DRAGON_AXE_USE_OSRS_SPEC = false @JvmField - var ENABLE_GLOBALCHAT = false + var ENABLE_GLOBAL_CHAT = false @JvmField var MAX_PATHFIND_DISTANCE = 25 @JvmField - var IRONMAN_ICONS = false + var XP_RATES = false + + @JvmField + var IRONMAN = false @JvmField var PLAYER_STOCK_CLEAR_INTERVAL = 1 @@ -310,19 +313,19 @@ class ServerConstants { var BOTSTOCK_LIMIT = 5000 @JvmField - var BETTER_AGILITY_PYRAMID_GP = true + var BETTER_AGILITY_PYRAMID_GP = false @JvmField - var BETTER_DFS = true + var BETTER_DFS = false @JvmField - var NEW_PLAYER_ANNOUNCEMENT = true + var NEW_PLAYER_ANNOUNCEMENT = false @JvmField var INAUTHENTIC_CANDLELIGHT_RANDOM = false @JvmField - var HOLIDAY_EVENT_RANDOMS = true + var HOLIDAY_EVENT_RANDOMS = false @JvmField var FORCE_HALLOWEEN_EVENTS = false @@ -334,7 +337,7 @@ class ServerConstants { var FORCE_EASTER_EVENTS = false @JvmField - var RUNECRAFTING_FORMULA_REVISION = 581 + var RUNECRAFTING_FORMULA_REVISION = 530 @JvmField var ENHANCED_DEEP_WILDERNESS = false diff --git a/Server/src/main/core/api/ContentAPI.kt b/Server/src/main/core/api/ContentAPI.kt index 7ec533eb5..813c355dc 100644 --- a/Server/src/main/core/api/ContentAPI.kt +++ b/Server/src/main/core/api/ContentAPI.kt @@ -1842,8 +1842,8 @@ fun sendItemDialogue(player: Player, item: Any, message: String) { */ fun sendDoubleItemDialogue(player: Player, item1: Any, item2: Any, message: String) { when (item1) { - is Item -> player.dialogueInterpreter.sendDoubleItemMessage(item1, item2 as Item, *splitLines(message)) - is Int -> player.dialogueInterpreter.sendDoubleItemMessage(item1, item2 as Int, *splitLines(message)) + is Item -> player.dialogueInterpreter.sendDoubleItemMessage(item1, item2 as Item, *splitLines(message)) + is Int -> player.dialogueInterpreter.sendDoubleItemMessage(item1, item2 as Int, *splitLines(message)) } } diff --git a/Server/src/main/core/api/utils/Permadeath.kt b/Server/src/main/core/api/utils/Permadeath.kt index c7e5069f4..a49d575cf 100644 --- a/Server/src/main/core/api/utils/Permadeath.kt +++ b/Server/src/main/core/api/utils/Permadeath.kt @@ -3,6 +3,7 @@ package core.api.utils import content.global.skill.construction.HouseLocation import content.minigame.blastfurnace.BFPlayerState import content.minigame.blastfurnace.BlastFurnace +import core.ServerConstants import core.api.isUsingSecondaryBankAccount import core.api.teleport import core.api.toggleBankAccount @@ -91,6 +92,9 @@ fun permadeath(target: Player) { } } + // Xp rate + target.skills.experienceMultiplier = 1.0 + // Ironman data target.ironmanManager.mode = IronmanMode.NONE diff --git a/Server/src/main/core/game/dialogue/DialogueLabeller.kt b/Server/src/main/core/game/dialogue/DialogueLabeller.kt index 6a48b86ae..8259474a0 100644 --- a/Server/src/main/core/game/dialogue/DialogueLabeller.kt +++ b/Server/src/main/core/game/dialogue/DialogueLabeller.kt @@ -1,9 +1,15 @@ package core.game.dialogue +import core.api.Event import core.api.InputType import core.api.face import core.api.openDialogue import core.api.splitLines +import core.game.component.Component +import core.game.component.Component.setUnclosable +import core.game.event.DialogueCloseEvent +import core.game.event.EventHook +import core.game.node.entity.Entity import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.node.item.Item @@ -73,11 +79,14 @@ abstract class DialogueLabeller : DialogueFile() { /** Implement this function instead of overriding handle. */ abstract fun addConversation() - /** Helper function to create an individual stage for each of the dialogue stages. */ - private fun assignIndividualStage(callback: () -> Unit) { + /** Helper functions to create an individual stage for each of the dialogue stages. */ + private fun assignIndividualStage(callback: () -> Component?, unclosable: Boolean) { if (startingStage == null) { startingStage = 0 } if (stage == dialogueCounter && jumpTo == null) { // Run this stage when the stage equals to the dialogueCounter of this dialogue - callback() // CALLBACK FUNCTION + val component = callback() // CALLBACK FUNCTION + if (unclosable && component != null) { + setUnclosable(player, component) + } super.stage++ // Increment the stage to the next stage (only applies after a pass) stageHit = true // Flag that the stage was hit, so that it doesn't close the dialogue } @@ -131,13 +140,13 @@ abstract class DialogueLabeller : DialogueFile() { } /** Manual stage. For custom creation of an individual stage. Must call interpreter in some form. **/ - fun manual(callback: (player: Player, npc: NPC) -> Unit) { - assignIndividualStage { callback(player!!, npc!!) } + fun manual(unclosable: Boolean = false, callback: (player: Player, npc: NPC) -> Component?) { + assignIndividualStage({ return@assignIndividualStage callback(player!!, npc!!) }, unclosable) } /** Dialogue player/playerl. Shows player chathead with text. **/ - fun player(chatAnim: ChatAnim = ChatAnim.NEUTRAL, vararg messages: String) { - assignIndividualStage { interpreter!!.sendDialogues(player, chatAnim, *formatMessages(messages)) } + fun player(chatAnim: ChatAnim = ChatAnim.NEUTRAL, vararg messages: String, unclosable: Boolean = false) { + assignIndividualStage({ return@assignIndividualStage interpreter!!.sendDialogues(player, chatAnim, *formatMessages(messages)) }, unclosable) } /** Dialogue player/playerl. Shows player chathead with text. **/ fun player(vararg messages: String) { player(ChatAnim.NEUTRAL, *messages) } @@ -147,16 +156,17 @@ abstract class DialogueLabeller : DialogueFile() { fun playerl(vararg messages: String) { throw Exception("Deprecated DialogueLabel: Use player() instead.") } /** Dialogue npc/npcl. Shows npc chathead with text. **/ - fun npc(chatAnim: ChatAnim = ChatAnim.NEUTRAL, vararg messages: String) { - assignIndividualStage { interpreter!!.sendDialogues(npc, chatAnim, *formatMessages(messages)) } + fun npc(chatAnim: ChatAnim = ChatAnim.NEUTRAL, vararg messages: String, unclosable: Boolean = false) { + val callback = callback@{ return@callback interpreter!!.sendDialogues(npc, chatAnim, *formatMessages(messages)) } + assignIndividualStage(callback, unclosable) } /** Dialogue npc/npcl. Shows npcId chathead with text. **/ - fun npc(chatAnim: ChatAnim = ChatAnim.NEUTRAL, npcId: Int = npc!!.id, vararg messages: String) { - assignIndividualStage { interpreter!!.sendDialogues(NPC(npcId), chatAnim, *formatMessages(messages)) } + fun npc(chatAnim: ChatAnim = ChatAnim.NEUTRAL, npcId: Int = npc!!.id, vararg messages: String, unclosable: Boolean = false) { + assignIndividualStage({ return@assignIndividualStage interpreter!!.sendDialogues(NPC(npcId), chatAnim, *formatMessages(messages)) }, unclosable) } /** Dialogue npc/npcl. Shows npcId chathead with text. **/ - fun npc(npcId: Int = npc!!.id, vararg messages: String) { - assignIndividualStage { interpreter!!.sendDialogues(NPC(npcId), ChatAnim.NEUTRAL, *formatMessages(messages)) } + fun npc(npcId: Int = npc!!.id, vararg messages: String, unclosable: Boolean = false) { + assignIndividualStage({ return@assignIndividualStage interpreter!!.sendDialogues(NPC(npcId), ChatAnim.NEUTRAL, *formatMessages(messages)) }, unclosable) } /** Dialogue npc/npcl. Shows npc chathead with text. **/ fun npc(vararg messages: String) { npc(ChatAnim.NEUTRAL, *messages) } @@ -166,40 +176,45 @@ abstract class DialogueLabeller : DialogueFile() { fun npcl(vararg messages: String) { throw Exception("Deprecated DialogueLabel: Use npc() instead.") } /** Dialogue item/iteml. Shows item with text. **/ - fun item(item: Item, vararg messages: String, message: String = "") { - assignIndividualStage { interpreter!!.sendItemMessage(item, *formatMessages(messages, message)) } + fun item(item: Item, vararg messages: String, message: String = "", unclosable: Boolean = false) { + val callback = callback@{ return@callback interpreter!!.sendItemMessage(item, *formatMessages(messages, message)) } + assignIndividualStage(callback, unclosable) } @Deprecated("Use item() instead.", ReplaceWith("item(item, *messages)")) fun iteml(item: Item, vararg messages: String) { throw Exception("Deprecated DialogueLabel: Use item() instead.") } /** Dialogue overloaded doubleItem/doubleIteml. Shows two items with text. **/ - fun item(item: Item, item2: Item, vararg messages: String, message: String = "") { - assignIndividualStage { interpreter!!.sendDoubleItemMessage(item, item2, formatMessages(messages, message).joinToString(" ")) } + fun item(item: Item, item2: Item, vararg messages: String, message: String = "", unclosable: Boolean = false) { + assignIndividualStage({ return@assignIndividualStage interpreter!!.sendDoubleItemMessage(item, item2, *formatMessages(messages, message)) }, unclosable) } /** Dialogue line/linel. Simply shows text. **/ - fun line(vararg messages: String) { - assignIndividualStage { interpreter!!.sendDialogue(*messages) } + fun line(vararg messages: String, unclosable: Boolean = false) { + val callback = callback@{ return@callback interpreter!!.sendDialogue(*messages) } + assignIndividualStage(callback, unclosable) } @Deprecated("Use line() instead.", ReplaceWith("line(*messages)")) fun linel(vararg messages: String) { throw Exception("Deprecated DialogueLabel: Use line() instead.") } /** Dialogue option. Shows the option dialogue with choices for the user to select. **/ - fun options(vararg options: DialogueOption, title: String = "Select an Option") { + fun options(vararg options: DialogueOption, title: String = "Select an Option", unclosable: Boolean = false) { // Filter out options that aren't shown. val filteredOptions = options.filter{ if (it.optionIf != null) { it.optionIf.invoke(player!!, npc!!) } else { true } } // Stage Part 1: Options List Dialogue - assignIndividualStage { interpreter!!.sendOptions(title, *filteredOptions.map{ it.option }.toTypedArray()) } + val callback = callback@{ return@callback interpreter!!.sendOptions(title, *filteredOptions.map{ it.option }.toTypedArray()) } + assignIndividualStage(callback, unclosable) // Stage Part 2: Show spoken text. - var opt = if (buttonID != null && buttonID in 1..filteredOptions.size) { filteredOptions[buttonID!! - 1] } else { null } - assignIndividualStage { + val opt = if (buttonID != null && buttonID in 1..filteredOptions.size) { filteredOptions[buttonID!! - 1] } else { null } + assignIndividualStage({ + var component: Component? = null if (opt?.skipPlayer == true) { jumpTo = stage + 1 } else { - interpreter!!.sendDialogues(player, opt?.expression ?: ChatAnim.NEUTRAL, *(splitLines(opt?.spokenText ?: " "))) + component = interpreter!!.sendDialogues(player, opt?.expression ?: ChatAnim.NEUTRAL, *(splitLines(opt?.spokenText ?: " "))) } optButton = buttonID // transfer the buttonID to a temp memory for the next stage - } + return@assignIndividualStage component + }, unclosable) // Stage Part 3: Jump To goto if (stage == dialogueCounter && optButton != null && optButton in 1..filteredOptions.size) { jumpTo = labelStageMap[filteredOptions[optButton!! - 1].goto] @@ -211,7 +226,7 @@ abstract class DialogueLabeller : DialogueFile() { /** Dialogue input. Shows the input dialogue with an input box for the user to type in. Read [optInput] for the value. **/ fun input(type: InputType, prompt: String = "Enter the amount") { - assignIndividualStage { + assignIndividualStage({ // These are similar to calling sendInputDialogue when (type) { InputType.AMOUNT -> interpreter!!.sendInput(true, prompt) @@ -230,14 +245,31 @@ abstract class DialogueLabeller : DialogueFile() { interpreter!!.handle(player!!.interfaceManager.chatbox.id, 2) } player!!.setAttribute("input-type", type) - } + return@assignIndividualStage null + }, false) } /** Dialogue input. Shows the input dialogue with an input box for the user to type in. Read [optInput] in an [exec] function for the value. **/ fun input(numeric: Boolean, prompt: String = "Enter the amount") { input( if (numeric) { InputType.NUMERIC } else { InputType.STRING_SHORT }, prompt) } + /** Runs arbitrary code when the dialogue closes, once. **/ + fun afterClose(callback: (player: Player) -> Unit) { + val hook = object : EventHook { + override fun process(entity: Entity, event: DialogueCloseEvent) { + val you = entity as Player + you.unhook(this) + callback(you) + } + } + player!!.hook(Event.DialogueClosed, hook) + } + /** Calls another dialogue file. Always use this to open another dialogue file instead of calling openDialogue() in exec{} due to interfaces clashing. **/ fun open(player: Player, dialogue: Any, vararg args: Any) { - assignIndividualStage { core.api.openDialogue(player, dialogue, *args) } + val callback = callback@{ + core.api.openDialogue(player, dialogue, *args) + return@callback null + } + assignIndividualStage(callback, false) } /** WARNING: DIALOGUE LABELLER WILL BREAK IN CERTAIN FUNCTIONS. USE open() instead. */ @@ -269,7 +301,10 @@ abstract class DialogueLabeller : DialogueFile() { break } } - if (!stageHit) { end() } // If a dialogue stage is not hit, end the dialogues. + // If a dialogue stage is not hit, end the dialogue. + if (!stageHit) { + end() + } } } diff --git a/Server/src/main/core/game/dialogue/DialoguePlugin.java b/Server/src/main/core/game/dialogue/DialoguePlugin.java index 635b41c2b..18d17dd37 100644 --- a/Server/src/main/core/game/dialogue/DialoguePlugin.java +++ b/Server/src/main/core/game/dialogue/DialoguePlugin.java @@ -276,8 +276,8 @@ public abstract class DialoguePlugin implements Plugin { * Method used to send options. * @param options the options. */ - public void options(final String... options) { - interpreter.sendOptions("Select an Option", options); + public Component options(final String... options) { + return interpreter.sendOptions("Select an Option", options); } /** diff --git a/Server/src/main/core/game/node/entity/player/info/Rights.java b/Server/src/main/core/game/node/entity/player/info/Rights.java index c50de0e95..95009c23a 100644 --- a/Server/src/main/core/game/node/entity/player/info/Rights.java +++ b/Server/src/main/core/game/node/entity/player/info/Rights.java @@ -25,11 +25,11 @@ public enum Rights { if (c != Rights.REGULAR_PLAYER && c != null) { return c.toInteger(); } - if (ServerConstants.IRONMAN_ICONS) { - if (player.getIronmanManager().isIronman()) { - return player.getIronmanManager().getMode().getIcon(); - } - } + if (ServerConstants.IRONMAN) { + if (player.getIronmanManager().isIronman()) { + return player.getIronmanManager().getMode().getIcon(); + } + } return 0; } diff --git a/Server/src/main/core/game/node/entity/player/info/login/PlayerSaveParser.kt b/Server/src/main/core/game/node/entity/player/info/login/PlayerSaveParser.kt index 03fc75d67..028e8121d 100644 --- a/Server/src/main/core/game/node/entity/player/info/login/PlayerSaveParser.kt +++ b/Server/src/main/core/game/node/entity/player/info/login/PlayerSaveParser.kt @@ -330,9 +330,6 @@ class PlayerSaveParser(val player: Player) { player.skills.parse(skillData) player.skills.experienceGained = saveFile!!["totalEXP"].toString().toDouble() player.skills.experienceMultiplier = saveFile!!["exp_multiplier"].toString().toDouble() - if (GameWorld.settings?.default_xp_rate != 5.0) { - player.skills.experienceMultiplier = GameWorld.settings?.default_xp_rate!! - } val divisor: Double if(player.skills.experienceMultiplier >= 10 && !player.attributes.containsKey("permadeath")){ //exclude permadeath HCIMs from XP squish divisor = player.skills.experienceMultiplier / 5.0 diff --git a/Server/src/main/core/game/node/entity/player/info/login/PlayerSaver.kt b/Server/src/main/core/game/node/entity/player/info/login/PlayerSaver.kt index 11c15b4d8..470818396 100644 --- a/Server/src/main/core/game/node/entity/player/info/login/PlayerSaver.kt +++ b/Server/src/main/core/game/node/entity/player/info/login/PlayerSaver.kt @@ -322,7 +322,6 @@ class PlayerSaver (val player: Player){ fun saveGlobalData(root: JSONObject){ val globalData = JSONObject() - globalData.put("tutorialStage",player.savedData.globalData.tutorialStage.toString()) globalData.put("homeTeleportDelay",player.savedData.globalData.homeTeleportDelay.toString()) globalData.put("lumbridgeRope",player.savedData.globalData.hasTiedLumbridgeRope()) globalData.put("apprentice",player.savedData.globalData.hasSpokenToApprentice()) diff --git a/Server/src/main/core/game/node/entity/player/link/GlobalData.java b/Server/src/main/core/game/node/entity/player/link/GlobalData.java index 29878c2f6..62873b245 100644 --- a/Server/src/main/core/game/node/entity/player/link/GlobalData.java +++ b/Server/src/main/core/game/node/entity/player/link/GlobalData.java @@ -10,12 +10,6 @@ import org.json.simple.JSONObject; * @author 'Vexia */ public final class GlobalData { - - /** - * Represents the tutorial stage. - */ - private int tutorialStage; - /** * Represents the home teleport delay. */ @@ -298,7 +292,6 @@ public final class GlobalData { private boolean macroDisabled = false; public void parse(JSONObject data){ - tutorialStage = Integer.parseInt( data.get("tutorialStage").toString()); homeTeleportDelay = Long.parseLong(data.get("homeTeleportDelay").toString()); lumbridgeRope = (boolean) data.get("lumbridgeRope"); apprentice = (boolean) data.get("apprentice"); @@ -467,22 +460,6 @@ public final class GlobalData { playerTestStage = stage; } - /** - * Gets the tutorialStage. - * @return The tutorialStage. - */ - public int getTutorialStage() { - return tutorialStage; - } - - /** - * Sets the tutorialStage. - * @param tutorialStage The tutorialStage to set. - */ - public void setTutorialStage(int tutorialStage) { - this.tutorialStage = tutorialStage; - } - /** * Gets the homeTeleportDelay. * @return The homeTeleportDelay. diff --git a/Server/src/main/core/game/node/entity/player/link/IronmanMode.java b/Server/src/main/core/game/node/entity/player/link/IronmanMode.java index 2573b3fe4..3aa5e2a7a 100644 --- a/Server/src/main/core/game/node/entity/player/link/IronmanMode.java +++ b/Server/src/main/core/game/node/entity/player/link/IronmanMode.java @@ -5,7 +5,7 @@ package core.game.node.entity.player.link; * @author Vexia */ public enum IronmanMode { - // HARDCORE_DEAD has to be before Ultimate so that it does not adopt it's restrictions (on the basis of >= in IronmanManager.java?) + // HARDCORE_DEAD has to be before Ultimate so that it does not adopt its restrictions (on the basis of >= in IronmanManager.java) NONE(-1), STANDARD(5), HARDCORE(6), ULTIMATE(7); /** diff --git a/Server/src/main/core/game/node/entity/player/link/TeleportManager.java b/Server/src/main/core/game/node/entity/player/link/TeleportManager.java index 140ecf405..e9f8e5b04 100644 --- a/Server/src/main/core/game/node/entity/player/link/TeleportManager.java +++ b/Server/src/main/core/game/node/entity/player/link/TeleportManager.java @@ -135,7 +135,7 @@ public class TeleportManager { /** * Fires a random event. * @param entity The entity teleporting. - * @param location The destination lcoation. + * @param location The destination location. */ public static void fireRandom(Entity entity, Location location) { if (entity instanceof Player && entity.getTeleporter().getTeleportType() == 0) { diff --git a/Server/src/main/core/game/node/entity/skill/Skills.java b/Server/src/main/core/game/node/entity/skill/Skills.java index 19debcc0e..a9f1bfd5a 100644 --- a/Server/src/main/core/game/node/entity/skill/Skills.java +++ b/Server/src/main/core/game/node/entity/skill/Skills.java @@ -41,7 +41,7 @@ public final class Skills { /** * Represents the constant modifier of experience. */ - public double experienceMultiplier = 5.0; + public double experienceMultiplier = 1.0; /** * The maximum experience multiplier. @@ -397,6 +397,7 @@ public final class Skills { } public void correct(double divisor){ + // XP squish for legacy x20 accounts for(int i = 0; i < staticLevels.length; i++){ experience[i] /= divisor; staticLevels[i] = getStaticLevelByExperience(i); diff --git a/Server/src/main/core/game/system/config/ServerConfigParser.kt b/Server/src/main/core/game/system/config/ServerConfigParser.kt index d0ececce1..273b36531 100644 --- a/Server/src/main/core/game/system/config/ServerConfigParser.kt +++ b/Server/src/main/core/game/system/config/ServerConfigParser.kt @@ -76,8 +76,6 @@ object ServerConfigParser { isQuickChat = false, isLootshare = false, msAddress = data.getString("server.msip"), - default_xp_rate = data.getDouble("world.default_xp_rate"), - allow_slayer_reroll = data.getBoolean("world.allow_slayer_reroll"), enable_default_clan = data.getBoolean("world.enable_default_clan"), enable_bots = data.getBoolean("world.enable_bots"), autostock_ge = data.getBoolean("world.autostock_ge"), @@ -148,24 +146,25 @@ object ServerConfigParser { ServerConstants.NOAUTH_DEFAULT_ADMIN = data.getBoolean("server.noauth_default_admin", false) ServerConstants.DRAGON_AXE_USE_OSRS_SPEC = data.getBoolean("world.dragon_axe_use_osrs_spec", false) ServerConstants.DISCORD_OPENRSC_HOOK = data.getString("integrations.openrsc_integration_webhook", "") - ServerConstants.ENABLE_GLOBALCHAT = data.getBoolean("world.enable_globalchat", true) + ServerConstants.ENABLE_GLOBAL_CHAT = data.getBoolean("world.enable_global_chat", false) ServerConstants.MAX_PATHFIND_DISTANCE = data.getLong("server.max_pathfind_dist", 25L).toInt() - ServerConstants.IRONMAN_ICONS = data.getBoolean("world.ironman_icons", false) + ServerConstants.XP_RATES = data.getBoolean("world.xp_rates", false) + ServerConstants.IRONMAN = data.getBoolean("world.ironman", false) ServerConstants.PLAYER_STOCK_CLEAR_INTERVAL = data.getLong("world.playerstock_clear_mins", 180L).toInt() ServerConstants.PLAYER_STOCK_RECIRCULATE = data.getBoolean("world.playerstock_bot_offers", true) ServerConstants.BOTSTOCK_LIMIT = data.getLong("world.botstock_limit", 5000L).toInt() - ServerConstants.BETTER_AGILITY_PYRAMID_GP = data.getBoolean("world.better_agility_pyramid_gp", true) + ServerConstants.BETTER_AGILITY_PYRAMID_GP = data.getBoolean("world.better_agility_pyramid_gp", false) ServerConstants.GRAFANA_PATH = data.getPath("integrations.grafana_log_path") ServerConstants.GRAFANA_LOGGING = data.getBoolean("integrations.grafana_logging", false) ServerConstants.GRAFANA_TTL_DAYS = data.getLong("integrations.grafana_log_ttl_days", 7L).toInt() - ServerConstants.BETTER_DFS = data.getBoolean("world.better_dfs", true) - ServerConstants.NEW_PLAYER_ANNOUNCEMENT = data.getBoolean("world.new_player_announcement", true) + ServerConstants.BETTER_DFS = data.getBoolean("world.better_dfs", false) + ServerConstants.NEW_PLAYER_ANNOUNCEMENT = data.getBoolean("world.new_player_announcement", false) ServerConstants.INAUTHENTIC_CANDLELIGHT_RANDOM = data.getBoolean("world.inauthentic_candlelight_random", false) - ServerConstants.HOLIDAY_EVENT_RANDOMS = data.getBoolean("world.holiday_event_randoms", true) + ServerConstants.HOLIDAY_EVENT_RANDOMS = data.getBoolean("world.holiday_event_randoms", false) ServerConstants.FORCE_HALLOWEEN_EVENTS = data.getBoolean("world.force_halloween_randoms", false) ServerConstants.FORCE_CHRISTMAS_EVENTS = data.getBoolean("world.force_christmas_randoms", false) ServerConstants.FORCE_EASTER_EVENTS = data.getBoolean("world.force_easter_randoms", false) - ServerConstants.RUNECRAFTING_FORMULA_REVISION = data.getLong("world.runecrafting_formula_revision", 581).toInt() + ServerConstants.RUNECRAFTING_FORMULA_REVISION = data.getLong("world.runecrafting_formula_revision", 530).toInt() ServerConstants.ENHANCED_DEEP_WILDERNESS = data.getBoolean("world.enhanced_deep_wilderness", false) ServerConstants.WILDERNESS_EXCLUSIVE_LOOT = data.getBoolean("world.wilderness_exclusive_loot", false) ServerConstants.SHOOTING_STAR_RING = data.getBoolean("world.shooting_star_ring", false) diff --git a/Server/src/main/core/game/world/GameSettings.kt b/Server/src/main/core/game/world/GameSettings.kt index 7c8c6e9d5..e113dbbe3 100644 --- a/Server/src/main/core/game/world/GameSettings.kt +++ b/Server/src/main/core/game/world/GameSettings.kt @@ -69,8 +69,6 @@ class GameSettings * The address of the Management server. */ var msAddress: String, - var default_xp_rate: Double, - var allow_slayer_reroll: Boolean, var enable_default_clan: Boolean, var enable_bots: Boolean, var autostock_ge: Boolean, @@ -126,8 +124,6 @@ class GameSettings val activity = data["activity"].toString() val pvpWorld = data["pvpWorld"] as Boolean val msip = data["msip"].toString() - val default_xp_rate = data["default_xp_rate"].toString().toDouble() - val allow_slayer_reroll = data["allow_slayer_reroll"] as Boolean val enable_default_clan = data["enable_default_clan"] as Boolean val enable_bots = data["enable_bots"] as Boolean val autostock_ge = data["autostock_ge"] as Boolean @@ -157,8 +153,6 @@ class GameSettings false, false, msip, - default_xp_rate, - allow_slayer_reroll, enable_default_clan, enable_bots, autostock_ge, diff --git a/Server/src/main/core/net/packet/PacketProcessor.kt b/Server/src/main/core/net/packet/PacketProcessor.kt index d6028d648..820c6798c 100644 --- a/Server/src/main/core/net/packet/PacketProcessor.kt +++ b/Server/src/main/core/net/packet/PacketProcessor.kt @@ -208,7 +208,7 @@ object PacketProcessor { if (pkt.player.details.isMuted) pkt.player.sendMessage("You have been muted due to breaking a rule.") else { - if (ServerConstants.ENABLE_GLOBALCHAT && pkt.message.startsWith("//")) { + if (ServerConstants.ENABLE_GLOBAL_CHAT && pkt.message.startsWith("//")) { if (getAttribute(pkt.player, GlobalChat.ATTR_GLOBAL_MUTE, false)) return diff --git a/Server/src/test/resources/test.conf b/Server/src/test/resources/test.conf index fd8697901..b143db9e0 100644 --- a/Server/src/test/resources/test.conf +++ b/Server/src/test/resources/test.conf @@ -27,8 +27,6 @@ members = true #activity as displayed on the world list activity = "2009Scape Classic." pvp = false -default_xp_rate = 5.0 -allow_slayer_reroll = false #enables a default clan for players to join automatically. Should be an account with the same name as @name, with a clan set up already. enable_default_clan = true enable_bots = true @@ -49,6 +47,7 @@ max_adv_bots = 100 enable_doubling_money_scammers = true wild_pvp_enabled = false jad_practice_enabled = false +enable_global_chat = false enable_castle_wars = false personalized_shops = false diff --git a/Server/worldprops/default.conf b/Server/worldprops/default.conf index 511b7e9ce..fcb90f72b 100644 --- a/Server/worldprops/default.conf +++ b/Server/worldprops/default.conf @@ -58,8 +58,6 @@ members = true #activity as displayed on the world list activity = "2009Scape Classic." pvp = false -default_xp_rate = 5.0 -allow_slayer_reroll = false #enables a default clan for players to join automatically. Should be an account with the same name as @name, with a clan set up already. enable_default_clan = true enable_bots = true @@ -72,44 +70,61 @@ new_player_location = "2524,5002,0" #the location of home teleport home_location = "3222,3218,0" autostock_ge = false -allow_token_purchase = true -skillcape_perks = true +allow_token_purchase = false +skillcape_perks = false increased_door_time = false enable_botting = false max_adv_bots = 100 enable_doubling_money_scammers = true -wild_pvp_enabled = true -jad_practice_enabled = true +wild_pvp_enabled = false +jad_practice_enabled = false +enable_global_chat = false #minimum HA value for announcements of bots selling on ge ge_announcement_limit = 500 enable_castle_wars = false -personalized_shops = true +personalized_shops = false bots_influence_ge_price = true #verbose cutscene logging (for cutscenes in the new system) verbose_cutscene = false #show the rules the first time a player logs in -show_rules = true +show_rules = false #the number of revenants active at a time revenant_population = 30 #enable auto-buy/auto-sell on the GE. i_want_to_cheat = false #better agility pyramid gp reward (gp reward = 1000 + ((agility level / 99) * 9000)) -better_agility_pyramid_gp = true +better_agility_pyramid_gp = false #better dragonfire shield attack (30 second cooldown instead of 2 minutes) -better_dfs = true +better_dfs = false #new player announcement -new_player_announcement = true +new_player_announcement = false #enables inauthentic candlelight random event (adds an additional normal random event) -inauthentic_candlelight_random = true +inauthentic_candlelight_random = false #enables holiday random events (no effect on normal random events) -holiday_event_randoms = true +holiday_event_randoms = false #force holiday randoms (can only force one at a time) force_halloween_randoms = false force_christmas_randoms = false #runecrafting formula revision (573 introduced probabilistic multiple runes, 581 extrapolated probabilistic runes past 99) -runecrafting_formula_revision = 581 -#enable the enhanced deep wilderness, where the area past the members' fence applies a red skull that boosts brawler/pvp drop rates -enhanced_deep_wilderness = true +runecrafting_formula_revision = 530 +#enables the enhanced deep wilderness, where the area past the members' fence applies a red skull that boosts brawler/pvp drop rates +enhanced_deep_wilderness = false +#enables wilderness-exclusive loot, i.e. brawling gloves and PvP gear, from revenants and the Chaos Elemental +wilderness_exclusive_loot = false +#enables the xp rates option on tutorial island +xp_rates = false +#enables the ironman option on tutorial island and the inauthentic game protocol addition to transmit chat icons for ironmen +ironman = false +#enables the custom-content ancient blueprint and ring of the star sprite +shooting_star_ring = false +#enables the inauthentic teleport option on the ring of wealth +ring_of_wealth_teleport = false +#enables second bank +second_bank = false +#enables inauthentic but non-dangerous commands for regular players +player_commands = false +#enables boosted rewards from fishing trawler consisting of key halves and pirate outfit pieces +boosted_trawler_rewards = false [paths] #path to the data folder, which contains the cache subfolder and such From 6241ff9ce5cca8fdb4f1cb9086842807bceae9db Mon Sep 17 00:00:00 2001 From: dam <27978131-real_damighty@users.noreply.gitlab.com> Date: Tue, 25 Nov 2025 15:14:39 +0200 Subject: [PATCH 101/117] Improved pathfinding to handle cases when both player and target are moving --- .../core/game/interaction/MovementPulse.java | 76 ++++++++++--------- 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/Server/src/main/core/game/interaction/MovementPulse.java b/Server/src/main/core/game/interaction/MovementPulse.java index 04c9d2a8d..3eb7c43bd 100644 --- a/Server/src/main/core/game/interaction/MovementPulse.java +++ b/Server/src/main/core/game/interaction/MovementPulse.java @@ -11,6 +11,7 @@ import core.game.world.GameWorld; import core.game.world.map.Direction; import core.game.world.map.Location; import core.game.world.map.Point; +import core.game.world.map.RegionManager; import core.game.world.map.path.Path; import core.game.world.map.path.Pathfinder; import core.net.packet.PacketRepository; @@ -380,44 +381,49 @@ public abstract class MovementPulse extends Pulse { return canMove; } - private Location checkForEntityPathInterrupt(Location loc) { - Location ml = mover.getLocation(); - Location dl = destination.getLocation(); - // Lead the target if they're walking/running, unless they're already within interaction range - if(loc != null && destination instanceof Entity) { - WalkingQueue wq = ((Entity)destination).getWalkingQueue(); - if(wq.hasPath()) { - Point[] points = wq.getQueue().toArray(new Point[0]); - if(points.length > 0) { - Point p = points[0]; - Point predictiveIntersection = null; - for(int i=0; i 0) { + Point p = points[0]; + Point predictiveIntersection = null; + for (int i = 0; i < points.length; i++) { + Location closestBorder = getClosestBorderToPoint(points[i], loc.getZ()); - int moverDist = Math.max(Math.abs(ml.getX() - closestBorder.getX()), Math.abs(ml.getY() - closestBorder.getY())); - float movementRatio = moverDist / (float) ((i + 1) / (mover.getWalkingQueue().isRunning() ? 2 : 1)); - if (predictiveIntersection == null && movementRatio <= 1.0) { //try to predict an intersection point on the path if possible - predictiveIntersection = points[i]; - break; - } + if (!RegionManager.isTeleportPermitted(closestBorder)) { // A nasty hack to discard invalid intersection points + continue; + } + int moverDist = Math.max(Math.abs(ml.getX() - closestBorder.getX()), Math.abs(ml.getY() - closestBorder.getY())); + float movementRatio = moverDist / (float) ((i + 1) / (mover.getWalkingQueue().isRunning() ? 2 : 1)); + if (predictiveIntersection == null && movementRatio <= 1.0) { //try to predict an intersection point on the path if possible + predictiveIntersection = points[i]; + break; + } + // Otherwise, we target the farthest point along target's planned movement that's within 1 tick's running, + // this ensures the player will run to catch up to the target if able. + if (moverDist <= 2) { + p = points[i]; + } + } + if (predictiveIntersection != null) + p = predictiveIntersection; - // Otherwise, we target the farthest point along target's planned movement that's within 1 tick's running, - // this ensures the player will run to catch up to the target if able. - if(moverDist <= 2) { - p = points[i]; - } - } + Location endLoc = getClosestBorderToPoint(p, loc.getZ()); - if (predictiveIntersection != null) - p = predictiveIntersection; - - Location endLoc = getClosestBorderToPoint(p, loc.getZ()); - return endLoc; - } - } - } - return loc; - } + if (!RegionManager.isTeleportPermitted(endLoc)) { // Basically a prayer + return loc; + } + return endLoc; + } + } + } + return loc; + } private Location getClosestBorderToPoint (Point p, int plane) { Vector pathDiff = Vector.betweenLocs (destination.getLocation(), Location.create(p.getX(), p.getY(), plane)); From 56986ad6bc719874d092418343fd8a54bfc683a7 Mon Sep 17 00:00:00 2001 From: Kennynes Date: Tue, 25 Nov 2025 13:16:47 +0000 Subject: [PATCH 102/117] Corrected rune essence mining behavior and speeds Also corrected various mining messages across many rock types to be authentic --- .../shootingstar/ShootingStarMiningPulse.kt | 7 +- .../skill/gather/mining/MiningListener.kt | 80 ++++++++++++++----- 2 files changed, 64 insertions(+), 23 deletions(-) diff --git a/Server/src/main/content/global/activity/shootingstar/ShootingStarMiningPulse.kt b/Server/src/main/content/global/activity/shootingstar/ShootingStarMiningPulse.kt index da17fe79b..9d6591ca6 100644 --- a/Server/src/main/content/global/activity/shootingstar/ShootingStarMiningPulse.kt +++ b/Server/src/main/content/global/activity/shootingstar/ShootingStarMiningPulse.kt @@ -13,6 +13,9 @@ import core.game.world.GameWorld import core.game.world.repository.Repository import core.tools.colorize +// TODO: Shooting stars should roll for bonus gems while mining +// See: https://youtu.be/6OqZ2TGc6fM?si=U8nB5IDQREhWXApD + /** * The pulse used to handle mining shooting stars. */ @@ -53,7 +56,7 @@ class ShootingStarMiningPulse(player: Player?, node: Scenery?, val star: Shootin ShootingStarPlugin.getStoreFile()["isDiscovered"] = star.isDiscovered return player.skills.getLevel(Skills.MINING) >= star.miningLevel } - + if (player.skills.getLevel(Skills.MINING) < star.miningLevel) { player.dialogueInterpreter.sendDialogue("You need a Mining level of at least " + star.miningLevel + " in order to mine this layer.") return false @@ -130,7 +133,7 @@ class ShootingStarMiningPulse(player: Player?, node: Scenery?, val star: Shootin override fun message(type: Int) { when (type) { - 0 -> player.packetDispatch.sendMessage("You swing your pickaxe at the rock...") + 0 -> player.packetDispatch.sendMessage("You swing your pickaxe at the rock.") } } diff --git a/Server/src/main/content/global/skill/gather/mining/MiningListener.kt b/Server/src/main/content/global/skill/gather/mining/MiningListener.kt index dffafdb7f..ea9de8c49 100644 --- a/Server/src/main/content/global/skill/gather/mining/MiningListener.kt +++ b/Server/src/main/content/global/skill/gather/mining/MiningListener.kt @@ -26,11 +26,11 @@ import org.rs09.consts.Items class MiningListener : InteractionListener { override fun defineListeners() { defineInteraction( - IntType.SCENERY, - MiningNode.values().map { it.id }.toIntArray(), - "mine", - persistent = true, allowedDistance = 1, - handler = ::handleMining + IntType.SCENERY, + MiningNode.values().map { it.id }.toIntArray(), + "mine", + persistent = true, allowedDistance = 1, + handler = ::handleMining ) } private val GEM_REWARDS = arrayOf(ChanceItem(1623, 1, DropFrequency.COMMON), ChanceItem(1621, 1, DropFrequency.COMMON), ChanceItem(1619, 1, DropFrequency.UNCOMMON), ChanceItem(1617, 1, DropFrequency.RARE)) @@ -38,25 +38,25 @@ class MiningListener : InteractionListener { private fun handleMining(player: Player, node: Node, state: Int) : Boolean { val resource = MiningNode.forId(node.id) val tool = SkillingTool.getPickaxe(player) - val isEssence = resource.id == 2491 + val isEssence = resource == MiningNode.RUNE_ESSENCE val isGems = resource.identifier == MiningNode.GEM_ROCK_0.identifier if (!finishedMoving(player)) return true if (state == 0) { - if (!checkRequirements(player, resource, node)) { - player.scripts.reset() - return true - } + if (!checkRequirements(player, resource, node)) { + player.scripts.reset() + return true + } anim(player, tool) - sendMessage(player, "You swing your pickaxe at the rock...") - return delayScript(player, getDelay(resource)) + sendMessage(player, "You swing your pickaxe at the rock.") + return delayScript(player, getDelay(resource, tool)) } anim(player, tool) if (!checkReward(player, resource, tool)) - return delayScript(player, getDelay(resource)) + return delayScript(player, getDelay(resource, tool)) // Reward logic var reward = resource!!.reward @@ -94,13 +94,23 @@ class MiningListener : InteractionListener { if (reward == Items.GOLD_ORE_444 && inBorders(player, familyCrestGoldOreArea)) { reward = Items.PERFECT_GOLD_ORE_446 } - val rewardName = getItemName(reward).lowercase() + + // Prepare reward name - gems stay lowercase without "Uncut", others are lowercase + val rewardName = if (isGems) { + getItemName(reward).replace("Uncut ", "").lowercase() + } else { + getItemName(reward).substringBefore(" (").lowercase().removeSuffix(" ore") + } // Send the message for the resource reward if (isGems) { - sendMessage(player, "You get ${prependArticle(rewardName)}.") - } else { - sendMessage(player, "You get some ${rewardName.lowercase()}.") + val withArticle = prependArticle(rewardName) // "an emerald" or "a red topaz" + val parts = withArticle.split(" ", limit = 2) // ["an", "emerald"] or ["a", "red topaz"] + val article = parts[0] // "an" or "a" + val gemName = parts[1].replaceFirstChar { it.uppercase() } // "Emerald" or "Red topaz" + sendMessage(player, "You just mined $article $gemName!") + } else if (!isEssence) { + sendMessage(player, "You manage to mine some ${rewardName}.") } // Give the mining reward, increment 'rocks mined' attribute @@ -124,7 +134,7 @@ class MiningListener : InteractionListener { } if (RandomFunction.roll(chance)) { val gem = GEM_REWARDS.random() - sendMessage(player,"You find a ${gem.name}!") + sendMessage(player,"You just found ${prependArticle(gem.name.replace("Uncut ", "").lowercase())}!") if (freeSlots(player) == 0) { sendMessage(player,"You do not have enough space in your inventory, so you drop the gem on the floor.") } @@ -138,6 +148,14 @@ class MiningListener : InteractionListener { node.setActive(false) return true } + + // For essence, check inventory and continue mining + if (isEssence) { + if (freeSlots(player) == 0) { + return true + } + return delayScript(player, getDelay(resource, tool)) + } } return true } @@ -189,6 +207,10 @@ class MiningListener : InteractionListener { } private fun checkReward(player: Player, resource: MiningNode?, tool: SkillingTool): Boolean { + // Essence mining always succeeds + if (resource == MiningNode.RUNE_ESSENCE) { + return true + } val level = 1 + getDynLevel(player, Skills.MINING) + getFamiliarBoost(player, Skills.MINING) val hostRatio = Math.random() * (100.0 * resource!!.rate) var toolRatio = tool.ratio @@ -199,8 +221,20 @@ class MiningListener : InteractionListener { return hostRatio < clientRatio } - fun getDelay(resource: MiningNode) : Int { - return if (resource.id == 2491) 3 else 4 + fun getDelay(resource: MiningNode, tool: SkillingTool) : Int { + if (resource == MiningNode.RUNE_ESSENCE) { // Essence mining - speed varies by pickaxe + return when (tool) { + SkillingTool.BRONZE_PICKAXE -> 7 + SkillingTool.IRON_PICKAXE -> 6 + SkillingTool.STEEL_PICKAXE -> 5 + SkillingTool.MITHRIL_PICKAXE -> 4 + SkillingTool.ADAMANT_PICKAXE -> 3 + SkillingTool.RUNE_PICKAXE -> 2 + SkillingTool.INFERNO_ADZE2 -> if (RandomFunction.random(2) == 0) 1 else 2 //https://www.youtube.com/watch?v=9XhjSdJ4qro + else -> 4 // fallback + } + } + return 4 // normal rocks } fun anim(player: Player, tool: SkillingTool) { @@ -222,9 +256,13 @@ class MiningListener : InteractionListener { sendDialogue(player,"Your inventory is too full to hold any more gems.") return false } + if (resource == MiningNode.RUNE_ESSENCE) { + sendDialogue(player,"Your inventory is too full to hold any more essence.") + return false + } sendDialogue(player,"Your inventory is too full to hold any more ${ItemDefinition.forId(resource!!.reward).name.lowercase()}.") return false } return node.isActive } -} +} \ No newline at end of file From feb2b10247508362d4a1c691091e622336de7bad Mon Sep 17 00:00:00 2001 From: Poseidon Date: Thu, 27 Nov 2025 06:10:23 -0500 Subject: [PATCH 103/117] Fixed Ghost Disciple dialogue typo at Ectofuntus --- .../region/morytania/phas/dialogue/GhostDiscipleDialogue.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Server/src/main/content/region/morytania/phas/dialogue/GhostDiscipleDialogue.java b/Server/src/main/content/region/morytania/phas/dialogue/GhostDiscipleDialogue.java index 23d8093fe..40f395cf4 100644 --- a/Server/src/main/content/region/morytania/phas/dialogue/GhostDiscipleDialogue.java +++ b/Server/src/main/content/region/morytania/phas/dialogue/GhostDiscipleDialogue.java @@ -82,13 +82,13 @@ public final class GhostDiscipleDialogue extends DialoguePlugin { stage++; break; case 1: - options("What is the Ectofunuts?", "Where do I get ectoplasm from?", "How do I grind bones?", "How do I receive Ectotokens?", "Thanks for your time."); + options("What is the Ectofuntus?", "Where do I get ectoplasm from?", "How do I grind bones?", "How do I receive Ectotokens?", "Thanks for your time."); stage++; break; case 2: switch (buttonId) { case 1: - player("What is the Ectofunuts?"); + player("What is the Ectofuntus?"); stage = 10; break; case 2: From 3d5cbd90edd4c95f33a0ed5f78e888c872334d18 Mon Sep 17 00:00:00 2001 From: Player Name Date: Thu, 27 Nov 2025 11:44:43 +0000 Subject: [PATCH 104/117] Improved random event kidnapping logic Rewrote home teleport Corrected shades and zombies to only multiply xp by 1/16 if they were random events Unified non-hostile random event chat mechanics Fixed random events repeating their opening message and/or saying their timeout message prematurely Genie will now authentically address female players as 'Mistress' Fixed hostile randoms wrongly teleporting the player after 3 minutes Made quizmaster dialogues unclosable and added a hook to restart them if you lose it Made the pillory dialog unclosable Implemented rock golem switch between ranged and melee Replaced sandwich lady chat lines with authentic quotes Added authentic dialogue for interacting with another player's sandwich lady Added a teleport block for the surprise exam random event --- .../main/content/global/ame/KidnapHelper.kt | 49 ++++++- .../main/content/global/ame/RandomEventNPC.kt | 24 +++- .../main/content/global/ame/RandomEvents.kt | 1 - .../ame/events/HostileRandomEventBehavior.kt | 7 +- .../ame/events/candlelight/PiousPeteNPC.kt | 31 +---- .../global/ame/events/certer/CerterNPC.kt | 37 +++--- .../ame/events/drilldemon/DrillDemonUtils.kt | 11 -- .../events/drilldemon/SergeantDamienNPC.kt | 28 ++-- .../events/drunkendwarf/DrunkenDwarfNPC.kt | 36 +++-- .../global/ame/events/evilbob/EvilBobNPC.kt | 32 +---- .../global/ame/events/evilbob/EvilBobUtils.kt | 8 -- .../ame/events/freakyforester/FreakUtils.kt | 7 - .../freakyforester/FreakyForesterNPC.kt | 30 +---- .../global/ame/events/genie/GenieNPC.kt | 21 ++- .../content/global/ame/events/maze/MazeNPC.kt | 49 ++----- .../global/ame/events/pillory/PilloryNPC.kt | 35 +---- .../quizmaster/QuizMasterDialogueFile.kt | 125 +++++++++++------- .../ame/events/quizmaster/QuizMasterNPC.kt | 42 ++---- .../ame/events/rockgolem/RockGolemBehavior.kt | 25 ++++ .../events/sandwichlady/SandwichLadyRENPC.kt | 31 ++++- .../surpriseexam/MysteriousOldManNPC.kt | 22 +-- .../surpriseexam/SupriseExamListeners.kt | 3 +- .../events/surpriseexam/SurpriseExamUtils.kt | 3 - .../global/handlers/npc/NPCTalkListener.kt | 12 +- .../global/skill/magic/HomeTeleportHelper.kt | 46 +++++++ .../magic/ancient/AncientTeleportPlugin.java | 14 +- .../skill/magic/lunar/LunarListeners.kt | 3 +- .../skill/magic/modern/ModernListeners.kt | 5 +- .../core/game/node/entity/player/Player.java | 16 +-- .../entity/player/link/TeleportManager.java | 98 +------------- 30 files changed, 381 insertions(+), 470 deletions(-) create mode 100644 Server/src/main/content/global/ame/events/rockgolem/RockGolemBehavior.kt create mode 100644 Server/src/main/content/global/skill/magic/HomeTeleportHelper.kt diff --git a/Server/src/main/content/global/ame/KidnapHelper.kt b/Server/src/main/content/global/ame/KidnapHelper.kt index 6fa40c6d1..716fb6277 100644 --- a/Server/src/main/content/global/ame/KidnapHelper.kt +++ b/Server/src/main/content/global/ame/KidnapHelper.kt @@ -2,22 +2,57 @@ package content.global.ame import core.ServerConstants import core.api.* +import core.game.interaction.QueueStrength +import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.node.entity.player.link.TeleportManager.TeleportType import core.game.world.map.Location +import core.game.world.update.flag.context.Graphics +import org.rs09.consts.Sounds -fun kidnapPlayer(player: Player, loc: Location, type: TeleportType) { - setAttribute(player, "kidnapped-by-random", true) //only used in POH code when you leave the hut, so does not need /save. Do not rely on this outside of its intended POH use case. - if (getAttribute(player, "/save:original-loc", null) == null) { - setAttribute(player, "/save:original-loc", player.location) +fun kidnapPlayer(npc: NPC, player: Player, dest: Location, playerLine: String? = null, callback: (player: Player, npc: NPC) -> Unit) { + val lockDuration = if (playerLine != null) 4 else 6 + lock(player, lockDuration) + queueScript(player, 1, QueueStrength.SOFT) { stage: Int -> + when (stage) { + 0 -> { + if (playerLine != null) { + sendChat(player, playerLine) + return@queueScript delayScript(player, 2) + } + return@queueScript delayScript(player, 0) + } + 1 -> { + sendGraphics(Graphics(1576, 0, 0), player.location) + animate(player,8939) + playAudio(player, Sounds.TELEPORT_ALL_200) + return@queueScript delayScript(player, 3) + } + 2 -> { + setAttribute(player, "kidnapped-by-random", true) + if (getAttribute(player, "/save:original-loc", null) == null) { + setAttribute(player, "/save:original-loc", player.location) + } + teleport(player, dest, TeleportType.INSTANT) + sendGraphics(Graphics(1577, 0, 0), player.location) + animate(player, 8941) + resetAnimator(player) + callback(player, npc) + return@queueScript delayScript(player, 2) + } + 3 -> { + removeAttribute(player, "kidnapped-by-random") //this is not needed at this point anymore and will reenable the original-loc sanity check tick action + return@queueScript stopExecuting(player) + } + else -> return@queueScript stopExecuting(player) + } } - teleport(player, loc, type) } fun returnPlayer(player: Player) { player.locks.unlockTeleport() - val destination = getAttribute(player, "/save:original-loc", ServerConstants.HOME_LOCATION ?: Location.create(3222, 3218, 0)) - teleport(player, destination) + val destination = getAttribute(player, "/save:original-loc", ServerConstants.HOME_LOCATION) + teleport(player, destination!!) unlock(player) removeAttributes(player, "/save:original-loc", "kidnapped-by-random") } diff --git a/Server/src/main/content/global/ame/RandomEventNPC.kt b/Server/src/main/content/global/ame/RandomEventNPC.kt index b03a075f9..b4da55bb3 100644 --- a/Server/src/main/content/global/ame/RandomEventNPC.kt +++ b/Server/src/main/content/global/ame/RandomEventNPC.kt @@ -4,6 +4,7 @@ import content.global.ame.events.surpriseexam.MysteriousOldManNPC import core.api.playGlobalAudio import core.api.poofClear import core.api.sendMessage +import core.api.setAttribute import core.api.utils.WeightBasedTable import core.game.interaction.MovementPulse import core.game.node.entity.Entity @@ -19,8 +20,10 @@ import core.game.world.map.RegionManager import core.game.world.map.path.Pathfinder import core.game.world.update.flag.context.Graphics import core.integrations.discord.Discord +import core.tools.RandomFunction import core.tools.secondsToTicks import core.tools.ticksToCycles +import org.rs09.consts.NPCs import org.rs09.consts.Sounds import kotlin.math.ceil import kotlin.math.min @@ -43,6 +46,7 @@ abstract class RandomEventNPC(id: Int) : NPC(id) { event.loot = loot event.player = player event.spawnLocation = RegionManager.getSpawnLocation(player, this) + setAttribute(event, "spawned-by-ame", true) return event } @@ -101,7 +105,6 @@ abstract class RandomEventNPC(id: Int) : NPC(id) { } open fun onTimeUp() { - noteAndTeleport() terminate() } @@ -144,4 +147,23 @@ abstract class RandomEventNPC(id: Int) : NPC(id) { val index = min(ids.size, ceil(player.properties.currentCombatLevel / 20.0).toInt()) - 1 return ids[index] } + + fun sayLine(npc: NPC, phrases: Array, hasOpeningPhrase: Boolean, hasOverTimePhrase: Boolean) { + if (!timerPaused && (ticksLeft % 20 == 0 || ticksLeft <= 2)) { //unless the Certer interface is up, speak every 20 ticks, or in the 2nd-to-last tick before attack/note-&-teleport + var playDwarfWhistle = true + if (ticksLeft == secondsToTicks(180) && hasOpeningPhrase) { + sendChat(phrases[0]) + } else if (ticksLeft <= 2 && hasOverTimePhrase) { + sendChat(phrases[phrases.size - 1]) + playDwarfWhistle = false + } else { + val start = if (hasOpeningPhrase) 0 else 1 + val end = if (hasOverTimePhrase) phrases.size - 2 else phrases.size - 1 + sendChat(phrases[RandomFunction.random(start, end + 1)]) + } + if (npc.id == NPCs.DRUNKEN_DWARF_956 && playDwarfWhistle) { + playGlobalAudio(this.location, Sounds.DWARF_WHISTLE_2297) + } + } + } } diff --git a/Server/src/main/content/global/ame/RandomEvents.kt b/Server/src/main/content/global/ame/RandomEvents.kt index 88522b4a0..db5ad9b2e 100644 --- a/Server/src/main/content/global/ame/RandomEvents.kt +++ b/Server/src/main/content/global/ame/RandomEvents.kt @@ -97,5 +97,4 @@ enum class RandomEvents(val npc: RandomEventNPC, val loot: WeightBasedTable? = n } } } - } diff --git a/Server/src/main/content/global/ame/events/HostileRandomEventBehavior.kt b/Server/src/main/content/global/ame/events/HostileRandomEventBehavior.kt index 37083b045..952bd0289 100644 --- a/Server/src/main/content/global/ame/events/HostileRandomEventBehavior.kt +++ b/Server/src/main/content/global/ame/events/HostileRandomEventBehavior.kt @@ -1,5 +1,6 @@ package content.global.ame.events +import core.api.getAttribute import core.game.node.entity.Entity import core.game.node.entity.npc.NPC import core.game.node.entity.npc.NPCBehavior @@ -8,12 +9,12 @@ import org.rs09.consts.NPCs class HostileRandomEventBehavior : NPCBehavior( NPCs.EVIL_CHICKEN_2463, NPCs.EVIL_CHICKEN_2464, NPCs.EVIL_CHICKEN_2465, NPCs.EVIL_CHICKEN_2466, NPCs.EVIL_CHICKEN_2467, NPCs.EVIL_CHICKEN_2468, NPCs.RIVER_TROLL_391, NPCs.RIVER_TROLL_392, NPCs.RIVER_TROLL_393, NPCs.RIVER_TROLL_394, NPCs.RIVER_TROLL_395, NPCs.RIVER_TROLL_396, - NPCs.ROCK_GOLEM_413, NPCs.ROCK_GOLEM_414, NPCs.ROCK_GOLEM_415, NPCs.ROCK_GOLEM_416, NPCs.ROCK_GOLEM_417, NPCs.ROCK_GOLEM_418, NPCs.SHADE_425, NPCs.SHADE_426, NPCs.SHADE_427, NPCs.SHADE_428, NPCs.SHADE_429, NPCs.SHADE_430, NPCs.SHADE_431, NPCs.TREE_SPIRIT_438, NPCs.TREE_SPIRIT_439, NPCs.TREE_SPIRIT_440, NPCs.TREE_SPIRIT_441, NPCs.TREE_SPIRIT_442, NPCs.TREE_SPIRIT_443, NPCs.ZOMBIE_419, NPCs.ZOMBIE_420, NPCs.ZOMBIE_421, NPCs.ZOMBIE_422, NPCs.ZOMBIE_423, NPCs.ZOMBIE_424 ) { override fun getXpMultiplier(self: NPC, attacker: Entity): Double { - return super.getXpMultiplier(self, attacker) / 16.0 + val xprate = super.getXpMultiplier(self, attacker) + return if (getAttribute(self, "spawned-by-ame", false)) xprate / 16.0 else xprate } -} +} \ No newline at end of file diff --git a/Server/src/main/content/global/ame/events/candlelight/PiousPeteNPC.kt b/Server/src/main/content/global/ame/events/candlelight/PiousPeteNPC.kt index 70cf1e11c..5231b6f9c 100644 --- a/Server/src/main/content/global/ame/events/candlelight/PiousPeteNPC.kt +++ b/Server/src/main/content/global/ame/events/candlelight/PiousPeteNPC.kt @@ -4,42 +4,19 @@ import content.global.ame.RandomEventNPC import content.global.ame.kidnapPlayer import core.api.* import core.api.utils.WeightBasedTable -import core.game.interaction.QueueStrength import core.game.node.entity.npc.NPC -import core.game.node.entity.player.link.TeleportManager import core.game.world.map.Location -import core.game.world.update.flag.context.Graphics import org.rs09.consts.NPCs -import org.rs09.consts.Sounds -/** "::revent -p player_name -e candlelight" **/ class PiousPeteNPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.PRIEST_3206) { - override fun init() { super.init() // Supposed to be "I'm sorry to drag you away from your tasks, but I need a little help with something." but it's too goddamn long. - sendChat("${player.username.capitalize()}! I need a little help with something.") + sendChat("${player.username}! I need a little help with something.") face(player) - queueScript(player, 4, QueueStrength.SOFT) { stage: Int -> - when (stage) { - 0 -> { - lock(player, 6) - sendGraphics(Graphics(1576, 0, 0), player.location) - animate(player,8939) - playAudio(player, Sounds.TELEPORT_ALL_200) - return@queueScript delayScript(player, 3) - } - 1 -> { - CandlelightInterface.initCandlelight(player) - kidnapPlayer(player, Location(1972, 5002, 0), TeleportManager.TeleportType.INSTANT) - // AntiMacro.terminateEventNpc(player) - sendGraphics(Graphics(1577, 0, 0), player.location) - animate(player,8941) - openDialogue(player, PiousPeteStartingDialogueFile(), NPC(NPCs.PIOUS_PETE_3207)) - return@queueScript stopExecuting(player) - } - else -> return@queueScript stopExecuting(player) - } + kidnapPlayer(this, player, Location(1972, 5002, 0)) { player, _ -> + CandlelightInterface.initCandlelight(player) + openDialogue(player, PiousPeteStartingDialogueFile(), NPC(NPCs.PIOUS_PETE_3207)) } } diff --git a/Server/src/main/content/global/ame/events/certer/CerterNPC.kt b/Server/src/main/content/global/ame/events/certer/CerterNPC.kt index daf3bc822..20b172404 100644 --- a/Server/src/main/content/global/ame/events/certer/CerterNPC.kt +++ b/Server/src/main/content/global/ame/events/certer/CerterNPC.kt @@ -2,29 +2,19 @@ package content.global.ame.events.certer import core.game.node.entity.npc.NPC import core.game.node.entity.player.link.emote.Emotes -import core.tools.RandomFunction import org.rs09.consts.NPCs import content.global.ame.RandomEventNPC import core.api.animate +import core.api.lock import core.api.utils.WeightBasedTable class CerterNPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.GILES_2538) { - lateinit var pName: String lateinit var phrases: Array override fun tick() { - // Don't speak if we have the interface opened - if (!timerPaused) { - // Over allotted time phrase - if (ticksLeft <= 2) { - player.lock(2) - sendChat(phrases[4]) - - // Say a phrase every 20 ticks starting at 280 ticks - // as to not interfere with the init chat phrase - } else if (ticksLeft <= 280 && ticksLeft % 20 == 0) { - sendChat(phrases[RandomFunction.random(1, 3)]) - } + sayLine(this, phrases, true, true) + if (ticksLeft == 2) { + lock(player, 2) } super.tick() } @@ -36,15 +26,20 @@ class CerterNPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(NP override fun init() { super.init() - pName = player.username.capitalize() - phrases = arrayOf("Greetings $pName, I need your help.", - "ehem... Hello $pName, please talk to me!", - "Hello, are you there $pName?", - "It's really rude to ignore someone, $pName!", - "No-one ignores me!") + phrases = arrayOf( + "Greetings ${player.username}, I need your help.", + "ehem... Hello ${player.username}, please talk to me!", + "Hello, are you there ${player.username}?", + "It's really rude to ignore someone, ${player.username}!", + "No-one ignores me!" + ) player.setAttribute("random:pause", false) player.setAttribute("certer:reward", false) - sendChat(phrases[0]) animate(this, Emotes.BOW.animation, true) } + + override fun onTimeUp() { + noteAndTeleport() + terminate() + } } \ No newline at end of file diff --git a/Server/src/main/content/global/ame/events/drilldemon/DrillDemonUtils.kt b/Server/src/main/content/global/ame/events/drilldemon/DrillDemonUtils.kt index a6638fecb..8c90bd77f 100644 --- a/Server/src/main/content/global/ame/events/drilldemon/DrillDemonUtils.kt +++ b/Server/src/main/content/global/ame/events/drilldemon/DrillDemonUtils.kt @@ -1,12 +1,9 @@ package content.global.ame.events.drilldemon -import content.global.ame.kidnapPlayer import content.global.ame.returnPlayer import core.api.* import core.game.interaction.QueueStrength import core.game.node.entity.player.Player -import core.game.node.entity.player.link.TeleportManager -import core.game.world.map.Location import core.game.world.map.zone.ZoneBorders import core.game.world.update.flag.context.Animation import org.rs09.consts.Items @@ -24,13 +21,6 @@ object DrillDemonUtils { val DD_AREA = ZoneBorders(3158, 4817, 3168, 4823) val DD_NPC = NPCs.SERGEANT_DAMIEN_2790 - fun teleport(player: Player) { - kidnapPlayer(player, Location.create(3163, 4819, 0), TeleportManager.TeleportType.INSTANT) - player.interfaceManager.closeDefaultTabs() - setComponentVisibility(player, 548, 69, true) - setComponentVisibility(player, 746, 12, true) - } - fun changeSignsAndAssignTask(player: Player) { setVarp(player, DD_SIGN_VARP, 0) val tempList = arrayListOf(DD_SIGN_JOG, DD_SIGN_JUMP, DD_SIGN_PUSHUP, DD_SIGN_SITUP).shuffled().toMutableList() @@ -94,6 +84,5 @@ object DrillDemonUtils { } return@queueScript stopExecuting(player) } - } } diff --git a/Server/src/main/content/global/ame/events/drilldemon/SergeantDamienNPC.kt b/Server/src/main/content/global/ame/events/drilldemon/SergeantDamienNPC.kt index af4e8fe3c..2a3ec7f51 100644 --- a/Server/src/main/content/global/ame/events/drilldemon/SergeantDamienNPC.kt +++ b/Server/src/main/content/global/ame/events/drilldemon/SergeantDamienNPC.kt @@ -3,32 +3,22 @@ package content.global.ame.events.drilldemon import core.game.node.entity.npc.NPC import org.rs09.consts.NPCs import content.global.ame.RandomEventNPC +import content.global.ame.kidnapPlayer import core.api.* import core.api.utils.WeightBasedTable -import core.game.interaction.QueueStrength -import core.game.system.timer.impl.AntiMacro -import core.tools.secondsToTicks +import core.game.world.map.Location class SergeantDamienNPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.SERGEANT_DAMIEN_2790) { - override fun init() { super.init() - sendChat(player.username+ "! Drop and give me 20!") - queueScript(player, 4, QueueStrength.SOFT) { stage: Int -> - when (stage) { - 0 -> { - lock(player, secondsToTicks(30)) - DrillDemonUtils.teleport(player) - AntiMacro.terminateEventNpc(player) - return@queueScript delayScript(player, 2) - } - 1 -> { - openDialogue(player, SeargentDamienDialogue(isCorrect = true, eventStart = true), NPCs.SERGEANT_DAMIEN_2790) - return@queueScript stopExecuting(player) - } - else -> return@queueScript stopExecuting(player) - } + sendChat("${player.username}! Drop and give me 20!") + face(player) + kidnapPlayer(this, player, Location(3163, 4819, 0)) { player, _ -> + player.interfaceManager.closeDefaultTabs() + setComponentVisibility(player, 548, 69, true) + setComponentVisibility(player, 746, 12, true) + openDialogue(player, SeargentDamienDialogue(isCorrect = true, eventStart = true), NPCs.SERGEANT_DAMIEN_2790) } } diff --git a/Server/src/main/content/global/ame/events/drunkendwarf/DrunkenDwarfNPC.kt b/Server/src/main/content/global/ame/events/drunkendwarf/DrunkenDwarfNPC.kt index 1485dd8c4..f4a6842ff 100644 --- a/Server/src/main/content/global/ame/events/drunkendwarf/DrunkenDwarfNPC.kt +++ b/Server/src/main/content/global/ame/events/drunkendwarf/DrunkenDwarfNPC.kt @@ -9,35 +9,27 @@ import org.rs09.consts.NPCs import org.rs09.consts.Sounds class DrunkenDwarfNPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.DRUNKEN_DWARF_956) { - private val phrases = arrayOf("Oi, are you der @name!","Dunt ignore your matey!","Aww comeon, talk to ikle me @name!") - private var attackPhrase = false + lateinit var phrases: Array private var attackDelay = 0 - private var lastPhraseTime = 0 - - private fun sendPhrases() { - if (getWorldTicks() > lastPhraseTime + 5) { - playGlobalAudio(this.location, Sounds.DWARF_WHISTLE_2297) - sendChat(this, phrases.random().replace("@name",player.username.capitalize())) - this.face(player) - lastPhraseTime = getWorldTicks() - } - } override fun init() { super.init() - playGlobalAudio(this.location, Sounds.DWARF_WHISTLE_2297) - sendChat(this, "'Ello der ${player.username.capitalize()}! *hic*") + phrases = arrayOf( + "'Ello der ${player.username}! *hic*", + "Oi, are you der ${player.username}!", + "Dunt ignore your matey!", + "Aww comeon, talk to ikle me ${player.username}!", + "I hates you, ${player.username}!" + ) } override fun tick() { - if (RandomFunction.roll(20) && !attackPhrase) - sendPhrases() + sayLine(this, phrases, true, true) if (ticksLeft <= 10) { ticksLeft = 10 - if (!attackPhrase) - sendChat("I hates you, ${player.username.capitalize()}!").also { attackPhrase = true } - if (attackDelay <= getWorldTicks()) + if (attackDelay <= getWorldTicks()) { this.attack(player) + } } super.tick() } @@ -47,4 +39,10 @@ class DrunkenDwarfNPC(override var loot: WeightBasedTable? = null) : RandomEvent this.pulseManager.clear() openDialogue(player, DrunkenDwarfDialogue(), this.asNpc()) } + + override fun onTimeUp() { + if (attackDelay <= getWorldTicks()) { + this.attack(player) + } + } } \ No newline at end of file diff --git a/Server/src/main/content/global/ame/events/evilbob/EvilBobNPC.kt b/Server/src/main/content/global/ame/events/evilbob/EvilBobNPC.kt index fc0fe2826..4995b6b41 100644 --- a/Server/src/main/content/global/ame/events/evilbob/EvilBobNPC.kt +++ b/Server/src/main/content/global/ame/events/evilbob/EvilBobNPC.kt @@ -1,40 +1,22 @@ package content.global.ame.events.evilbob import content.global.ame.RandomEventNPC +import content.global.ame.kidnapPlayer import core.api.* import core.api.utils.WeightBasedTable -import core.game.interaction.QueueStrength import core.game.node.entity.npc.NPC -import core.game.system.timer.impl.AntiMacro +import core.game.world.map.Location import org.rs09.consts.NPCs -import org.rs09.consts.Sounds class EvilBobNPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.EVIL_BOB_2478) { - override fun init() { super.init() sendChat("meow") - queueScript(player, 4, QueueStrength.SOFT) { stage: Int -> - when (stage) { - 0 -> { - lock(player, 6) - sendChat(player, "No... what? Nooooooooooooo!") - animate(player, EvilBobUtils.teleAnim) - player.graphics(EvilBobUtils.telegfx) - playAudio(player, Sounds.TELEPORT_ALL_200) - EvilBobUtils.giveEventFishingSpot(player) - return@queueScript delayScript(player, 3) - } - 1 -> { - sendMessage(player, "Welcome to Scape2009.") - EvilBobUtils.teleport(player) - resetAnimator(player) - openDialogue(player, EvilBobDialogue(), NPCs.EVIL_BOB_2479) - AntiMacro.terminateEventNpc(player) - return@queueScript stopExecuting(player) - } - else -> return@queueScript stopExecuting(player) - } + face(player) + kidnapPlayer(this, player, Location(3419, 4776, 0), "No... what? Nooooooooooooo!") { player, _ -> + EvilBobUtils.giveEventFishingSpot(player) + sendMessage(player, "Welcome to Scape2009.") + openDialogue(player, EvilBobDialogue(), NPCs.EVIL_BOB_2479) } } diff --git a/Server/src/main/content/global/ame/events/evilbob/EvilBobUtils.kt b/Server/src/main/content/global/ame/events/evilbob/EvilBobUtils.kt index d70eab940..58f8e5aee 100644 --- a/Server/src/main/content/global/ame/events/evilbob/EvilBobUtils.kt +++ b/Server/src/main/content/global/ame/events/evilbob/EvilBobUtils.kt @@ -1,12 +1,8 @@ package content.global.ame.events.evilbob -import content.global.ame.kidnapPlayer -import content.global.ame.returnPlayer import core.api.* import core.game.node.entity.player.Player -import core.game.node.entity.player.link.TeleportManager import core.game.node.entity.skill.Skills -import core.game.world.map.Location import core.game.world.map.zone.ZoneBorders import core.game.world.update.flag.context.Animation import core.game.world.update.flag.context.Graphics @@ -53,10 +49,6 @@ object EvilBobUtils { } } - fun teleport(player: Player) { - kidnapPlayer(player, Location.create(3419, 4776, 0), TeleportManager.TeleportType.INSTANT) - } - fun cleanup(player: Player) { removeAttributes(player, assignedFishingZone, eventComplete, attentive, servantHelpDialogueSeen, attentiveNewSpot, startingDialogueSeen) removeAll(player, Items.FISHLIKE_THING_6202) diff --git a/Server/src/main/content/global/ame/events/freakyforester/FreakUtils.kt b/Server/src/main/content/global/ame/events/freakyforester/FreakUtils.kt index e247bfeb1..84e491f89 100644 --- a/Server/src/main/content/global/ame/events/freakyforester/FreakUtils.kt +++ b/Server/src/main/content/global/ame/events/freakyforester/FreakUtils.kt @@ -1,13 +1,10 @@ package content.global.ame.events.freakyforester -import content.global.ame.kidnapPlayer import content.global.ame.returnPlayer import core.api.* import org.rs09.consts.Items import org.rs09.consts.NPCs import core.game.node.entity.player.Player -import core.game.node.entity.player.link.TeleportManager -import core.game.world.map.Location import core.game.world.map.zone.ZoneBorders import core.tools.RandomFunction @@ -28,10 +25,6 @@ object FreakUtils{ player.dialogueInterpreter.open(FreakyForesterDialogue(), freakNpc) } - fun teleport(player: Player) { - kidnapPlayer(player, Location.create(2599, 4777 ,0), TeleportManager.TeleportType.INSTANT) - } - fun cleanup(player: Player) { returnPlayer(player) removeAttributes(player, freakTask, freakComplete, pheasantKilled) diff --git a/Server/src/main/content/global/ame/events/freakyforester/FreakyForesterNPC.kt b/Server/src/main/content/global/ame/events/freakyforester/FreakyForesterNPC.kt index 4b0366263..1ef52dde6 100644 --- a/Server/src/main/content/global/ame/events/freakyforester/FreakyForesterNPC.kt +++ b/Server/src/main/content/global/ame/events/freakyforester/FreakyForesterNPC.kt @@ -1,39 +1,21 @@ package content.global.ame.events.freakyforester import content.global.ame.RandomEventNPC +import content.global.ame.kidnapPlayer import core.api.* import org.rs09.consts.NPCs import core.api.utils.WeightBasedTable -import core.game.interaction.QueueStrength import core.game.node.entity.npc.NPC -import core.game.system.timer.impl.AntiMacro -import core.game.world.update.flag.context.Graphics -import org.rs09.consts.Sounds +import core.game.world.map.Location class FreakyForesterNPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.FREAKY_FORESTER_2458) { - override fun init() { super.init() sendChat("Ah, ${player.username}, just the person I need!") - queueScript(player, 4, QueueStrength.SOFT) { stage: Int -> - when (stage) { - 0 -> { - lock(player, 6) - sendGraphics(Graphics(308, 100, 50), player.location) - animate(player,714) - playAudio(player, Sounds.TELEPORT_ALL_200) - return@queueScript delayScript(player, 3) - } - 1 -> { - FreakUtils.teleport(player) - FreakUtils.giveFreakTask(player) - AntiMacro.terminateEventNpc(player) - openDialogue(player, FreakyForesterDialogue(), FreakUtils.freakNpc) - resetAnimator(player) - return@queueScript stopExecuting(player) - } - else -> return@queueScript stopExecuting(player) - } + face(player) + kidnapPlayer(this, player, Location(2599, 4777, 0)) { player, _ -> + FreakUtils.giveFreakTask(player) + openDialogue(player, FreakyForesterDialogue(), FreakUtils.freakNpc) } } diff --git a/Server/src/main/content/global/ame/events/genie/GenieNPC.kt b/Server/src/main/content/global/ame/events/genie/GenieNPC.kt index 6f3cf39a1..b4700a9a5 100644 --- a/Server/src/main/content/global/ame/events/genie/GenieNPC.kt +++ b/Server/src/main/content/global/ame/events/genie/GenieNPC.kt @@ -4,27 +4,40 @@ import core.game.node.entity.npc.NPC import core.tools.RandomFunction import org.rs09.consts.NPCs import content.global.ame.RandomEventNPC +import core.api.lock import core.api.playAudio import core.api.utils.WeightBasedTable import org.rs09.consts.Sounds class GenieNPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.GENIE_409) { - val phrases = arrayOf("Greetings, @name!","Ehem... Master @name?","Are you there, Master @name?","No one ignores me!") + lateinit var phrases: Array override fun tick() { - if(RandomFunction.random(1,15) == 5){ - sendChat(phrases.random().replace("@name",player.username.capitalize())) + sayLine(this, phrases, true, true) + if (ticksLeft == 2) { + lock(player, 2) } super.tick() } override fun init() { super.init() + val honorific = if (player.isMale) "Master" else "Mistress" + phrases = arrayOf( + "Greetings, ${player.username}!", + "Ehem... $honorific ${player.username}?", + "Are you there, $honorific ${player.username}?", + "No one ignores me!" + ) playAudio(player, Sounds.GENIE_APPEAR_2301) - sendChat(phrases.random().replace("@name",player.username.capitalize())) } override fun talkTo(npc: NPC) { player.dialogueInterpreter.open(GenieDialogue(),npc) } + + override fun onTimeUp() { + noteAndTeleport() + terminate() + } } diff --git a/Server/src/main/content/global/ame/events/maze/MazeNPC.kt b/Server/src/main/content/global/ame/events/maze/MazeNPC.kt index e4befe00e..a51f0b044 100644 --- a/Server/src/main/content/global/ame/events/maze/MazeNPC.kt +++ b/Server/src/main/content/global/ame/events/maze/MazeNPC.kt @@ -4,54 +4,29 @@ import content.global.ame.RandomEventNPC import content.global.ame.kidnapPlayer import core.api.* import core.api.utils.WeightBasedTable -import core.game.interaction.QueueStrength import core.game.node.entity.npc.NPC -import core.game.node.entity.player.link.TeleportManager -import core.game.system.timer.impl.AntiMacro -import core.game.world.map.Location -import core.game.world.map.build.DynamicRegion -import core.game.world.update.flag.context.Graphics import org.rs09.consts.NPCs -import org.rs09.consts.Sounds class MazeNPC(var type: String = "", override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.MYSTERIOUS_OLD_MAN_410) { - override fun init() { super.init() sendChat("Aha, you'll do ${player.username}!") face(player) - queueScript(player, 4, QueueStrength.SOFT) { stage: Int -> - when (stage) { - 0 -> { - lock(player, 6) - sendGraphics(Graphics(1576, 0, 0), player.location) - animate(player,8939) - playAudio(player, Sounds.TELEPORT_ALL_200) - return@queueScript delayScript(player, 3) - } - 1 -> { - MazeInterface.initMaze(player) - // Note: This event is NOT instanced: - // Sources: - // https://youtu.be/2gpzn9oNdy0 (2007) - // https://youtu.be/Tni1HURgnxg (2008) - // https://youtu.be/igdwDZOv9LU (2008) - // https://youtu.be/0oBCkLArUmc (2011 - even with personal Mysterious Old Man) - "Sorry, this is not the old man you are looking for." - // https://youtu.be/FMuKZm-Ikgs (2011) - // val region = DynamicRegion.create(11591) - kidnapPlayer(player, MazeInterface.STARTING_POINTS.random(), TeleportManager.TeleportType.INSTANT) // 10 random spots - AntiMacro.terminateEventNpc(player) - sendGraphics(Graphics(1577, 0, 0), player.location) - animate(player,8941) - removeAttribute(player, MazeInterface.MAZE_ATTRIBUTE_CHESTS_OPEN) - return@queueScript stopExecuting(player) - } - else -> return@queueScript stopExecuting(player) - } + // Note: This event is NOT instanced: + // Sources: + // https://youtu.be/2gpzn9oNdy0 (2007) + // https://youtu.be/Tni1HURgnxg (2008) + // https://youtu.be/igdwDZOv9LU (2008) + // https://youtu.be/0oBCkLArUmc (2011 - even with personal Mysterious Old Man) - "Sorry, this is not the old man you are looking for." + // https://youtu.be/FMuKZm-Ikgs (2011) + // val region = DynamicRegion.create(11591) + kidnapPlayer(this, player, MazeInterface.STARTING_POINTS.random()) { player, _ -> + MazeInterface.initMaze(player) + removeAttribute(player, MazeInterface.MAZE_ATTRIBUTE_CHESTS_OPEN) } } override fun talkTo(npc: NPC) { - // Do nothing. + sendMessage(player, "He isn't interested in talking to you.") } } \ No newline at end of file diff --git a/Server/src/main/content/global/ame/events/pillory/PilloryNPC.kt b/Server/src/main/content/global/ame/events/pillory/PilloryNPC.kt index f833d9ce2..3917b035c 100644 --- a/Server/src/main/content/global/ame/events/pillory/PilloryNPC.kt +++ b/Server/src/main/content/global/ame/events/pillory/PilloryNPC.kt @@ -4,47 +4,22 @@ import content.global.ame.RandomEventNPC import content.global.ame.kidnapPlayer import core.api.* import core.api.utils.WeightBasedTable -import core.game.interaction.QueueStrength +import core.game.component.Component.setUnclosable import core.game.node.entity.npc.NPC -import core.game.node.entity.player.link.TeleportManager -import core.game.system.timer.impl.AntiMacro -import core.game.world.map.Location -import core.game.world.update.flag.context.Graphics import org.rs09.consts.NPCs -import org.rs09.consts.Sounds -// "::revent [-p] player name [-e event name]" class PilloryNPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.PILLORY_GUARD_2791) { - override fun init() { super.init() sendChat("${player.username}, you're under arrest!") face(player) - player.dialogueInterpreter.sendPlainMessage(true, "", "Solve the pillory puzzle to be returned to where you came from.") - queueScript(player, 4, QueueStrength.SOFT) { stage: Int -> - when (stage) { - 0 -> { - lock(player, 6) - sendGraphics(Graphics(1576, 0, 0), player.location) - animate(player,8939) - playAudio(player, Sounds.TELEPORT_ALL_200) - return@queueScript delayScript(player, 3) - } - 1 -> { - PilloryInterface.initPillory(player) - val dest = PilloryInterface.LOCATIONS.random() //9 random spots! - kidnapPlayer(player, dest, TeleportManager.TeleportType.INSTANT) - AntiMacro.terminateEventNpc(player) - sendGraphics(Graphics(1577, 0, 0), player.location) - animate(player,8941) - return@queueScript stopExecuting(player) - } - else -> return@queueScript stopExecuting(player) - } + kidnapPlayer(this, player, PilloryInterface.LOCATIONS.random()) { player, _ -> + PilloryInterface.initPillory(player) + setUnclosable(player, player.dialogueInterpreter.sendPlainMessage(true, "", "Solve the pillory puzzle to be returned to where you came from.")) } } override fun talkTo(npc: NPC) { - //player.dialogueInterpreter.open(FreakyForesterDialogue(),npc) + sendMessage(player, "He isn't interested in talking to you.") } } \ No newline at end of file diff --git a/Server/src/main/content/global/ame/events/quizmaster/QuizMasterDialogueFile.kt b/Server/src/main/content/global/ame/events/quizmaster/QuizMasterDialogueFile.kt index 4790a8550..4bb2ff79d 100644 --- a/Server/src/main/content/global/ame/events/quizmaster/QuizMasterDialogueFile.kt +++ b/Server/src/main/content/global/ame/events/quizmaster/QuizMasterDialogueFile.kt @@ -5,15 +5,17 @@ import core.ServerConstants import core.api.* import core.api.utils.WeightBasedTable import core.api.utils.WeightedItem -import core.game.dialogue.DialogueFile +import core.game.component.Component +import core.game.dialogue.DialogueLabeller +import core.game.dialogue.DialogueOption import core.game.dialogue.FacialExpression import core.game.interaction.QueueStrength import core.game.node.entity.player.Player -import core.tools.END_DIALOGUE import org.rs09.consts.Components import org.rs09.consts.Items +import org.rs09.consts.NPCs -class QuizMasterDialogueFile : DialogueFile() { +class QuizMasterDialogueFile : DialogueLabeller() { companion object { const val QUIZMASTER_INTERFACE = Components.MACRO_QUIZSHOW_191 const val QUIZMASTER_ATTRIBUTE_QUESTIONS_CORRECT = "/save:quizmaster:questions-correct" @@ -74,56 +76,77 @@ class QuizMasterDialogueFile : DialogueFile() { } - override fun handle(componentID: Int, buttonID: Int) { - when (stage) { - 0 -> npc(FacialExpression.FRIENDLY,"WELCOME to the GREATEST QUIZ SHOW in the", "whole of ${ServerConstants.SERVER_NAME}:", "O D D O N E O U T").also { stage++ } - 1 -> player(FacialExpression.THINKING, "I'm sure I didn't ask to take part in a quiz show...").also { stage++ } - 2 -> npc(FacialExpression.FRIENDLY,"Please welcome our newest contestant:", "${player?.username}!", "Just pick the O D D O N E O U T.", "Four questions right, and then you win!").also { stage++ } - 3 -> { - setAttribute(player!!, QUIZMASTER_ATTRIBUTE_RANDOM_ANSWER, randomQuestion(player!!)) - player!!.interfaceManager.openChatbox(QUIZMASTER_INTERFACE) - stage++ - } - 4-> { - if (buttonID == getAttribute(player!!, QUIZMASTER_ATTRIBUTE_RANDOM_ANSWER, 0)) { - // Correct Answer - setAttribute(player!!, QUIZMASTER_ATTRIBUTE_QUESTIONS_CORRECT, getAttribute(player!!, QUIZMASTER_ATTRIBUTE_QUESTIONS_CORRECT, 0) + 1) - if (getAttribute(player!!, QUIZMASTER_ATTRIBUTE_QUESTIONS_CORRECT, 0) >= 4) { - npc(FacialExpression.FRIENDLY,"CONGRATULATIONS!", "You are a WINNER!", "Please choose your PRIZE!") - stage = 5 - } else { - npc(FacialExpression.FRIENDLY,"Wow, you're a smart one!", "You're absolutely RIGHT!", "Okay, next question!") - stage = 3 - } + override fun addConversation() { + assignToIds(NPCs.QUIZ_MASTER_2477) + afterClose { player -> + loadLabel(player, "question") + } + + npc(FacialExpression.FRIENDLY,"WELCOME to the GREATEST QUIZ SHOW in the", "whole of ${ServerConstants.SERVER_NAME}:", "O D D O N E O U T", unclosable = true) + player(FacialExpression.THINKING, "I'm sure I didn't ask to take part in a quiz show...", unclosable = true) + npc(FacialExpression.FRIENDLY,"Please welcome our newest contestant:", "${player?.username}!", "Just pick the O D D O N E O U T.", "Four questions right, and then you win!", unclosable = true) + goto("question") + + label("question") + manual(unclosable = true) { player, _ -> + setAttribute(player, QUIZMASTER_ATTRIBUTE_RANDOM_ANSWER, randomQuestion(player)) + val comp = Component(QUIZMASTER_INTERFACE) + player.interfaceManager.openChatbox(comp) + return@manual comp + } + exec { player, _ -> + if (buttonID == getAttribute(player, QUIZMASTER_ATTRIBUTE_RANDOM_ANSWER, 0)) { + // Correct Answer + setAttribute(player, QUIZMASTER_ATTRIBUTE_QUESTIONS_CORRECT, getAttribute(player, QUIZMASTER_ATTRIBUTE_QUESTIONS_CORRECT, 0) + 1) + if (getAttribute(player, QUIZMASTER_ATTRIBUTE_QUESTIONS_CORRECT, 0) >= 4) { + goto("winner") } else { - // Wrong Answer - npc(FacialExpression.FRIENDLY,"WRONG!", "That's just WRONG!", "Okay, next question!") - stage = 3 + goto("right") } - } - // Random Item should be "Mystery Box", but the current MYSTERY_BOX_6199 is already inauthentically used by Giftmas. - 5 -> options("1000 Coins", "Random Item").also { stage++ } - 6 -> { - resetAnimator(player!!) - returnPlayer(player!!) - when (buttonID) { - 1 -> { - queueScript(player!!, 0, QueueStrength.SOFT) { stage: Int -> - addItemOrDrop(player!!, Items.COINS_995, 1000) - return@queueScript stopExecuting(player!!) - } - } - 2 -> { - queueScript(player!!, 0, QueueStrength.SOFT) { stage: Int -> - addItemOrDrop(player!!, tableRoll.roll()[0].id) - return@queueScript stopExecuting(player!!) - } - } - } - removeAttributes(player!!, QUIZMASTER_ATTRIBUTE_QUESTIONS_CORRECT, QUIZMASTER_ATTRIBUTE_RANDOM_ANSWER) - stage = END_DIALOGUE - end() + } else { + goto("wrong") } } + + label("right") + npc(FacialExpression.FRIENDLY,"Wow, you're a smart one!", "You're absolutely RIGHT!", "Okay, next question!", unclosable = true) + goto("question") + + label("wrong") + npc(FacialExpression.FRIENDLY,"WRONG!", "That's just WRONG!", "Okay, next question!", unclosable = true) + goto("question") + + label("winner") + npc(FacialExpression.FRIENDLY,"CONGRATULATIONS!", "You are a WINNER!", "Please choose your PRIZE!", unclosable = true) + options( + DialogueOption("money", "1000 Coins", skipPlayer = true), + DialogueOption("item", "Random Item", skipPlayer = true) + ) + + label("money") + exec { player, _ -> + queueScript(player, 0, QueueStrength.SOFT) { _ -> + addItemOrDrop(player, Items.COINS_995, 1000) + return@queueScript stopExecuting(player) + } + } + goto("cleanup") + + label("item") + exec { player, _ -> + queueScript(player, 0, QueueStrength.SOFT) { _ -> + addItemOrDrop(player, tableRoll.roll()[0].id) + return@queueScript stopExecuting(player) + } + } + goto("cleanup") + + label("cleanup") + exec { player, _ -> + resetAnimator(player) + returnPlayer(player) + removeAttributes(player, QUIZMASTER_ATTRIBUTE_QUESTIONS_CORRECT, QUIZMASTER_ATTRIBUTE_RANDOM_ANSWER) + } + goto("nowhere") } -} \ No newline at end of file +} diff --git a/Server/src/main/content/global/ame/events/quizmaster/QuizMasterNPC.kt b/Server/src/main/content/global/ame/events/quizmaster/QuizMasterNPC.kt index 26407a1dc..444fc1f1b 100644 --- a/Server/src/main/content/global/ame/events/quizmaster/QuizMasterNPC.kt +++ b/Server/src/main/content/global/ame/events/quizmaster/QuizMasterNPC.kt @@ -4,14 +4,9 @@ import content.global.ame.RandomEventNPC import content.global.ame.kidnapPlayer import core.api.* import core.api.utils.WeightBasedTable -import core.game.interaction.QueueStrength import core.game.node.entity.npc.NPC -import core.game.node.entity.player.link.TeleportManager -import core.game.system.timer.impl.AntiMacro import core.game.world.map.Location -import core.game.world.update.flag.context.Graphics import org.rs09.consts.NPCs -import org.rs09.consts.Sounds /** * Quiz Master NPC: @@ -27,35 +22,14 @@ class QuizMasterNPC(var type: String = "", override var loot: WeightBasedTable? override fun init() { super.init() sendChat("Hey ${player.username}! It's your lucky day!") - queueScript(player, 4, QueueStrength.SOFT) { stage: Int -> - when (stage) { - 0 -> { - lock(player, 6) - sendGraphics(Graphics(1576, 0, 0), player.location) - animate(player,8939) - playAudio(player, Sounds.TELEPORT_ALL_200) - return@queueScript delayScript(player, 3) - } - 1 -> { - kidnapPlayer(player, Location(1952, 4764, 1), TeleportManager.TeleportType.INSTANT) - setAttribute(player, QuizMasterDialogueFile.QUIZMASTER_ATTRIBUTE_QUESTIONS_CORRECT, 0) - AntiMacro.terminateEventNpc(player) - sendGraphics(Graphics(1577, 0, 0), player.location) - animate(player,8941) - sendMessage(player, "Answer four questions correctly in a row to be teleported back where you came from.") - sendMessage(player, "You will need to relog in if you lose the quiz dialog.") // Inauthentic, but there to notify the player in case. - return@queueScript delayScript(player, 6) - } - 2 -> { - face(player, Location(1952, 4768, 1)) - animate(player,2378) - // This is not needed as when you enter the QuizMasterBorders, it should fire off the dialogue - // openDialogue(player, QuizMasterDialogueFile(), this.asNpc()) - return@queueScript stopExecuting(player) - } - else -> return@queueScript stopExecuting(player) - } - + face(player) + kidnapPlayer(this, player, Location(1952, 4764, 0)) { player, _ -> + setAttribute(player, QuizMasterDialogueFile.QUIZMASTER_ATTRIBUTE_QUESTIONS_CORRECT, 0) + sendMessage(player, "Answer four questions correctly in a row to be teleported back where you came from.") + sendMessage(player, "You will need to relog in if you lose the quiz dialog.") // Inauthentic, but there to notify the player in case. + face(player, Location(1952, 4768, 1)) + animate(player,2378) + // Quiz dialogue gets opened automatically on zone entry. } } diff --git a/Server/src/main/content/global/ame/events/rockgolem/RockGolemBehavior.kt b/Server/src/main/content/global/ame/events/rockgolem/RockGolemBehavior.kt new file mode 100644 index 000000000..bd397a5b9 --- /dev/null +++ b/Server/src/main/content/global/ame/events/rockgolem/RockGolemBehavior.kt @@ -0,0 +1,25 @@ +package content.global.ame.events.rockgolem + +import core.game.node.entity.Entity +import core.game.node.entity.combat.CombatStyle +import core.game.node.entity.combat.CombatSwingHandler +import core.game.node.entity.combat.MultiSwingHandler +import core.game.node.entity.combat.equipment.SwitchAttack +import core.game.node.entity.npc.NPC +import core.game.node.entity.npc.NPCBehavior +import org.rs09.consts.NPCs + +class RockGolemBehavior() : NPCBehavior( + NPCs.ROCK_GOLEM_413, NPCs.ROCK_GOLEM_414, NPCs.ROCK_GOLEM_415, NPCs.ROCK_GOLEM_416, NPCs.ROCK_GOLEM_417, NPCs.ROCK_GOLEM_418 +) { + val rangeHandler = SwitchAttack(CombatStyle.RANGE) + val meleeHandler = SwitchAttack(CombatStyle.MELEE) + val combatHandler = MultiSwingHandler(rangeHandler, meleeHandler) + override fun getSwingHandlerOverride(self: NPC, original: CombatSwingHandler): CombatSwingHandler { + return combatHandler + } + + override fun getXpMultiplier(self: NPC, attacker: Entity): Double { + return super.getXpMultiplier(self, attacker) / 16.0 + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/ame/events/sandwichlady/SandwichLadyRENPC.kt b/Server/src/main/content/global/ame/events/sandwichlady/SandwichLadyRENPC.kt index 1c5a14956..99e0a0e68 100644 --- a/Server/src/main/content/global/ame/events/sandwichlady/SandwichLadyRENPC.kt +++ b/Server/src/main/content/global/ame/events/sandwichlady/SandwichLadyRENPC.kt @@ -5,32 +5,49 @@ import core.tools.RandomFunction import org.rs09.consts.Items import org.rs09.consts.NPCs import content.global.ame.RandomEventNPC +import core.api.lock import core.api.utils.WeightBasedTable class SandwichLadyRENPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.SANDWICH_LADY_3117) { - val phrases = arrayOf("Hello, @name, can you hear me?","Sandwiches, @name!","Are you ignoring me, @name??","Yoohoo! Sandwiches, @name!","Hello, @name?", "Come get your sandwiches, @name!", "How could you ignore me like this, @name?!", "Do you even want your sandwiches, @name?") + lateinit var phrases: Array var assigned_item = 0 - val items = arrayOf(Items.BAGUETTE_6961,Items.TRIANGLE_SANDWICH_6962,Items.SQUARE_SANDWICH_6965,Items.ROLL_6963,Items.MEAT_PIE_2327,Items.KEBAB_1971,Items.CHOCOLATE_BAR_1973) + val items = arrayOf(Items.BAGUETTE_6961, Items.TRIANGLE_SANDWICH_6962, Items.SQUARE_SANDWICH_6965, Items.ROLL_6963, Items.MEAT_PIE_2327, Items.KEBAB_1971, Items.CHOCOLATE_BAR_1973) override fun tick() { - if(RandomFunction.random(1,15) == 5){ - sendChat(phrases.random().replace("@name",player.username.capitalize())) + sayLine(this, phrases, true, true) + if (ticksLeft == 2) { + lock(player, 2) } super.tick() } override fun init() { super.init() + phrases = arrayOf( + // https://www.youtube.com/watch?v=ek8r3ZS929E + // She always starts with "Sandwiches, ${player.username}!" but she ALSO picks that at random, hence duplicate it with hasOpeningPhrase = true + "Sandwiches, ${player.username}!", + "Sandwiches, ${player.username}!", + "Come on ${player.username}, I made these specially!!", + "All types of sandwiches, ${player.username}.", + "Did you hear me ${player.username}?", + "You think I made these just for fun?!!?", + "How could you ignore me like this, ${player.username}?!" //unknown if authentic but it was already here + ) assignItem() - sendChat(phrases.random().replace("@name",player.username.capitalize())) + } + + override fun onTimeUp() { + noteAndTeleport() + terminate() } fun assignItem(){ assigned_item = items.random() - player.setAttribute("sandwich-lady:item",assigned_item) + player.setAttribute("sandwich-lady:item", assigned_item) } override fun talkTo(npc: NPC) { - player.dialogueInterpreter.open(SandwichLadyDialogue(false),npc) + player.dialogueInterpreter.open(SandwichLadyDialogue(false), npc) } } diff --git a/Server/src/main/content/global/ame/events/surpriseexam/MysteriousOldManNPC.kt b/Server/src/main/content/global/ame/events/surpriseexam/MysteriousOldManNPC.kt index 04818dda8..2a3896836 100644 --- a/Server/src/main/content/global/ame/events/surpriseexam/MysteriousOldManNPC.kt +++ b/Server/src/main/content/global/ame/events/surpriseexam/MysteriousOldManNPC.kt @@ -5,32 +5,16 @@ import content.global.ame.kidnapPlayer import core.api.* import org.rs09.consts.NPCs import core.api.utils.WeightBasedTable -import core.game.interaction.QueueStrength import core.game.node.entity.npc.NPC -import core.game.node.entity.player.link.TeleportManager import core.game.world.map.Location -import core.game.world.update.flag.context.Graphics -import org.rs09.consts.Sounds class MysteriousOldManNPC(var type: String = "", override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.MYSTERIOUS_OLD_MAN_410) { override fun init() { super.init() sendChat("Surprise exam, ${player.username}!") - queueScript(player, 4, QueueStrength.SOFT) { stage: Int -> - when (stage) { - 0 -> { - lock(player, 6) - sendGraphics(Graphics(1576, 0, 0), player.location) - animate(player,8939) - playAudio(player, Sounds.TELEPORT_ALL_200) - return@queueScript delayScript(player, 3) - } - 1 -> { - kidnapPlayer(player, Location(1886, 5025, 0), TeleportManager.TeleportType.INSTANT) - return@queueScript stopExecuting(player) - } - else -> return@queueScript stopExecuting(player) - } + face(player) + kidnapPlayer(this, player, Location(1886, 5025, 0)) { _, _ -> + /* nothing needed */ } } diff --git a/Server/src/main/content/global/ame/events/surpriseexam/SupriseExamListeners.kt b/Server/src/main/content/global/ame/events/surpriseexam/SupriseExamListeners.kt index 6d8ad6b4f..ad19ffbda 100644 --- a/Server/src/main/content/global/ame/events/surpriseexam/SupriseExamListeners.kt +++ b/Server/src/main/content/global/ame/events/surpriseexam/SupriseExamListeners.kt @@ -2,7 +2,6 @@ package content.global.ame.events.surpriseexam import core.game.component.Component import core.game.node.entity.player.Player -import core.game.node.item.Item import core.game.world.map.Location import org.rs09.consts.Items import org.rs09.consts.NPCs @@ -66,6 +65,6 @@ class SupriseExamListeners : InteractionListener, MapArea { } override fun getRestrictions(): Array { - return arrayOf(ZoneRestriction.RANDOM_EVENTS, ZoneRestriction.CANNON, ZoneRestriction.FOLLOWERS, ZoneRestriction.OFF_MAP) + return arrayOf(ZoneRestriction.RANDOM_EVENTS, ZoneRestriction.CANNON, ZoneRestriction.FOLLOWERS, ZoneRestriction.TELEPORT, ZoneRestriction.OFF_MAP) } } diff --git a/Server/src/main/content/global/ame/events/surpriseexam/SurpriseExamUtils.kt b/Server/src/main/content/global/ame/events/surpriseexam/SurpriseExamUtils.kt index 4f1530eeb..e1cbdd5a8 100644 --- a/Server/src/main/content/global/ame/events/surpriseexam/SurpriseExamUtils.kt +++ b/Server/src/main/content/global/ame/events/surpriseexam/SurpriseExamUtils.kt @@ -1,15 +1,12 @@ package content.global.ame.events.surpriseexam -import content.global.ame.kidnapPlayer import content.global.ame.returnPlayer import core.api.* import core.game.node.entity.impl.PulseType import core.game.node.entity.player.Player import core.game.system.task.Pulse -import core.game.world.map.Location import org.rs09.consts.Components import org.rs09.consts.Items -import core.game.node.entity.player.link.TeleportManager object SurpriseExamUtils { val SE_KEY_INDEX = "supexam:index" diff --git a/Server/src/main/content/global/handlers/npc/NPCTalkListener.kt b/Server/src/main/content/global/handlers/npc/NPCTalkListener.kt index 5235fc82a..4f5764b0d 100644 --- a/Server/src/main/content/global/handlers/npc/NPCTalkListener.kt +++ b/Server/src/main/content/global/handlers/npc/NPCTalkListener.kt @@ -12,6 +12,7 @@ import core.game.system.timer.impl.AntiMacro import core.game.worldevents.holiday.HolidayRandomEventNPC import core.game.worldevents.holiday.HolidayRandomEvents import core.game.worldevents.holiday.HolidayRandoms +import org.rs09.consts.NPCs /** * Handles the NPC talk-to option. @@ -37,11 +38,18 @@ class NPCTalkListener : InteractionListener { val npc = node.asNpc() if(RandomEvents.randomIDs.contains(node.id)){ if(AntiMacro.getEventNpc(player) == null || AntiMacro.getEventNpc(player) != node.asNpc() || AntiMacro.getEventNpc(player)?.finalized == true) { - player.sendMessage("They aren't interested in talking to you.") + // Why the fuck is this here of all places? Now look at what you've made me do: + if (npc.id == NPCs.SANDWICH_LADY_3117) { + // https://www.youtube.com/watch?v=ek8r3ZS929E + player.dialogueInterpreter.sendDialogue("The sandwich lady doesn't seem interested in selling you any", "refreshments.") + } else { + sendMessage(player, "They aren't interested in talking to you.") + } } else { AntiMacro.getEventNpc(player)?.talkTo(node.asNpc()) } return@on true + //TODO bring sanity here } if (HolidayRandomEvents.holidayRandomIDs.contains(node.id) && node is HolidayRandomEventNPC) { if(HolidayRandoms.getEventNpc(player) == null || HolidayRandoms.getEventNpc(player) != node.asNpc() || HolidayRandoms.getEventNpc(player)?.finalized == true) { @@ -79,4 +87,4 @@ class NPCTalkListener : InteractionListener { return@on player.dialogueInterpreter.open(npc.id, npc) } } -} \ No newline at end of file +} diff --git a/Server/src/main/content/global/skill/magic/HomeTeleportHelper.kt b/Server/src/main/content/global/skill/magic/HomeTeleportHelper.kt new file mode 100644 index 000000000..1b67a38e9 --- /dev/null +++ b/Server/src/main/content/global/skill/magic/HomeTeleportHelper.kt @@ -0,0 +1,46 @@ +package content.global.skill.magic + +import core.api.delayScript +import core.api.playGlobalAudio +import core.api.queueScript +import core.api.sendMessage +import core.api.stopExecuting +import core.game.event.TeleportEvent +import core.game.interaction.QueueStrength +import core.game.node.entity.player.Player +import core.game.node.entity.player.link.TeleportManager +import core.game.world.map.Location +import core.game.world.map.zone.ZoneRestriction + +val HOME_ANIMATIONS = arrayOf(1722, 1723, 1724, 1725, 2798, 2799, 2800, 3195, 4643, 4645, 4646, 4847, 4848, 4849, 4850, 4851, 4852, 65535) +val HOME_GRAPHICS = arrayOf(775, 800, 801, 802, 803, 804, 1703, 1704, 1705, 1706, 1707, 1708, 1709, 1710, 1711, 1712, 1713, 65535) +fun getAudio(count: Int): Int { + return when (count) { + 0 -> 193 + 4 -> 194 + 11 -> 195 + else -> -1 + } +} + +fun homeTeleport(player: Player, dest: Location) { + if (player.timers.getTimer("teleblock") != null) { + sendMessage(player, "A magical force prevents you from teleporting.") + return + } + if (player.locks.isTeleportLocked || player.zoneMonitor.isRestricted(ZoneRestriction.TELEPORT)) { + sendMessage(player, "A magical force has stopped you from teleporting.") + return + } + queueScript(player, 0, QueueStrength.WEAK) { stage -> + if (stage == 18) { + player.properties.teleportLocation = dest + player.dispatch(TeleportEvent(TeleportManager.TeleportType.NORMAL, TeleportMethod.SPELL, -1, dest)) + return@queueScript stopExecuting(player) + } + playGlobalAudio(player.location, getAudio(stage)) + player.packetDispatch.sendGraphic(HOME_GRAPHICS[stage]) + player.packetDispatch.sendAnimation(HOME_ANIMATIONS[stage]) + return@queueScript delayScript(player, 1) + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/magic/ancient/AncientTeleportPlugin.java b/Server/src/main/content/global/skill/magic/ancient/AncientTeleportPlugin.java index 3944c3c7e..f212f3877 100644 --- a/Server/src/main/content/global/skill/magic/ancient/AncientTeleportPlugin.java +++ b/Server/src/main/content/global/skill/magic/ancient/AncientTeleportPlugin.java @@ -1,5 +1,6 @@ package content.global.skill.magic.ancient; +import core.game.node.entity.player.Player; import core.game.node.entity.player.link.diary.DiaryType; import core.game.node.entity.combat.spell.MagicSpell; import core.game.node.entity.combat.spell.Runes; @@ -13,8 +14,12 @@ import core.game.world.GameWorld; import core.game.world.map.Location; import core.plugin.Initializable; import core.plugin.Plugin; +import core.tools.Log; import core.tools.RandomFunction; +import static content.global.skill.magic.HomeTeleportHelperKt.homeTeleport; +import static core.api.ContentAPIKt.log; + /** * Represents the plugin used to handle all ancient teleporting plugins. * @author 'Vexia @@ -55,7 +60,14 @@ public final class AncientTeleportPlugin extends MagicSpell { entity.asPlayer().sendMessage("A magical force has stopped you from teleporting."); return false; } - if (entity.getTeleporter().send(location.transform(0, RandomFunction.random(3), 0), getSpellId() == 28 ? TeleportType.HOME : TeleportType.ANCIENT)) { + boolean isHomeTeleport = getSpellId() == 28; + if (isHomeTeleport) { + if (!entity.isPlayer()) { + log(this.getClass(), Log.ERR, "Why the fuck is a non-player entity trying to cast ancient-magick home teleport?!"); + return false; + } + homeTeleport((Player) entity, Location.create(3087, 3495, 0)); + } else if (entity.getTeleporter().send(location.transform(0, RandomFunction.random(3), 0), TeleportType.ANCIENT)) { if (!super.meetsRequirements(entity, true, true)) { entity.getTeleporter().getCurrentTeleport().stop(); return false; diff --git a/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt b/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt index 632173652..fedf14c27 100644 --- a/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt +++ b/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt @@ -5,6 +5,7 @@ import content.global.skill.farming.CompostBins import content.global.skill.farming.CompostType import content.global.skill.farming.FarmingPatch import content.global.skill.magic.SpellListener +import content.global.skill.magic.homeTeleport import content.global.skill.magic.spellconsts.Lunar import core.api.* import core.game.component.CloseEvent @@ -36,7 +37,7 @@ class LunarListeners : SpellListener("lunar"), Commands { // Level 0 onCast(Lunar.HOME_TELEPORT, NONE) { player, _ -> requires(player) - player.teleporter.send(Location.create(2100, 3914, 0),TeleportManager.TeleportType.HOME) + homeTeleport(player, Location.create(2100, 3914, 0)) setDelay(player,true) } diff --git a/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt b/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt index d070123a3..d817cd31c 100644 --- a/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt +++ b/Server/src/main/content/global/skill/magic/modern/ModernListeners.kt @@ -4,10 +4,12 @@ import content.data.Quests import content.global.skill.magic.SpellListener import content.global.skill.magic.SpellUtils.hasRune import content.global.skill.magic.TeleportMethod +import content.global.skill.magic.homeTeleport import content.global.skill.magic.spellconsts.Modern import content.global.skill.prayer.Bones import content.global.skill.smithing.smelting.Bar import content.global.skill.smithing.smelting.SmeltingPulse +import content.region.kandarin.ardougne.quest.plaguecity.PlagueCityListeners import core.ServerConstants import core.api.* import core.game.event.ItemAlchemizationEvent @@ -23,7 +25,6 @@ import core.game.node.entity.player.Player import core.game.node.entity.player.link.TeleportManager import core.game.node.entity.player.link.diary.DiaryType import core.game.node.entity.skill.Skills -import content.region.kandarin.ardougne.quest.plaguecity.PlagueCityListeners import core.game.node.item.Item import core.game.world.map.Location import core.game.world.update.flag.context.Animation @@ -39,7 +40,7 @@ class ModernListeners : SpellListener("modern"){ return@onCast } requires(player) - player.teleporter.send(ServerConstants.HOME_LOCATION,TeleportManager.TeleportType.HOME) + homeTeleport(player, ServerConstants.HOME_LOCATION ?: Location(3222, 3218, 0)) setDelay(player,true) } diff --git a/Server/src/main/core/game/node/entity/player/Player.java b/Server/src/main/core/game/node/entity/player/Player.java index 05867a01d..0bac8c9f7 100644 --- a/Server/src/main/core/game/node/entity/player/Player.java +++ b/Server/src/main/core/game/node/entity/player/Player.java @@ -468,15 +468,13 @@ public class Player extends Entity { // Check if the player is on the map, runs only every 6 seconds for performance reasons. // This is only a sanity check to detect improper usage of the 'original-loc' attribute, hence only do this work if the attribute is set. - // Only runs when the player is not movement/interaction-locked, so that original-loc does not get wiped e.g. in the middle of the player teleporting to their POH. - if (GameWorld.getTicks() % 10 == 0 && !getLocks().isMovementLocked() && !getLocks().isInteractionLocked()) { - if (ContentAPIKt.getAttribute(this, "/save:original-loc", null) != null) { - int rid = location.getRegionId(); - Region r = RegionManager.forId(rid); - if (!(r instanceof DynamicRegion) && !getZoneMonitor().isRestricted(ZoneRestriction.OFF_MAP)) { - log(this.getClass(), Log.ERR, "Player " + getUsername() + " has the original-loc attribute set but isn't actually off-map! This indicates a bug in the code that set that attribute. The original-loc is " + getAttribute("/save:original-loc") + ", the current region is " + rid + ". Good luck debugging!"); - ContentAPIKt.removeAttribute(this, "original-loc"); - } + // Does not run if the player is currently in the middle of a kidnap sequence (to avoid false positives while the teleport is taking place). + if (GameWorld.getTicks() % 10 == 0 && ContentAPIKt.getAttribute(this, "/save:original-loc", null) != null && !ContentAPIKt.getAttribute(this, "kidnapped-by-random", false)) { + int rid = location.getRegionId(); + Region r = RegionManager.forId(rid); + if (!(r instanceof DynamicRegion) && !getZoneMonitor().isRestricted(ZoneRestriction.OFF_MAP)) { + log(this.getClass(), Log.ERR, "Player " + getUsername() + " has the original-loc attribute set but isn't actually off-map! This indicates a bug in the code that set that attribute. The original-loc is " + getAttribute("/save:original-loc") + ", the current region is " + rid + ". Good luck debugging!"); + ContentAPIKt.removeAttribute(this, "original-loc"); } } } diff --git a/Server/src/main/core/game/node/entity/player/link/TeleportManager.java b/Server/src/main/core/game/node/entity/player/link/TeleportManager.java index e9f8e5b04..3da78d55b 100644 --- a/Server/src/main/core/game/node/entity/player/link/TeleportManager.java +++ b/Server/src/main/core/game/node/entity/player/link/TeleportManager.java @@ -26,16 +26,6 @@ public class TeleportManager { */ public static final int WILDY_TELEPORT = 1 << 16 | 8; - /** - * The animations used in the home teleport. - */ - private final static int[] HOME_ANIMATIONS = {1722, 1723, 1724, 1725, 2798, 2799, 2800, 3195, 4643, 4645, 4646, 4847, 4848, 4849, 4850, 4851, 4852, 65535}; - - /** - * The graphics used in the home teleport. - */ - private final static int[] HOME_GRAPHICS = {775, 800, 801, 802, 803, 804, 1703, 1704, 1705, 1706, 1707, 1708, 1709, 1710, 1711, 1712, 1713, 65535}; - /** * The entity being handled. */ @@ -62,7 +52,6 @@ public class TeleportManager { */ public TeleportManager(Entity entity) { this.entity = entity; - lastTeleport = TeleportType.HOME.getPulse(entity, ServerConstants.HOME_LOCATION); } /** @@ -116,16 +105,11 @@ public class TeleportManager { } this.teleportType = teleportType; entity.getWalkingQueue().reset(); - lastTeleport = currentTeleport; currentTeleport = type.getPulse(entity, location); entity.getPulseManager().clear(); - if (type == TeleportType.HOME) { - entity.getPulseManager().run(type.getPulse(entity, location)); - } else { - entity.lock(12); - entity.getImpactHandler().setDisabledTicks(teleportType == -1 ? 5 : 12); - GameWorld.getPulser().submit(currentTeleport); - } + entity.lock(12); + entity.getImpactHandler().setDisabledTicks(teleportType == -1 ? 5 : 12); + GameWorld.getPulser().submit(currentTeleport); if (entity instanceof Player) { ((Player) entity).getInterfaceManager().close(); } @@ -143,22 +127,6 @@ public class TeleportManager { } } - /** - * Get the home teleport audio based on tick count. - * @param count - */ - private static int getAudio(int count){ - switch(count){ - case 0: - return 193; - case 4: - return 194; - case 11: - return 195; - } - return -1; - } - /** * Gets the entity. * @return the Entity @@ -167,14 +135,6 @@ public class TeleportManager { return entity; } - /** - * Gets the last teleport pulse. - * @return the Pulse - */ - public final Pulse getLastTeleport() { - return lastTeleport; - } - /** * Gets the current teleport pulse. * @return the Pulse @@ -183,7 +143,6 @@ public class TeleportManager { return currentTeleport; } - /** * Represents a NodeType for Teleporter * @author SonicForce41 @@ -330,57 +289,6 @@ public class TeleportManager { }; } }, - HOME(new TeleportSettings(4847, 4857, 800, 804)) { - @Override - public Pulse getPulse(final Entity entity, final Location location) { - return new TeleportPulse(entity) { - int count; - Player player; - - @Override - public boolean pulse() { - switch (count) { - case 18: - player.getProperties().setTeleportLocation(location); - return true; - default: - playGlobalAudio(entity.getLocation(), getAudio(count)); - player.getPacketDispatch().sendGraphic(HOME_GRAPHICS[count]); - player.getPacketDispatch().sendAnimation(HOME_ANIMATIONS[count]); - break; - } - count++; - return false; - } - @Override - public void start() { - player = ((Player) entity); - /*if (player.getSavedData().getGlobalData().getHomeTeleportDelay() > System.currentTimeMillis() && !player.isDonator()) { - long milliseconds = player.getSavedData().getGlobalData().getHomeTeleportDelay() - System.currentTimeMillis(); - int minutes = (int) Math.round(milliseconds / 120000); - if (minutes > 15) { - player.getSavedData().getGlobalData().setHomeTeleportDelay(System.currentTimeMillis() + 1200000); - milliseconds = player.getSavedData().getGlobalData().getHomeTeleportDelay() - System.currentTimeMillis(); - minutes = (int) Math.round(milliseconds / 120000); - } - if (minutes != 0) { - player.getPacketDispatch().sendMessage("You need to wait another " + minutes + " " + (minutes == 1 ? "minute" : "minutes") + " to cast this spell."); - stop(); - return; - } - }*/ - super.start(); - } - - @Override - public void stop() { - super.stop(); - entity.getAnimator().forceAnimation(new Animation(-1)); - player.graphics(new Graphics(-1)); - } - }; - } - }, OBELISK(new TeleportSettings(8939, 8941, 661, -1)) { @Override public Pulse getPulse(final Entity entity, final Location location) { From 82e5965220acaf79169d808917d6d39b1d1e8d6e Mon Sep 17 00:00:00 2001 From: Player Name Date: Thu, 27 Nov 2025 11:48:23 +0000 Subject: [PATCH 105/117] Implemented periodic Grand Exchange update notifications --- Server/src/main/core/game/ge/GrandExchange.kt | 19 ++++++--------- .../main/core/game/ge/GrandExchangeRecords.kt | 1 + .../main/core/game/ge/GrandExchangeTimer.kt | 24 +++++++++++++++++++ 3 files changed, 32 insertions(+), 12 deletions(-) create mode 100644 Server/src/main/core/game/ge/GrandExchangeTimer.kt diff --git a/Server/src/main/core/game/ge/GrandExchange.kt b/Server/src/main/core/game/ge/GrandExchange.kt index 9fba63030..a7ead4d1c 100644 --- a/Server/src/main/core/game/ge/GrandExchange.kt +++ b/Server/src/main/core/game/ge/GrandExchange.kt @@ -6,14 +6,11 @@ import core.cache.def.impl.ItemDefinition import core.game.node.entity.player.Player import core.game.node.entity.player.info.PlayerDetails import core.game.system.command.Privilege -import core.game.system.config.ItemConfigParser import core.game.system.task.Pulse import core.game.world.GameWorld import core.game.world.repository.Repository import core.tools.Log import core.tools.SystemLogger -import core.tools.colorize -import org.rs09.consts.Sounds import java.lang.Integer.max import java.util.concurrent.LinkedBlockingDeque @@ -266,9 +263,6 @@ class GrandExchange : StartupListener, Commands { seller.completedAmount += amount buyer.completedAmount += amount - if(seller.amountLeft < 1 && seller.player != null) - playAudio(seller.player!!, Sounds.GE_COLLECT_COINS_4042) - seller.addWithdrawItem(995, amount * if(sellerBias) buyer.offeredValue else seller.offeredValue) buyer.addWithdrawItem(seller.itemID, amount) @@ -298,12 +292,13 @@ class GrandExchange : StartupListener, Commands { } */ - seller.update() - val sellerPlayer = Repository.uid_map[seller.playerUID] - sellerPlayer?.let { GrandExchangeRecords.getInstance(sellerPlayer).visualizeRecords() } - buyer.update() - val buyerPlayer = Repository.uid_map[buyer.playerUID] - buyerPlayer?.let { GrandExchangeRecords.getInstance(buyerPlayer).visualizeRecords() } + for (entity in arrayOf(buyer, seller)) { + entity.update() + val player = Repository.uid_map[entity.playerUID] ?: continue + val records = GrandExchangeRecords.getInstance(player) + records.visualizeRecords() + records.updateNotification = true + } } private fun canUpdatePriceIndex(seller: GrandExchangeOffer, buyer: GrandExchangeOffer): Boolean { diff --git a/Server/src/main/core/game/ge/GrandExchangeRecords.kt b/Server/src/main/core/game/ge/GrandExchangeRecords.kt index 63aa20848..e970c01bb 100644 --- a/Server/src/main/core/game/ge/GrandExchangeRecords.kt +++ b/Server/src/main/core/game/ge/GrandExchangeRecords.kt @@ -22,6 +22,7 @@ import java.util.* class GrandExchangeRecords(private val player: Player? = null) : PersistPlayer, LoginListener { var history = arrayOfNulls(5) val offerRecords = arrayOfNulls(6) + var updateNotification = false override fun login(player: Player) { val instance = GrandExchangeRecords(player) diff --git a/Server/src/main/core/game/ge/GrandExchangeTimer.kt b/Server/src/main/core/game/ge/GrandExchangeTimer.kt new file mode 100644 index 000000000..2e68ca5f4 --- /dev/null +++ b/Server/src/main/core/game/ge/GrandExchangeTimer.kt @@ -0,0 +1,24 @@ +package core.game.ge + +import core.api.hasAwaitingGrandExchangeCollections +import core.api.playJingle +import core.api.sendMessage +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import core.game.system.timer.RSTimer + +class GrandExchangeTimer : RSTimer(500, "GE periodic poll", isSoft = true, isAuto = true) { + override fun run(entity: Entity) : Boolean { + if (entity !is Player) return false + val player = entity + val records = GrandExchangeRecords.getInstance(player) + if (records.updateNotification) { + records.updateNotification = false + if (hasAwaitingGrandExchangeCollections(player)) { + sendMessage(player, "One or more of your Grand Exchange offers have been updated.") + playJingle(player, 284) + } + } + return true + } +} From 8ba45e0ee1088785c730fb43ec7226420fab3df3 Mon Sep 17 00:00:00 2001 From: Player Name Date: Thu, 27 Nov 2025 11:55:00 +0000 Subject: [PATCH 106/117] Corrected the deep wildy murder message to the authentic one Fixed a bug that caused the prayer-recharge jingle to be played over the death jingle when the player died Fixed redundant overload checks Fixed lunar spells consuming double runes when a previous cast attempt failed Fixed HP on halloween random-event spiders Corrected the Dream spell logic --- .../skill/magic/lunar/LunarListeners.kt | 94 ++++++++++--------- .../core/game/node/entity/player/Player.java | 11 +-- .../core/game/node/entity/skill/Skills.java | 3 - .../game/system/timer/impl/SkillRestore.kt | 2 +- .../world/map/zone/impl/WildernessZone.java | 11 --- .../randoms/SpiderHolidayRandomNPC.kt | 6 +- 6 files changed, 55 insertions(+), 72 deletions(-) diff --git a/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt b/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt index fedf14c27..b89b1a3c7 100644 --- a/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt +++ b/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt @@ -23,6 +23,7 @@ import core.game.system.command.Privilege import core.game.system.config.NPCConfigParser import core.game.system.task.Pulse import core.game.system.timer.impl.PoisonImmunity +import core.game.system.timer.impl.SkillRestore import core.game.world.map.Location import core.game.world.map.RegionManager import core.game.world.repository.Repository @@ -299,7 +300,7 @@ class LunarListeners : SpellListener("lunar"), Commands { if(playerPies.isEmpty()){ player.sendMessage("You have no pies which you have the level to cook.") - return + throw IllegalStateException() } player.pulseManager.run(object : Pulse(){ @@ -327,33 +328,33 @@ class LunarListeners : SpellListener("lunar"), Commands { fun curePlant(player: Player, obj: Scenery) { if (CompostBins.forObject(obj) != null) { sendMessage(player, "Bins don't often get diseased.") - return + throw IllegalStateException() } val fPatch = FarmingPatch.forObject(obj) if (fPatch == null) { sendMessage(player, "Umm... this spell won't cure that!") - return + throw IllegalStateException() } val patch = fPatch.getPatchFor(player) if (patch.isWeedy()) { sendMessage(player, "The weeds are healthy enough already.") - return + throw IllegalStateException() } if (patch.isEmptyAndWeeded()) { sendMessage(player, "There's nothing there to cure.") - return + throw IllegalStateException() } if (patch.isGrown()) { sendMessage(player, "That's not diseased.") - return + throw IllegalStateException() } if (patch.isDead) { sendMessage(player, "It says 'Cure' not 'Resurrect'. Although death may arise from disease, it is not in itself a disease and hence cannot be cured. So there.") - return + throw IllegalStateException() } if (!patch.isDiseased) { sendMessage(player, "It is growing just fine.") - return + throw IllegalStateException() } patch.cureDisease() @@ -367,7 +368,7 @@ class LunarListeners : SpellListener("lunar"), Commands { private fun monsterExamine(player: Player, npc: NPC){ if(!npc.location.withinDistance(player.location)){ sendMessage(player, "You must get closer to use this spell.") - return + throw IllegalStateException() } face(player, npc) visualizeSpell(player, Animations.LUNAR_SPELLBOOK_STATSPY_6293, Graphics.LUNAR_SPELLBOOK_STAT_SPY_OVER_PLAYER_1060, soundID = Sounds.LUNAR_STAT_SPY_3620) @@ -403,20 +404,20 @@ class LunarListeners : SpellListener("lunar"), Commands { private fun cureOther(player: Player, target: Node) { if(!isPlayer(target)) { sendMessage(player, "You can only cast this spell on other players.") - return + throw IllegalStateException() } val p = target.asPlayer() if(!p.isActive || p.locks.isInteractionLocked) { sendMessage(player, "This player is busy.") - return + throw IllegalStateException() } if(!p.settings.isAcceptAid) { sendMessage(player, "This player is not accepting any aid.") - return + throw IllegalStateException() } if(!isPoisoned(p)) { sendMessage(player, "This player is not poisoned.") - return + throw IllegalStateException() } player.face(p) visualizeSpell(player, Animations.LUNAR_SPELLBOOK_CURE_OTHER_4411, Graphics.LUNAR_SPELLBOOK_CURE_OTHER_736, 130, Sounds.LUNAR_CURE_OTHER_2886) @@ -461,7 +462,7 @@ class LunarListeners : SpellListener("lunar"), Commands { private fun cureMe(player: Player) { if(!isPoisoned(player)) { sendMessage(player, "You are not poisoned.") - return + throw IllegalStateException() } removeRunes(player, true) visualizeSpell(player, Animations.LUNAR_SPELLBOOK_CURE_ME_4411, Graphics.LUNAR_SPELLBOOK_CURE_ME_742, 90, Sounds.LUNAR_CURE_2884) @@ -504,7 +505,7 @@ class LunarListeners : SpellListener("lunar"), Commands { private fun statSpy(player: Player, target: Node) { if(target !is Player) { sendMessage(player, "You can only cast this spell on players.") - return + throw IllegalStateException() } val stat = Components.DREAM_PLAYER_STATS_523 val statCloseEvent = CloseEvent { p, _ -> @@ -583,33 +584,36 @@ class LunarListeners : SpellListener("lunar"), Commands { private fun dream(player: Player) { if(player.skills.lifepoints >= getStatLevel(player, Skills.HITPOINTS)) { sendMessage(player, "You have no need to cast this spell since your hitpoints are already full.") - return + throw IllegalStateException() } + // https://runescape.wiki/w/Dream?oldid=880976 claims Dream makes you heal 1 hp every 20 seconds + // https://oldschool.runescape.wiki/w/Dream claims that Dream has its own timer, so the Dream heals don't need + // to align with the natural heals + val timer = getOrStartTimer(player) animate(player, Animations.LUNAR_SPELLBOOK_DREAM_START_6295) + removeRunes(player, true) + addXP(player, 82.0) delayEntity(player, 4) queueScript(player, 4, QueueStrength.WEAK) { stage: Int -> - when(stage) { - 0 -> { - animate(player, Animations.LUNAR_SPELLBOOK_DREAM_MID_6296) - sendGraphics(Graphics.LUNAR_SPELLBOOK_DREAM_1056, player.location) - playAudio(player, Sounds.LUNAR_SLEEP_3619) - return@queueScript delayScript(player, 5) - } - else -> { - sendGraphics(Graphics.LUNAR_SPELLBOOK_DREAM_1056, player.location) - // This heals 2 HP every min. Naturally you heal 1 for a total of 3 - // The script steps every 5 ticks and we want 50 ticks before a heal - if (stage.mod(10) == 0){ - heal(player, 1) - if(player.skills.lifepoints >= getStatLevel(player, Skills.HITPOINTS)) { - animate(player, Animations.LUNAR_SPELLBOOK_DREAM_END_6297) - return@queueScript stopExecuting(player) - } - } - return@queueScript delayScript(player, 5) + if (stage == 0) { + sendGraphics(Graphics.LUNAR_SPELLBOOK_DREAM_1056, player.location) + playAudio(player, Sounds.LUNAR_SLEEP_3619) + return@queueScript delayScript(player, 5) + } + animate(player, Animations.LUNAR_SPELLBOOK_DREAM_MID_6296) + sendGraphics(Graphics.LUNAR_SPELLBOOK_DREAM_1056, player.location) + // This heals 2 HP every min. Naturally you heal 1 for a total of 3 + // The script steps every 5 ticks and we want 50 ticks before a heal + if (stage.mod(10) == 0) { + val amt = timer.getHealAmount(player) //accounts for regen brace + heal(player, amt) + if (player.skills.lifepoints >= getStatLevel(player, Skills.HITPOINTS)) { + animate(player, Animations.LUNAR_SPELLBOOK_DREAM_END_6297) + return@queueScript stopExecuting(player) } } + return@queueScript delayScript(player, 5) } } @@ -661,23 +665,23 @@ class LunarListeners : SpellListener("lunar"), Commands { private fun fertileSoil(player: Player, target: Scenery) { if (CompostBins.forObjectID(target.id) != null) { sendMessage(player, "No, that would be silly.") - return + throw IllegalStateException() } val fPatch = FarmingPatch.forObject(target) if(fPatch == null) { sendMessage(player, "Um... I don't want to fertilize that!") - return + throw IllegalStateException() } val patch = fPatch.getPatchFor(player) if (patch.isGrown()) { sendMessage(player, "Composting isn't going to make it get any bigger.") - return + throw IllegalStateException() } if (patch.isFertilized()) { sendMessage(player, "This patch has already been composted.") - return + throw IllegalStateException() } removeRunes(player, true) animate(player, Animations.LUNAR_SPELLBOOK_FERTILE_SOIL_4413) @@ -698,11 +702,11 @@ class LunarListeners : SpellListener("lunar"), Commands { val plankType = PlankType.getForLog(item) if (plankType == null) { sendMessage(player, "You need to use this spell on logs.") - return + throw IllegalStateException() } if (amountInInventory(player, Items.COINS_995) < plankType.price || !removeItem(player, Item(Items.COINS_995, plankType.price))) { sendMessage(player, "You need ${plankType.price} coins to convert that log into a plank.") - return + throw IllegalStateException() } lock(player, 3) setDelay(player, false) @@ -717,20 +721,20 @@ class LunarListeners : SpellListener("lunar"), Commands { private fun energyTransfer(player: Player, target: Node) { if(!isPlayer(target)) { sendMessage(player, "You can only cast this spell on other players.") - return + throw IllegalStateException() } val targetPlayer = target.asPlayer() if(!targetPlayer.isActive || targetPlayer.locks.isInteractionLocked) { sendMessage(player, "This player is busy.") - return + throw IllegalStateException() } if(!targetPlayer.settings.isAcceptAid) { sendMessage(player, "This player is not accepting any aid.") - return + throw IllegalStateException() } if(10 >= player.skills.lifepoints) { sendMessage(player, "You need more hitpoints to cast this spell.") - return + throw IllegalStateException() } player.face(targetPlayer) visualizeSpell(player, Animations.LUNAR_SPELLBOOK_ENERGY_TRANSFER_4411, Graphics.LUNAR_SPELLBOOK_ENERGY_TRANSFER_738, 90, Sounds.LUNAR_ENERGY_TRANSFER_2885) diff --git a/Server/src/main/core/game/node/entity/player/Player.java b/Server/src/main/core/game/node/entity/player/Player.java index 0bac8c9f7..dfe357ff0 100644 --- a/Server/src/main/core/game/node/entity/player/Player.java +++ b/Server/src/main/core/game/node/entity/player/Player.java @@ -4,10 +4,8 @@ import content.global.handlers.item.equipment.BarrowsEquipment; import content.global.handlers.item.equipment.special.SalamanderSwingHandler; import content.global.skill.runecrafting.PouchManager; import core.api.ContentAPIKt; -import core.api.EquipmentSlot; import core.game.component.Component; import core.game.container.Container; -import core.game.container.ContainerType; import core.game.container.impl.BankContainer; import core.game.container.impl.EquipmentContainer; import core.game.container.impl.InventoryListener; @@ -616,13 +614,8 @@ public class Player extends Entity { if (this.isArtificial() && killer instanceof NPC) { return; } - if (killer instanceof Player && killer.getName() != getName()) { // the latter happens if you died via typeless damage from an external cause, e.g. bugs in a dark cave without a light source - long unixSeconds = System.currentTimeMillis() / 1000L; - if (unixSeconds - killer.getAttribute("/save:last-murder-news", 0L) >= 300) { - Item wep = getItemFromEquipment((Player) killer, EquipmentSlot.WEAPON); - sendNews(killer.getUsername() + " has murdered " + getUsername() + " with " + (wep == null ? "their fists." : (StringUtils.isPlusN(wep.getName()) ? "an " : "a ") + wep.getName())); - killer.setAttribute("/save:last-murder-news", unixSeconds); - } + if (killer instanceof Player && !Objects.equals(killer.getName(), getName())) { // the latter happens if you died via typeless damage from an external cause, e.g. bugs in a dark cave without a light source + ContentAPIKt.sendMessage((Player) killer, "You have defeated " + getUsername() + "."); } getPacketDispatch().sendMessage("Oh dear, you are dead!"); incrementAttribute("/save:"+STATS_BASE+":"+STATS_DEATHS); diff --git a/Server/src/main/core/game/node/entity/skill/Skills.java b/Server/src/main/core/game/node/entity/skill/Skills.java index a9f1bfd5a..c545ed949 100644 --- a/Server/src/main/core/game/node/entity/skill/Skills.java +++ b/Server/src/main/core/game/node/entity/skill/Skills.java @@ -363,9 +363,6 @@ public final class Skills { int staticLevel = getStaticLevel(i); setLevel(i, staticLevel); } - if (entity instanceof Player) { - playAudio(entity.asPlayer(), Sounds.PRAYER_RECHARGE_2674); - } rechargePrayerPoints(); } diff --git a/Server/src/main/core/game/system/timer/impl/SkillRestore.kt b/Server/src/main/core/game/system/timer/impl/SkillRestore.kt index b6b456618..f14d46abe 100644 --- a/Server/src/main/core/game/system/timer/impl/SkillRestore.kt +++ b/Server/src/main/core/game/system/timer/impl/SkillRestore.kt @@ -48,7 +48,7 @@ class SkillRestore : RSTimer (1, "skillrestore", isAuto = true, isSoft = true) { (entity as? Player)?.debug("Registered skill restoration timer.") } - private fun getHealAmount (entity: Entity) : Int { + fun getHealAmount (entity: Entity) : Int { if (entity !is Player) return 1 val gloves = getItemFromEquipment (entity, EquipmentSlot.HANDS) diff --git a/Server/src/main/core/game/world/map/zone/impl/WildernessZone.java b/Server/src/main/core/game/world/map/zone/impl/WildernessZone.java index c06ba08ce..e4886e5f6 100644 --- a/Server/src/main/core/game/world/map/zone/impl/WildernessZone.java +++ b/Server/src/main/core/game/world/map/zone/impl/WildernessZone.java @@ -153,17 +153,6 @@ public final class WildernessZone extends MapZone { p.getSkullManager().setWilderness(true); p.getSkullManager().setLevel(getWilderness(p)); } - for (int i = 0; i < 7; i++) { - if (i == 5 || i == 3) { - continue; - } - if(p.getAttributes().containsKey("overload") || p.getSkills().getLevel(i) > 118){ - if (p.getSkills().getLevel(i) > p.getSkills().getStaticLevel(i)) { - p.getSkills().setLevel(i, p.getSkills().getStaticLevel(i)); - p.removeAttribute("overload"); - } - } - } if (p.getFamiliarManager().hasFamiliar() && !p.getFamiliarManager().hasPet()) { Familiar familiar = p.getFamiliarManager().getFamiliar(); familiar.transform(); diff --git a/Server/src/main/core/game/worldevents/holiday/halloween/randoms/SpiderHolidayRandomNPC.kt b/Server/src/main/core/game/worldevents/holiday/halloween/randoms/SpiderHolidayRandomNPC.kt index dea7ff063..037c9ba10 100644 --- a/Server/src/main/core/game/worldevents/holiday/halloween/randoms/SpiderHolidayRandomNPC.kt +++ b/Server/src/main/core/game/worldevents/holiday/halloween/randoms/SpiderHolidayRandomNPC.kt @@ -15,7 +15,7 @@ class SpiderHolidayRandomNPC() : HolidayRandomEventNPC(61) { this.behavior = SpiderHolidayRandomBehavior() playGlobalAudio(this.location, Sounds.SPIDER_4375) var stomped = false - queueScript(this,4, QueueStrength.SOFT) { stage: Int -> + queueScript(this, 4, QueueStrength.SOFT) { stage: Int -> when (stage) { 0 -> { sendChat(player, "Eww a spider!") @@ -32,7 +32,7 @@ class SpiderHolidayRandomNPC() : HolidayRandomEventNPC(61) { } 2 -> { if (stomped) { - impact(this, 1, ImpactHandler.HitsplatType.NORMAL) + impact(this, 2, ImpactHandler.HitsplatType.NORMAL) } else { sendMessage(player, "The spider runs away.") playGlobalAudio(this.location, Sounds.SPIDER_4375) @@ -47,4 +47,4 @@ class SpiderHolidayRandomNPC() : HolidayRandomEventNPC(61) { override fun talkTo(npc: NPC) { } -} \ No newline at end of file +} From 2239168074b441070c37a82aa5186f046ef0c213 Mon Sep 17 00:00:00 2001 From: Player Name Date: Thu, 27 Nov 2025 11:56:04 +0000 Subject: [PATCH 107/117] Corrected the retreat mechanic to not heal the npc --- Server/src/main/core/game/node/entity/npc/NPC.java | 1 - 1 file changed, 1 deletion(-) diff --git a/Server/src/main/core/game/node/entity/npc/NPC.java b/Server/src/main/core/game/node/entity/npc/NPC.java index 8c2600642..2ce5481cc 100644 --- a/Server/src/main/core/game/node/entity/npc/NPC.java +++ b/Server/src/main/core/game/node/entity/npc/NPC.java @@ -476,7 +476,6 @@ public class NPC extends Entity { public boolean pulse() { getProperties().getCombatPulse().stop(); getLocks().unlockMovement(); - fullRestore(); getImpactHandler().setDisabledTicks(0); removeAttribute("return-to-spawn"); removeAttribute("return-to-spawn-pulse"); From da7c62d10c8968e422ad134dde3cd4adb6fcaae7 Mon Sep 17 00:00:00 2001 From: Syndromeramo <21965004-syndromeramo@users.noreply.gitlab.com> Date: Thu, 27 Nov 2025 11:59:01 +0000 Subject: [PATCH 108/117] Implemented Observatory Quest --- Server/data/configs/ground_spawns.json | 8 + Server/data/configs/item_configs.json | 2 +- Server/data/configs/npc_configs.json | 38 +- Server/data/configs/npc_spawns.json | 138 ++-- .../cchallange/ChampionScrollsDropHandler.kt | 2 +- .../content/global/handlers/npc/GuardNPC.java | 2 +- .../skill/crafting/silver/SilverProduct.kt | 2 +- .../region/kandarin/dialogue/AstronomyBook.kt | 130 ---- .../quest/observatoryquest/AstronomyBook.kt | 169 +++++ .../quest/observatoryquest/GoblinDialogues.kt | 152 ++++ .../quest/observatoryquest/GoblinGuardNPC.kt | 31 + .../ObservatoryAssistantDialogue.kt | 238 ++++++ .../observatoryquest/ObservatoryCutscene.kt | 199 ++++++ .../ObservatoryProfessorDialogue.kt | 676 ++++++++++++++++++ .../observatoryquest/ObservatoryQuest.kt | 159 ++++ .../ObservatoryQuestInterfaces.kt | 59 ++ .../ObservatoryQuestListeners.kt | 294 ++++++++ .../observatoryquest/PoisonSpiderBehavior.kt | 20 + .../SpiritOfScorpiusDialogue.kt | 110 +++ .../src/main/core/game/activity/Cutscene.kt | 12 + 20 files changed, 2242 insertions(+), 199 deletions(-) delete mode 100644 Server/src/main/content/region/kandarin/dialogue/AstronomyBook.kt create mode 100644 Server/src/main/content/region/kandarin/quest/observatoryquest/AstronomyBook.kt create mode 100644 Server/src/main/content/region/kandarin/quest/observatoryquest/GoblinDialogues.kt create mode 100644 Server/src/main/content/region/kandarin/quest/observatoryquest/GoblinGuardNPC.kt create mode 100644 Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryAssistantDialogue.kt create mode 100644 Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryCutscene.kt create mode 100644 Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryProfessorDialogue.kt create mode 100644 Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryQuest.kt create mode 100644 Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryQuestInterfaces.kt create mode 100644 Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryQuestListeners.kt create mode 100644 Server/src/main/content/region/kandarin/quest/observatoryquest/PoisonSpiderBehavior.kt create mode 100644 Server/src/main/content/region/kandarin/quest/observatoryquest/SpiritOfScorpiusDialogue.kt diff --git a/Server/data/configs/ground_spawns.json b/Server/data/configs/ground_spawns.json index 632bf2fc7..7ddcf821d 100644 --- a/Server/data/configs/ground_spawns.json +++ b/Server/data/configs/ground_spawns.json @@ -207,6 +207,10 @@ "item_id": "590", "loc_data": "{1,2368,3135,0,140}-{1,2431,3072,0,140}-{1,3112,3369,2,140}-{1,3209,3734,0,100}-" }, + { + "item_id": "600", + "loc_data": "{1,2438,3187,0,200}-" + }, { "item_id": "677", "loc_data": "{1,3369,3378,0,150}-" @@ -671,6 +675,10 @@ "item_id": "11065", "loc_data": "{1,2928,3289,0,90}-" }, + { + "item_id": "11656", + "loc_data": "{1,2438,3185,0,200}-" + }, { "item_id": "12494", "loc_data": "{1,2762,2973,0,60}-" diff --git a/Server/data/configs/item_configs.json b/Server/data/configs/item_configs.json index 951d647f5..bf79f32e1 100644 --- a/Server/data/configs/item_configs.json +++ b/Server/data/configs/item_configs.json @@ -5575,7 +5575,7 @@ "id": "601" }, { - "destroy_message": "You'll have to get another down in the Dungeon", + "destroy_message": "You'll have to get another down in the dungeon.", "examine": "An unusual clay mould in the shape of a disc.", "durability": null, "name": "Lens mould", diff --git a/Server/data/configs/npc_configs.json b/Server/data/configs/npc_configs.json index 8bbc43f55..2c705af7f 100644 --- a/Server/data/configs/npc_configs.json +++ b/Server/data/configs/npc_configs.json @@ -6227,6 +6227,22 @@ "range_level": "1", "attack_level": "1" }, + { + "examine": "A man, learned in the ways of the stars.", + "melee_animation": "0", + "range_animation": "0", + "defence_animation": "0", + "magic_animation": "0", + "death_animation": "0", + "name": "Observatory professor", + "defence_level": "1", + "safespot": null, + "lifepoints": "10", + "strength_level": "1", + "id": "488", + "range_level": "1", + "attack_level": "1" + }, { "examine": "He doesn't look like he'd trust his own mother.", "melee_animation": "6199", @@ -6237,13 +6253,13 @@ "magic_animation": "0", "death_animation": "6190", "name": "Goblin guard", - "defence_level": "20", + "defence_level": "37", "safespot": null, - "lifepoints": "28", - "strength_level": "20", + "lifepoints": "43", + "strength_level": "37", "id": "489", "range_level": "1", - "attack_level": "20" + "attack_level": "32" }, { "examine": "If the mummy is at school", @@ -10518,13 +10534,13 @@ "death_animation": "5329", "name": "Poison spider", "safespot": null, - "defence_level": "52", - "lifepoints": "64", - "strength_level": "65", + "defence_level": "28", + "lifepoints": "25", + "strength_level": "28", "id": "1009", "aggressive": "true", "range_level": "1", - "attack_level": "50" + "attack_level": "28" }, { "examine": "A green skinned croaker", @@ -53255,6 +53271,7 @@ "attack_level": "1" }, { + "examine": "An ugly green creature.", "name": "Goblin", "defence_level": "1", "safespot": null, @@ -73515,10 +73532,6 @@ "name": "Kalron", "id": "486" }, - { - "name": "Observatory professor", - "id": "488" - }, { "name": "Hajedy", "id": "510" @@ -82651,6 +82664,7 @@ "id": "6114" }, { + "examine": "A man, learned in the ways of the stars.", "name": "Observatory professor", "id": "6119" }, diff --git a/Server/data/configs/npc_spawns.json b/Server/data/configs/npc_spawns.json index fd15f374c..46a5a18e7 100644 --- a/Server/data/configs/npc_spawns.json +++ b/Server/data/configs/npc_spawns.json @@ -157,7 +157,7 @@ }, { "npc_id": "47", - "loc_data": "{2821,3170,0,1,1}-{3341,3267,0,1,5}-{3076,3282,0,1,5}-{3089,3266,0,1,4}-{3091,3266,0,1,4}-{3097,3364,0,1,3}-{3102,3363,0,1,5}-{3127,3487,0,1,4}-{3125,3486,0,1,6}-{3127,3486,0,1,4}-{2603,9480,0,1,1}-{2600,9477,0,1,0}-{2579,9496,0,1,4}-{2580,9508,0,1,0}-{2571,9522,0,1,4}-{2565,9505,0,1,1}-{2566,9510,0,1,6}-{2594,9497,0,1,4}-{2852,9642,0,1,6}-{2858,9632,0,1,3}-{2568,9620,0,1,0}-{2573,9612,0,1,0}-{2579,9631,0,1,0}-{2580,9600,0,1,0}-{2580,9614,0,1,0}-{2580,9620,0,1,0}-{2580,9626,0,1,0}-{2583,9632,0,1,0}-{2584,9625,0,1,0}-{2584,9637,0,1,0}-{2589,9644,0,1,0}-{2590,9601,0,1,0}-{2590,9638,0,1,0}-{2591,9601,0,1,0}-{2591,9621,0,1,0}-{2594,9636,0,1,0}-{2594,9644,0,1,0}-{2597,9604,0,1,0}-{2607,9615,0,1,0}-{2608,9628,0,1,0}-{2614,9651,0,1,0}-{2614,9656,0,1,0}-{2615,9647,0,1,0}-{2615,9661,0,1,0}-{2616,9633,0,1,0}-{2618,9630,0,1,0}-{3108,9754,0,1,5}-{3110,9754,0,1,5}-{3108,9750,0,1,5}-{2592,9831,0,1,3}-{2588,9825,0,1,6}-{2583,9829,0,1,4}-{2581,9841,0,1,0}-{2597,9823,0,1,2}-{2579,9805,0,1,3}-{2576,9804,0,1,0}-{2573,9805,0,1,5}-{2571,9808,0,1,3}-{2576,9810,0,1,2}-{2587,9802,0,1,2}-{2592,9800,0,1,4}-{2596,9805,0,1,6}-{2601,9802,0,1,5}-{2585,9801,0,1,7}-{2594,9803,0,1,0}-{2590,9806,0,1,3}-{2612,9808,0,1,6}-{2604,9810,0,1,6}-{2579,9821,0,1,2}-{2576,9812,0,1,6}-{2580,9813,0,1,6}-{2600,9813,0,1,4}-{2599,9809,0,1,4}-{3158,3226,0,1,5}-{3160,3202,0,1,4}-{3192,3203,0,1,0}-{3194,3204,0,1,0}-{3196,3206,0,1,0}-{3197,3204,0,1,0}-{2654,9640,0,1,6}-{2655,9637,0,1,4}-{2656,9639,0,1,7}-{2651,9636,0,1,5}-{2648,9637,0,1,4}-{2651,9642,0,1,1}-{2654,9640,0,1,0}-{2654,9635,0,1,6}-{2655,9635,0,1,3}-{2664,9626,0,1,6}-{2664,9624,0,1,1}-{2661,9623,0,1,1}-{2663,9623,0,1,3}-{2664,9626,0,1,6}-{2930,9699,0,1,0}-{2933,9697,0,1,0}-{2932,9685,0,1,0}-{2930,9693,0,1,0}-{3235,3224,0,1,3}-{3229,3220,0,1,4}-{3211,3211,0,1,3}-{3225,3220,0,1,1}-{3237,3215,0,1,5}-{3211,3210,0,1,7}-{3227,3220,0,1,7}-{3233,3227,0,1,5}-{3227,3210,0,1,6}-{3228,3222,0,1,4}-{3229,3226,0,1,0}-{3236,3217,0,1,4}-{3259,3230,0,1,4}-{3233,3237,0,1,7}-{3205,3204,0,1,0}-{3206,3204,0,1,0}-{3205,3203,0,1,0}-{3206,3202,0,1,0}-{3207,3202,0,1,0}-{3208,3203,0,1,0}-{3001,3202,0,1,5}-{3243,3687,0,1,5}-{3249,3669,0,1,3}-{3252,3675,0,1,4}-{3252,3680,0,1,3}-{3259,3683,0,1,0}-{3475,9840,0,1,6}-{3481,9842,0,1,1}-{3486,9843,0,1,7}-{3483,9824,0,1,4}-{3496,9808,0,0,5}-{3490,9815,0,1,1}-{3478,9834,0,0,3}-{3490,9824,0,1,4}-{3225,9862,0,1,4}-{3222,9861,0,1,6}-{3220,9860,0,1,6}-{3219,9865,0,1,6}-{3237,9862,0,1,4}-{2536,2982,0,1,3}-{2531,2980,0,1,0}-{2522,2981,0,1,4}-{2545,2989,0,1,4}-{2523,2970,0,1,2}-{3026,3174,0,1,5}-{3019,3176,0,1,7}-{2801,3158,0,1,2}-{2514,3193,0,1,6}-{2518,3192,0,1,3}-{2507,3181,0,1,3}-{2508,3178,0,1,6}-{2511,3183,0,1,3}-{2515,3182,0,1,1}-{3021,3205,0,1,6}-{3019,3292,0,1,7}-{3018,3295,0,1,7}-{2531,3325,0,1,3}-{2530,3327,0,1,5}-{2521,3331,0,1,3}-{2526,3328,0,1,3}-{2523,3331,0,1,4}-{2523,3334,0,1,1}-{2531,3329,0,1,5}-{2532,3333,0,1,5}-{3276,9871,0,1,1}-{3277,9871,0,1,3}-" + "loc_data": "{2821,3170,0,1,1}-{3341,3267,0,1,5}-{3076,3282,0,1,5}-{3089,3266,0,1,4}-{3091,3266,0,1,4}-{3097,3364,0,1,3}-{3102,3363,0,1,5}-{3127,3487,0,1,4}-{3125,3486,0,1,6}-{3127,3486,0,1,4}-{2339,9356,0,1,0}-{2354,9390,0,1,0}-{2361,9403,0,1,0}-{2362,9347,0,1,0}-{2603,9480,0,1,1}-{2600,9477,0,1,0}-{2579,9496,0,1,4}-{2580,9508,0,1,0}-{2571,9522,0,1,4}-{2565,9505,0,1,1}-{2566,9510,0,1,6}-{2594,9497,0,1,4}-{2852,9642,0,1,6}-{2858,9632,0,1,3}-{2568,9620,0,1,0}-{2573,9612,0,1,0}-{2579,9631,0,1,0}-{2580,9600,0,1,0}-{2580,9614,0,1,0}-{2580,9620,0,1,0}-{2580,9626,0,1,0}-{2583,9632,0,1,0}-{2584,9625,0,1,0}-{2584,9637,0,1,0}-{2589,9644,0,1,0}-{2590,9601,0,1,0}-{2590,9638,0,1,0}-{2591,9601,0,1,0}-{2591,9621,0,1,0}-{2594,9636,0,1,0}-{2594,9644,0,1,0}-{2597,9604,0,1,0}-{2607,9615,0,1,0}-{2608,9628,0,1,0}-{2614,9651,0,1,0}-{2614,9656,0,1,0}-{2615,9647,0,1,0}-{2615,9661,0,1,0}-{2616,9633,0,1,0}-{2618,9630,0,1,0}-{3108,9754,0,1,5}-{3110,9754,0,1,5}-{3108,9750,0,1,5}-{2592,9831,0,1,3}-{2588,9825,0,1,6}-{2583,9829,0,1,4}-{2581,9841,0,1,0}-{2597,9823,0,1,2}-{2579,9805,0,1,3}-{2576,9804,0,1,0}-{2573,9805,0,1,5}-{2571,9808,0,1,3}-{2576,9810,0,1,2}-{2587,9802,0,1,2}-{2592,9800,0,1,4}-{2596,9805,0,1,6}-{2601,9802,0,1,5}-{2585,9801,0,1,7}-{2594,9803,0,1,0}-{2590,9806,0,1,3}-{2612,9808,0,1,6}-{2604,9810,0,1,6}-{2579,9821,0,1,2}-{2576,9812,0,1,6}-{2580,9813,0,1,6}-{2600,9813,0,1,4}-{2599,9809,0,1,4}-{3158,3226,0,1,5}-{3160,3202,0,1,4}-{3192,3203,0,1,0}-{3194,3204,0,1,0}-{3196,3206,0,1,0}-{3197,3204,0,1,0}-{2654,9640,0,1,6}-{2655,9637,0,1,4}-{2656,9639,0,1,7}-{2651,9636,0,1,5}-{2648,9637,0,1,4}-{2651,9642,0,1,1}-{2654,9640,0,1,0}-{2654,9635,0,1,6}-{2655,9635,0,1,3}-{2664,9626,0,1,6}-{2664,9624,0,1,1}-{2661,9623,0,1,1}-{2663,9623,0,1,3}-{2664,9626,0,1,6}-{2930,9699,0,1,0}-{2933,9697,0,1,0}-{2932,9685,0,1,0}-{2930,9693,0,1,0}-{3235,3224,0,1,3}-{3229,3220,0,1,4}-{3211,3211,0,1,3}-{3225,3220,0,1,1}-{3237,3215,0,1,5}-{3211,3210,0,1,7}-{3227,3220,0,1,7}-{3233,3227,0,1,5}-{3227,3210,0,1,6}-{3228,3222,0,1,4}-{3229,3226,0,1,0}-{3236,3217,0,1,4}-{3259,3230,0,1,4}-{3233,3237,0,1,7}-{3205,3204,0,1,0}-{3206,3204,0,1,0}-{3205,3203,0,1,0}-{3206,3202,0,1,0}-{3207,3202,0,1,0}-{3208,3203,0,1,0}-{3001,3202,0,1,5}-{3243,3687,0,1,5}-{3249,3669,0,1,3}-{3252,3675,0,1,4}-{3252,3680,0,1,3}-{3259,3683,0,1,0}-{3475,9840,0,1,6}-{3481,9842,0,1,1}-{3486,9843,0,1,7}-{3483,9824,0,1,4}-{3496,9808,0,0,5}-{3490,9815,0,1,1}-{3478,9834,0,0,3}-{3490,9824,0,1,4}-{3225,9862,0,1,4}-{3222,9861,0,1,6}-{3220,9860,0,1,6}-{3219,9865,0,1,6}-{3237,9862,0,1,4}-{2536,2982,0,1,3}-{2531,2980,0,1,0}-{2522,2981,0,1,4}-{2545,2989,0,1,4}-{2523,2970,0,1,2}-{3026,3174,0,1,5}-{3019,3176,0,1,7}-{2801,3158,0,1,2}-{2514,3193,0,1,6}-{2518,3192,0,1,3}-{2507,3181,0,1,3}-{2508,3178,0,1,6}-{2511,3183,0,1,3}-{2515,3182,0,1,1}-{3021,3205,0,1,6}-{3019,3292,0,1,7}-{3018,3295,0,1,7}-{2531,3325,0,1,3}-{2530,3327,0,1,5}-{2521,3331,0,1,3}-{2526,3328,0,1,3}-{2523,3331,0,1,4}-{2523,3334,0,1,1}-{2531,3329,0,1,5}-{2532,3333,0,1,5}-{3276,9871,0,1,1}-{3277,9871,0,1,3}-" }, { "npc_id": "48", @@ -193,7 +193,7 @@ }, { "npc_id": "59", - "loc_data": "{3082,3362,0,1,4}-{2602,9640,0,1,0}-{2603,9635,0,1,0}-{2603,9638,0,1,0}-{2605,9637,0,1,0}-{2605,9639,0,1,0}-{2606,9635,0,1,0}-{2606,9646,0,1,0}-{2607,9637,0,1,0}-{2607,9641,0,1,0}-{2607,9644,0,1,0}-{2607,9648,0,1,0}-{2608,9635,0,1,0}-{2608,9642,0,1,0}-{2609,9639,0,1,0}-{2609,9643,0,1,0}-{3102,9881,0,1,3}-{3095,9883,0,1,2}-{3182,3244,0,1,1}-{3162,3223,0,1,7}-{3164,3242,0,1,0}-{3169,3246,0,1,0}-{3170,3250,0,1,0}-{3166,3247,0,1,0}-{3164,3249,0,1,0}-{3157,3226,0,1,0}-{3163,3227,0,1,0}-{3165,3223,0,1,0}-{3194,3236,0,1,0}-{3146,3347,0,1,3}-{2648,9766,0,1,4}-{2653,9761,0,1,3}-{2483,2877,0,1,3}-{2481,2876,0,1,4}-{2457,2867,0,1,4}-{2481,2847,0,1,0}-{2475,2854,0,1,3}-{2482,2873,0,1,3}-{2485,2876,0,1,2}-{2449,2865,0,1,1}-{2461,2880,0,1,5}-{2489,2935,0,1,7}-{2492,2907,0,1,1}-{2487,2894,0,1,2}-{2487,2888,0,1,7}-{2478,2916,0,1,5}-{2485,2902,0,1,3}-{2487,2902,0,1,2}-{2489,2894,0,1,2}-{2490,2905,0,1,7}-{2490,2917,0,1,5}-{2484,2890,0,1,1}-{2491,2927,0,1,1}-{3250,3239,0,1,1}-{3241,3241,0,1,1}-{3249,3249,0,1,1}-{3218,9890,0,1,3}-{3220,9887,0,1,0}-{3218,9887,0,1,1}-{3218,9889,0,1,5}-{3213,9890,0,1,1}-{2496,2890,0,1,4}-{2503,2889,0,1,1}-{2497,2939,0,1,2}-{2369,3374,0,1,0}-{2372,3379,0,1,0}-{2378,3366,0,1,0}-{2379,3378,0,1,0}-{2387,3370,0,1,0}-{2394,3365,0,1,0}-{2402,3386,0,1,0}-{2405,3381,0,1,0}-{2407,3387,0,1,0}-{2412,3384,0,1,0}-" + "loc_data": "{3082,3362,0,1,4}-{2602,9640,0,1,0}-{2603,9635,0,1,0}-{2603,9638,0,1,0}-{2605,9637,0,1,0}-{2605,9639,0,1,0}-{2606,9635,0,1,0}-{2606,9646,0,1,0}-{2607,9637,0,1,0}-{2607,9641,0,1,0}-{2607,9644,0,1,0}-{2607,9648,0,1,0}-{2608,9635,0,1,0}-{2608,9642,0,1,0}-{2609,9639,0,1,0}-{2609,9643,0,1,0}-{3102,9881,0,1,3}-{3095,9883,0,1,2}-{3182,3244,0,1,1}-{3162,3223,0,1,7}-{3164,3242,0,1,0}-{3169,3246,0,1,0}-{3170,3250,0,1,0}-{3166,3247,0,1,0}-{3164,3249,0,1,0}-{3157,3226,0,1,0}-{3163,3227,0,1,0}-{3165,3223,0,1,0}-{3194,3236,0,1,0}-{3146,3347,0,1,3}-{2369,3374,0,1,0}-{2372,3379,0,1,0}-{2378,3366,0,1,0}-{2379,3378,0,1,0}-{2387,3370,0,1,0}-{2394,3365,0,1,0}-{2402,3386,0,1,0}-{2405,3381,0,1,0}-{2407,3387,0,1,0}-{2412,3384,0,1,0}-{2648,9766,0,1,4}-{2653,9761,0,1,3}-{2483,2877,0,1,3}-{2481,2876,0,1,4}-{2457,2867,0,1,4}-{2481,2847,0,1,0}-{2475,2854,0,1,3}-{2482,2873,0,1,3}-{2485,2876,0,1,2}-{2449,2865,0,1,1}-{2461,2880,0,1,5}-{2489,2935,0,1,7}-{2492,2907,0,1,1}-{2487,2894,0,1,2}-{2487,2888,0,1,7}-{2478,2916,0,1,5}-{2485,2902,0,1,3}-{2487,2902,0,1,2}-{2489,2894,0,1,2}-{2490,2905,0,1,7}-{2490,2917,0,1,5}-{2484,2890,0,1,1}-{2491,2927,0,1,1}-{3250,3239,0,1,1}-{3241,3241,0,1,1}-{3249,3249,0,1,1}-{3218,9890,0,1,3}-{3220,9887,0,1,0}-{3218,9887,0,1,1}-{3218,9889,0,1,5}-{3213,9890,0,1,1}-{2496,2890,0,1,4}-{2503,2889,0,1,1}-{2497,2939,0,1,2}-" }, { "npc_id": "60", @@ -217,15 +217,15 @@ }, { "npc_id": "66", - "loc_data": "{2417,3493,1,1,0}-{2418,3495,1,1,0}-{2383,3452,0,1,0}-{2401,3417,0,1,0}-{2402,3422,0,1,0}-{2402,3441,0,1,0}-{2403,3430,0,1,0}-{2408,3441,0,1,0}-{2409,3430,0,1,0}-{2423,3426,0,1,0}-{2427,3428,0,1,0}-{2427,3440,0,1,0}-{2526,3168,0,1,0}-{2529,3163,0,1,0}-{2530,3172,0,1,0}-{2536,3169,0,1,0}-{2435,3460,0,1,0}-{2440,3470,0,1,0}-{2447,3502,0,1,0}-{2457,3462,0,1,0}-{2478,3502,0,1,0}-{2449,3492,1,1,0}-{2450,3490,1,1,0}-{2474,3490,1,1,0}-{2482,3492,1,1,0}-{2482,3498,1,1,0}-{2399,3356,0,1,0}-" + "loc_data": "{2399,3356,0,1,0}-{2383,3452,0,1,0}-{2401,3417,0,1,0}-{2402,3422,0,1,0}-{2402,3441,0,1,0}-{2403,3430,0,1,0}-{2408,3441,0,1,0}-{2409,3430,0,1,0}-{2423,3426,0,1,0}-{2427,3428,0,1,0}-{2427,3440,0,1,0}-{2417,3493,1,1,0}-{2418,3495,1,1,0}-{2435,3460,0,1,0}-{2440,3470,0,1,0}-{2447,3502,0,1,0}-{2457,3462,0,1,0}-{2478,3502,0,1,0}-{2449,3492,1,1,0}-{2450,3490,1,1,0}-{2474,3490,1,1,0}-{2482,3492,1,1,0}-{2482,3498,1,1,0}-{2526,3168,0,1,0}-{2529,3163,0,1,0}-{2530,3172,0,1,0}-{2536,3169,0,1,0}-" }, { "npc_id": "67", - "loc_data": "{2556,3226,0,1,0}-{2456,3425,0,1,0}-{2459,3421,0,1,0}-{2462,3431,0,1,0}-{2394,3500,1,1,0}-{2417,3483,1,1,0}-{2377,3442,0,1,0}-{2380,3425,0,1,0}-{2383,3433,0,1,0}-{2405,3447,0,1,0}-{2406,3440,0,1,0}-{2393,3451,1,1,0}-{2408,3437,1,1,0}-{2521,3169,0,1,0}-{2521,3171,0,1,0}-{2033,5530,1,1,0}-{2437,3478,0,1,0}-{2442,3465,0,1,0}-{2456,3467,0,1,0}-{2471,3496,0,1,0}-{2474,3508,0,1,0}-{2475,3471,0,1,0}-{2479,3468,0,1,0}-{2486,3470,0,1,0}-{2492,3474,0,1,0}-{2443,3464,1,1,0}-{2443,3502,1,1,0}-{2448,3496,1,1,0}-{2449,3506,1,1,0}-{2457,3498,1,1,0}-{2474,3498,1,1,0}-{2480,3502,1,1,0}-{2481,3482,1,1,0}-{2490,3503,1,1,0}-{2400,3356,0,1,0}-" + "loc_data": "{2400,3356,0,1,0}-{2377,3442,0,1,0}-{2380,3425,0,1,0}-{2383,3433,0,1,0}-{2405,3447,0,1,0}-{2406,3440,0,1,0}-{2393,3451,1,1,0}-{2408,3437,1,1,0}-{2394,3500,1,1,0}-{2417,3483,1,1,0}-{2456,3425,0,1,0}-{2459,3421,0,1,0}-{2462,3431,0,1,0}-{2437,3478,0,1,0}-{2442,3465,0,1,0}-{2456,3467,0,1,0}-{2471,3496,0,1,0}-{2474,3508,0,1,0}-{2475,3471,0,1,0}-{2479,3468,0,1,0}-{2486,3470,0,1,0}-{2492,3474,0,1,0}-{2443,3464,1,1,0}-{2443,3502,1,1,0}-{2448,3496,1,1,0}-{2449,3506,1,1,0}-{2457,3498,1,1,0}-{2474,3498,1,1,0}-{2480,3502,1,1,0}-{2481,3482,1,1,0}-{2490,3503,1,1,0}-{2521,3169,0,1,0}-{2521,3171,0,1,0}-{2556,3226,0,1,0}-{2033,5530,1,1,0}-" }, { "npc_id": "68", - "loc_data": "{2400,3514,1,1,0}-{2418,3485,1,1,0}-{2378,3423,0,1,0}-{2393,3435,0,1,0}-{2394,3425,0,1,0}-{2395,3449,0,1,0}-{2403,3433,0,1,0}-{2420,3429,0,1,0}-{2420,3438,0,1,0}-{2522,3172,0,1,0}-{2433,3474,0,1,0}-{2433,3492,0,1,0}-{2458,3497,0,1,0}-{2464,3505,0,1,0}-{2476,3458,0,1,0}-{2445,3502,1,1,0}-{2453,3488,1,1,0}-{2475,3502,1,1,0}-{2465,3490,2,1,0}-{2401,3357,0,1,0}-" + "loc_data": "{2401,3357,0,1,0}-{2378,3423,0,1,0}-{2393,3435,0,1,0}-{2394,3425,0,1,0}-{2395,3449,0,1,0}-{2403,3433,0,1,0}-{2420,3429,0,1,0}-{2420,3438,0,1,0}-{2400,3514,1,1,0}-{2418,3485,1,1,0}-{2433,3474,0,1,0}-{2433,3492,0,1,0}-{2458,3497,0,1,0}-{2464,3505,0,1,0}-{2476,3458,0,1,0}-{2445,3502,1,1,0}-{2453,3488,1,1,0}-{2475,3502,1,1,0}-{2465,3490,2,1,0}-{2522,3172,0,1,0}-" }, { "npc_id": "73", @@ -325,11 +325,11 @@ }, { "npc_id": "100", - "loc_data": "{2567,3440,0,1,3}-{2564,9653,0,1,0}-{2565,9655,0,1,0}-{2566,9626,0,1,0}-{2567,9653,0,1,0}-{2569,9633,0,1,0}-{2607,9621,0,1,0}-{2551,3408,0,1,6}-{2553,3405,0,1,3}-{2559,3452,0,1,3}-" + "loc_data": "{2567,3440,0,1,3}-{2316,9392,0,1,0}-{2319,9367,0,1,0}-{2321,9402,0,1,0}-{2322,9387,0,1,0}-{2325,9360,0,1,0}-{2333,9346,0,1,0}-{2335,9382,0,1,0}-{2337,9387,0,1,0}-{2343,9360,0,1,0}-{2344,9369,0,1,0}-{2348,9390,0,1,0}-{2349,9380,0,1,0}-{2349,9402,0,1,0}-{2359,9345,0,1,0}-{2359,9359,0,1,0}-{2359,9374,0,1,0}-{2359,9382,0,1,0}-{2363,9403,0,1,0}-{2564,9653,0,1,0}-{2565,9655,0,1,0}-{2566,9626,0,1,0}-{2567,9653,0,1,0}-{2569,9633,0,1,0}-{2607,9621,0,1,0}-{2551,3408,0,1,6}-{2553,3405,0,1,3}-{2559,3452,0,1,3}-" }, { "npc_id": "101", - "loc_data": "{2622,3389,0,1,4}-{2619,3390,0,1,4}-{2573,3437,0,1,3}-{2562,9659,0,1,0}-{2563,9661,0,1,0}-{2599,9626,0,1,0}-{2601,9627,0,1,0}-{2605,9621,0,1,0}-{2610,9620,0,1,0}-{2624,3391,0,1,1}-{2555,3407,0,1,3}-{2553,3457,0,1,3}-" + "loc_data": "{2622,3389,0,1,4}-{2619,3390,0,1,4}-{2573,3437,0,1,3}-{2305,9385,0,1,0}-{2307,9359,0,1,0}-{2307,9403,0,1,0}-{2310,9396,0,1,0}-{2317,9371,0,1,0}-{2317,9383,0,1,0}-{2324,9404,0,1,0}-{2333,9366,0,1,0}-{2335,9393,0,1,0}-{2339,9403,0,1,0}-{2342,9347,0,1,0}-{2351,9359,0,1,0}-{2359,9392,0,1,0}-{2562,9659,0,1,0}-{2563,9661,0,1,0}-{2599,9626,0,1,0}-{2601,9627,0,1,0}-{2605,9621,0,1,0}-{2610,9620,0,1,0}-{2624,3391,0,1,1}-{2555,3407,0,1,3}-{2553,3457,0,1,3}-" }, { "npc_id": "102", @@ -345,7 +345,7 @@ }, { "npc_id": "105", - "loc_data": "{3100,3594,0,1,6}-{3107,3608,0,1,2}-{3099,3602,0,1,0}-{2632,3280,0,1,1}-{2633,3274,0,1,3}-{2696,3329,0,1,4}-{2708,3336,0,1,4}-{3230,3500,0,1,5}-{2988,3671,0,1,0}-{3001,3674,0,1,0}-{2497,3164,0,1,2}-{2387,3376,0,1,0}-{2398,3366,0,1,0}-{2412,3378,0,1,0}-{2419,3372,0,1,0}-" + "loc_data": "{3100,3594,0,1,6}-{3107,3608,0,1,2}-{3099,3602,0,1,0}-{2632,3280,0,1,1}-{2633,3274,0,1,3}-{2387,3376,0,1,0}-{2398,3366,0,1,0}-{2412,3378,0,1,0}-{2419,3372,0,1,0}-{2696,3329,0,1,4}-{2708,3336,0,1,4}-{3230,3500,0,1,5}-{2988,3671,0,1,0}-{3001,3674,0,1,0}-{2497,3164,0,1,2}-" }, { "npc_id": "106", @@ -365,7 +365,7 @@ }, { "npc_id": "110", - "loc_data": "{2564,9887,0,1,4}-{2581,9897,0,1,1}-{2577,9888,0,1,1}-{3234,5497,0,1,1}-{3305,9400,0,1,1}-{3244,9356,0,1,1}-{3252,9370,0,1,1}-{3294,9375,0,1,3}-{3050,10337,0,1,4}-{3047,10340,0,1,4}-{3048,10346,0,1,4}-{3048,10346,0,1,3}-" + "loc_data": "{2564,9887,0,1,4}-{2581,9897,0,1,1}-{2577,9888,0,1,1}-{3234,5497,0,1,1}-{3244,9356,0,1,1}-{3252,9370,0,1,1}-{3305,9400,0,1,1}-{3294,9375,0,1,3}-{3050,10337,0,1,4}-{3047,10340,0,1,4}-{3048,10346,0,1,4}-{3048,10346,0,1,3}-" }, { "npc_id": "111", @@ -389,7 +389,7 @@ }, { "npc_id": "117", - "loc_data": "{2369,3404,0,1,0}-{3118,9845,0,1,4}-{3111,9844,0,1,6}-{3123,9845,0,1,3}-{3114,9833,0,1,3}-{3110,9841,0,1,1}-{3119,9839,0,1,1}-{3097,9832,0,1,0}-{3101,9832,0,1,1}-{3107,9829,0,1,3}-{3115,9831,0,1,4}-{3109,9835,0,1,4}-{2904,9734,0,1,0}-{2548,3146,0,1,1}-{2542,3145,0,1,4}-{2503,3150,0,1,4}-{3300,3649,0,1,0}-{3044,10321,0,1,2}-{3044,10316,0,1,4}-{3045,10308,0,1,4}-{3048,10317,0,1,2}-" + "loc_data": "{3118,9845,0,1,4}-{3111,9844,0,1,6}-{3123,9845,0,1,3}-{3114,9833,0,1,3}-{3110,9841,0,1,1}-{3119,9839,0,1,1}-{3097,9832,0,1,0}-{3101,9832,0,1,1}-{3107,9829,0,1,3}-{3115,9831,0,1,4}-{3109,9835,0,1,4}-{2369,3404,0,1,0}-{2904,9734,0,1,0}-{2548,3146,0,1,1}-{2542,3145,0,1,4}-{2503,3150,0,1,4}-{3300,3649,0,1,0}-{3044,10321,0,1,2}-{3044,10316,0,1,4}-{3045,10308,0,1,4}-{3048,10317,0,1,2}-" }, { "npc_id": "118", @@ -489,23 +489,23 @@ }, { "npc_id": "153", - "loc_data": "{2540,9820,0,1,0}-{2543,9813,0,1,0}-{2546,9817,0,1,0}-{2464,4421,0,1,0}-{2466,4423,0,1,0}-{2486,4464,0,1,0}-{2556,3444,0,1,0}-{3082,3077,0,1,0}-{3092,3082,0,1,0}-{3109,3090,0,1,0}-{3112,3093,0,1,0}-{2249,3260,0,1,0}-{2250,3257,0,1,0}-{2250,3259,0,1,0}-{2252,3261,0,1,0}-{2253,3256,0,1,0}-{2262,3226,0,1,0}-{2264,3225,0,1,0}-{2266,3229,0,1,0}-{2268,3227,0,1,0}-{2269,3222,0,1,0}-{2269,3224,0,1,0}-{2270,3223,0,1,0}-{2270,3226,0,1,0}-{3200,5954,0,1,0}-{3202,5958,0,1,0}-{3204,5955,0,1,0}-{3204,5959,0,1,0}-{3231,5967,0,1,0}-{3236,5974,0,1,0}-{3237,5968,0,1,0}-{3240,5992,0,1,0}-{3240,5994,0,1,0}-{3242,5990,0,1,0}-{3242,5993,0,1,0}-{3244,5994,0,1,0}-{3246,5989,0,1,0}-{2196,3191,0,1,0}-{2197,3189,0,1,0}-{2198,3181,0,1,0}-{2198,3187,0,1,0}-{2199,3183,0,1,0}-{2199,3187,0,1,0}-{2200,3185,0,1,0}-{2201,3185,0,1,0}-{2204,3180,0,1,0}-{2324,4599,0,1,0}-{2325,4597,0,1,0}-{2326,4589,0,1,0}-{2326,4595,0,1,0}-{2327,4591,0,1,0}-{2327,4595,0,1,0}-{2328,4593,0,1,0}-{2329,4593,0,1,0}-{2332,4588,0,1,0}-{2176,3202,0,1,0}-{2178,3206,0,1,0}-{2180,3203,0,1,0}-{2180,3207,0,1,0}-{2207,3215,0,1,0}-{2212,3222,0,1,0}-{2213,3216,0,1,0}-{2216,3240,0,1,0}-{2216,3242,0,1,0}-{2218,3238,0,1,0}-{2218,3241,0,1,0}-{2220,3242,0,1,0}-{2222,3237,0,1,0}-{2437,3425,0,1,0}-{2438,3422,0,1,0}-{2450,3419,0,1,0}-{2415,4466,0,1,0}-{2419,4427,0,1,0}-{2421,4468,0,1,0}-{2426,4431,0,1,0}-{3175,3226,0,1,0}-{3178,3228,0,1,0}-{3179,3226,0,1,0}-{3182,3251,0,1,0}-{3183,3254,0,1,0}-{2697,6204,0,1,0}-{2698,6201,0,1,0}-{2698,6203,0,1,0}-{2700,6205,0,1,0}-{2701,6200,0,1,0}-{2710,6170,0,1,0}-{2712,6169,0,1,0}-{2714,6173,0,1,0}-{2716,6171,0,1,0}-{2717,6166,0,1,0}-{2717,6168,0,1,0}-{2718,6167,0,1,0}-{2718,6170,0,1,0}-{2478,3373,0,1,0}-{2479,3381,0,1,0}-{2481,3384,0,1,0}-{2490,3371,0,1,0}-{2689,6083,0,1,0}-{2689,6088,0,1,0}-{2690,6094,0,1,0}-{2691,6084,0,1,0}-{2692,6081,0,1,0}-{2692,6090,0,1,0}-{2693,6083,0,1,0}-{2693,6085,0,1,0}-{2693,6090,0,1,0}-{2694,6083,0,1,0}-{2694,6086,0,1,0}-{2695,6082,0,1,0}-{2695,6085,0,1,0}-{2695,6088,0,1,0}-{2695,6091,0,1,0}-{2695,6121,0,1,0}-{2698,6121,0,1,0}-{2699,6082,0,1,0}-{2699,6085,0,1,0}-{2700,6084,0,1,0}-{2701,6085,0,1,0}-{2702,6081,0,1,0}-{2702,6083,0,1,0}-{2703,6119,0,1,0}-{2704,6117,0,1,0}-{2705,6121,0,1,0}-{2730,6091,0,1,0}-{2732,6095,0,1,0}-{2734,6090,0,1,0}-{2735,6094,0,1,0}-{2735,6097,0,1,0}-{2738,6098,0,1,0}-{2416,3404,0,1,0}-{2424,3410,0,1,0}-{2430,3405,0,1,0}-{1906,5223,0,1,0}-{1907,5221,0,1,0}-{3273,6012,0,1,0}-{3274,6009,0,1,0}-{3274,6011,0,1,0}-{3276,6013,0,1,0}-{3277,6008,0,1,0}-{3286,5978,0,1,0}-{3288,5977,0,1,0}-{3290,5981,0,1,0}-{3292,5979,0,1,0}-{3293,5974,0,1,0}-{3293,5976,0,1,0}-{3294,5975,0,1,0}-{3294,5978,0,1,0}-{2828,5091,0,1,0}-{2832,5108,0,1,0}-{2835,5079,0,1,0}-{2846,5067,0,1,0}-{2850,5111,0,1,0}-{2853,5086,0,1,0}-{2864,5071,0,1,0}-{2549,3139,0,1,0}-{2241,3139,0,1,0}-{2241,3144,0,1,0}-{2242,3150,0,1,0}-{2243,3140,0,1,0}-{2244,3137,0,1,0}-{2244,3146,0,1,0}-{2245,3139,0,1,0}-{2245,3141,0,1,0}-{2245,3146,0,1,0}-{2246,3139,0,1,0}-{2246,3142,0,1,0}-{2247,3138,0,1,0}-{2247,3141,0,1,0}-{2247,3144,0,1,0}-{2247,3147,0,1,0}-{2247,3177,0,1,0}-{2250,3177,0,1,0}-{2251,3138,0,1,0}-{2251,3141,0,1,0}-{2252,3140,0,1,0}-{2253,3141,0,1,0}-{2254,3137,0,1,0}-{2254,3139,0,1,0}-{2255,3175,0,1,0}-{2256,3173,0,1,0}-{2257,3177,0,1,0}-{2282,3147,0,1,0}-{2284,3151,0,1,0}-{2286,3146,0,1,0}-{2287,3150,0,1,0}-{2287,3153,0,1,0}-{2290,3154,0,1,0}-" + "loc_data": "{3082,3077,0,1,0}-{3092,3082,0,1,0}-{3109,3090,0,1,0}-{3112,3093,0,1,0}-{2324,4599,0,1,0}-{2325,4597,0,1,0}-{2326,4589,0,1,0}-{2326,4595,0,1,0}-{2327,4591,0,1,0}-{2327,4595,0,1,0}-{2328,4593,0,1,0}-{2329,4593,0,1,0}-{2332,4588,0,1,0}-{2828,5091,0,1,0}-{2832,5108,0,1,0}-{2835,5079,0,1,0}-{2846,5067,0,1,0}-{2850,5111,0,1,0}-{2853,5086,0,1,0}-{2864,5071,0,1,0}-{3175,3226,0,1,0}-{3178,3228,0,1,0}-{3179,3226,0,1,0}-{3182,3251,0,1,0}-{3183,3254,0,1,0}-{2416,3404,0,1,0}-{2424,3410,0,1,0}-{2430,3405,0,1,0}-{2415,4466,0,1,0}-{2419,4427,0,1,0}-{2421,4468,0,1,0}-{2426,4431,0,1,0}-{1906,5223,0,1,0}-{1907,5221,0,1,0}-{2196,3191,0,1,0}-{2197,3189,0,1,0}-{2198,3181,0,1,0}-{2198,3187,0,1,0}-{2199,3183,0,1,0}-{2199,3187,0,1,0}-{2200,3185,0,1,0}-{2201,3185,0,1,0}-{2204,3180,0,1,0}-{2176,3202,0,1,0}-{2178,3206,0,1,0}-{2180,3203,0,1,0}-{2180,3207,0,1,0}-{2207,3215,0,1,0}-{2212,3222,0,1,0}-{2213,3216,0,1,0}-{2216,3240,0,1,0}-{2216,3242,0,1,0}-{2218,3238,0,1,0}-{2218,3241,0,1,0}-{2220,3242,0,1,0}-{2222,3237,0,1,0}-{2478,3373,0,1,0}-{2479,3381,0,1,0}-{2481,3384,0,1,0}-{2490,3371,0,1,0}-{2437,3425,0,1,0}-{2438,3422,0,1,0}-{2450,3419,0,1,0}-{2464,4421,0,1,0}-{2466,4423,0,1,0}-{2486,4464,0,1,0}-{3200,5954,0,1,0}-{3202,5958,0,1,0}-{3204,5955,0,1,0}-{3204,5959,0,1,0}-{3231,5967,0,1,0}-{3236,5974,0,1,0}-{3237,5968,0,1,0}-{3240,5992,0,1,0}-{3240,5994,0,1,0}-{3242,5990,0,1,0}-{3242,5993,0,1,0}-{3244,5994,0,1,0}-{3246,5989,0,1,0}-{2689,6083,0,1,0}-{2689,6088,0,1,0}-{2690,6094,0,1,0}-{2691,6084,0,1,0}-{2692,6081,0,1,0}-{2692,6090,0,1,0}-{2693,6083,0,1,0}-{2693,6085,0,1,0}-{2693,6090,0,1,0}-{2694,6083,0,1,0}-{2694,6086,0,1,0}-{2695,6082,0,1,0}-{2695,6085,0,1,0}-{2695,6088,0,1,0}-{2695,6091,0,1,0}-{2695,6121,0,1,0}-{2698,6121,0,1,0}-{2699,6082,0,1,0}-{2699,6085,0,1,0}-{2700,6084,0,1,0}-{2701,6085,0,1,0}-{2702,6081,0,1,0}-{2702,6083,0,1,0}-{2703,6119,0,1,0}-{2704,6117,0,1,0}-{2705,6121,0,1,0}-{2730,6091,0,1,0}-{2732,6095,0,1,0}-{2734,6090,0,1,0}-{2735,6094,0,1,0}-{2735,6097,0,1,0}-{2738,6098,0,1,0}-{2697,6204,0,1,0}-{2698,6201,0,1,0}-{2698,6203,0,1,0}-{2700,6205,0,1,0}-{2701,6200,0,1,0}-{2710,6170,0,1,0}-{2712,6169,0,1,0}-{2714,6173,0,1,0}-{2716,6171,0,1,0}-{2717,6166,0,1,0}-{2717,6168,0,1,0}-{2718,6167,0,1,0}-{2718,6170,0,1,0}-{2549,3139,0,1,0}-{2241,3139,0,1,0}-{2241,3144,0,1,0}-{2242,3150,0,1,0}-{2243,3140,0,1,0}-{2244,3137,0,1,0}-{2244,3146,0,1,0}-{2245,3139,0,1,0}-{2245,3141,0,1,0}-{2245,3146,0,1,0}-{2246,3139,0,1,0}-{2246,3142,0,1,0}-{2247,3138,0,1,0}-{2247,3141,0,1,0}-{2247,3144,0,1,0}-{2247,3147,0,1,0}-{2247,3177,0,1,0}-{2250,3177,0,1,0}-{2251,3138,0,1,0}-{2251,3141,0,1,0}-{2252,3140,0,1,0}-{2253,3141,0,1,0}-{2254,3137,0,1,0}-{2254,3139,0,1,0}-{2255,3175,0,1,0}-{2256,3173,0,1,0}-{2257,3177,0,1,0}-{2282,3147,0,1,0}-{2284,3151,0,1,0}-{2286,3146,0,1,0}-{2287,3150,0,1,0}-{2287,3153,0,1,0}-{2290,3154,0,1,0}-{2249,3260,0,1,0}-{2250,3257,0,1,0}-{2250,3259,0,1,0}-{2252,3261,0,1,0}-{2253,3256,0,1,0}-{2262,3226,0,1,0}-{2264,3225,0,1,0}-{2266,3229,0,1,0}-{2268,3227,0,1,0}-{2269,3222,0,1,0}-{2269,3224,0,1,0}-{2270,3223,0,1,0}-{2270,3226,0,1,0}-{2556,3444,0,1,0}-{3273,6012,0,1,0}-{3274,6009,0,1,0}-{3274,6011,0,1,0}-{3276,6013,0,1,0}-{3277,6008,0,1,0}-{3286,5978,0,1,0}-{3288,5977,0,1,0}-{3290,5981,0,1,0}-{3292,5979,0,1,0}-{3293,5974,0,1,0}-{3293,5976,0,1,0}-{3294,5975,0,1,0}-{3294,5978,0,1,0}-{2540,9820,0,1,0}-{2543,9813,0,1,0}-{2546,9817,0,1,0}-" }, { "npc_id": "154", - "loc_data": "{2475,4459,0,1,0}-{2480,4458,0,1,0}-{2481,4454,0,1,0}-{2488,4465,0,1,0}-{3125,3084,0,1,0}-{2268,3228,0,1,0}-{3202,5955,0,1,0}-{3206,5953,0,1,0}-{3231,5976,0,1,0}-{3233,5974,0,1,0}-{3235,5970,0,1,0}-{3241,5996,0,1,0}-{3242,5988,0,1,0}-{3244,5992,0,1,0}-{2180,3173,0,1,0}-{2193,3187,0,1,0}-{2196,3184,0,1,0}-{2200,3181,0,1,0}-{2200,3188,0,1,0}-{2203,3189,0,1,0}-{2308,4581,0,1,0}-{2321,4595,0,1,0}-{2324,4592,0,1,0}-{2328,4589,0,1,0}-{2328,4596,0,1,0}-{2331,4597,0,1,0}-{2178,3203,0,1,0}-{2182,3201,0,1,0}-{2207,3224,0,1,0}-{2209,3222,0,1,0}-{2211,3218,0,1,0}-{2217,3244,0,1,0}-{2218,3236,0,1,0}-{2220,3240,0,1,0}-{2446,3396,0,1,0}-{2470,3397,0,1,0}-{2394,4453,0,1,0}-{2394,4457,0,1,0}-{2406,4447,0,1,0}-{2422,3489,0,1,0}-{2716,6172,0,1,0}-{2688,6092,0,1,0}-{2690,6087,0,1,0}-{2690,6095,0,1,0}-{2692,6082,0,1,0}-{2692,6088,0,1,0}-{2692,6123,0,1,0}-{2693,6081,0,1,0}-{2694,6125,0,1,0}-{2695,6087,0,1,0}-{2697,6086,0,1,0}-{2697,6087,0,1,0}-{2698,6081,0,1,0}-{2698,6124,0,1,0}-{2700,6083,0,1,0}-{2702,6084,0,1,0}-{2726,6085,0,1,0}-{2727,6081,0,1,0}-{2729,6083,0,1,0}-{2732,6084,0,1,0}-{2378,3418,0,1,0}-{2380,3422,0,1,0}-{2393,3442,0,1,0}-{2397,3440,0,1,0}-{2423,3399,0,1,0}-{2425,3406,0,1,0}-{2427,3402,0,1,0}-{1908,5222,0,1,0}-{3292,5980,0,1,0}-{2831,5070,0,1,0}-{2843,5106,0,1,0}-{2844,5080,0,1,0}-{2860,5079,0,1,0}-{2866,5104,0,1,0}-{2868,5096,0,1,0}-{2240,3148,0,1,0}-{2242,3143,0,1,0}-{2242,3151,0,1,0}-{2244,3138,0,1,0}-{2244,3144,0,1,0}-{2244,3179,0,1,0}-{2245,3137,0,1,0}-{2246,3181,0,1,0}-{2247,3143,0,1,0}-{2249,3142,0,1,0}-{2249,3143,0,1,0}-{2250,3137,0,1,0}-{2250,3180,0,1,0}-{2252,3139,0,1,0}-{2254,3140,0,1,0}-{2278,3141,0,1,0}-{2279,3137,0,1,0}-{2281,3139,0,1,0}-{2284,3140,0,1,0}-" + "loc_data": "{3125,3084,0,1,0}-{2308,4581,0,1,0}-{2321,4595,0,1,0}-{2324,4592,0,1,0}-{2328,4589,0,1,0}-{2328,4596,0,1,0}-{2331,4597,0,1,0}-{2831,5070,0,1,0}-{2843,5106,0,1,0}-{2844,5080,0,1,0}-{2860,5079,0,1,0}-{2866,5104,0,1,0}-{2868,5096,0,1,0}-{2378,3418,0,1,0}-{2380,3422,0,1,0}-{2393,3442,0,1,0}-{2397,3440,0,1,0}-{2423,3399,0,1,0}-{2425,3406,0,1,0}-{2427,3402,0,1,0}-{2422,3489,0,1,0}-{2394,4453,0,1,0}-{2394,4457,0,1,0}-{2406,4447,0,1,0}-{1908,5222,0,1,0}-{2180,3173,0,1,0}-{2193,3187,0,1,0}-{2196,3184,0,1,0}-{2200,3181,0,1,0}-{2200,3188,0,1,0}-{2203,3189,0,1,0}-{2178,3203,0,1,0}-{2182,3201,0,1,0}-{2207,3224,0,1,0}-{2209,3222,0,1,0}-{2211,3218,0,1,0}-{2217,3244,0,1,0}-{2218,3236,0,1,0}-{2220,3240,0,1,0}-{2446,3396,0,1,0}-{2470,3397,0,1,0}-{2475,4459,0,1,0}-{2480,4458,0,1,0}-{2481,4454,0,1,0}-{2488,4465,0,1,0}-{3202,5955,0,1,0}-{3206,5953,0,1,0}-{3231,5976,0,1,0}-{3233,5974,0,1,0}-{3235,5970,0,1,0}-{3241,5996,0,1,0}-{3242,5988,0,1,0}-{3244,5992,0,1,0}-{2688,6092,0,1,0}-{2690,6087,0,1,0}-{2690,6095,0,1,0}-{2692,6082,0,1,0}-{2692,6088,0,1,0}-{2692,6123,0,1,0}-{2693,6081,0,1,0}-{2694,6125,0,1,0}-{2695,6087,0,1,0}-{2697,6086,0,1,0}-{2697,6087,0,1,0}-{2698,6081,0,1,0}-{2698,6124,0,1,0}-{2700,6083,0,1,0}-{2702,6084,0,1,0}-{2726,6085,0,1,0}-{2727,6081,0,1,0}-{2729,6083,0,1,0}-{2732,6084,0,1,0}-{2716,6172,0,1,0}-{2240,3148,0,1,0}-{2242,3143,0,1,0}-{2242,3151,0,1,0}-{2244,3138,0,1,0}-{2244,3144,0,1,0}-{2244,3179,0,1,0}-{2245,3137,0,1,0}-{2246,3181,0,1,0}-{2247,3143,0,1,0}-{2249,3142,0,1,0}-{2249,3143,0,1,0}-{2250,3137,0,1,0}-{2250,3180,0,1,0}-{2252,3139,0,1,0}-{2254,3140,0,1,0}-{2278,3141,0,1,0}-{2279,3137,0,1,0}-{2281,3139,0,1,0}-{2284,3140,0,1,0}-{2268,3228,0,1,0}-{3292,5980,0,1,0}-" }, { "npc_id": "155", - "loc_data": "{3093,3086,0,1,0}-{3097,3085,0,1,0}-{3129,3091,0,1,0}-{3135,3084,0,1,0}-{2190,3180,0,1,0}-{2318,4588,0,1,0}-{2374,3469,0,1,0}-{2376,3466,0,1,0}-{3254,3230,0,1,0}-{1986,5564,0,1,0}-{2434,3516,0,1,0}-{2479,3501,0,1,0}-" + "loc_data": "{3093,3086,0,1,0}-{3097,3085,0,1,0}-{3129,3091,0,1,0}-{3135,3084,0,1,0}-{2318,4588,0,1,0}-{2374,3469,0,1,0}-{2376,3466,0,1,0}-{2190,3180,0,1,0}-{3254,3230,0,1,0}-{2434,3516,0,1,0}-{2479,3501,0,1,0}-{1986,5564,0,1,0}-" }, { "npc_id": "156", - "loc_data": "{2371,3460,0,1,0}-{2372,3456,0,1,0}-{2422,3467,0,1,0}-{2725,6127,0,1,0}-{2444,3491,0,1,0}-{2277,3183,0,1,0}-" + "loc_data": "{2371,3460,0,1,0}-{2372,3456,0,1,0}-{2422,3467,0,1,0}-{2444,3491,0,1,0}-{2725,6127,0,1,0}-{2277,3183,0,1,0}-" }, { "npc_id": "157", - "loc_data": "{2180,2798,0,1,0}-{2209,2812,0,1,0}-{2217,2780,0,1,0}-{1921,5931,0,1,0}-{1947,5908,0,1,0}-{1955,5949,0,1,0}-{1973,5888,0,1,0}-{1975,5918,0,1,0}-{1988,5870,0,1,0}-{2017,5884,0,1,0}-{2025,5852,0,1,0}-{2182,2979,0,1,0}-{2211,2963,0,1,0}-{1931,6038,0,1,0}-{1983,6037,0,1,0}-{2153,2810,0,1,0}-{2172,2806,0,1,0}-{1961,5882,0,1,0}-{1980,5878,0,1,0}-{2242,2788,0,1,0}-{2248,2858,0,1,0}-{2256,2828,0,1,0}-{2284,2874,0,1,0}-{2255,3216,0,1,0}-{2262,3210,0,1,0}-{2271,3204,0,1,0}-{2294,3219,0,1,0}-{2296,3206,0,1,0}-{2298,3201,0,1,0}-{2056,5930,0,1,0}-{2064,5900,0,1,0}-{2092,5946,0,1,0}-{2050,5860,0,1,0}-{3201,5986,0,1,0}-{3254,5972,0,1,0}-{3255,5986,0,1,0}-{3256,6008,0,1,0}-{3257,5973,0,1,0}-{3257,5975,0,1,0}-{3260,5974,0,1,0}-{3261,5977,0,1,0}-{3262,5957,0,1,0}-{2194,3158,0,1,0}-{2216,3188,0,1,0}-{2217,3190,0,1,0}-{2220,3158,0,1,0}-{2222,3141,0,1,0}-{2229,3138,0,1,0}-{2231,3181,0,1,0}-{2236,3155,0,1,0}-{2278,2956,0,1,0}-{2322,4566,0,1,0}-{2344,4596,0,1,0}-{2345,4598,0,1,0}-{2348,4566,0,1,0}-{2350,4549,0,1,0}-{2357,4546,0,1,0}-{2359,4589,0,1,0}-{2364,4563,0,1,0}-{2177,3234,0,1,0}-{2230,3220,0,1,0}-{2231,3234,0,1,0}-{2232,3256,0,1,0}-{2233,3221,0,1,0}-{2233,3223,0,1,0}-{2236,3222,0,1,0}-{2237,3225,0,1,0}-{2238,3205,0,1,0}-{2479,3396,0,1,0}-{2102,2942,0,1,0}-{2108,2919,0,1,0}-{2104,2873,0,1,0}-{2122,6032,0,1,0}-{1912,5945,0,1,0}-{3143,3210,0,1,0}-{3155,3253,0,1,0}-{3163,3261,0,1,0}-{3168,3258,0,1,0}-{2086,6028,0,1,0}-{2703,6160,0,1,0}-{2710,6154,0,1,0}-{2719,6148,0,1,0}-{2742,6163,0,1,0}-{2744,6150,0,1,0}-{2746,6145,0,1,0}-{2314,2960,0,1,0}-{2723,6105,0,1,0}-{2739,6123,0,1,0}-{2744,6108,0,1,0}-{2326,2894,0,1,0}-{2333,2920,0,1,0}-{3279,5968,0,1,0}-{3286,5962,0,1,0}-{3295,5956,0,1,0}-{3318,5971,0,1,0}-{3320,5958,0,1,0}-{3322,5953,0,1,0}-{2134,5966,0,1,0}-{2141,5992,0,1,0}-{2123,2966,0,1,0}-{2175,2965,0,1,0}-{1910,6014,0,1,0}-{1916,5991,0,1,0}-{2540,3167,0,1,0}-{2110,2955,0,1,0}-{1990,6051,0,1,0}-{2019,6035,0,1,0}-{2447,3467,0,1,0}-{2448,3469,0,1,0}-{1918,6027,0,1,0}-{2275,3161,0,1,0}-{2291,3179,0,1,0}-{2296,3164,0,1,0}-{2113,2859,0,1,0}-{2139,2836,0,1,0}-{2147,2877,0,1,0}-{2165,2816,0,1,0}-{2167,2846,0,1,0}-" + "loc_data": "{2104,2873,0,1,0}-{2102,2942,0,1,0}-{2108,2919,0,1,0}-{2326,2894,0,1,0}-{2333,2920,0,1,0}-{2314,2960,0,1,0}-{2110,2955,0,1,0}-{2322,4566,0,1,0}-{2344,4596,0,1,0}-{2345,4598,0,1,0}-{2348,4566,0,1,0}-{2350,4549,0,1,0}-{2357,4546,0,1,0}-{2359,4589,0,1,0}-{2364,4563,0,1,0}-{2050,5860,0,1,0}-{2056,5930,0,1,0}-{2064,5900,0,1,0}-{2092,5946,0,1,0}-{2086,6028,0,1,0}-{2153,2810,0,1,0}-{2172,2806,0,1,0}-{2113,2859,0,1,0}-{2139,2836,0,1,0}-{2147,2877,0,1,0}-{2165,2816,0,1,0}-{2167,2846,0,1,0}-{2123,2966,0,1,0}-{2175,2965,0,1,0}-{3143,3210,0,1,0}-{3155,3253,0,1,0}-{3163,3261,0,1,0}-{3168,3258,0,1,0}-{1912,5945,0,1,0}-{2134,5966,0,1,0}-{2141,5992,0,1,0}-{1910,6014,0,1,0}-{1916,5991,0,1,0}-{2122,6032,0,1,0}-{1918,6027,0,1,0}-{2180,2798,0,1,0}-{2209,2812,0,1,0}-{2217,2780,0,1,0}-{2182,2979,0,1,0}-{2211,2963,0,1,0}-{2194,3158,0,1,0}-{2216,3188,0,1,0}-{2217,3190,0,1,0}-{2220,3158,0,1,0}-{2222,3141,0,1,0}-{2229,3138,0,1,0}-{2231,3181,0,1,0}-{2236,3155,0,1,0}-{2177,3234,0,1,0}-{2230,3220,0,1,0}-{2231,3234,0,1,0}-{2232,3256,0,1,0}-{2233,3221,0,1,0}-{2233,3223,0,1,0}-{2236,3222,0,1,0}-{2237,3225,0,1,0}-{2238,3205,0,1,0}-{2479,3396,0,1,0}-{2447,3467,0,1,0}-{2448,3469,0,1,0}-{1961,5882,0,1,0}-{1980,5878,0,1,0}-{1921,5931,0,1,0}-{1947,5908,0,1,0}-{1955,5949,0,1,0}-{1973,5888,0,1,0}-{1975,5918,0,1,0}-{3201,5986,0,1,0}-{3254,5972,0,1,0}-{3255,5986,0,1,0}-{3256,6008,0,1,0}-{3257,5973,0,1,0}-{3257,5975,0,1,0}-{3260,5974,0,1,0}-{3261,5977,0,1,0}-{3262,5957,0,1,0}-{1931,6038,0,1,0}-{1983,6037,0,1,0}-{2723,6105,0,1,0}-{2739,6123,0,1,0}-{2744,6108,0,1,0}-{2703,6160,0,1,0}-{2710,6154,0,1,0}-{2719,6148,0,1,0}-{2742,6163,0,1,0}-{2744,6150,0,1,0}-{2746,6145,0,1,0}-{2242,2788,0,1,0}-{2248,2858,0,1,0}-{2256,2828,0,1,0}-{2284,2874,0,1,0}-{2278,2956,0,1,0}-{2540,3167,0,1,0}-{2275,3161,0,1,0}-{2291,3179,0,1,0}-{2296,3164,0,1,0}-{2255,3216,0,1,0}-{2262,3210,0,1,0}-{2271,3204,0,1,0}-{2294,3219,0,1,0}-{2296,3206,0,1,0}-{2298,3201,0,1,0}-{1988,5870,0,1,0}-{2017,5884,0,1,0}-{2025,5852,0,1,0}-{3279,5968,0,1,0}-{3286,5962,0,1,0}-{3295,5956,0,1,0}-{3318,5971,0,1,0}-{3320,5958,0,1,0}-{3322,5953,0,1,0}-{1990,6051,0,1,0}-{2019,6035,0,1,0}-" }, { "npc_id": "158", @@ -513,7 +513,7 @@ }, { "npc_id": "159", - "loc_data": "{2392,3475,0,1,0}-{2394,3506,0,1,0}-{2396,3471,0,1,0}-{2405,3499,0,1,0}-{2416,3487,0,1,0}-{2413,3445,1,1,0}-{2415,3435,1,1,0}-{2416,3416,1,1,0}-{2424,3434,1,1,0}-{2483,3500,1,1,0}-" + "loc_data": "{2413,3445,1,1,0}-{2415,3435,1,1,0}-{2416,3416,1,1,0}-{2424,3434,1,1,0}-{2392,3475,0,1,0}-{2394,3506,0,1,0}-{2396,3471,0,1,0}-{2405,3499,0,1,0}-{2416,3487,0,1,0}-{2483,3500,1,1,0}-" }, { "npc_id": "160", @@ -529,11 +529,11 @@ }, { "npc_id": "163", - "loc_data": "{2451,3414,0,1,0}-{2459,3395,0,1,0}-{2459,3438,0,1,0}-{2462,3395,0,1,0}-{2445,3429,1,1,0}-{2409,3470,1,1,0}-{2416,3466,1,1,0}-{2420,3466,1,1,0}-{2461,9895,0,1,0}-{2465,9894,0,1,0}-{2465,9899,0,1,0}-{2459,3385,0,1,0}-{2463,3385,0,1,0}-{2392,3454,0,1,0}-{2394,3436,0,1,0}-{2408,3452,0,1,0}-{2421,3413,0,1,0}-{2442,3489,0,1,0}-{2450,3485,0,1,0}-{2451,3507,0,1,0}-{2453,3490,0,1,0}-{2459,3503,0,1,0}-{2461,3509,0,1,0}-{2464,3465,0,1,0}-{2464,3468,0,1,0}-{2468,3465,0,1,0}-{2468,3468,0,1,0}-{2468,3506,0,1,0}-{2472,3484,0,1,0}-{2475,3490,0,1,0}-{2475,3502,0,1,0}-{2477,3512,0,1,0}-{2481,3485,0,1,0}-{2482,3501,0,1,0}-{2467,3495,1,1,0}-{2473,3495,1,1,0}-{2448,3497,2,1,0}-{2463,3480,2,1,0}-{2465,3497,2,1,0}-{2465,3510,2,1,0}-{2466,3480,2,1,0}-" + "loc_data": "{2392,3454,0,1,0}-{2394,3436,0,1,0}-{2408,3452,0,1,0}-{2421,3413,0,1,0}-{2409,3470,1,1,0}-{2416,3466,1,1,0}-{2420,3466,1,1,0}-{2459,3385,0,1,0}-{2463,3385,0,1,0}-{2451,3414,0,1,0}-{2459,3395,0,1,0}-{2459,3438,0,1,0}-{2462,3395,0,1,0}-{2445,3429,1,1,0}-{2442,3489,0,1,0}-{2450,3485,0,1,0}-{2451,3507,0,1,0}-{2453,3490,0,1,0}-{2459,3503,0,1,0}-{2461,3509,0,1,0}-{2464,3465,0,1,0}-{2464,3468,0,1,0}-{2468,3465,0,1,0}-{2468,3468,0,1,0}-{2468,3506,0,1,0}-{2472,3484,0,1,0}-{2475,3490,0,1,0}-{2475,3502,0,1,0}-{2477,3512,0,1,0}-{2481,3485,0,1,0}-{2482,3501,0,1,0}-{2467,3495,1,1,0}-{2473,3495,1,1,0}-{2448,3497,2,1,0}-{2463,3480,2,1,0}-{2465,3497,2,1,0}-{2465,3510,2,1,0}-{2466,3480,2,1,0}-{2461,9895,0,1,0}-{2465,9894,0,1,0}-{2465,9899,0,1,0}-" }, { "npc_id": "164", - "loc_data": "{2459,3392,0,1,0}-{2461,3422,0,1,0}-{2462,3392,0,1,0}-{2460,3382,0,1,0}-{2462,3382,0,1,0}-{2410,3416,0,1,0}-{2420,3435,0,1,0}-{2420,3447,0,1,0}-{2427,3410,0,1,0}-{2012,5535,2,1,0}-{2023,5544,2,1,0}-{2441,3497,0,1,0}-{2447,3510,0,1,0}-{2461,3487,0,1,0}-{2464,3472,0,1,0}-{2464,3489,0,1,0}-{2467,3489,0,1,0}-{2468,3472,0,1,0}-{2473,3503,0,1,0}-{2478,3497,0,1,0}-{2464,3496,1,1,0}-{2448,3495,2,1,0}-{2460,3487,2,1,0}-{2467,3494,2,1,0}-{2467,3510,2,1,0}-{2471,3496,2,1,0}-{2483,3495,2,1,0}-{2483,3497,2,1,0}-" + "loc_data": "{2410,3416,0,1,0}-{2420,3435,0,1,0}-{2420,3447,0,1,0}-{2427,3410,0,1,0}-{2460,3382,0,1,0}-{2462,3382,0,1,0}-{2459,3392,0,1,0}-{2461,3422,0,1,0}-{2462,3392,0,1,0}-{2441,3497,0,1,0}-{2447,3510,0,1,0}-{2461,3487,0,1,0}-{2464,3472,0,1,0}-{2464,3489,0,1,0}-{2467,3489,0,1,0}-{2468,3472,0,1,0}-{2473,3503,0,1,0}-{2478,3497,0,1,0}-{2464,3496,1,1,0}-{2448,3495,2,1,0}-{2460,3487,2,1,0}-{2467,3494,2,1,0}-{2467,3510,2,1,0}-{2471,3496,2,1,0}-{2483,3495,2,1,0}-{2483,3497,2,1,0}-{2012,5535,2,1,0}-{2023,5544,2,1,0}-" }, { "npc_id": "166", @@ -541,11 +541,11 @@ }, { "npc_id": "168", - "loc_data": "{2434,3436,0,1,0}-{2437,3451,0,1,0}-{2438,3427,0,1,0}-{2441,3411,0,1,0}-{2466,3449,0,1,0}-{2470,3399,0,1,0}-{2472,3400,0,1,0}-{2473,3412,0,1,0}-{2476,3454,0,1,0}-{2482,3397,0,1,0}-{2489,3401,0,1,0}-{2479,3407,1,1,0}-{2379,3482,0,1,0}-{2381,3496,0,1,0}-{2384,3497,0,1,0}-{2391,3476,0,1,0}-{2410,3496,0,1,0}-{2421,3481,0,1,0}-{2397,3514,1,1,0}-{2398,3451,1,1,0}-{2414,3447,1,1,0}-{2438,3465,0,1,0}-{2442,3505,0,1,0}-{2448,3486,0,1,0}-{2449,3487,0,1,0}-{2450,3489,0,1,0}-{2450,3505,0,1,0}-{2454,3465,0,1,0}-{2449,3486,1,1,0}-{2457,3488,1,1,0}-{2476,3488,1,1,0}-{2450,3496,2,1,0}-{2467,3488,2,1,0}-{2470,3503,2,1,0}-{2481,3498,2,1,0}-" + "loc_data": "{2398,3451,1,1,0}-{2414,3447,1,1,0}-{2379,3482,0,1,0}-{2381,3496,0,1,0}-{2384,3497,0,1,0}-{2391,3476,0,1,0}-{2410,3496,0,1,0}-{2421,3481,0,1,0}-{2397,3514,1,1,0}-{2434,3436,0,1,0}-{2437,3451,0,1,0}-{2438,3427,0,1,0}-{2441,3411,0,1,0}-{2466,3449,0,1,0}-{2470,3399,0,1,0}-{2472,3400,0,1,0}-{2473,3412,0,1,0}-{2476,3454,0,1,0}-{2482,3397,0,1,0}-{2489,3401,0,1,0}-{2479,3407,1,1,0}-{2438,3465,0,1,0}-{2442,3505,0,1,0}-{2448,3486,0,1,0}-{2449,3487,0,1,0}-{2450,3489,0,1,0}-{2450,3505,0,1,0}-{2454,3465,0,1,0}-{2449,3486,1,1,0}-{2457,3488,1,1,0}-{2476,3488,1,1,0}-{2450,3496,2,1,0}-{2467,3488,2,1,0}-{2470,3503,2,1,0}-{2481,3498,2,1,0}-" }, { "npc_id": "169", - "loc_data": "{2439,3433,0,1,0}-{2441,3449,0,1,0}-{2442,3428,0,1,0}-{2446,3403,0,1,0}-{2450,3416,0,1,0}-{2468,3441,0,1,0}-{2480,3408,0,1,0}-{2480,3406,1,1,0}-{2486,3400,1,1,0}-{2378,3482,0,1,0}-{2383,3496,0,1,0}-{2402,3507,0,1,0}-{2406,3476,0,1,0}-{2422,3485,0,1,0}-{2382,3506,1,1,0}-{2418,3472,1,1,0}-{2392,3450,1,1,0}-{2415,3415,1,1,0}-{2416,3434,1,1,0}-{2423,3425,1,1,0}-{2424,3442,1,1,0}-{2448,3489,0,1,0}-{2450,3488,0,1,0}-{2463,3508,0,1,0}-{2473,3489,0,1,0}-{2474,3457,0,1,0}-{2479,3503,0,1,0}-{2486,3467,0,1,0}-{2437,3463,1,1,0}-{2448,3489,1,1,0}-{2482,3508,1,1,0}-" + "loc_data": "{2392,3450,1,1,0}-{2415,3415,1,1,0}-{2416,3434,1,1,0}-{2423,3425,1,1,0}-{2424,3442,1,1,0}-{2378,3482,0,1,0}-{2383,3496,0,1,0}-{2402,3507,0,1,0}-{2406,3476,0,1,0}-{2422,3485,0,1,0}-{2382,3506,1,1,0}-{2418,3472,1,1,0}-{2439,3433,0,1,0}-{2441,3449,0,1,0}-{2442,3428,0,1,0}-{2446,3403,0,1,0}-{2450,3416,0,1,0}-{2468,3441,0,1,0}-{2480,3408,0,1,0}-{2480,3406,1,1,0}-{2486,3400,1,1,0}-{2448,3489,0,1,0}-{2450,3488,0,1,0}-{2463,3508,0,1,0}-{2473,3489,0,1,0}-{2474,3457,0,1,0}-{2479,3503,0,1,0}-{2486,3467,0,1,0}-{2437,3463,1,1,0}-{2448,3489,1,1,0}-{2482,3508,1,1,0}-" }, { "npc_id": "170", @@ -1357,11 +1357,11 @@ }, { "npc_id": "479", - "loc_data": "{1970,5522,3,1,0}-{2384,3481,0,1,0}-{2388,3473,0,1,0}-{2418,3474,3,1,0}-{2415,3433,3,1,0}-{2466,3500,2,1,0}-" + "loc_data": "{2415,3433,3,1,0}-{2384,3481,0,1,0}-{2388,3473,0,1,0}-{2418,3474,3,1,0}-{2466,3500,2,1,0}-{1970,5522,3,1,0}-" }, { "npc_id": "480", - "loc_data": "{1964,5522,3,1,0}-{2458,3417,1,1,0}-{2460,3417,1,1,0}-{2409,3507,0,1,0}-{2412,3474,3,1,0}-{2463,3504,2,1,0}-" + "loc_data": "{2409,3507,0,1,0}-{2412,3474,3,1,0}-{2458,3417,1,1,0}-{2460,3417,1,1,0}-{2463,3504,2,1,0}-{1964,5522,3,1,0}-" }, { "npc_id": "481", @@ -1387,6 +1387,10 @@ "npc_id": "490", "loc_data": "{1887,5026,0,0,6}-" }, + { + "npc_id": "492", + "loc_data": "{2464,3227,0,1,0}-" + }, { "npc_id": "494", "loc_data": "{2615,3094,0,0,3}-{2615,3092,0,0,3}-{2615,3092,0,0,3}-{2615,3094,0,0,3}-{3122,3125,0,0,6}-{3120,3125,0,0,6}-{3090,3242,0,0,4}-{3090,3245,0,0,4}-{3090,3243,0,0,4}-{2618,3330,0,0,0}-{2619,3330,0,0,0}-{2584,3422,0,0,4}-{2584,3419,0,0,4}-{2584,3418,0,0,4}-{2657,3283,0,0,3}-{2657,3286,0,0,3}-{2807,3443,0,0,6}-{2810,3443,0,0,6}-" @@ -2433,11 +2437,11 @@ }, { "npc_id": "839", - "loc_data": "{3265,3066,0,1,0}-{3267,3066,0,1,0}-{3310,3068,0,1,0}-{3322,3011,0,1,0}-{3322,3052,0,1,0}-{3324,3030,0,1,0}-{3150,3044,0,1,0}-{3172,3009,0,1,0}-{3197,3012,0,1,0}-{3198,3040,0,1,0}-{3217,3092,0,1,0}-{3235,3074,0,1,0}-{3252,3125,0,1,0}-{3258,3078,0,1,0}-{3237,2968,0,1,0}-{3224,3013,0,1,0}-{3225,3034,0,1,0}-{3226,3060,0,1,0}-{3250,3057,0,1,0}-{3283,3108,0,1,0}-{3323,3094,0,1,0}-" + "loc_data": "{3150,3044,0,1,0}-{3172,3009,0,1,0}-{3197,3012,0,1,0}-{3198,3040,0,1,0}-{3237,2968,0,1,0}-{3224,3013,0,1,0}-{3225,3034,0,1,0}-{3226,3060,0,1,0}-{3250,3057,0,1,0}-{3217,3092,0,1,0}-{3235,3074,0,1,0}-{3252,3125,0,1,0}-{3258,3078,0,1,0}-{3265,3066,0,1,0}-{3267,3066,0,1,0}-{3310,3068,0,1,0}-{3322,3011,0,1,0}-{3322,3052,0,1,0}-{3324,3030,0,1,0}-{3283,3108,0,1,0}-{3323,3094,0,1,0}-" }, { "npc_id": "840", - "loc_data": "{3268,3052,0,1,0}-{3281,3056,0,1,0}-{3307,3055,0,1,0}-{3318,3020,0,1,0}-{3318,3040,0,1,0}-{3190,3054,0,1,0}-{3192,3016,0,1,0}-{3217,3111,0,1,0}-{3222,3086,0,1,0}-{3238,3101,0,1,0}-{3244,3080,0,1,0}-{3253,3116,0,1,0}-{3255,3095,0,1,0}-{3237,3000,0,1,0}-{3245,2960,0,1,0}-{3208,3032,0,1,0}-{3217,3064,0,1,0}-{3238,3015,0,1,0}-{3258,3063,0,1,0}-{3267,3077,0,1,0}-{3269,3110,0,1,0}-{3275,3094,0,1,0}-{3291,3078,0,1,0}-{3291,3100,0,1,0}-{3305,3089,0,1,0}-{3318,3078,0,1,0}-{3321,3104,0,1,0}-" + "loc_data": "{3190,3054,0,1,0}-{3192,3016,0,1,0}-{3237,3000,0,1,0}-{3245,2960,0,1,0}-{3208,3032,0,1,0}-{3217,3064,0,1,0}-{3238,3015,0,1,0}-{3258,3063,0,1,0}-{3217,3111,0,1,0}-{3222,3086,0,1,0}-{3238,3101,0,1,0}-{3244,3080,0,1,0}-{3253,3116,0,1,0}-{3255,3095,0,1,0}-{3268,3052,0,1,0}-{3281,3056,0,1,0}-{3307,3055,0,1,0}-{3318,3020,0,1,0}-{3318,3040,0,1,0}-{3267,3077,0,1,0}-{3269,3110,0,1,0}-{3275,3094,0,1,0}-{3291,3078,0,1,0}-{3291,3100,0,1,0}-{3305,3089,0,1,0}-{3318,3078,0,1,0}-{3321,3104,0,1,0}-" }, { "npc_id": "841", @@ -2781,7 +2785,7 @@ }, { "npc_id": "1019", - "loc_data": "{3187,5555,0,1,4}-{3190,5563,0,1,1}-{3193,5555,0,1,3} -{3213,9377,0,1,3}-{3209,9397,0,1,4}-{3245,9401,0,1,6}- {3237,9402,0,1,2}-{3207,9349,0,1,3}-{3220,9347,0,1,6} -{3233,9359,0,1,4}-{3235,9354,0,0,6}-{3259,9370,0,1,1}- {3258,9387,0,1,6}-{2707,9880,0,1,0}-{2711,9876,0,1,0}- {2712,9871,0,1,0}-{2715,9874,0,1,0}-{2720,9871,0,1,0} -{2717,9880,0,1,0}-{2722,9879,0,1,0}-{2723,9875,0,1,0}- {3278,9368,0,1,5}-{3271,9359,0,1,3}-{3287,9359,0,1,5}- {3301,9394,0,1,5}-{3318,9352,0,1,5}-" + "loc_data": "{3187,5555,0,1,4}-{3190,5563,0,1,1}-{3193,5555,0,1,3}-{3213,9377,0,1,3}-{3209,9397,0,1,4}-{3245,9401,0,1,6}-{3237,9402,0,1,2}-{3207,9349,0,1,3}-{3220,9347,0,1,6}-{3233,9359,0,1,4}-{3235,9354,0,0,6}-{3259,9370,0,1,1}-{3258,9387,0,1,6}-{2707,9880,0,1,0}-{2711,9876,0,1,0}-{2712,9871,0,1,0}-{2715,9874,0,1,0}-{2720,9871,0,1,0}-{2717,9880,0,1,0}-{2722,9879,0,1,0}-{2723,9875,0,1,0}-{3278,9368,0,1,5}-{3271,9359,0,1,3}-{3287,9359,0,1,5}-{3301,9394,0,1,5}-{3318,9352,0,1,5}-" }, { "npc_id": "1020", @@ -2829,7 +2833,7 @@ }, { "npc_id": "1043", - "loc_data": "{3726,3381,0,0,0}-{3745,3359,0,0,0}-{3749,3365,0,0,0}-{3750,3354,0,0,0}-{3757,3363,0,0,0}-{3763,3337,0,0,0}-{3459,3457,0,0,0}-{3460,3462,0,0,0}-{3462,3459,0,0,0}-{3463,3490,0,0,0}-{3464,3511,0,0,0}-{3465,3466,0,0,0}-{3466,3495,0,0,0}-{3466,3497,0,0,0}-{3467,3485,0,0,0}-{3467,3497,0,0,0}-{3467,3509,0,0,0}-{3469,3470,0,0,0}-{3470,3469,0,0,0}-{3471,3477,0,0,0}-{3474,3505,0,0,0}-{3476,3507,0,0,0}-{3479,3511,0,0,0}-{3480,3468,0,0,0}-{3480,3470,0,0,0}-{3482,3469,0,0,0}-{3483,3511,0,0,0}-{3484,3464,0,0,0}-{3485,3509,0,0,0}-{3490,3461,0,0,0}-{3491,3460,0,0,0}-{3494,3461,0,0,0}-{3494,3512,0,0,0}-{3498,3461,0,0,0}-{3499,3513,0,0,0}-{3501,3513,0,0,0}-{3502,3464,0,0,0}-{3504,3463,0,0,0}-{3506,3512,0,0,0}-{3506,3515,0,0,0}-{3507,3515,0,0,0}-{3509,3512,0,0,0}-{3511,3488,0,0,0}-{3513,3467,0,0,0}-{3513,3487,0,0,0}-{3513,3489,0,0,0}-{3513,3503,0,0,0}-{3514,3490,0,0,0}-{3515,3495,0,0,0}-{3515,3499,0,0,0}-{3516,3469,0,0,0}-{3409,3369,0,0,0}-{3420,3349,0,0,0}-{3430,3340,0,0,0}-{3435,3330,0,0,0}-{3435,3359,0,0,0}-{3437,3374,0,0,0}-{3446,3385,0,0,0}-{3447,3388,0,0,0}-{3450,3370,0,0,0}-{3450,3387,0,0,0}-{3451,3336,0,0,0}-{3452,3355,0,0,0}-{3726,3289,0,0,0}-{3732,3291,0,0,0}-{3737,3280,0,0,0}-{3745,3285,0,0,0}-{3418,3420,0,0,0}-{3424,3452,0,0,0}-{3428,3409,0,0,0}-{3433,3416,0,0,0}-{3434,3394,0,0,0}-{3436,3407,0,0,0}-{3438,3414,0,0,0}-{3439,3449,0,0,0}-{3446,3439,0,0,0}-{3454,3410,0,0,0}-{3454,3423,0,0,0}-{2852,4561,0,0,0}-{2853,4566,0,0,0}-{2856,4568,0,0,0}-{2864,4561,0,0,0}-{2864,4563,0,0,0}-" + "loc_data": "{2852,4561,0,0,0}-{2853,4566,0,0,0}-{2856,4568,0,0,0}-{2864,4561,0,0,0}-{2864,4563,0,0,0}-{3409,3369,0,0,0}-{3420,3349,0,0,0}-{3430,3340,0,0,0}-{3435,3330,0,0,0}-{3435,3359,0,0,0}-{3437,3374,0,0,0}-{3446,3385,0,0,0}-{3447,3388,0,0,0}-{3450,3370,0,0,0}-{3450,3387,0,0,0}-{3451,3336,0,0,0}-{3452,3355,0,0,0}-{3418,3420,0,0,0}-{3424,3452,0,0,0}-{3428,3409,0,0,0}-{3433,3416,0,0,0}-{3434,3394,0,0,0}-{3436,3407,0,0,0}-{3438,3414,0,0,0}-{3439,3449,0,0,0}-{3446,3439,0,0,0}-{3454,3410,0,0,0}-{3454,3423,0,0,0}-{3726,3289,0,0,0}-{3732,3291,0,0,0}-{3737,3280,0,0,0}-{3745,3285,0,0,0}-{3726,3381,0,0,0}-{3745,3359,0,0,0}-{3749,3365,0,0,0}-{3750,3354,0,0,0}-{3757,3363,0,0,0}-{3763,3337,0,0,0}-{3459,3457,0,0,0}-{3460,3462,0,0,0}-{3462,3459,0,0,0}-{3463,3490,0,0,0}-{3464,3511,0,0,0}-{3465,3466,0,0,0}-{3466,3495,0,0,0}-{3466,3497,0,0,0}-{3467,3485,0,0,0}-{3467,3497,0,0,0}-{3467,3509,0,0,0}-{3469,3470,0,0,0}-{3470,3469,0,0,0}-{3471,3477,0,0,0}-{3474,3505,0,0,0}-{3476,3507,0,0,0}-{3479,3511,0,0,0}-{3480,3468,0,0,0}-{3480,3470,0,0,0}-{3482,3469,0,0,0}-{3483,3511,0,0,0}-{3484,3464,0,0,0}-{3485,3509,0,0,0}-{3490,3461,0,0,0}-{3491,3460,0,0,0}-{3494,3461,0,0,0}-{3494,3512,0,0,0}-{3498,3461,0,0,0}-{3499,3513,0,0,0}-{3501,3513,0,0,0}-{3502,3464,0,0,0}-{3504,3463,0,0,0}-{3506,3512,0,0,0}-{3506,3515,0,0,0}-{3507,3515,0,0,0}-{3509,3512,0,0,0}-{3511,3488,0,0,0}-{3513,3467,0,0,0}-{3513,3487,0,0,0}-{3513,3489,0,0,0}-{3513,3503,0,0,0}-{3514,3490,0,0,0}-{3515,3495,0,0,0}-{3515,3499,0,0,0}-{3516,3469,0,0,0}-" }, { "npc_id": "1044", @@ -3345,11 +3349,11 @@ }, { "npc_id": "1212", - "loc_data": "{2247,3226,0,0,0}-{2249,3227,0,0,0}-{2249,3258,0,0,0}-{2251,3260,0,0,0}-{2259,3211,0,0,0}-{2267,3223,0,0,0}-{2267,3225,0,0,0}-{2298,3262,0,0,0}-{3202,5990,0,0,0}-{3232,5977,0,0,0}-{3234,5969,0,0,0}-{3237,5995,0,0,0}-{3242,5997,0,0,0}-{3244,5995,0,0,0}-{3245,5995,0,0,0}-{2194,3189,0,0,0}-{2196,3187,0,0,0}-{2196,3189,0,0,0}-{2206,3164,0,0,0}-{2209,3164,0,0,0}-{2216,3142,0,0,0}-{2218,3139,0,0,0}-{2178,3238,0,0,0}-{2208,3225,0,0,0}-{2210,3217,0,0,0}-{2213,3243,0,0,0}-{2218,3245,0,0,0}-{2220,3243,0,0,0}-{2221,3243,0,0,0}-{2695,6170,0,0,0}-{2697,6171,0,0,0}-{2697,6202,0,0,0}-{2699,6204,0,0,0}-{2707,6155,0,0,0}-{2715,6167,0,0,0}-{2715,6169,0,0,0}-{2746,6206,0,0,0}-{2729,6086,0,0,0}-{2731,6080,0,0,0}-{2731,6089,0,0,0}-{2744,6139,0,0,0}-{3271,5978,0,0,0}-{3273,5979,0,0,0}-{3273,6010,0,0,0}-{3275,6012,0,0,0}-{3283,5963,0,0,0}-{3291,5975,0,0,0}-{3291,5977,0,0,0}-{3322,6014,0,0,0}-{2281,3142,0,0,0}-{2283,3136,0,0,0}-{2283,3145,0,0,0}-{2296,3195,0,0,0}-" + "loc_data": "{2194,3189,0,0,0}-{2196,3187,0,0,0}-{2196,3189,0,0,0}-{2206,3164,0,0,0}-{2209,3164,0,0,0}-{2216,3142,0,0,0}-{2218,3139,0,0,0}-{2178,3238,0,0,0}-{2208,3225,0,0,0}-{2210,3217,0,0,0}-{2213,3243,0,0,0}-{2218,3245,0,0,0}-{2220,3243,0,0,0}-{2221,3243,0,0,0}-{3202,5990,0,0,0}-{3232,5977,0,0,0}-{3234,5969,0,0,0}-{3237,5995,0,0,0}-{3242,5997,0,0,0}-{3244,5995,0,0,0}-{3245,5995,0,0,0}-{2729,6086,0,0,0}-{2731,6080,0,0,0}-{2731,6089,0,0,0}-{2744,6139,0,0,0}-{2695,6170,0,0,0}-{2697,6171,0,0,0}-{2697,6202,0,0,0}-{2699,6204,0,0,0}-{2707,6155,0,0,0}-{2715,6167,0,0,0}-{2715,6169,0,0,0}-{2746,6206,0,0,0}-{2281,3142,0,0,0}-{2283,3136,0,0,0}-{2283,3145,0,0,0}-{2296,3195,0,0,0}-{2247,3226,0,0,0}-{2249,3227,0,0,0}-{2249,3258,0,0,0}-{2251,3260,0,0,0}-{2259,3211,0,0,0}-{2267,3223,0,0,0}-{2267,3225,0,0,0}-{2298,3262,0,0,0}-{3271,5978,0,0,0}-{3273,5979,0,0,0}-{3273,6010,0,0,0}-{3275,6012,0,0,0}-{3283,5963,0,0,0}-{3291,5975,0,0,0}-{3291,5977,0,0,0}-{3322,6014,0,0,0}-" }, { "npc_id": "1213", - "loc_data": "{2248,3225,0,0,0}-{2248,3227,0,0,0}-{2251,3257,0,0,0}-{2253,3259,0,0,0}-{2268,3224,0,0,0}-{2291,3262,0,0,0}-{3204,5988,0,0,0}-{3232,5979,0,0,0}-{3234,5967,0,0,0}-{3234,5976,0,0,0}-{3238,5996,0,0,0}-{3239,5997,0,0,0}-{3243,5996,0,0,0}-{2192,3188,0,0,0}-{2192,3191,0,0,0}-{2194,3190,0,0,0}-{2208,3163,0,0,0}-{2209,3163,0,0,0}-{2216,3136,0,0,0}-{2216,3140,0,0,0}-{2180,3236,0,0,0}-{2208,3227,0,0,0}-{2210,3215,0,0,0}-{2210,3224,0,0,0}-{2214,3244,0,0,0}-{2215,3245,0,0,0}-{2219,3244,0,0,0}-{2696,6169,0,0,0}-{2696,6171,0,0,0}-{2699,6201,0,0,0}-{2701,6203,0,0,0}-{2716,6168,0,0,0}-{2739,6206,0,0,0}-{2730,6084,0,0,0}-{3272,5977,0,0,0}-{3272,5979,0,0,0}-{3275,6009,0,0,0}-{3277,6011,0,0,0}-{3292,5976,0,0,0}-{3315,6014,0,0,0}-{2282,3140,0,0,0}-" + "loc_data": "{2192,3188,0,0,0}-{2192,3191,0,0,0}-{2194,3190,0,0,0}-{2208,3163,0,0,0}-{2209,3163,0,0,0}-{2216,3136,0,0,0}-{2216,3140,0,0,0}-{2180,3236,0,0,0}-{2208,3227,0,0,0}-{2210,3215,0,0,0}-{2210,3224,0,0,0}-{2214,3244,0,0,0}-{2215,3245,0,0,0}-{2219,3244,0,0,0}-{3204,5988,0,0,0}-{3232,5979,0,0,0}-{3234,5967,0,0,0}-{3234,5976,0,0,0}-{3238,5996,0,0,0}-{3239,5997,0,0,0}-{3243,5996,0,0,0}-{2730,6084,0,0,0}-{2696,6169,0,0,0}-{2696,6171,0,0,0}-{2699,6201,0,0,0}-{2701,6203,0,0,0}-{2716,6168,0,0,0}-{2739,6206,0,0,0}-{2282,3140,0,0,0}-{2248,3225,0,0,0}-{2248,3227,0,0,0}-{2251,3257,0,0,0}-{2253,3259,0,0,0}-{2268,3224,0,0,0}-{2291,3262,0,0,0}-{3272,5977,0,0,0}-{3272,5979,0,0,0}-{3275,6009,0,0,0}-{3277,6011,0,0,0}-{3292,5976,0,0,0}-{3315,6014,0,0,0}-" }, { "npc_id": "1214", @@ -4061,7 +4065,7 @@ }, { "npc_id": "1633", - "loc_data": "{1837,3244,0,1,0}-{1836,3250,0,1,0}-{1845,3251,0,1,0}-{1845,3247,0,1,0}-{1849,3241,0,1,0}-{1853,3247,0,1,0}-{1848,3254,0,1,0}-{1858,3251,0,1,0}-{1935,3217,0,1,0}-{1933,3209,0,1,0}-{1930,3208,0,1,0}-{1929,3217,0,1,0}-{1927,3219,0,1,0}-{1924,3211,0,1,0}-{1920,3216,0,1,0}-{1925,3213,0,1,0}-{3263,9399,0,1,3}-{3274,9397,0,1,1}-{3275,9393,0,1,6}-{3271,9384,0,1,0}-{3270,9380,0,1,2}-{3283,9378,0,1,6}-{3285,9386,0,1,5}-{3284,9401,0,1,4}-{3278,9353,0,1,1}-{3299,9380,0,1,1}-{3319,9402,0,1,1}-{3305,9350,0,1,4}-{3248,9374,0,1,3}-{3253,9363,0,1,3}-{3249,9355,0,1,3}-{2761,10007,0,1,0}-{2757,10010,0,1,3}-{2763,10000,0,1,5}-{2760,10011,0,1,0}-{2761,9997,0,1,4}-" + "loc_data": "{1837,3244,0,1,0}-{1836,3250,0,1,0}-{1845,3251,0,1,0}-{1845,3247,0,1,0}-{1849,3241,0,1,0}-{1853,3247,0,1,0}-{1848,3254,0,1,0}-{1858,3251,0,1,0}-{1935,3217,0,1,0}-{1933,3209,0,1,0}-{1930,3208,0,1,0}-{1929,3217,0,1,0}-{1927,3219,0,1,0}-{1924,3211,0,1,0}-{1920,3216,0,1,0}-{1925,3213,0,1,0}-{3263,9399,0,1,3}-{3248,9374,0,1,3}-{3253,9363,0,1,3}-{3249,9355,0,1,3}-{3274,9397,0,1,1}-{3275,9393,0,1,6}-{3271,9384,0,1,0}-{3270,9380,0,1,2}-{3283,9378,0,1,6}-{3285,9386,0,1,5}-{3284,9401,0,1,4}-{3278,9353,0,1,1}-{3299,9380,0,1,1}-{3319,9402,0,1,1}-{3305,9350,0,1,4}-{2761,10007,0,1,0}-{2757,10010,0,1,3}-{2763,10000,0,1,5}-{2760,10011,0,1,0}-{2761,9997,0,1,4}-" }, { "npc_id": "1634", @@ -4261,7 +4265,7 @@ }, { "npc_id": "1752", - "loc_data": "{2437,3442,0,1,0}-{2449,3421,0,1,0}-{2457,3394,0,1,0}-{2464,3394,0,1,0}-{2480,3406,0,1,0}-{2432,3387,0,1,0}-{2442,3388,0,1,0}-{2453,3363,0,1,0}-{2463,3375,0,1,0}-{2381,3428,0,1,0}-{2392,3405,0,1,0}-{2413,3397,0,1,0}-{2415,3408,0,1,0}-{2463,3456,0,1,0}-{2468,3456,0,1,0}-" + "loc_data": "{2381,3428,0,1,0}-{2392,3405,0,1,0}-{2413,3397,0,1,0}-{2415,3408,0,1,0}-{2432,3387,0,1,0}-{2442,3388,0,1,0}-{2453,3363,0,1,0}-{2463,3375,0,1,0}-{2437,3442,0,1,0}-{2449,3421,0,1,0}-{2457,3394,0,1,0}-{2464,3394,0,1,0}-{2480,3406,0,1,0}-{2463,3456,0,1,0}-{2468,3456,0,1,0}-" }, { "npc_id": "1754", @@ -4545,7 +4549,7 @@ }, { "npc_id": "1874", - "loc_data": "{3371,9301,0,1,0}-{3371,9303,0,1,0}-{3371,9305,0,1,0}-{3371,9307,0,1,0}-{3371,9309,0,1,0}-{3373,9301,0,1,0}-{3373,9303,0,1,0}-{3373,9306,0,1,0}-{3373,9308,0,1,0}-{3375,9301,0,1,0}-{3375,9303,0,1,0}-{3375,9305,0,1,0}-{3375,9307,0,1,0}-{3375,9309,0,1,0}-{3403,2963,0,1,0}-{3429,2976,0,1,0}-{3328,2952,0,1,0}-{3328,2957,0,1,0}-{3331,2955,0,1,0}-{3331,2961,0,1,0}-{3266,2955,0,1,0}-{3271,2967,0,1,0}-{3279,2957,0,1,0}-{3280,2975,0,1,0}-{3284,2959,0,1,0}-{3294,2964,0,1,0}-{3295,2978,0,1,0}-{3305,2966,0,1,0}-{3307,2959,0,1,0}-{3309,2973,0,1,0}-{3396,3029,0,1,0}-{3397,3044,0,1,0}-{3398,3038,0,1,0}-" + "loc_data": "{3328,2952,0,1,0}-{3328,2957,0,1,0}-{3331,2955,0,1,0}-{3331,2961,0,1,0}-{3371,9301,0,1,0}-{3371,9303,0,1,0}-{3371,9305,0,1,0}-{3371,9307,0,1,0}-{3371,9309,0,1,0}-{3373,9301,0,1,0}-{3373,9303,0,1,0}-{3373,9306,0,1,0}-{3373,9308,0,1,0}-{3375,9301,0,1,0}-{3375,9303,0,1,0}-{3375,9305,0,1,0}-{3375,9307,0,1,0}-{3375,9309,0,1,0}-{3403,2963,0,1,0}-{3429,2976,0,1,0}-{3396,3029,0,1,0}-{3397,3044,0,1,0}-{3398,3038,0,1,0}-{3266,2955,0,1,0}-{3271,2967,0,1,0}-{3279,2957,0,1,0}-{3280,2975,0,1,0}-{3284,2959,0,1,0}-{3294,2964,0,1,0}-{3295,2978,0,1,0}-{3305,2966,0,1,0}-{3307,2959,0,1,0}-{3309,2973,0,1,0}-" }, { "npc_id": "1875", @@ -4705,19 +4709,19 @@ }, { "npc_id": "1961", - "loc_data": "{3156,5477,0,1,5}-{3162,5479,0,1,4}-{3159,5477,0,1,6}-{3150,5474,0,1,3}-{3147,5480,0,1,1}-{3159,5484,0,1,3}-{2764,4944,1,1,0}-{2796,4976,1,1,0}-{2798,4950,1,1,0}-{2806,4939,1,1,0}-{2832,4959,2,1,0}-{3201,9293,0,1,0}-{3205,9303,0,1,0}-{3206,9328,0,1,0}-{3207,9310,0,1,0}-{3211,9284,0,1,0}-{3220,9289,0,1,0}-{3224,9334,0,1,0}-{3249,9286,0,1,0}-{3251,9303,0,1,0}-{3252,9327,0,1,0}-{3252,9332,0,1,0}-{3255,9315,0,1,0}-{3261,9296,0,1,0}-{3261,9306,0,1,0}-" + "loc_data": "{2832,4959,2,1,0}-{3156,5477,0,1,5}-{3162,5479,0,1,4}-{3159,5477,0,1,6}-{3150,5474,0,1,3}-{3147,5480,0,1,1}-{3159,5484,0,1,3}-{3201,9293,0,1,0}-{3205,9303,0,1,0}-{3206,9328,0,1,0}-{3207,9310,0,1,0}-{3211,9284,0,1,0}-{3220,9289,0,1,0}-{3224,9334,0,1,0}-{3249,9286,0,1,0}-{3251,9303,0,1,0}-{3252,9327,0,1,0}-{3252,9332,0,1,0}-{3255,9315,0,1,0}-{3261,9296,0,1,0}-{3261,9306,0,1,0}-{2764,4944,1,1,0}-{2796,4976,1,1,0}-{2798,4950,1,1,0}-{2806,4939,1,1,0}-" }, { "npc_id": "1962", - "loc_data": "{3168,5458,0,1,2}-{2900,4948,3,1,0}-{2762,4962,1,1,0}-{2763,4973,1,1,0}-{2780,4977,1,1,0}-{2787,4967,1,1,0}-{2796,4959,1,1,0}-{2838,4948,2,1,0}-{3205,9329,0,1,0}-{3210,9292,0,1,0}-{3221,9310,0,1,0}-{3225,9323,0,1,0}-{3243,9310,0,1,0}-{3246,9290,0,1,0}-{3255,9301,0,1,0}-{3255,9321,0,1,0}-{3261,9331,0,1,0}-" + "loc_data": "{2838,4948,2,1,0}-{2900,4948,3,1,0}-{3168,5458,0,1,2}-{3205,9329,0,1,0}-{3210,9292,0,1,0}-{3221,9310,0,1,0}-{3225,9323,0,1,0}-{3243,9310,0,1,0}-{3246,9290,0,1,0}-{3255,9301,0,1,0}-{3255,9321,0,1,0}-{3261,9331,0,1,0}-{2762,4962,1,1,0}-{2763,4973,1,1,0}-{2780,4977,1,1,0}-{2787,4967,1,1,0}-{2796,4959,1,1,0}-" }, { "npc_id": "1963", - "loc_data": "{3166,5465,0,1,7}-{3168,5462,0,1,0}-{3167,5467,0,1,1}-{3167,5467,0,1,7}-{2926,4965,3,1,0}-{2771,4947,1,1,0}-{2775,4963,1,1,0}-{2781,4948,1,1,0}-{2792,4942,1,1,0}-{2798,4954,1,1,0}-{2858,4964,2,1,0}-{3204,9309,0,1,0}-{3214,9333,0,1,0}-{3219,9297,0,1,0}-{3239,9300,0,1,0}-{3240,9286,0,1,0}-{3240,9330,0,1,0}-{3245,9306,0,1,0}-{3250,9316,0,1,0}-{3250,9317,0,1,0}-{3257,9290,0,1,0}-" + "loc_data": "{2858,4964,2,1,0}-{2926,4965,3,1,0}-{3166,5465,0,1,7}-{3168,5462,0,1,0}-{3167,5467,0,1,1}-{3167,5467,0,1,7}-{3204,9309,0,1,0}-{3214,9333,0,1,0}-{3219,9297,0,1,0}-{3239,9300,0,1,0}-{3240,9286,0,1,0}-{3240,9330,0,1,0}-{3245,9306,0,1,0}-{3250,9316,0,1,0}-{3250,9317,0,1,0}-{3257,9290,0,1,0}-{2771,4947,1,1,0}-{2775,4963,1,1,0}-{2781,4948,1,1,0}-{2792,4942,1,1,0}-{2798,4954,1,1,0}-" }, { "npc_id": "1964", - "loc_data": "{2761,4950,1,1,0}-{2765,4938,1,1,0}-{2770,4955,1,1,0}-{2772,4940,1,1,0}-{2777,4943,1,1,0}-{2782,4967,1,1,0}-{2797,4965,1,1,0}-{2799,4937,1,1,0}-{2799,4941,1,1,0}-{2807,4968,1,1,0}-{2808,4975,1,1,0}-{2809,4953,1,1,0}-{2864,4946,2,1,0}-{3202,9283,0,1,0}-{3206,9333,0,1,0}-{3209,9299,0,1,0}-{3219,9301,0,1,0}-{3226,9285,0,1,0}-{3227,9303,0,1,0}-{3228,9293,0,1,0}-{3229,9333,0,1,0}-{3237,9293,0,1,0}-{3246,9321,0,1,0}-{3256,9296,0,1,0}-{3260,9285,0,1,0}-{3262,9317,0,1,0}-" + "loc_data": "{2864,4946,2,1,0}-{3202,9283,0,1,0}-{3206,9333,0,1,0}-{3209,9299,0,1,0}-{3219,9301,0,1,0}-{3226,9285,0,1,0}-{3227,9303,0,1,0}-{3228,9293,0,1,0}-{3229,9333,0,1,0}-{3237,9293,0,1,0}-{3246,9321,0,1,0}-{3256,9296,0,1,0}-{3260,9285,0,1,0}-{3262,9317,0,1,0}-{2761,4950,1,1,0}-{2765,4938,1,1,0}-{2770,4955,1,1,0}-{2772,4940,1,1,0}-{2777,4943,1,1,0}-{2782,4967,1,1,0}-{2797,4965,1,1,0}-{2799,4937,1,1,0}-{2799,4941,1,1,0}-{2807,4968,1,1,0}-{2808,4975,1,1,0}-{2809,4953,1,1,0}-" }, { "npc_id": "1970", @@ -4729,7 +4733,7 @@ }, { "npc_id": "1973", - "loc_data": "{2693,5075,0,1,0}-{2695,5089,0,1,0}-{2697,5096,0,1,0}-{2703,5064,0,1,0}-{2710,5105,0,1,0}-{2719,5078,0,1,0}-{2719,5112,0,1,0}-{2720,5096,0,1,0}-{2726,5086,0,1,0}-{2729,5096,0,1,0}-{2735,5061,0,1,0}-{2740,5069,0,1,0}-{2740,5085,0,1,0}-{2746,5092,0,1,0}-{2746,5114,0,1,0}-{2626,5065,0,1,0}-{2637,5099,0,1,0}-{2638,5058,0,1,0}-{2644,5090,0,1,0}-{2658,5082,0,1,0}-{2666,5097,0,1,0}-{2680,5074,0,1,0}-" + "loc_data": "{2626,5065,0,1,0}-{2637,5099,0,1,0}-{2638,5058,0,1,0}-{2644,5090,0,1,0}-{2658,5082,0,1,0}-{2666,5097,0,1,0}-{2680,5074,0,1,0}-{2693,5075,0,1,0}-{2695,5089,0,1,0}-{2697,5096,0,1,0}-{2703,5064,0,1,0}-{2710,5105,0,1,0}-{2719,5078,0,1,0}-{2719,5112,0,1,0}-{2720,5096,0,1,0}-{2726,5086,0,1,0}-{2729,5096,0,1,0}-{2735,5061,0,1,0}-{2740,5069,0,1,0}-{2740,5085,0,1,0}-{2746,5092,0,1,0}-{2746,5114,0,1,0}-" }, { "npc_id": "1976", @@ -4741,11 +4745,11 @@ }, { "npc_id": "1993", - "loc_data": "{3264,2886,0,1,0}-{3267,2891,0,1,0}-{3268,2881,0,1,0}-{3269,2885,0,1,0}-{3296,2912,0,1,0}-{3297,2919,0,1,0}-{3301,2920,0,1,0}-{3308,2916,0,1,0}-{3258,2868,0,1,0}-{3259,2823,0,1,0}-{3259,2845,0,1,0}-{3260,2830,0,1,0}-{3260,2859,0,1,0}-{3261,2819,0,1,0}-{3261,2851,0,1,0}-{3337,2922,0,1,0}-{3343,2931,0,1,0}-{3349,2922,0,1,0}-{3351,2927,0,1,0}-{3357,2922,0,1,0}-{3359,2927,0,1,0}-{3364,2937,0,1,0}-{3268,2854,0,1,0}-{3269,2827,0,1,0}-{3269,2866,0,1,0}-{3269,2875,0,1,0}-{3270,2818,0,1,0}-{3272,2841,0,1,0}-{3276,2842,0,1,0}-{3282,2839,0,1,0}-{3290,2847,0,1,0}-" + "loc_data": "{3337,2922,0,1,0}-{3343,2931,0,1,0}-{3349,2922,0,1,0}-{3351,2927,0,1,0}-{3357,2922,0,1,0}-{3359,2927,0,1,0}-{3364,2937,0,1,0}-{3258,2868,0,1,0}-{3259,2823,0,1,0}-{3259,2845,0,1,0}-{3260,2830,0,1,0}-{3260,2859,0,1,0}-{3261,2819,0,1,0}-{3261,2851,0,1,0}-{3268,2854,0,1,0}-{3269,2827,0,1,0}-{3269,2866,0,1,0}-{3269,2875,0,1,0}-{3270,2818,0,1,0}-{3272,2841,0,1,0}-{3276,2842,0,1,0}-{3282,2839,0,1,0}-{3290,2847,0,1,0}-{3264,2886,0,1,0}-{3267,2891,0,1,0}-{3268,2881,0,1,0}-{3269,2885,0,1,0}-{3296,2912,0,1,0}-{3297,2919,0,1,0}-{3301,2920,0,1,0}-{3308,2916,0,1,0}-" }, { "npc_id": "1994", - "loc_data": "{3265,2931,0,1,0}-{3265,2935,0,1,0}-{3268,2932,0,1,0}-{3268,2935,0,1,0}-{3316,2900,0,1,0}-{3319,2897,0,1,0}-{3320,2901,0,1,0}-{3321,2899,0,1,0}-{3212,2863,0,1,0}-{3213,2866,0,1,0}-{3215,2863,0,1,0}-{3216,2865,0,1,0}-{3216,2868,0,1,0}-{3218,2831,0,1,0}-{3220,2830,0,1,0}-{3220,2833,0,1,0}-{3221,2831,0,1,0}-{3238,2845,0,1,0}-{3240,2845,0,1,0}-{3241,2843,0,1,0}-{3241,2847,0,1,0}-{3400,2997,0,1,0}-{3402,2999,0,1,0}-{3403,2997,0,1,0}-{3445,2993,0,1,0}-{3446,2992,0,1,0}-{3448,2991,0,1,0}-{3448,2994,0,1,0}-{3329,2933,0,1,0}-{3330,2931,0,1,0}-{3331,2933,0,1,0}-{3343,2895,0,1,0}-{3345,2896,0,1,0}-{3346,2894,0,1,0}-{3348,2894,0,1,0}-{3376,2934,0,1,0}-{3378,2933,0,1,0}-{3378,2935,0,1,0}-{3381,2907,0,1,0}-{3383,2905,0,1,0}-{3383,2908,0,1,0}-{3384,2907,0,1,0}-{3306,2817,0,1,0}-{3308,2818,0,1,0}-{3309,2816,0,1,0}-{3312,2817,0,1,0}-{3314,2861,0,1,0}-{3319,2873,0,1,0}-{3321,2871,0,1,0}-{3324,2870,0,1,0}-{3327,2858,0,1,0}-{3406,3014,0,1,0}-{3408,3013,0,1,0}-{3408,3016,0,1,0}-{3410,3015,0,1,0}-" + "loc_data": "{3329,2933,0,1,0}-{3330,2931,0,1,0}-{3331,2933,0,1,0}-{3343,2895,0,1,0}-{3345,2896,0,1,0}-{3346,2894,0,1,0}-{3348,2894,0,1,0}-{3376,2934,0,1,0}-{3378,2933,0,1,0}-{3378,2935,0,1,0}-{3381,2907,0,1,0}-{3383,2905,0,1,0}-{3383,2908,0,1,0}-{3384,2907,0,1,0}-{3400,2997,0,1,0}-{3402,2999,0,1,0}-{3403,2997,0,1,0}-{3445,2993,0,1,0}-{3446,2992,0,1,0}-{3448,2991,0,1,0}-{3448,2994,0,1,0}-{3406,3014,0,1,0}-{3408,3013,0,1,0}-{3408,3016,0,1,0}-{3410,3015,0,1,0}-{3212,2863,0,1,0}-{3213,2866,0,1,0}-{3215,2863,0,1,0}-{3216,2865,0,1,0}-{3216,2868,0,1,0}-{3218,2831,0,1,0}-{3220,2830,0,1,0}-{3220,2833,0,1,0}-{3221,2831,0,1,0}-{3238,2845,0,1,0}-{3240,2845,0,1,0}-{3241,2843,0,1,0}-{3241,2847,0,1,0}-{3306,2817,0,1,0}-{3308,2818,0,1,0}-{3309,2816,0,1,0}-{3312,2817,0,1,0}-{3314,2861,0,1,0}-{3319,2873,0,1,0}-{3321,2871,0,1,0}-{3324,2870,0,1,0}-{3327,2858,0,1,0}-{3265,2931,0,1,0}-{3265,2935,0,1,0}-{3268,2932,0,1,0}-{3268,2935,0,1,0}-{3316,2900,0,1,0}-{3319,2897,0,1,0}-{3320,2901,0,1,0}-{3321,2899,0,1,0}-" }, { "npc_id": "1995", @@ -4897,7 +4901,7 @@ }, { "npc_id": "2057", - "loc_data": "{2442,9436,2,1,0}-{2458,9413,2,1,0}-{2470,9435,2,1,0}-{2472,9460,2,1,0}-{2483,9413,2,1,0}-{2485,9447,2,1,0}-{2480,3046,0,1,0}-{2453,9393,2,1,0}-{2461,9403,2,1,0}-{2464,9380,2,1,0}-" + "loc_data": "{2480,3046,0,1,0}-{2453,9393,2,1,0}-{2461,9403,2,1,0}-{2464,9380,2,1,0}-{2442,9436,2,1,0}-{2458,9413,2,1,0}-{2470,9435,2,1,0}-{2472,9460,2,1,0}-{2483,9413,2,1,0}-{2485,9447,2,1,0}-" }, { "npc_id": "2058", @@ -6317,7 +6321,7 @@ }, { "npc_id": "2803", - "loc_data": "{3387,3068,0,1,0}-{3387,3017,0,1,0}-{3404,3062,0,1,0}-{3441,3061,0,1,0}-{3445,3034,0,1,0}-{3338,2804,0,1,0}-{3339,2815,0,1,0}-{3342,2809,0,1,0}-{3344,2800,0,1,0}-{3348,2809,0,1,0}-{3349,2814,0,1,0}-{3357,2813,0,1,0}-{3357,2806,0,1,0}-{3365,2815,0,1,0}-" + "loc_data": "{3338,2804,0,1,0}-{3339,2815,0,1,0}-{3342,2809,0,1,0}-{3344,2800,0,1,0}-{3348,2809,0,1,0}-{3349,2814,0,1,0}-{3357,2813,0,1,0}-{3357,2806,0,1,0}-{3365,2815,0,1,0}-{3387,3068,0,1,0}-{3387,3017,0,1,0}-{3404,3062,0,1,0}-{3441,3061,0,1,0}-{3445,3034,0,1,0}-" }, { "npc_id": "2804", @@ -7237,7 +7241,7 @@ }, { "npc_id": "3675", - "loc_data": "{3333,2864,0,1,1}-{3335,2860,0,1,2}-{3338,2864,0,1,4}-{3215,2841,0,1,0}-{3217,2845,0,1,0}-{3220,2841,0,1,0}-{3295,2866,0,1,0}-{3297,2863,0,1,0}-{3301,2864,0,1,0}-{3336,2778,0,1,0}-{3343,2793,0,1,0}-{3367,2791,0,1,0}-" + "loc_data": "{3336,2778,0,1,0}-{3343,2793,0,1,0}-{3367,2791,0,1,0}-{3333,2864,0,1,1}-{3335,2860,0,1,2}-{3338,2864,0,1,4}-{3215,2841,0,1,0}-{3217,2845,0,1,0}-{3220,2841,0,1,0}-{3295,2866,0,1,0}-{3297,2863,0,1,0}-{3301,2864,0,1,0}-" }, { "npc_id": "3677", @@ -8245,19 +8249,19 @@ }, { "npc_id": "4690", - "loc_data": "{2372,3401,0,1,0}-{3104,3875,0,1,2}-{2912,9731,0,1,0}-{3256,3624,0,1,5}-{3308,3661,0,1,2}-" + "loc_data": "{3104,3875,0,1,2}-{2372,3401,0,1,0}-{2912,9731,0,1,0}-{3256,3624,0,1,5}-{3308,3661,0,1,2}-" }, { "npc_id": "4691", - "loc_data": "{2371,3398,0,1,0}-{3110,3854,0,1,0}-" + "loc_data": "{3110,3854,0,1,0}-{2371,3398,0,1,0}-" }, { "npc_id": "4692", - "loc_data": "{2372,3395,0,1,0}-{3116,3858,0,1,0}-" + "loc_data": "{3116,3858,0,1,0}-{2372,3395,0,1,0}-" }, { "npc_id": "4693", - "loc_data": "{2369,3394,0,1,0}-{3094,3849,0,1,0}-{2912,9741,0,1,0}-{2906,9736,0,1,0}-" + "loc_data": "{3094,3849,0,1,0}-{2369,3394,0,1,0}-{2912,9741,0,1,0}-{2906,9736,0,1,0}-" }, { "npc_id": "4694", @@ -9009,7 +9013,7 @@ }, { "npc_id": "5359", - "loc_data": "{2693,5065,0,1,0}-{2693,5089,0,1,0}-{2704,5091,0,1,0}-{2710,5095,0,1,0}-{2712,5076,0,1,0}-{2714,5108,0,1,0}-{2716,5092,0,1,0}-{2719,5071,0,1,0}-{2722,5061,0,1,0}-{2732,5087,0,1,0}-{2739,5078,0,1,0}-{2740,5104,0,1,0}-{2747,5083,0,1,0}-{2627,5093,0,1,0}-{2629,5061,0,1,0}-{2638,5090,0,1,0}-{2651,5105,0,1,0}-{2654,5079,0,1,0}-{2663,5116,0,1,0}-{2683,5059,0,1,0}-{2685,5111,0,1,0}-" + "loc_data": "{2627,5093,0,1,0}-{2629,5061,0,1,0}-{2638,5090,0,1,0}-{2651,5105,0,1,0}-{2654,5079,0,1,0}-{2663,5116,0,1,0}-{2683,5059,0,1,0}-{2685,5111,0,1,0}-{2693,5065,0,1,0}-{2693,5089,0,1,0}-{2704,5091,0,1,0}-{2710,5095,0,1,0}-{2712,5076,0,1,0}-{2714,5108,0,1,0}-{2716,5092,0,1,0}-{2719,5071,0,1,0}-{2722,5061,0,1,0}-{2732,5087,0,1,0}-{2739,5078,0,1,0}-{2740,5104,0,1,0}-{2747,5083,0,1,0}-" }, { "npc_id": "5361", @@ -10293,11 +10297,11 @@ }, { "npc_id": "6050", - "loc_data": "{3264,3008,0,1,0}-{3266,3008,0,1,0}-{3285,3066,0,1,0}-{3322,3032,0,1,0}-{3323,3055,0,1,0}-{3325,3010,0,1,0}-{3174,3010,0,1,0}-{3198,3061,0,1,0}-{3199,3016,0,1,0}-{3219,3125,0,1,0}-{3233,3075,0,1,0}-{3250,3126,0,1,0}-{3223,3011,0,1,0}-{3228,3060,0,1,0}-{3251,3059,0,1,0}-{3310,3076,0,1,0}-" + "loc_data": "{3174,3010,0,1,0}-{3198,3061,0,1,0}-{3199,3016,0,1,0}-{3223,3011,0,1,0}-{3228,3060,0,1,0}-{3251,3059,0,1,0}-{3219,3125,0,1,0}-{3233,3075,0,1,0}-{3250,3126,0,1,0}-{3264,3008,0,1,0}-{3266,3008,0,1,0}-{3285,3066,0,1,0}-{3322,3032,0,1,0}-{3323,3055,0,1,0}-{3325,3010,0,1,0}-{3310,3076,0,1,0}-" }, { "npc_id": "6051", - "loc_data": "{3266,3010,0,1,0}-{3267,3068,0,1,0}-{3287,3065,0,1,0}-{3287,3067,0,1,0}-{3310,3070,0,1,0}-{3312,3068,0,1,0}-{3320,3054,0,1,0}-{3324,3013,0,1,0}-{3325,3033,0,1,0}-{3153,3047,0,1,0}-{3196,3063,0,1,0}-{3199,3036,0,1,0}-{3215,3092,0,1,0}-{3217,3124,0,1,0}-{3260,3077,0,1,0}-{3223,3035,0,1,0}-{3225,3011,0,1,0}-{3283,3084,0,1,0}-" + "loc_data": "{3153,3047,0,1,0}-{3196,3063,0,1,0}-{3199,3036,0,1,0}-{3223,3035,0,1,0}-{3225,3011,0,1,0}-{3215,3092,0,1,0}-{3217,3124,0,1,0}-{3260,3077,0,1,0}-{3266,3010,0,1,0}-{3267,3068,0,1,0}-{3287,3065,0,1,0}-{3287,3067,0,1,0}-{3310,3070,0,1,0}-{3312,3068,0,1,0}-{3320,3054,0,1,0}-{3324,3013,0,1,0}-{3325,3033,0,1,0}-{3283,3084,0,1,0}-" }, { "npc_id": "6052", @@ -10423,6 +10427,38 @@ "npc_id": "6117", "loc_data": "{1884,5020,0,0,6}-" }, + { + "npc_id": "6118", + "loc_data": "{2443,3190,0,0,6}-" + }, + { + "npc_id": "6119", + "loc_data": "{2439,3186,0,1,4}-" + }, + { + "npc_id": "6120", + "loc_data": "{2437,3160,1,1,4}-" + }, + { + "npc_id": "6122", + "loc_data": "{2327,9394,0,0,1}-" + }, + { + "npc_id": "6123", + "loc_data": "{2364,9399,0,0,1}-" + }, + { + "npc_id": "6124", + "loc_data": "{2364,9398,0,0,1}-" + }, + { + "npc_id": "6125", + "loc_data": "{2323,9377,0,1,0}-" + }, + { + "npc_id": "6126", + "loc_data": "{2351,9358,0,1,0}-" + }, { "npc_id": "6127", "loc_data": "{2467,3183,0,0,3}-" @@ -12139,10 +12175,6 @@ "npc_id": "7804", "loc_data": "{3279,4350,0,1,0}-{3294,4353,0,1,0}-{3294,4366,0,1,0}-" }, - { - "npc_id": "7823", - "loc_data": "" - }, { "npc_id": "7891", "loc_data": "{3207,3250,0,0,0}-{3208,3250,0,0,0}-{3209,3250,0,0,0}-" @@ -12305,7 +12337,7 @@ }, { "npc_id": "8324", - "loc_data": "{2911,3811,0,1,0}-{2911,3812,0,1,0}-{2911,3813,0,1,0}-{2925,3821,0,1,0}-{2925,3822,0,1,0}-{2925,3823,0,1,0}-{2929,3798,0,1,0}-{2936,3790,0,1,0}-{2936,3810,0,1,0}-{2939,3823,0,1,0}-{2949,3819,1,1,0}-{2949,3822,1,1,0}-{2955,3822,1,1,0}-{2957,3822,1,1,0}-{3016,9977,1,1,0}-{3021,9939,1,1,0}-{3024,9954,1,1,0}-{3025,9943,1,1,0}-{3025,9954,1,1,0}-{3026,9966,1,1,0}-{3032,9952,1,1,0}-{3039,9954,1,1,0}-{3044,9967,1,1,0}-{3044,9971,1,1,0}-{3057,9936,1,1,0}-{3059,9953,1,1,0}-{3041,9975,2,1,0}-{3043,9975,2,1,0}-{3045,9975,2,1,0}-{3016,10022,2,1,0}-{3017,10039,2,1,0}-{3027,10028,2,1,0}-{3029,10013,2,1,0}-{3036,10038,2,1,0}-{3037,10006,2,1,0}-{3041,10024,2,1,0}-{3043,10032,2,1,0}-{3057,10002,2,1,0}-{3058,10021,2,1,0}-{3065,10006,2,1,0}-{3027,10092,1,1,0}-{3035,10097,1,1,0}-{3051,10099,1,1,0}-{3427,5102,0,1,0}-{3428,5099,0,1,0}-{3428,5102,0,1,0}-{3430,5099,0,1,0}-" + "loc_data": "{2911,3811,0,1,0}-{2911,3812,0,1,0}-{2911,3813,0,1,0}-{2925,3821,0,1,0}-{2925,3822,0,1,0}-{2925,3823,0,1,0}-{2929,3798,0,1,0}-{2936,3790,0,1,0}-{2936,3810,0,1,0}-{2939,3823,0,1,0}-{3427,5102,0,1,0}-{3428,5099,0,1,0}-{3428,5102,0,1,0}-{3430,5099,0,1,0}-{2949,3819,1,1,0}-{2949,3822,1,1,0}-{2955,3822,1,1,0}-{2957,3822,1,1,0}-{3016,9977,1,1,0}-{3021,9939,1,1,0}-{3024,9954,1,1,0}-{3025,9943,1,1,0}-{3025,9954,1,1,0}-{3026,9966,1,1,0}-{3032,9952,1,1,0}-{3039,9954,1,1,0}-{3044,9967,1,1,0}-{3044,9971,1,1,0}-{3057,9936,1,1,0}-{3059,9953,1,1,0}-{3041,9975,2,1,0}-{3043,9975,2,1,0}-{3045,9975,2,1,0}-{3016,10022,2,1,0}-{3017,10039,2,1,0}-{3027,10028,2,1,0}-{3029,10013,2,1,0}-{3036,10038,2,1,0}-{3037,10006,2,1,0}-{3041,10024,2,1,0}-{3043,10032,2,1,0}-{3057,10002,2,1,0}-{3058,10021,2,1,0}-{3065,10006,2,1,0}-{3027,10092,1,1,0}-{3035,10097,1,1,0}-{3051,10099,1,1,0}-" }, { "npc_id": "8328", diff --git a/Server/src/main/content/global/activity/cchallange/ChampionScrollsDropHandler.kt b/Server/src/main/content/global/activity/cchallange/ChampionScrollsDropHandler.kt index 02b3b2b0e..bfbe78a76 100644 --- a/Server/src/main/content/global/activity/cchallange/ChampionScrollsDropHandler.kt +++ b/Server/src/main/content/global/activity/cchallange/ChampionScrollsDropHandler.kt @@ -70,7 +70,7 @@ class ChampionScrollsDropHandler : ChampionScrollsEventHookBase() { NPCs.CAVE_GOBLIN_GUARD_2073, NPCs.CAVE_GOBLIN_GUARD_2074, - NPCs.GOBLIN_GUARD_489, NPCs.GOBLIN_GUARD_6496, NPCs.GOBLIN_GUARD_6497, + NPCs.GOBLIN_GUARD_6496, NPCs.GOBLIN_GUARD_6497, NPCs.SERGEANT_GRIMSPIKE_6265, NPCs.SERGEANT_STEELWILL_6263, NPCs.SERGEANT_STRONGSTACK_6261 diff --git a/Server/src/main/content/global/handlers/npc/GuardNPC.java b/Server/src/main/content/global/handlers/npc/GuardNPC.java index ccdf7e10d..9cde38cc7 100644 --- a/Server/src/main/content/global/handlers/npc/GuardNPC.java +++ b/Server/src/main/content/global/handlers/npc/GuardNPC.java @@ -15,7 +15,7 @@ public final class GuardNPC extends AbstractNPC { /** * The NPC ids of NPCs using this plugin. */ - private static final int[] ID = { 9, 32, 163, 164, 196, 197, 206, 253, 254, 255, 256, 274, 275, 296, 298, 299, 447, 448, 449, 489, 609, 678, 799, 837, 842, 862, 870, 877, 917, 1200, 1203, 1204, 1317, 1710, 1711, 1712, 2073, 2074, 2134, 2135, 2136, 2236, 2571, 2699, 2700, 2701, 2702, 2703, 3228, 3229, 3230, 3231, 3232, 3233, 3241, 3407, 3408, 3715, 4257, 4258, 4259, 4260, 4307, 4308, 4309, 4310, 4311, 4336, 4375, 4603, 4604, 4605, 4606, 5800, 5801, 5919, 5920 }; + private static final int[] ID = { 9, 32, 163, 164, 196, 197, 206, 253, 254, 255, 256, 274, 275, 296, 298, 299, 447, 448, 449, 609, 678, 799, 837, 842, 862, 870, 877, 917, 1200, 1203, 1204, 1317, 1710, 1711, 1712, 2073, 2074, 2134, 2135, 2136, 2236, 2571, 2699, 2700, 2701, 2702, 2703, 3228, 3229, 3230, 3231, 3232, 3233, 3241, 3407, 3408, 3715, 4257, 4258, 4259, 4260, 4307, 4308, 4309, 4310, 4311, 4336, 4375, 4603, 4604, 4605, 4606, 5800, 5801, 5919, 5920 }; /** * Constructs a new {@code GuardNPC} {@code Object}. diff --git a/Server/src/main/content/global/skill/crafting/silver/SilverProduct.kt b/Server/src/main/content/global/skill/crafting/silver/SilverProduct.kt index 83becbf16..98bcc014e 100644 --- a/Server/src/main/content/global/skill/crafting/silver/SilverProduct.kt +++ b/Server/src/main/content/global/skill/crafting/silver/SilverProduct.kt @@ -27,7 +27,7 @@ enum class SilverProduct( val strungId: Int ) { HOLY(BUTTON_UNBLESSED, Items.HOLY_MOULD_1599, Items.UNSTRUNG_SYMBOL_1714, 1, 16, 50.0, Items.UNBLESSED_SYMBOL_1716), - UNHOLY(BUTTON_UNHOLY, Items.UNHOLY_MOULD_1594, Items.UNSTRUNG_EMBLEM_1720, 1, 17, 50.0, Items.UNHOLY_SYMBOL_1724), + UNHOLY(BUTTON_UNHOLY, Items.UNHOLY_MOULD_1594, Items.UNSTRUNG_EMBLEM_1720, 1, 17, 50.0, Items.UNPOWERED_SYMBOL_1722), SICKLE(BUTTON_SICKLE, Items.SICKLE_MOULD_2976, Items.SILVER_SICKLE_2961, 1, 18, 50.0, -1), TIARA(BUTTON_TIARA, Items.TIARA_MOULD_5523, Items.TIARA_5525, 1, 23, 52.5, -1), SILVTHRIL_CHAIN(BUTTON_SILVTHRIL_CHAIN, Items.CHAIN_LINK_MOULD_13153, Items.SILVTHRIL_CHAIN_13154, 1, 47, 100.0, -1), diff --git a/Server/src/main/content/region/kandarin/dialogue/AstronomyBook.kt b/Server/src/main/content/region/kandarin/dialogue/AstronomyBook.kt deleted file mode 100644 index 80df6aa8c..000000000 --- a/Server/src/main/content/region/kandarin/dialogue/AstronomyBook.kt +++ /dev/null @@ -1,130 +0,0 @@ -package content.region.kandarin.dialogue - -import content.global.handlers.iface.BookInterface -import content.global.handlers.iface.BookLine -import content.global.handlers.iface.Page -import content.global.handlers.iface.PageSet -import core.ServerConstants -import core.api.setAttribute -import core.game.interaction.IntType -import core.game.interaction.InteractionListener -import core.game.node.entity.player.Player -import core.plugin.Initializable -import org.rs09.consts.Items - -@Initializable -class AstronomyBook : InteractionListener { - companion object { - private val TITLE = "The tales of Scorpius" - private val CONTENTS = arrayOf( - PageSet( - Page( - BookLine("A History of Astronomy", 55), - BookLine("in ${ServerConstants.SERVER_NAME}.", 56), - BookLine("", 57), - BookLine("At the start of the 4th age,", 58), - BookLine("a learned man by the", 59), - BookLine("name of Scorpius, known well", 60), - BookLine("for his powers of vision and", 61), - BookLine("magic, sought communion with", 62), - BookLine("the gods of the world.", 63), - BookLine("So many unanswered questions", 64), - BookLine("had he that devoted his entire", 65), - ), - Page( - BookLine("life to this cause.", 66), - BookLine("After many years of study,", 67), - BookLine("seeking knowledge from the", 68), - BookLine("wise of that time, he developed", 69), - BookLine("a machine infused with", 70), - BookLine("magical power, infused with", 71), - BookLine("the ability to pierce", 72), - BookLine("into the heavens - a huge eye", 73), - BookLine("that gave the user", 74), - BookLine("incredible site, like never", 75), - BookLine("seen before.", 76), - ) - ), - PageSet( - Page( - BookLine("As time passed, Scorpius", 55), - BookLine("grew adept at using his", 56), - BookLine("specialized skills, and followed", 57), - BookLine("the movements of stars", 58), - BookLine("in ${ServerConstants.SERVER_NAME}, which are", 59), - BookLine("mapped and named, and still", 60), - BookLine("used to this day.", 61), - BookLine("", 62), - BookLine("Before long, Scorpius", 63), - BookLine("used his knowledge", 64), - BookLine("for predicting the future,", 65), - ), - Page( - BookLine("and, in turn, he called upon", 66), - BookLine("the dark knowledge of", 67), - BookLine("Zamorakian worshipers", 68), - BookLine("to further his cause. Living", 69), - BookLine("underground, the followers", 70), - BookLine("of the dark god remained", 71), - BookLine("until the civilization of", 72), - BookLine("Ardougne grew in strength", 73), - BookLine("and control.", 74), - ) - ), - PageSet( - Page( - BookLine("The kings of that time", 55), - BookLine("worked to banish the", 56), - BookLine("Zamorakian followers in", 57), - BookLine("the area, hiding all", 58), - BookLine("reference to Scorpius's", 59), - BookLine("invention, due to", 60), - BookLine("its 'evil nature'.", 61), - BookLine("", 62), - BookLine("Years later, when the", 63), - BookLine("minds of the kings lent", 64), - BookLine("more towards the research", 65), - ), - Page( - BookLine("of the unknown, the plans", 66), - BookLine("of Scorpius were uncovered", 67), - BookLine("and the heavenly eye", 68), - BookLine("constructed again. Since then,", 69), - BookLine("many have studied the ways of", 70), - BookLine("the astronomer, Scorpius", 71), - BookLine("and in his memory a grave", 72), - BookLine("was constructed near the", 73), - BookLine("Observatory. Some claim his", 74), - BookLine("ghost still wanders nearby,", 75), - BookLine("in torment as he seeks", 76), - ) - ), - PageSet( - Page( - BookLine("the secrets of the heavens", 55), - BookLine("that can never be solved.", 56), - BookLine("Tales tell that he will", 57), - BookLine("grant those adept in the", 58), - BookLine("arts of the astronomer a", 59), - BookLine("blessing of unusual power.", 60), - BookLine("", 61), - BookLine("Here ends the tale of", 62), - BookLine("how astronomy entered", 63), - BookLine("the known world.", 64), - ) - ), - ) - } - - private fun display(player: Player, pageNum: Int, buttonID: Int): Boolean { - BookInterface.pageSetup(player, BookInterface.FANCY_BOOK_3_49, TITLE, CONTENTS) - return true - } - - override fun defineListeners() { - on(Items.ASTRONOMY_BOOK_600, IntType.ITEM, "read") { player, _ -> - BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display) - return@on true - } - } -} diff --git a/Server/src/main/content/region/kandarin/quest/observatoryquest/AstronomyBook.kt b/Server/src/main/content/region/kandarin/quest/observatoryquest/AstronomyBook.kt new file mode 100644 index 000000000..f228e0864 --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/observatoryquest/AstronomyBook.kt @@ -0,0 +1,169 @@ +package content.region.kandarin.quest.observatoryquest + +import content.global.handlers.iface.* +import core.ServerConstants +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.player.Player +import org.rs09.consts.Items + +/** + * Astronomy Book + */ +class AstronomyBook : InteractionListener { + companion object { + private const val TITLE = "THE TALE OF SCORPIUS" + private val CONTENTS = arrayOf( + PageSet( + Page( + BookLine("THE TALE OF", 55), + BookLine("SCORPIUS: ", 56), + BookLine("", 57), + BookLine("A History of Astronomy", 58), + BookLine("in ${ServerConstants.SERVER_NAME}.", 59), + BookLine("", 60), + BookLine("At the start of the 4th", 61), + BookLine("Age, a learned man by", 62), + BookLine("the name of Scorpius,", 63), + BookLine("known well for his powers", 64), + BookLine("of vision and magic,", 65), + ), + Page( + BookLine("sought communion with", 66), + BookLine("the gods of the world. So", 67), + BookLine("many unanswered", 68), + BookLine("questions had he that he", 69), + BookLine("devoted his entire lif to", 70), + BookLine("the cause.", 71), + BookLine("", 72), + BookLine("After many years of", 73), + BookLine("study, seeking knowledge", 74), + BookLine("from the wise of that", 75), + BookLine("time, he developed a", 76), + ) + ), + PageSet( + Page( + BookLine("machine infused with", 55), + BookLine("magical power, infused", 56), + BookLine("with the ability to pierce", 57), + BookLine("into the very heavens - a", 58), + BookLine("huge eye that gave the", 59), + BookLine("user incredible sight, like", 60), + BookLine("never before.", 61), + BookLine("", 62), + BookLine("", 63), + BookLine("", 64), + BookLine("", 65), + ), + Page( + BookLine("As time passed, Scorpius", 66), + BookLine("grew adept at using his", 67), + BookLine("specialized skills, and", 68), + BookLine("followed the movements of", 69), + BookLine("the stars in ${ServerConstants.SERVER_NAME}", 70), + BookLine("which he mapped and", 71), + BookLine("named, and are still used", 72), + BookLine("to this very day.", 73), + BookLine("", 74), + BookLine("Before long, Scorpius", 75), + BookLine("used his knowledge for", 76), + ) + ), + PageSet( + Page( + BookLine("predicting the future,", 55), + BookLine("and, in turn, he called", 56), + BookLine("upon the dark knowledge", 57), + BookLine("of Zamorakian", 58), + BookLine("worshippers to further his", 59), + BookLine("cause. Living below", 60), + BookLine("ground, the followers of", 61), + BookLine("the dark god remained", 62), + BookLine("until the civilisation of", 63), + BookLine("Ardougne grew in", 64), + BookLine("strength and control.", 65), + ), + Page( + BookLine("", 66), + BookLine("", 67), + BookLine("", 68), + BookLine("", 69), + BookLine("", 70), + BookLine("", 71), + BookLine("The kings of that time", 72), + BookLine("worked to banish the", 73), + BookLine("Zamorakian followers in", 74), + BookLine("the area, hiding all", 75), + BookLine("references to Scorpius's", 76), + ) + ), + PageSet( + Page( + BookLine("invention, due to its 'evil", 55), + BookLine("nature'.", 56), + BookLine("", 57), + BookLine("Years after, when the", 58), + BookLine("minds of the kings lent", 59), + BookLine("more towards the", 60), + BookLine("research of the unknown,", 61), + BookLine("the plans of Scorpius", 62), + BookLine("were uncovered and the", 63), + BookLine("heavenly eye constructed", 64), + BookLine("again.", 65), + ), + Page( + BookLine("Since then, many have", 66), + BookLine("studied the ways of the", 67), + BookLine("astronomer, Scorpius, and", 68), + BookLine("in his memory a grave", 69), + BookLine("was constructed near the", 70), + BookLine("Observatory.", 71), + BookLine("", 72), + BookLine("", 73), + BookLine("", 74), + BookLine("", 75), + BookLine("", 76), + ) + ), + + PageSet( + Page(), + Page( + BookLine("Some claim his ghost still", 66), + BookLine("wanders nearby, in", 67), + BookLine("torment as he seeks the", 68), + BookLine("secrets of the heavens", 69), + BookLine("that can never be solved.", 70), + BookLine("Tales tell that he will", 71), + BookLine("grant those adept in the", 72), + BookLine("arts of the astronomer a", 73), + BookLine("blessing of unusual", 74), + BookLine("power.", 75), + BookLine("", 76), + ) + ), + PageSet( + Page( + BookLine("Here ends the tale of how", 55), + BookLine("astronomy entered the", 56), + BookLine("known world.", 57), + ), + Page() + ), + ) + private fun display(player:Player, pageNum: Int, buttonID: Int) : Boolean { + BookInterface.pageSetup(player, BookInterface.FANCY_BOOK_3_49, TITLE, CONTENTS) + BookInterface.setModelOnPage(player,2, 27071, BookInterface.FANCY_BOOK_3_49, 33, 34, 1020, 0, 0) + BookInterface.setModelOnPage(player,4, 27069, BookInterface.FANCY_BOOK_3_49, 17, 18, 1224, 0, 0) + return true + } + } + + override fun defineListeners() { + on(Items.ASTRONOMY_BOOK_600, IntType.ITEM, "read") { player, _ -> + BookInterface.openBook(player, BookInterface.FANCY_BOOK_3_49, ::display) + return@on true + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/observatoryquest/GoblinDialogues.kt b/Server/src/main/content/region/kandarin/quest/observatoryquest/GoblinDialogues.kt new file mode 100644 index 000000000..5d676d5de --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/observatoryquest/GoblinDialogues.kt @@ -0,0 +1,152 @@ +package content.region.kandarin.quest.observatoryquest + +import core.api.* +import core.game.dialogue.* +import core.game.interaction.InteractionListener +import core.game.node.entity.npc.AbstractNPC +import core.game.node.entity.npc.NPC +import core.game.world.map.Location +import core.plugin.Initializable +import core.tools.RandomFunction +import org.rs09.consts.NPCs + +class GoblinDialogues : InteractionListener { + override fun defineListeners() { + on(NPCs.GREASYCHEEKS_6127, NPC, "talk-to") { player, node -> + openDialogue(player, GreasycheeksDialogueFile(), node as NPC) + return@on true + } + on(NPCs.SMELLYTOES_6128, NPC, "talk-to") { player, node -> + openDialogue(player, SmellytoesDialogueFile(), node as NPC) + return@on true + } + on(NPCs.CREAKYKNEES_6129, NPC, "talk-to") { player, node -> + openDialogue(player, CreakykneesDialogueFile(), node as NPC) + return@on true + } + } +} + +class GreasycheeksDialogueFile : DialogueLabeller() { + override fun addConversation() { + assignToIds(NPCs.GREASYCHEEKS_6127) + player("Hello.") + npc(ChatAnim.OLD_NORMAL, "Shush! I'm concentrating.") + player("Oh, sorry.") + } +} + +class SmellytoesDialogueFile : DialogueLabeller() { + override fun addConversation() { + assignToIds(NPCs.SMELLYTOES_6128) + player("Hi there.") + npc(ChatAnim.OLD_NORMAL, "Hey, ids me matesh!") + player("Sorry, have we met?") + npc(ChatAnim.OLD_NORMAL, "Yeah! you wazsh wiv me in dat pub overy by hill!") + player("I have no idea what you're going on about.") + npc(ChatAnim.OLD_NORMAL, "Glad yeeash remembers.") + } +} + +class CreakykneesDialogueFile : DialogueLabeller() { + override fun addConversation() { + assignToIds(NPCs.CREAKYKNEES_6129) + player("Where did you get that lens?") + npc(ChatAnim.OLD_NORMAL, "From that strange metal thing up on the hill.") + player(ChatAnim.ANGRY, "You should give that back!") + npc(ChatAnim.OLD_NORMAL, "Even if it's cracked?") + player("Ah, well, I suppose it's of no use. But, still.") + } +} + +@Initializable +class GreasycheeksNPC(id: Int = 0, location: Location? = null) : AbstractNPC(id, location) { + override fun construct(id: Int, location: Location?, vararg objects: Any?): AbstractNPC { + return GreasycheeksNPC(id, location) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.GREASYCHEEKS_6127) + } + + override fun handleTickActions() { + super.handleTickActions() + if (RandomFunction.roll(12)) { + sendChat(this, arrayOf( + "Cook, cook, cook!", + "I'm so hungry!", + "This is gonna taste sooo good!", + ).random()) + } + } +} + +@Initializable +class SmellytoesNPC(id: Int = 0, location: Location? = null) : AbstractNPC(id, location) { + override fun construct(id: Int, location: Location?, vararg objects: Any?): AbstractNPC { + return SmellytoesNPC(id, location) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.SMELLYTOES_6128) + } + + override fun handleTickActions() { + super.handleTickActions() + if (RandomFunction.roll(12)) { + sendChat(this, arrayOf( + "Doh ray meeee laa doh faaa!", + "La la la! Do di dum dii!", + ).random()) + } + } +} + +@Initializable +class CreakykneesNPC(id: Int = 0, location: Location? = null) : AbstractNPC(id, location) { + override fun construct(id: Int, location: Location?, vararg objects: Any?): AbstractNPC { + return CreakykneesNPC(id, location) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.CREAKYKNEES_6129) + } + + override fun handleTickActions() { + super.handleTickActions() + if (RandomFunction.roll(12)) { + sendChat(this, arrayOf( + "Was that a spark?", + "Come on! Please light!", + ).random()) + } + } +} +@Initializable +class LostGoblinNPC(id: Int = 0, location: Location? = null) : AbstractNPC(id, location) { + override fun construct(id: Int, location: Location?, vararg objects: Any?): AbstractNPC { + return LostGoblinNPC(id, location) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.GOBLIN_6125) + } + + override fun handleTickActions() { + super.handleTickActions() + if (RandomFunction.roll(12)) { + sendChat(this, arrayOf( + "Which way should I go?", + "These dungeons are such a maze.", + "Where's the exit?!?", + "This is the fifth time this week. I'm lost!", + "I've been wandering around down here for hours.", + "How do you get back to the village?", + "I hate being so lost!", + "How could I be so disoriented?", + "Where am I? I'm so lost.", + "I know the exit's around here, somewhere." + ).random()) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/observatoryquest/GoblinGuardNPC.kt b/Server/src/main/content/region/kandarin/quest/observatoryquest/GoblinGuardNPC.kt new file mode 100644 index 000000000..963438628 --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/observatoryquest/GoblinGuardNPC.kt @@ -0,0 +1,31 @@ +package content.region.kandarin.quest.observatoryquest + +import content.data.Quests +import core.api.* +import core.game.node.entity.Entity +import core.game.node.entity.npc.AbstractNPC +import core.game.node.entity.player.Player +import core.game.world.map.Location +import core.plugin.Initializable +import org.rs09.consts.NPCs + +@Initializable +class GoblinGuardNPC(id: Int = 0, location: Location? = null) : AbstractNPC(id, location) { + override fun construct(id: Int, location: Location, vararg objects: Any): AbstractNPC { + return GoblinGuardNPC(id, location) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.SLEEPING_GUARD_6122, NPCs.GOBLIN_GUARD_489) + } + + override fun finalizeDeath(entity: Entity) { + if (entity is Player) { + val player = entity.asPlayer() + if (getQuestStage(player, Quests.OBSERVATORY_QUEST) >= 4) { + setAttribute(player, ObservatoryQuest.attributeKilledGuard, true) + } + super.finalizeDeath(player) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryAssistantDialogue.kt b/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryAssistantDialogue.kt new file mode 100644 index 000000000..5c7f1f7eb --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryAssistantDialogue.kt @@ -0,0 +1,238 @@ +package content.region.kandarin.quest.observatoryquest + +import content.data.Quests +import content.region.kandarin.quest.observatoryquest.ObservatoryQuest.Companion.attributeReceivedWine +import core.api.* +import core.game.dialogue.* +import core.game.node.entity.player.Player +import core.game.node.item.Item +import core.plugin.Initializable +import org.rs09.consts.Items +import org.rs09.consts.NPCs + +@Initializable +class ObservatoryAssistantDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun newInstance(player: Player): DialoguePlugin { + return ObservatoryAssistantDialogue(player) + } + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, ObservatoryAssistantDialogueFile(), npc) + return false + } + override fun getIds(): IntArray { + return intArrayOf(NPCs.OBSERVATORY_ASSISTANT_6118, 10022) + } +} + +class ObservatoryAssistantDialogueFile : DialogueLabeller() { + override fun addConversation() { + assignToIds(NPCs.OBSERVATORY_ASSISTANT_6118) + + exec { player, npc -> + if (getQuestStage(player, Quests.OBSERVATORY_QUEST) == 0) { + loadLabel(player, "observatoryQuest0") + } else if (getAttribute(player, attributeReceivedWine, false)) { + loadLabel(player, "receivedwine") + } else if (getQuestStage(player, Quests.OBSERVATORY_QUEST) == 100) { + loadLabel(player, "observatoryqueststage100") + } else { + loadLabel(player, "randomStart" + (1..4).random()) + } + } + + label("observatoryQuest0") + player(FacialExpression.ASKING, "Hi, are you busy?") + npc("Me? I'm always busy.") + npc("See that man there? That's the professor. If he had his way, I think he'd never let me sleep!") + npc("Anyway, how might I help you?") + options( + DialogueOption("wonderingwhat", "I was wondering what you do here."), + DialogueOption("justlooking", "Just looking around, thanks."), + DialogueOption("looktelescope", "Can I have a look through that telescope?", expression = FacialExpression.ASKING) + ) + + label("wonderingwhat") + npc("Glad you ask. This is the Observatory reception. Up on the cliff is the Observatory dome, from which you can view the heavens. Usually...") + player(FacialExpression.ASKING, "What do you mean, 'usually'?") + npc("*Ahem*. Back to work, please.") + npc("I'd speak with the professor. He'll explain.") + + label("justlooking") + npc("Okay, just don't break anything. If you need any help, let me know.") + line("The assistant continues with his work.") + + label("looktelescope") + npc("You can. You won't see much though.") + player(FacialExpression.ASKING, "And that's because?") + npc("Just talk to the professor. He'll fill you in.") + + label("randomStart1") + player(FacialExpression.ASKING, "Can I interrupt you?") + npc("I suppose so. Please be quick though.") + goto("afterRandomStart") + + label("randomStart2") + player(FacialExpression.ASKING, "Might I have a word?") + npc("Sure, how can I help you?") + goto("afterRandomStart") + + label("randomStart3") + player("Hello there.") + npc("Yes?") + goto("afterRandomStart") + + label("randomStart4") + player(FacialExpression.ASKING, "Can I speak with you?") + npc("Why, of course. What is it?") + goto("afterRandomStart") + + + label("afterRandomStart") + exec { player, npc -> + loadLabel(player, "observatoryqueststage" + getQuestStage(player, Quests.OBSERVATORY_QUEST)) + } + + label("observatoryqueststage1") + exec { player, _ -> + if (inInventory(player, Items.PLANK_960, 3)) { + loadLabel(player, "enoughplanks") + } + else if (inInventory(player, Items.PLANK_960)) { + loadLabel(player, "someplanks") + } + else { + loadLabel(player, "notenoughplanks") + } + } + + label("notenoughplanks") + player(FacialExpression.ASKING,"Where can I find planks of wood? I need some for the telescope's base.") + npc("I understand planks can be found at Port Khazard, to the east of here. There are some at the Barbarian Outpost, too.") + npc("Failing that, you could always ask the Sawmill Operator. He's to the north-east of Varrock, by the Lumber Yard.") + + label("enoughplanks") + player("I've got some planks for the telescope's base.") + npc("Good work! The professor has been eagerly awaiting them.") + + label("someplanks") + player("I've got a plank.") + npc("That's nice.") + player("You know, for the telescope's base.") + npc("Well done. Remember that you'll need three in total.") + + label("observatoryqueststage2") + exec { player, _ -> + if(inInventory(player, Items.BRONZE_BAR_2349)) { + loadLabel(player, "hasbronzebar") + } else { + loadLabel(player, "nohasbronzebar") + } + } + + label("nohasbronzebar") + player(FacialExpression.ASKING, "Can you help me? How do I go about getting a bronze bar?") + npc("You'll need to use tin and copper ore on a furnace to produce this metal.") + player("Right you are.") + + label("hasbronzebar") + player("The bronze bar is ready, and waiting for the professor.") + npc("He'll surely be pleased. Go ahead and give it to him.") + + label("observatoryqueststage3") + exec { player, _ -> + if(inInventory(player, Items.MOLTEN_GLASS_1775)) { + loadLabel(player, "hasmoltenglass") + } else { + loadLabel(player, "nohasmoltenglass") + } + } + + label("nohasmoltenglass") + player(FacialExpression.ASKING, "What's the best way for me to get molten glass?") + npc("There are many ways, but I'd suggest making it yourself. Get yourself a bucket of sand and some soda ash, which you can get from using seaweed with a furnace. Use the soda ash and sand together in a") + npc("furnace and bang - molten glass is all yours. There's a book about it on the table if you want to know more.") + player("Thank you!") + + label("hasmoltenglass") + player("I've managed to get hold of some molten glass.") + npc("I suggest you have a word with the professor, in that case.") + + label("observatoryqueststage4") + exec { player, _ -> + if(inInventory(player, Items.LENS_MOULD_602)) { + loadLabel(player, "haslensmould") + } else { + loadLabel(player, "nohaslensmould") + } + } + + label("nohaslensmould") + player(FacialExpression.ASKING, "Where can I find this lens mould you mentioned?") + npc("I'm sure I heard one of those goblins talking about it. I bet they've hidden it somewhere. Probably using it for some strange purpose, I'm sure.") + player(FacialExpression.ASKING, "What makes you say that?") + npc("I had a nice new star chart, until recently. I went out to do an errand for the professor the other day, only to see a goblin using it...") + npc("...as some kind of makeshift hankey to blow his nose!") + player("Oh dear.") + npc("You may want to look through the dungeon they have under their little village.") + player("Thanks for the advice.") + + label("haslensmould") + player("I have the lens mould.") + npc("Well done on finding that! I am honestly quite impressed. Make sure you take it straight to the professor.") + player("Will do.") + + label("observatoryqueststage5") + exec { player, _ -> + if(inInventory(player, Items.OBSERVATORY_LENS_603)) { + loadLabel(player, "haslens") + } else { + loadLabel(player, "nohaslens") + } + } + + label("nohaslens") + player(FacialExpression.ASKING, "How should I make this lens?") + npc("Just use the molten glass with the mould. Simple.") + player("Thanks!") + + label("haslens") + player("Do you like this lens? Good, huh?") + npc("Nice. What's that scratch?") + player("Oh, erm, that's a feature.") + player("Yes, that's it! Indubitably, it facilitates the triangulation of photonic illumination to the correct...") + npc("Stop! You can't confuse me with big words. Just pray the professor doesn't notice.") + + label("observatoryqueststage6") + player("Hello again.") + npc("Ah, it's the telescope repairman! The professor is waiting for you in the Observatory.") + player("How can I get to the Observatory?") + npc("Well, since the bridge was ruined, you will have to travel through the dungeon under the goblins' settlement.") + + label("observatoryqueststage100") + player("Hi assistant.") + player("Wait a minute.") + player(FacialExpression.ASKING, "What's your real name?") + npc(FacialExpression.ASKING, "My real name?") + player(FacialExpression.ASKING, "I only know you as the professor's assistant. What's your actual name?") + npc("Patrick.") + player("Hi Patrick, I'm @name.") + npc("Well, hello again, and thanks for helping out the professor.") + npc(" Oh, and, believe it or not, his name is Mambo-duna-roona, but don't tell him I told you.") + player(FacialExpression.AMAZED, "You made that up!") + npc("No, honest! Anyways, you've made my life much easier. Have a drink on me!") + item(Item(Items.JUG_OF_WINE_1993), "The assistant gives you some wine.") + exec { player, _ -> + addItemOrDrop(player, Items.JUG_OF_WINE_1993) + setAttribute(player, attributeReceivedWine, true) + } + player("Thanks very much.") + npc("No problem. Scorpius would be proud.") + player(FacialExpression.ASKING, "Sorry?") + npc("You may want to check out that book on the table, and perhaps look around for a grave...") + + label("receivedwine") + npc(FacialExpression.ASKING, "How was the wine?") + player(FacialExpression.DRUNK, "That was good stuff, *hic*! Wheresh the professher?") + npc(FacialExpression.ASKING, "The professor? He's up in the Observatory. Since the bridge was ruined, you will have to travel through the dungeon under the goblins' settlement.") + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryCutscene.kt b/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryCutscene.kt new file mode 100644 index 000000000..0eae2b1ab --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryCutscene.kt @@ -0,0 +1,199 @@ +package content.region.kandarin.quest.observatoryquest + +import core.api.* +import core.game.activity.Cutscene +import core.game.dialogue.FacialExpression +import core.game.node.entity.player.Player +import core.game.world.map.Direction +import org.rs09.consts.Animations +import org.rs09.consts.NPCs +import content.region.kandarin.quest.observatoryquest.ObservatoryQuest.Companion.attributeFinishedCutscene +import core.game.world.map.Location + +class ObservatoryCutscene(player: Player) : Cutscene(player) { + override fun setup() { + setExit(Location(2438, 3163)) + loadRegion(9777) + addNPC(PROFESSOR, 7, 25, Direction.SOUTH) + } + + override fun runStage(stage: Int) { + when (stage) { + 0 -> { + fadeToBlack() + timedUpdate(5) + } + 1 -> { + teleport(player, 6, 27) + face(player, getNPC(PROFESSOR)!!.location) + rotateCamera(9, 25, 300) + moveCamera(25, 22, 1800) + timedUpdate(5) + } + 2 -> { + fadeFromBlack() + dialogueLinesUpdate("~ The Observatory ~", "The great eye into the heavens.") + } + 3 -> { + moveCamera(14, 10, 1800, 10) + timedUpdate(3) + } + 4 -> { + moveCamera(6, 10, 1800, 10) + timedUpdate(3) + } + 5 -> { + moveCamera(0, 32, 1800, 10) + timedUpdate(3) + } + 6 -> { + moveCamera(4, 36, 1600, 10) + timedUpdate(3) + } + 7 -> { + rotateCamera(7, 25, 300, 5) + moveCamera(13, 40, 1800, 10) + timedUpdate(3) + } + 8 -> { + moveCamera(12, 36, 1800, 5) + timedUpdate(5) + } + 9 -> { + fadeToBlack() + timedUpdate(5) + } + 10 -> { + moveCamera(5, 21, 500) + rotateCamera(6, 27, 100) + timedUpdate(3) + } + 11 -> { + fadeFromBlack() + timedUpdate(5) + } + 12 -> { + playerDialogueUpdate(FacialExpression.NEUTRAL, "Hi professor!") + } + 13 -> { + face(getNPC(PROFESSOR)!!, player.location) + timedUpdate(3) + } + 14 -> { + animate(getNPC(PROFESSOR)!!, WAVE) + dialogueUpdate(PROFESSOR, FacialExpression.HAPPY, "Oh, hi there.") + } + 15 -> { + dialogueUpdate(PROFESSOR, FacialExpression.HAPPY, "I'm just adding the finishing touches.") + } + 16 -> { + playerDialogueUpdate(FacialExpression.HAPPY, "Okay, don't let me interrupt.") + } + 17 -> { + dialogueUpdate(PROFESSOR, FacialExpression.NEUTRAL, "Thank you.") + } + 18 -> { + face(getNPC(PROFESSOR)!!, Location(136, 25)) + dialogueUpdate(PROFESSOR, FacialExpression.NEUTRAL, "Right, let's get this finished.") + } + 19 -> { + animate(getNPC(PROFESSOR)!!, THINKING) + sendChat(getNPC(PROFESSOR)!!, "Hmmmm...") + timedUpdate(5) + } + 20 -> { + move(getNPC(PROFESSOR)!!, 7, 23) + rotateCamera(9, 23, 100) + moveCamera(12, 30, 300) + timedUpdate(3) + } + 21 -> { + move(getNPC(PROFESSOR)!!, 10, 26) + timedUpdate(10) + } + 22 -> { + face(getNPC(PROFESSOR)!!, Location(135, 25)) + animate(getNPC(PROFESSOR)!!, FIX) + sendChat(getNPC(PROFESSOR)!!, "Bit of a tap here...") + timedUpdate(3) + } + 23 -> { + move(getNPC(PROFESSOR)!!, 11, 26) + timedUpdate(5) + } + 24 -> { + face(getNPC(PROFESSOR)!!, player.location) + dialogueUpdate(PROFESSOR, FacialExpression.HAPPY, "@name, I'm just going upstairs to finish off.") + } + 25 -> { + playerDialogueUpdate(FacialExpression.HAPPY, "Right-oh.") + } + 26 -> { + face(getNPC(PROFESSOR)!!, Location(140, 25)) + timedUpdate(3) + } + 27 -> { + teleport(getNPC(PROFESSOR)!!, 11, 22, 1) + teleport(player, 14,29,1) + face(getNPC(PROFESSOR)!!, Location(2442, 3158, 1)) + moveCamera(6, 20, 400) + rotateCamera(10, 24, 400) + timedUpdate(5) + } + 28 -> { + move(getNPC(PROFESSOR)!!, 7, 27) + moveCamera(10, 20, 400, 1) + rotateCamera(7, 27, 50, 20) + timedUpdate(10) + } + 29 -> { + sendChat(getNPC(PROFESSOR)!!, "In goes the lens.") + timedUpdate(3) + } + 30 -> { + animate(getNPC(PROFESSOR)!!, TUNEUP) + timedUpdate(20) + } + 31 -> { + sendChat(getNPC(PROFESSOR)!!, "And one final adjustment.") + move(getNPC(PROFESSOR)!!, 10, 29) + timedUpdate(6) + } + 32 -> { + move(getNPC(PROFESSOR)!!, 10, 25) + face(getNPC(PROFESSOR)!!, Location(137, 26)) + timedUpdate(6) + } + 33 -> { + face(getNPC(PROFESSOR)!!, Location(137, 26)) + animate(getNPC(PROFESSOR)!!, CRANK) + sendChat(getNPC(PROFESSOR)!!, "And all our work pays off.") + timedUpdate(6) + } + 34 -> { + animate(getNPC(PROFESSOR)!!, CHEER) + timedUpdate(10) + } + 35 -> { + moveCamera(6, 27, 500, 5) + rotateCamera(10, 25, 50, 5) + timedUpdate(3) + } + 36 -> { + end { + setAttribute(player, attributeFinishedCutscene, true) + } + } + } + } + + companion object { + val PROFESSOR = NPCs.OBSERVATORY_PROFESSOR_6121 + val WAVE = Animations.HUMAN_WAVE_863 + val THINKING = 6844 + val FIX = 6847 + val TUNEUP = 6848 + val CRANK = 6845 + val CHEER = Animations.HUMAN_CHEER_862 + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryProfessorDialogue.kt b/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryProfessorDialogue.kt new file mode 100644 index 000000000..380334049 --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryProfessorDialogue.kt @@ -0,0 +1,676 @@ +package content.region.kandarin.quest.observatoryquest + +import content.data.Quests +import core.api.* +import core.game.dialogue.* +import content.region.kandarin.quest.observatoryquest.ObservatoryQuest.Companion.attributeReceivedWine +import core.game.interaction.QueueStrength +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import core.plugin.Initializable +import org.rs09.consts.Components +import org.rs09.consts.Items +import org.rs09.consts.NPCs + +@Initializable +class ObservatoryProfessorDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun newInstance(player: Player): DialoguePlugin { + return ObservatoryProfessorDialogue(player) + } + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, ObservatoryProfessorDialogueFile(), npc) + return false + } + override fun getIds(): IntArray { + return intArrayOf(NPCs.OBSERVATORY_ASSISTANT_6119, NPCs.OBSERVATORY_ASSISTANT_6120) + } +} + +class ObservatoryProfessorDialogueFile : DialogueLabeller() { + override fun addConversation() { + assignToIds(NPCs.OBSERVATORY_PROFESSOR_488) + + npc("What would you like to talk about?") + options( + DialogueOption("observatoryquest", "Talk about the Observatory Quest.", skipPlayer = true), + DialogueOption("treasuretrails", "Talk about the Treasure Trails.", skipPlayer = true), + ) + + label("observatoryquest") + exec { player, npc -> + loadLabel(player, "observatoryqueststage" + getQuestStage(player, Quests.OBSERVATORY_QUEST)) + } + + label("observatoryqueststage0") + player("Hi, I was...") + npc(FacialExpression.FRIENDLY, "Welcome to the magnificent wonder of the Observatory, where wonder is all around you, where the stars can be clutched from the heavens!") + player("Wow, nice intro.") + npc(FacialExpression.FRIENDLY, "Why, thanks! How might I help you?") + options( + DialogueOption("totallylost", "I'm totally lost."), + DialogueOption("whatwhat", "An Observatory?", expression = FacialExpression.THINKING), + DialogueOption("nah", "I'm just passing through."), + ) + + label("totallylost") + npc(FacialExpression.THINKING, "Lost? It must have been those pesky goblins that led you astray.") + npc("Head north-east to find the city of Ardougne.") + player("I'm sure I'll find the way.") + player("Thanks for all your help.") + npc(FacialExpression.FRIENDLY, "No problem at all. Come and visit again!") + line("The professor carries on with his studies.") + + label("nah") + npc("Fair enough. Not everyone is interested in this place, I suppose.") + + label("whatwhat") + npc(FacialExpression.FRIENDLY, "Of course. We have a superb telescope up in the Observatory, on the hill.") + npc(FacialExpression.FRIENDLY, "A truly marvellous invention, the likes of which you'll never behold again.") + npc(FacialExpression.NEUTRAL, NPCs.OBSERVATORY_ASSISTANT_6118, "Well, it would be if it worked.") + npc(FacialExpression.ANGRY, "Don't interrupt!") + player(FacialExpression.THINKING, "What? It doesn't work?") + npc("Oh, no, no, no. Don't listen to him, he's joking. Aren't you, my FAITHFUL assistant?") + npc(FacialExpression.NEUTRAL, NPCs.OBSERVATORY_ASSISTANT_6118, "Nope, dead serious. Hasn't been working for a long time.") + npc(FacialExpression.ANGRY, "Arghhh! Get back to work and stop sticking your nose in!") + player("So, it's broken. How come?") + npc(FacialExpression.SAD, "Oh, I suppose there's no use keeping it secret. Did you see those houses outside?") + player("Up on the hill? Yes, I've seen them.") + npc("It's a horde of goblins.") + npc(FacialExpression.SAD, "Since they moved here they have caused nothing but trouble.") + npc("Last week, my telescope was tampered with.") + npc(FacialExpression.ANGRY, "Now, parts need replacing before it can be used again. They've even been messing around in the dungeons under this area. Something needs to be done.") + npc(FacialExpression.NEUTRAL, NPCs.OBSERVATORY_ASSISTANT_6118, "Strikes me that this visitor could help us.") + npc(FacialExpression.ANGRY, "Stop being so rude.") + npc("...") + npc("Although, he has a point. What do you say?") + player(FacialExpression.THINKING, "What, me?") + options( + DialogueOption("yestoquest", "Sounds interesting. How can I help?", "Sounds interesting, what can I do for you?", FacialExpression.FRIENDLY), + DialogueOption("notoquest", "Oh, sorry, I don't have time for that."), + ) + + label("notoquest") + npc("Oh dear. I really do need some help.") + npc("If you see anyone who can help then please send them my way.") + + label("yestoquest") + exec { player, _ -> + if(getQuestStage(player, Quests.OBSERVATORY_QUEST) == 0) { + setQuestStage(player, Quests.OBSERVATORY_QUEST, 1) + } + } + npc(FacialExpression.FRIENDLY, "Oh, thanks so much.") + npc(FacialExpression.FRIENDLY, "I shall need some materials for the telescope, so it can be used again.") + npc("Let's start with three planks of wood for the telescope base. My assistant will help with obtaining these, won't you?") + npc(FacialExpression.NEUTRAL, NPCs.OBSERVATORY_ASSISTANT_6118, "As if I don't have enough work to do. Seems I don't have a choice.") + npc(FacialExpression.FRIENDLY, "Go talk to him if you need some advice.") + player(FacialExpression.FRIENDLY, "Okay, I'll be right back.") + + label("observatoryqueststage1") + player(FacialExpression.FRIENDLY, "Hi again!") + npc(FacialExpression.FRIENDLY, "It's my helping hand, back again.") + npc(FacialExpression.THINKING, "Do you have the planks yet?") + exec { player, _ -> + if(inInventory(player, Items.PLANK_960, 3)) { + loadLabel(player, "enoughplanks") + } else { + loadLabel(player, "notenoughplanks") + } + } + + label("notenoughplanks") + player("Sorry, not yet. Three planks was it?") + npc("It was indeed.") + + label("enoughplanks") + player("Yes, I've got them. Here they are.") + exec { player, _ -> + if(removeItem(player, Item(Items.PLANK_960, 3))) { + if (getQuestStage(player, Quests.OBSERVATORY_QUEST) == 1) { + setQuestStage(player, Quests.OBSERVATORY_QUEST, 2) + } + } + } + npc(FacialExpression.FRIENDLY, "Well done. This will make a big difference.") + npc(FacialExpression.FRIENDLY, "Now, the bronze for the tube. Oh, assistant!") + npc(FacialExpression.NEUTRAL, NPCs.OBSERVATORY_ASSISTANT_6118, "Okay, okay, ask me if you need any help, @name.") + + label("observatoryqueststage2") + player("Hi.") + npc("The traveller returns!") + player("Still working hard?") + npc(FacialExpression.NEUTRAL, NPCs.OBSERVATORY_ASSISTANT_6118, "Some of us are.") + npc(FacialExpression.ANGRY, "What did I tell you about speaking when spoken to?") + npc(FacialExpression.THINKING, "So, @name, you have the bronze bar?") + exec { player, _ -> + if(inInventory(player, Items.BRONZE_BAR_2349)) { + loadLabel(player, "hasbronzebar") + } else { + loadLabel(player, "nohasbronzebar") + } + } + + label("nohasbronzebar") + player("Not yet.") + npc("Please bring me one, then.") + + label("hasbronzebar") + player(FacialExpression.FRIENDLY, "I certainly do. Here you go.") + exec { player, _ -> + if(removeItem(player, Item(Items.BRONZE_BAR_2349))) { + if (getQuestStage(player, Quests.OBSERVATORY_QUEST) == 2) { + setQuestStage(player, Quests.OBSERVATORY_QUEST, 3) + } + } + } + npc(FacialExpression.FRIENDLY, "Great. Now all I need is the lens made.") + npc(FacialExpression.FRIENDLY, "Please get me some molten glass.") + npc("Oi! Lazy bones!") + player(FacialExpression.AMAZED, "What? I'm not lazy.") + npc("Not you! I'm talking to my assistant.") + npc(FacialExpression.NEUTRAL, NPCs.OBSERVATORY_ASSISTANT_6118, "Calm down old man, I heard. @name, I'm here if you need any help.") + npc("Thank you. Wait a minute, who are you calling 'old'?") + + label("observatoryqueststage3") + npc("How are you getting on finding me some molten glass?") + exec { player, _ -> + if(inInventory(player, Items.MOLTEN_GLASS_1775)) { + loadLabel(player, "hasmoltenglass") + } else { + loadLabel(player, "nohasmoltenglass") + } + } + + label("nohasmoltenglass") + player("Still working on it.") + npc("I really need it. Please hurry.") + + label("hasmoltenglass") + player(FacialExpression.FRIENDLY, "Here it is.") + exec { player, _ -> + if(removeItem(player, Item(Items.MOLTEN_GLASS_1775))) { + if (getQuestStage(player, Quests.OBSERVATORY_QUEST) == 3) { + setQuestStage(player, Quests.OBSERVATORY_QUEST, 4) + } + } + } + npc(FacialExpression.FRIENDLY, "Excellent work, let's make the lens.") + npc(FacialExpression.NEUTRAL, NPCs.OBSERVATORY_ASSISTANT_6118, "It'll need to be made to an exact shape and size.") + npc("Well, obviously, hence why we have a lens mould.") + npc(FacialExpression.NEUTRAL, NPCs.OBSERVATORY_ASSISTANT_6118, "Not any more. One of those goblins took it.") + npc(FacialExpression.SAD, "Great, just what I need. @name, I don't suppose you could find it?") + player(FacialExpression.FRIENDLY, "I'll have a look - where should I start?") + npc(FacialExpression.FRIENDLY, "No idea. You could ask my USELESS assistant if you want.") + npc(FacialExpression.NEUTRAL, NPCs.OBSERVATORY_ASSISTANT_6118, "What have I done to deserve this?") + + label("observatoryqueststage4") + npc(FacialExpression.THINKING, "Did you bring me the mould?") + exec { player, _ -> + if(inInventory(player, Items.LENS_MOULD_602)) { + loadLabel(player, "haslensmould") + } else { + loadLabel(player, "nohaslensmould") + } + } + + label("nohaslensmould") + player("Still looking for it.") + npc("Please try and find it; my assistant may be able to help.") + + label("haslensmould") + player(FacialExpression.HAPPY, "I certainly have. You'll never guess what they were doing with it.") + npc("Well, from the smell I'd guess cooking some vile concoction.") + player(FacialExpression.HAPPY, "Wow, good guess. Well, here you go.") + npc(FacialExpression.NEUTRAL, NPCs.OBSERVATORY_ASSISTANT_6118, "Please don't give that to him. Last time he tried any Crafting, I had to spend a week cleaning up after the explosion.") + player("Explosion?") + npc(FacialExpression.SUSPICIOUS, "Erm, yes. I think in this instance you had probably better do it.") + player("I suppose it's better I don't ask.") + npc(FacialExpression.HAPPY,"You can use the mould with molten glass to make a new lens.") + exec { player, _ -> + if(inInventory(player, Items.LENS_MOULD_602)) { + addItemOrDrop(player, Items.MOLTEN_GLASS_1775) + if (getQuestStage(player, Quests.OBSERVATORY_QUEST) == 4) { + setQuestStage(player, Quests.OBSERVATORY_QUEST, 5) + } + } + } + item(Item(Items.MOLTEN_GLASS_1775), "The professor gives you back the molten glass.") + + + label("observatoryqueststage5") + npc("Is the lens finished?") + exec { player, _ -> + if(inInventory(player, Items.OBSERVATORY_LENS_603)) { + loadLabel(player, "haslens") + } else { + loadLabel(player, "nohaslens") + } + } + + label("nohaslens") + player("How do I make it again?") + npc("Use the molten glass with the mould.") + player("Huh. Simple.") + + label("haslens") + player("Yes, here it is. You may as well take this mould too.") //No source if no mould present + line("The player hands the observatory professor the observatory lens.") + npc("Wonderful, at last I can fix the telescope.") + npc("Would you accompany me to the Observatory? You simply must see the telescope in operation.") + player("Sounds interesting. Count me in.") + npc("Superb. You'll have to go via the dungeon under the goblin settlement, seeing as the bridge is broken. You'll find stairs up to the Observatory from there.") + player("Okay. See you there.") + exec { player, _ -> + setVarbit(player, ObservatoryQuest.telescopeVarbit, 1, true) + queueScript(player, 0, QueueStrength.SOFT) { stage: Int -> + when (stage) { + 0 -> { + openOverlay(player, Components.FADE_TO_BLACK_120) + return@queueScript delayScript(player, 4) + } + 1 -> { + if(removeItem(player, Item(Items.OBSERVATORY_LENS_603))) { + removeItem(player, Item(Items.LENS_MOULD_602)) + if (getQuestStage(player, Quests.OBSERVATORY_QUEST) == 5) { + setQuestStage(player, Quests.OBSERVATORY_QUEST, 6) + } + } + return@queueScript delayScript(player, 1) + } + 2 -> { + openOverlay(player, Components.FADE_FROM_BLACK_170) + return@queueScript delayScript(player, 2) + } + 3 -> { + sendDialogueLines(player, "The professor has gone ahead to the Observatory dome. Best you", "follow him to see the finished telescope.") + return@queueScript stopExecuting(player) + } + else -> return@queueScript stopExecuting(player) + } + } + + + } + + label("observatoryqueststage6") + npc("Hello, friend.") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) != null) { + loadLabel(player, "hasviewtelescope") + } else { + loadLabel(player, "nohasviewtelescope") + } + } + label("nohasviewtelescope") + // http://youtu.be/cK5suqcw3qU + player("Hi, this really is impressive.") + npc("Certainly is. Please, take a look through the telescope and tell me what you see.") + + label("hasviewtelescope") + player("I've had a look through the telescope.") + npc("What did you see? If you're not sure, you can find out by looking at the star charts dotted around the walls downstairs.") + player("It was...") + goto("page1") + + + label("page1") + options( + DialogueOption("Aquarius", "Aquarius", skipPlayer = true), + DialogueOption("Capricorn", "Capricorn", skipPlayer = true), + DialogueOption("Sagittarius", "Sagittarius", skipPlayer = true), + DialogueOption("Scorpio", "Scorpio", skipPlayer = true), + DialogueOption("page2", "~ next ~", skipPlayer = true), + ) + + label("page2") + options( + DialogueOption("page1", "~ previous ~", skipPlayer = true), + DialogueOption("Libra", "Libra", skipPlayer = true), + DialogueOption("Virgo", "Virgo", skipPlayer = true), + DialogueOption("Leo", "Leo", skipPlayer = true), + DialogueOption("page3", "~ next ~", skipPlayer = true), + ) + + label("page3") + options( + DialogueOption("page2", "~ previous ~", skipPlayer = true), + DialogueOption("Cancer", "Cancer", skipPlayer = true), + DialogueOption("Gemini", "Gemini", skipPlayer = true), + DialogueOption("Taurus", "Taurus", skipPlayer = true), + DialogueOption("page4", "~ next ~", skipPlayer = true), + ) + + label("page4") + options( + DialogueOption("page3", "~ previous ~", skipPlayer = true), + DialogueOption("Aries", "Aries", skipPlayer = true), + DialogueOption("Pisces", "Pisces", skipPlayer = true), + ) + + label("Aquarius") + player("Aquarius!") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == 19) { + loadLabel(player, "Aquariuscorrect") + } else { + loadLabel(player, "wrongstar") + } + } + + label("Aquariuscorrect") + npc("That's exactly it!") + player("Yes! Woo hoo!") + npc("That's Aquarius, the water-bearer.") + npc("It seems suitable, then, to award you with water runes!") + // Usually it is given here, but I don't want loopholes. + npc("By Saradomin's earlobes! You must be a friend of the gods indeed.") + npc("Look in your backpack for your reward, in payment for your work.") + exec { player, _ -> + addItemOrDrop(player, Items.WATER_RUNE_555, 25) + addItemOrDrop(player, Items.UNCUT_SAPPHIRE_1623) + finishQuest(player, Quests.OBSERVATORY_QUEST) + } + + label("Aries") + player("Aries!") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == 20) { + loadLabel(player, "Ariescorrect") + } else { + loadLabel(player, "wrongstar") + } + } + + label("Ariescorrect") + npc("That's exactly it!") + player("Yes! Woo hoo!") + npc("That's Aries, the ram.") + npc("A fierce fighter. I'm sure he'll look down on you and improve your Attack for such insight.") + // Usually it is given here, but I don't want loopholes. + npc("By Saradomin's earlobes! You must be a friend of the gods indeed.") + npc("Look in your backpack for your reward, in payment for your work.") + exec { player, _ -> + rewardXP(player, Skills.ATTACK, 875.0) + addItemOrDrop(player, Items.UNCUT_SAPPHIRE_1623) + finishQuest(player, Quests.OBSERVATORY_QUEST) + } + + label("Cancer") + player("Cancer!") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == 21) { + loadLabel(player, "Cancercorrect") + } else { + loadLabel(player, "wrongstar") + } + } + + label("Cancercorrect") + npc("That's exactly it!") + player("Yes! Woo hoo!") + npc("That's Cancer, the crab.") + npc("An armoured creature - I think I shall reward you with an amulet of protection.") + // Usually it is given here, but I don't want loopholes. + npc("By Saradomin's earlobes! You must be a friend of the gods indeed.") + npc("Look in your backpack for your reward, in payment for your work.") + exec { player, _ -> + addItemOrDrop(player, Items.AMULET_OF_DEFENCE_1729) + addItemOrDrop(player, Items.UNCUT_SAPPHIRE_1623) + finishQuest(player, Quests.OBSERVATORY_QUEST) + } + + label("Capricorn") + player("Capricorn!") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == 22) { + loadLabel(player, "Capricorncorrect") + } else { + loadLabel(player, "wrongstar") + } + } + + label("Capricorncorrect") + npc("That's exactly it!") + player("Yes! Woo hoo!") + npc("That's Capricorn, the goat.") + npc("Capricorn will surely reward your insight with an increase to your Strength.") + // Usually it is given here, but I don't want loopholes. + npc("By Saradomin's earlobes! You must be a friend of the gods indeed.") + npc("Look in your backpack for your reward, in payment for your work.") + exec { player, _ -> + rewardXP(player, Skills.STRENGTH, 875.0) + addItemOrDrop(player, Items.UNCUT_SAPPHIRE_1623) + finishQuest(player, Quests.OBSERVATORY_QUEST) + } + + label("Gemini") + player("Gemini!") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == 23) { + loadLabel(player, "Geminicorrect") + } else { + loadLabel(player, "wrongstar") + } + } + + label("Geminicorrect") + npc("That's exactly it!") + player("Yes! Woo hoo!") + npc("That's Gemini, the twins.") + npc("With the double nature of Gemini, I can't offer you anything more suitable than a two-handed weapon.") + // Usually it is given here, but I don't want loopholes. + npc("By Saradomin's earlobes! You must be a friend of the gods indeed.") + npc("Look in your backpack for your reward, in payment for your work.") + exec { player, _ -> + addItemOrDrop(player, Items.BLACK_2H_SWORD_1313) + addItemOrDrop(player, Items.UNCUT_SAPPHIRE_1623) + finishQuest(player, Quests.OBSERVATORY_QUEST) + } + + label("Leo") + player("Leo!") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == 24) { + loadLabel(player, "Leocorrect") + } else { + loadLabel(player, "wrongstar") + } + } + + label("Leocorrect") + npc("That's exactly it!") + player("Yes! Woo hoo!") + npc("That's Leo, the lion.") + npc("I think the majestic power of the lion will raise your Hitpoints.") + // Usually it is given here, but I don't want loopholes. + npc("By Saradomin's earlobes! You must be a friend of the gods indeed.") + npc("Look in your backpack for your reward, in payment for your work.") + exec { player, _ -> + rewardXP(player, Skills.HITPOINTS, 875.0) + addItemOrDrop(player, Items.UNCUT_SAPPHIRE_1623) + finishQuest(player, Quests.OBSERVATORY_QUEST) + } + + label("Libra") + player("Libra!") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == 25) { + loadLabel(player, "Libracorrect") + } else { + loadLabel(player, "wrongstar") + } + } + + label("Libracorrect") + npc("That's exactly it!") + player("Yes! Woo hoo!") + npc("That's Libra, the scales.") + npc("Hmmm, balance, law, order - I shall award you with law runes!") + // Usually it is given here, but I don't want loopholes. + npc("By Saradomin's earlobes! You must be a friend of the gods indeed.") + npc("Look in your backpack for your reward, in payment for your work.") + exec { player, _ -> + addItemOrDrop(player, Items.LAW_RUNE_563, 25) + addItemOrDrop(player, Items.UNCUT_SAPPHIRE_1623) + finishQuest(player, Quests.OBSERVATORY_QUEST) + } + + label("Pisces") + player("Pisces!") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == 26) { + loadLabel(player, "Piscescorrect") + } else { + loadLabel(player, "wrongstar") + } + } + + label("Piscescorrect") + npc("That's exactly it!") + player("Yes! Woo hoo!") + npc("That's Pisces, the fish.") + npc("What's more suitable as a reward than some tuna?") + // Usually it is given here, but I don't want loopholes. + npc("By Saradomin's earlobes! You must be a friend of the gods indeed.") + npc("Look in your backpack for your reward, in payment for your work.") + exec { player, _ -> + addItemOrDrop(player, Items.TUNA_361, 3) + addItemOrDrop(player, Items.UNCUT_SAPPHIRE_1623) + finishQuest(player, Quests.OBSERVATORY_QUEST) + } + + label("Sagittarius") + player("Sagittarius!") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == 27) { + loadLabel(player, "Sagittariuscorrect") + } else { + loadLabel(player, "wrongstar") + } + } + + label("Sagittariuscorrect") + npc("That's exactly it!") + player("Yes! Woo hoo!") + npc("That's Sagittarius, the centaur.") + npc("As you've spotted the archer, I shall reward you with a maple longbow.") + // Usually it is given here, but I don't want loopholes. + npc("By Saradomin's earlobes! You must be a friend of the gods indeed.") + npc("Look in your backpack for your reward, in payment for your work.") + exec { player, _ -> + addItemOrDrop(player, Items.MAPLE_LONGBOW_851) + addItemOrDrop(player, Items.UNCUT_SAPPHIRE_1623) + finishQuest(player, Quests.OBSERVATORY_QUEST) + } + + label("Scorpio") + player("Scorpio!") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == 28) { + loadLabel(player, "Scorpiocorrect") + } else { + loadLabel(player, "wrongstar") + } + } + + label("Scorpiocorrect") + npc("That's exactly it!") + player("Yes! Woo hoo!") + npc("That's Scorpio, the scorpion.") + npc("I think weapon poison would make a suitable reward.") + // Usually it is given here, but I don't want loopholes. + npc("By Saradomin's earlobes! You must be a friend of the gods indeed.") + npc("Look in your backpack for your reward, in payment for your work.") + exec { player, _ -> + addItemOrDrop(player, Items.WEAPON_POISON_187) + addItemOrDrop(player, Items.UNCUT_SAPPHIRE_1623) + finishQuest(player, Quests.OBSERVATORY_QUEST) + } + + label("Taurus") + player("Taurus!") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == 29) { + loadLabel(player, "Tauruscorrect") + } else { + loadLabel(player, "wrongstar") + } + } + + label("Tauruscorrect") + npc("That's exactly it!") + player("Yes! Woo hoo!") + npc("That's Taurus, the bull.") + npc("This Strength potion should be a suitable reward.") + // Usually it is given here, but I don't want loopholes. + npc("By Saradomin's earlobes! You must be a friend of the gods indeed.") + npc("Look in your backpack for your reward, in payment for your work.") + exec { player, _ -> + addItemOrDrop(player, Items.SUPER_STRENGTH1_161) + addItemOrDrop(player, Items.UNCUT_SAPPHIRE_1623) + finishQuest(player, Quests.OBSERVATORY_QUEST) + } + + label("Virgo") + player("Virgo!") + exec { player, _ -> + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == 30) { + loadLabel(player, "Virgocorrect") + } else { + loadLabel(player, "wrongstar") + } + } + + label("Virgocorrect") + npc("That's exactly it!") + player("Yes! Woo hoo!") + npc("That's Virgo, the virtuous.") + npc("Virgo will surely provide you with an increase to Defence.") + // Usually it is given here, but I don't want loopholes. + npc("By Saradomin's earlobes! You must be a friend of the gods indeed.") + npc("Look in your backpack for your reward, in payment for your work.") + exec { player, _ -> + rewardXP(player, Skills.DEFENCE, 875.0) + addItemOrDrop(player, Items.UNCUT_SAPPHIRE_1623) + finishQuest(player, Quests.OBSERVATORY_QUEST) + } + + label("wrongstar") + npc("I'm afraid not. Have another look. Remember, you can check the star charts on the walls for reference.") + + // http://youtu.be/Z5RRnZl2vTg + label("observatoryqueststage100") + npc("Thanks for all your help with the telescope. What can I do for you?") + options( + DialogueOption("needmorehelp", "Do you need any more help with the telescope?", expression = FacialExpression.ASKING), + DialogueOption("mambodunaroona", "Is it true your name is Mambo-duna-roona?", expression = FacialExpression.ASKING) { player, _ -> + return@DialogueOption getAttribute(player, attributeReceivedWine, false) + }, + DialogueOption("nevermind", "Nothing, thanks."), + ) + + label("needmorehelp") + npc("Not right now,") + npc("but the stars may hold a secret for you.") + + label("nevermind") + npc("Okay, no problem. See you again.") + + label("treasuretrails") + npc("Welcome back! How can I help you today?") + player("Can you teach me to solve treasure trail clues?") + npc("Ah, I get asked about treasure trails all the time! Listen carefully and I shall tell you what I know...") + npc("Lots of clues have degrees and minutes written on them. These are the coordinates of the place where the treasure is buried.") + npc("You have to walk to the correct spot, so that your coordinates are exactly the same as the values written on the clue scroll.") + npc("To do this, you must use a sextant, a watch and a chart to find your own coordinates.") + npc("Once you know the coordinates of the place where you are, you know which way you have to walk to get to the place where the treasure is!") + player("Riiight. So where do I get those items from?") + npc("I think Murphy, the owner of the fishing trawler moored south of Ardougne, might be able to spare you a sextant. Then the nearest Clock Tower is south of Ardougne - you could probably get a watch there. I've") + npc("got plenty of charts myself, here have one.") + + label("mambodunaroona") + npc(FacialExpression.AMAZED, "How do you know tha-") + npc(FacialExpression.SUSPICIOUS, "I mean, of course not, what a silly idea.") + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryQuest.kt b/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryQuest.kt new file mode 100644 index 000000000..ce6ba2513 --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryQuest.kt @@ -0,0 +1,159 @@ +package content.region.kandarin.quest.observatoryquest + +import content.data.Quests +import core.api.* +import core.api.setVarp +import core.game.node.entity.player.Player +import core.game.node.entity.player.link.quest.Quest +import core.game.node.entity.skill.Skills +import core.plugin.Initializable +import org.rs09.consts.Items + +// http://youtu.be/TWIkCRRea8A The initial quest log. +// http://youtu.be/onHNm9Z5L-o The best full quest log. +// http://youtu.be/cq-jGTXXHuE +// http://youtu.be/ekYr30h43ag Scorpius. +/** + * Observatory Quest + */ +@Initializable +class ObservatoryQuest : Quest(Quests.OBSERVATORY_QUEST, 96, 95, 2,112, 0, 1, 7) { + + companion object { + val questName = Quests.OBSERVATORY_QUEST + const val observatoryVarp = 112 + const val goblinStoveVarbit = 3837 + const val telescopeVarbit = 3838 + + const val starChartsInterface = 104 + const val telescopeInterface = 552 + const val dungeonWarning = 560 + + const val attributeKilledGuard = "/save:quest:observatoryquest-killedguard" + const val attributeUnlockedGate = "/save:quest:observatoryquest-unlockedgate" + const val attributeTelescopeStar = "/save:quest:observatoryquest-telescopestar" // NULL - not seen telescope, 19 - 30 one of the random star patterns + const val attributeReceivedWine = "/save:quest:observatoryquest-receivedwine" + const val attributeReceivedMould = "/save:quest:observatoryquest-receivedmould" + const val attributeRandomChest = "/save:quest:observatoryquest-randomchest" + const val attributeFinishedCutscene = "/save:quest:observatoryquest-finishedcutscene" + + } + override fun drawJournal(player: Player, stage: Int) { + super.drawJournal(player, stage) + var line = 12 + var stage = getStage(player) + + var started = getQuestStage(player, questName) > 0 + + if (!started) { + line(player, "I can start this quest by speaking to the !!professor?? in the", line++, false) + line(player, "!!Observatory reception, south-west of Ardougne.??", line++, false) + } else { + line(player, "I can start this quest by speaking to the professor in the", line++, true) + line(player, "Observatory reception, south-west of Ardougne.", line++, true) + + if (stage >= 5) { + line(player, "I should take the letter the Examiner has given me to the", line++, true) + line(player, "Curator of Varrock Museum, for his approval.", line++, true) + line++ + } else if (stage >= 1) { + line(player, "Seems the observatory telescope needs repairing, due to", line++) + line(player, "the nearby goblins. The !!professor?? wants me to help by", line++) + line(player, "getting the following, with the help of his !!assistant??:", line++) + line++ + } + if (stage >= 2) { + line(player, "!!3 plain wooden planks??", line++, true) + } else if (stage >= 1) { + line(player, "!!3 plain wooden planks??", line++, inInventory(player, Items.PLANK_960, 3)) + } + if (stage >= 3) { + line(player, "!!1 bronze bar??", line++, true) + } else if (stage >= 2) { + line(player, "!!1 bronze bar??", line++, inInventory(player, Items.BRONZE_BAR_2349)) + } + if (stage >= 4) { + line(player, "!!1 molten glass??", line++, true) + } else if (stage >= 3) { + line(player, "!!1 molten glass??", line++, inInventory(player, Items.MOLTEN_GLASS_1775)) + } + if (stage >= 5) { + line(player, "!!1 lens mould??", line++, true) + } else if (stage >= 4) { + line(player, "!!1 lens mould??", line++) + } + + if (stage >= 6) { + line(player, "The professor was pleased to have all the pieces needed", line++, true) + line(player, "to fix the telescope. Apparently, the professor's last", line++, true) + line(player, "attempt at Crafting ended in disaster. So, he wants me to", line++, true) + line(player, "create the lens by using the molten glass with the mould.", line++, true) + line(player, "Fine by me!", line++, true) + } else if (stage >= 5) { + line(player, "The !!professor?? was pleased to have all the pieces needed", line++) + line(player, "to fix the telescope. Apparently, the professor's last", line++) + line(player, "attempt at Crafting ended in disaster. So, he wants me to", line++) + line(player, "create the !!lens?? by using the !!molten glass?? with the !!mould??.", line++) + line(player, "Fine by me!", line++) + } + + if (stage >= 100) { + line(player, "The professor has gone ahead to the Observatory. He", line++, true) + line(player, "wants me to meet him there by travelling through the", line++, true) + line(player, "dungeon below it.", line++, true) + } else if (stage >= 6) { + line(player, "The !!professor?? has gone ahead to the !!Observatory??. He", line++) + line(player, "wants me to meet him there by travelling through the", line++) + line(player, "!!dungeon?? below it.", line++) + } + if (stage >= 100) { + line++ + line(player,"QUEST COMPLETE!", line) + } + } + } + + override fun finish(player: Player) { + var ln = 10 + super.finish(player) + player.packetDispatch.sendString("You have completed the Observatory Quest!", 277, 4) + // This image is special since it isn't an item, but a standalone model. + player.packetDispatch.sendModelOnInterface(1174, 277, 5, 0) // Scenery 2210, Model 1174 + player.packetDispatch.sendAngleOnInterface(277, 5, 2040, 0, 1836) + + drawReward(player, "2 Quest Points", ln++) + drawReward(player, "2,250 Crafting XP", ln++) + drawReward(player, "A payment depending on", ln++) + drawReward(player, "which constellation you", ln++) + drawReward(player, "observed", ln++) + + rewardXP(player, Skills.CRAFTING, 2250.0) + } + + override fun updateVarps(player: Player) { + setVarp(player, observatoryVarp, getQuestStage(player, questName), true) + if(getQuestStage(player, questName) >= 6) { + setVarbit(player, telescopeVarbit, 1, true) + } else { + setVarbit(player, telescopeVarbit, 0, true) + } + if(getQuestStage(player, questName) >= 7) { + setVarp(player, observatoryVarp, 7, true) + } + } + + override fun reset(player : Player) { + removeAttribute(player, attributeKilledGuard) + removeAttribute(player, attributeUnlockedGate) + removeAttribute(player, attributeTelescopeStar) + removeAttribute(player, attributeReceivedWine) + removeAttribute(player, attributeReceivedMould) + removeAttribute(player, attributeRandomChest) + removeAttribute(player, attributeFinishedCutscene) + setVarbit(player, 3837, 0, true) + } + + override fun newInstance(`object`: Any?): Quest { + return this + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryQuestInterfaces.kt b/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryQuestInterfaces.kt new file mode 100644 index 000000000..a6627b98a --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryQuestInterfaces.kt @@ -0,0 +1,59 @@ +package content.region.kandarin.quest.observatoryquest + +import core.api.* +import core.game.interaction.InterfaceListener +import core.game.world.map.Location + +class ObservatoryQuestInterfaces : InterfaceListener { + + companion object { + + val buttonToNameMapping = mapOf( + 19 to "Aquarius", + 20 to "Aries", + 21 to "Cancer", + 22 to "Capricorn", + 23 to "Gemini", + 24 to "Leo", + 25 to "Libra", + 26 to "Pisces", + 27 to "Sagittarius", + 28 to "Scorpio", + 29 to "Taurus", + 30 to "Virgo", + ) + + val buttonToStarObjectMapping = mapOf( + 19 to 27064, + 20 to 27066, + 21 to 27067, + 22 to 27061, + 23 to 27068, + 24 to 27058, + 25 to 27057, + 26 to 27062, + 27 to 27056, + 28 to 27055, + 29 to 27059, + 30 to 27060, + ) + } + + override fun defineInterfaceListeners() { + on(ObservatoryQuest.starChartsInterface) { player, component, opcode, buttonID, slot, itemID -> + if(buttonToStarObjectMapping.contains(buttonID)){ + // 55 57 + player.packetDispatch.sendModelOnInterface(buttonToStarObjectMapping[buttonID]!!, ObservatoryQuest.starChartsInterface, 55, 0) + setInterfaceText(player, buttonToNameMapping[buttonID]!!, ObservatoryQuest.starChartsInterface, 57) + } + return@on true + } + on(ObservatoryQuest.dungeonWarning) { player, _, _, buttonID, _, _ -> + when (buttonID) { + 17 -> teleport(player, Location(2355, 9394)).also { closeInterface(player) } + 18 -> closeInterface(player) + } + return@on true + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryQuestListeners.kt b/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryQuestListeners.kt new file mode 100644 index 000000000..c718f9a10 --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/observatoryquest/ObservatoryQuestListeners.kt @@ -0,0 +1,294 @@ +package content.region.kandarin.quest.observatoryquest + +import content.data.Quests +import content.region.kandarin.quest.observatoryquest.ObservatoryQuest.Companion.attributeFinishedCutscene +import content.region.kandarin.quest.observatoryquest.ObservatoryQuest.Companion.attributeRandomChest +import core.ServerConstants +import core.api.* +import core.game.dialogue.DialogueFile +import core.game.dialogue.FacialExpression +import core.game.global.action.DoorActionHandler +import core.game.interaction.InteractionListener +import core.game.interaction.QueueStrength +import core.game.node.entity.npc.NPC +import core.game.node.entity.skill.Skills +import core.game.world.map.Location +import core.tools.END_DIALOGUE +import core.tools.RandomFunction +import org.rs09.consts.Items +import org.rs09.consts.NPCs +import org.rs09.consts.Scenery + +class ObservatoryQuestListeners : InteractionListener { + + companion object { + val chestMap = mapOf( + Scenery.CHEST_2191 to Scenery.CHEST_2194, + Scenery.CHEST_25385 to Scenery.CHEST_25386, + Scenery.CHEST_25387 to Scenery.CHEST_25388, + Scenery.CHEST_25389 to Scenery.CHEST_25390, + Scenery.CHEST_25391 to Scenery.CHEST_25392 + ) + + val reverseChestMap = chestMap.map { (k, v) -> v to k }.toMap() + + /* Comments show only the default order BEFORE randomization. A randomized + number (0-10) is calculated the first time the player goes down the stairs, + and is then added to the map value modulo 11, rotating the map. + */ + + val chestLocations = mapOf( + Location(2333, 9405) to 0, // key + Location(2312, 9400) to 1, // spider + Location(2310, 9374) to 2, // spider + Location(2348, 9383) to 3, // empty + Location(2356, 9380) to 4, // spider + Location(2359, 9376) to 5, // empty + Location(2360, 9366) to 6, // empty + Location(2351, 9361) to 7, // spider + Location(2364, 9355) to 8, // antipoison + Location(2335, 9374) to 9, // spider + Location(2326, 9360) to 10) // empty + } + + override fun defineListeners() { + + on(Scenery.SIGNPOST_25397, SCENERY, "read") { player, _ -> + openDialogue(player, object : DialogueFile(){ + override fun handle(componentID: Int, buttonID: Int) { + val servername = ServerConstants.SERVER_NAME + when(stage){ + 0 -> sendDialogueLines(player, "~ The Observatory ~", "Step ahead to the reception if you wish to explore $servername's most", "magnificent invention.").also { + stage++ + } + 1 -> player(FacialExpression.NEUTRAL, "Magnificent invention? I've seen some pretty magnificent", "things in my time. It'll have to be pretty impressive.").also { stage = END_DIALOGUE } + } + } + }) + return@on true + } + + on(Scenery.ORRERY_25401, SCENERY, "view") { player, _ -> + sendChat(player, "Oooooh, bizarre!") + return@on true + } + + on(Scenery.STAIRS_25432, SCENERY, "climb-down") { player, _ -> + openInterface(player, 560) + if (getAttribute(player, attributeRandomChest, null) == null) { + setAttribute(player, attributeRandomChest, RandomFunction.random(11)) + } + return@on true + } + // http://youtu.be/Kg84MYXgo-M -> You can always go to the observatory, no matter which stage. + on(Scenery.STAIRS_25429, SCENERY, "climb up") { player, node -> + if (node.location == Location(2335, 9351)) { + if (!getAttribute(player, attributeFinishedCutscene, false) && getQuestStage(player, Quests.OBSERVATORY_QUEST) == 6) { + ObservatoryCutscene(player).start() + } + else teleport(player, Location(2439, 3164)) + } else { + teleport(player, Location(2457, 3186)) + } + return@on true + } + + on(Scenery.STAIRS_25434, SCENERY, "climb-down") { player, _ -> + teleport(player, Location(2335, 9350)) + return@on true + } + + on(Scenery.STAIRS_25431, SCENERY, "climb-up") { player, _ -> + teleport(player, Location(2443, 3160, 1)) + return@on true + } + + on(Scenery.STAIRS_25437, SCENERY, "climb-down") { player, _ -> + teleport(player, Location(2444, 3162, 0)) + return@on true + } + + // All chests + on(chestMap.keys.toIntArray(), SCENERY, "open") { player, node -> + animate(player, 536) + sendMessage(player, "You open the chest.") + chestMap[node.id]?.let { replaceScenery(node as core.game.node.scenery.Scenery, it, 240) } + return@on true + } + on(chestMap.values.toIntArray(), SCENERY, "close") { player, node -> + animate(player, 535) + reverseChestMap[node.id]?.let { replaceScenery(node as core.game.node.scenery.Scenery, it, -1) } + return@on true + } + + on(chestMap.values.toIntArray(), SCENERY, "search") { player, node -> + val chest = chestLocations[node.location]?.plus(getAttribute(player, attributeRandomChest, 0))?.mod(11) + when (chest) { + 0 -> { + if (inInventory(player, Items.GOBLIN_KITCHEN_KEY_601) || getQuestStage(player, Quests.OBSERVATORY_QUEST) != 4 || getAttribute(player, ObservatoryQuest.attributeUnlockedGate, false)) { + sendMessage(player, "You search the chest.") + sendMessage(player, "The chest is empty.") + } else { + lock(player,2) + queueScript(player, 2, QueueStrength.STRONG) { + if (inInventory(player, Items.GOBLIN_KITCHEN_KEY_601)) { + return@queueScript stopExecuting(player) + } + addItemOrDrop(player, Items.GOBLIN_KITCHEN_KEY_601) + sendItemDialogue(player, Items.GOBLIN_KITCHEN_KEY_601,"You find a kitchen key.") + return@queueScript stopExecuting(player) + } + } + animate(player, 537) + return@on true + } + 1, 2, 4, 7, 9 -> { + sendMessage(player, "You search the chest.") + if (findLocalNPC(player, NPCs.POISON_SPIDER_1009) != null) { + sendMessage(player, "The chest is empty.") + animate(player, 537) + return@on true + } + sendMessage(player, "The chest contains a poisonous spider.") + val npc = NPC(NPCs.POISON_SPIDER_1009) + npc.location = player.location + npc.init() + npc.isRespawn = false + npc.moveStep() + npc.face(player) + animate(player, 537) + return@on true + } + 8 -> { + sendMessage(player, "You search the chest.") + lock(player,2) + queueScript(player, 2, QueueStrength.STRONG) { + addItemOrDrop(player, Items.ANTIPOISON1_179) + sendMessage(player,"This chest contains some antipoison.") + return@queueScript stopExecuting(player) + } + animate(player, 537) + return@on true + } + else -> { + sendMessage(player, "You search the chest.") + sendMessage(player, "The chest is empty.") + animate(player, 537) + return@on true + } + } + } + + on(intArrayOf(Scenery.KITCHEN_GATE_2199, Scenery.KITCHEN_GATE_2200), SCENERY, "open") { player, node -> + if (getAttribute(player, ObservatoryQuest.attributeUnlockedGate, false)) { + DoorActionHandler.handleAutowalkDoor(player, node.asScenery()) + } else if (getAttribute(player, ObservatoryQuest.attributeKilledGuard, false)) { + if (removeItem(player, Items.GOBLIN_KITCHEN_KEY_601)) { + sendMessage(player, "The gate unlocks.") + sendMessage(player, "The key is useless now. You discard it.") + setAttribute(player, ObservatoryQuest.attributeUnlockedGate, true) + sendPlayerDialogue(player, "I had better be quick, there may be more guards about.") + DoorActionHandler.handleAutowalkDoor(player, node.asScenery()) + } else { + // http://youtu.be/ZkUF-0eonls + sendMessage(player, "The gate is locked.") + } + } else if (getQuestStage(player, Quests.OBSERVATORY_QUEST) >= 4){ + sendMessage(player, "If you open the gate, the guard will hear you. You need to get rid of him.") + } else sendMessage(player, "The gate is locked.") + return@on true + } + + on(NPCs.SLEEPING_GUARD_6122, NPC, "prod") { player, node -> + if (getQuestStage(player, Quests.OBSERVATORY_QUEST) < 4) { + return@on true + } + sendChat(node as NPC, "Oi, how dare you wake me up!") + transformNpc(node, NPCs.GOBLIN_GUARD_489, 400) + node.attack(player) + return@on true + } + + on(Scenery.GOBLIN_STOVE_25440, SCENERY, "inspect") { player, _ -> + openDialogue(player, object : DialogueFile(){ + override fun handle(componentID: Int, buttonID: Int) { + when(stage){ + 0 -> sendDialogueLines(player, "The goblins appear to have been using the lens mould to cook their", "stew!").also { stage++ } + 1 -> sendDialogueLines(player, "You shake out its contents and take it with you.").also { animate(player, 537); stage++ } + 2 -> end().also { + sendChat(player, "Euuuw, that smells awful!") + setVarbit(player, ObservatoryQuest.goblinStoveVarbit ,1) + addItemOrDrop(player, Items.LENS_MOULD_602) + } + } + } + }) + return@on true + } + + on(Scenery.GOBLIN_STOVE_25441, SCENERY, "inspect") { player, _ -> + if (!inInventory(player, Items.LENS_MOULD_602)) addItemOrDrop(player, Items.LENS_MOULD_602) + return@on true + } + + onUseWith(ITEM, Items.LENS_MOULD_602, Items.MOLTEN_GLASS_1775) { player, _, with -> + if (getStatLevel(player, Skills.CRAFTING) < 10) { + sendMessage(player, "You need a crafting level of 10 to do this.") + return@onUseWith true + } + + sendMessage(player, "You pour the molten glass into the mould.") + sendMessage(player, "You clasp it together.") + sendItemDialogue(player, Items.OBSERVATORY_LENS_603, "It has produced a small, convex glass disc.") + if (removeItem(player, with)) { + addItemOrDrop(player, Items.OBSERVATORY_LENS_603) + } + return@onUseWith true + } + + on(intArrayOf(Scenery.STAR_CHART_25578, Scenery.STAR_CHART_25579, Scenery.STAR_CHART_25580, Scenery.STAR_CHART_25581, Scenery.STAR_CHART_25582, Scenery.STAR_CHART_25583), SCENERY, "look-at") { player, _ -> + openInterface(player, ObservatoryQuest.starChartsInterface) + return@on true + } + + on(intArrayOf(Scenery.TELESCOPE_25438, Scenery.TELESCOPE_25439), SCENERY, "view") { player, _ -> + if (getQuestStage(player, Quests.OBSERVATORY_QUEST) >= 100) { + openDialogue(player, TelescopeDialogue(), NPC(NPCs.OBSERVATORY_PROFESSOR_488)) + } + else if (getQuestStage(player, Quests.OBSERVATORY_QUEST) >= 6) { + if (getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) == null) { + setAttribute(player, ObservatoryQuest.attributeTelescopeStar, (19..30).random()) + } + val randomStar = getAttribute(player, ObservatoryQuest.attributeTelescopeStar, null) + openInterface(player, ObservatoryQuest.telescopeInterface) + player.packetDispatch.sendModelOnInterface(ObservatoryQuestInterfaces.buttonToStarObjectMapping[randomStar]!!, ObservatoryQuest.telescopeInterface, 7, 0) + } else { + sendMessage(player, "The telescope is broken.") + } + return@on true + } + + on(intArrayOf(Scenery.DOOR_25526, Scenery.DOOR_25527), SCENERY, "open") { player, _ -> + sendMessage(player, "The door is locked.") + return@on true + } + + on(Scenery.GRAVE_OF_SCORPIUS_2211, SCENERY, "read") { player, _ -> + sendMessage(player, "Here lies Scorpius: Only those who have seen beyond the stars may seek his counsel.") + return@on true + } + } +} + +class TelescopeDialogue : DialogueFile() { + override fun handle(componentID: Int, buttonID: Int) { + when(stage){ + 0 -> npcl(FacialExpression.ASKING, "What do you see now?").also { stage++ } + 1 -> playerl("I can see a constellation through the telescope. It looks like Scorpio.").also { stage++ } + 2 -> npcl("Scorpio? Interesting. How very fitting.").also { stage++ } + 3 -> playerl(FacialExpression.ASKING, " What do you mean?").also { stage++ } + 4 -> npcl("Scorpius, the founder of all we know relating to astronomy. There's a book about him in the reception. Perhaps you should check it out, you might learn something.").also { stage++ } + 5 -> playerl("Then perhaps I shall.").also { stage = END_DIALOGUE } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/kandarin/quest/observatoryquest/PoisonSpiderBehavior.kt b/Server/src/main/content/region/kandarin/quest/observatoryquest/PoisonSpiderBehavior.kt new file mode 100644 index 000000000..dc4f0b74d --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/observatoryquest/PoisonSpiderBehavior.kt @@ -0,0 +1,20 @@ +package content.region.kandarin.quest.observatoryquest + +import core.api.getAttribute +import core.api.setAttribute +import core.game.node.entity.npc.NPC +import core.game.node.entity.npc.NPCBehavior +import core.game.world.GameWorld +import org.rs09.consts.NPCs + +class PoisonSpiderBehavior : NPCBehavior(NPCs.POISON_SPIDER_1009) { + override fun onCreation(self: NPC) { + setAttribute(self, "despawn-time", GameWorld.ticks + 100) + } + + override fun tick(self: NPC): Boolean { + if (getAttribute(self, "despawn-time", 0) <= GameWorld.ticks && !self.inCombat()) + self.clear() + return true + } +} diff --git a/Server/src/main/content/region/kandarin/quest/observatoryquest/SpiritOfScorpiusDialogue.kt b/Server/src/main/content/region/kandarin/quest/observatoryquest/SpiritOfScorpiusDialogue.kt new file mode 100644 index 000000000..6d8fd5815 --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/observatoryquest/SpiritOfScorpiusDialogue.kt @@ -0,0 +1,110 @@ +package content.region.kandarin.quest.observatoryquest + +import content.data.Quests +import content.region.kandarin.quest.observatoryquest.ObservatoryQuest.Companion.attributeReceivedMould +import core.api.* +import core.game.dialogue.* +import core.game.node.entity.player.Player +import core.game.node.item.Item +import core.plugin.Initializable +import org.rs09.consts.Items +import org.rs09.consts.NPCs + +@Initializable +class SpiritOfScorpiusDialogue (player: Player? = null) : DialoguePlugin(player) { + override fun newInstance(player: Player): DialoguePlugin { + return SpiritOfScorpiusDialogue(player) + } + override fun handle(interfaceId: Int, buttonId: Int): Boolean { + openDialogue(player, SpiritOfScorpiusDialogueFile(), npc) + return false + } + override fun getIds(): IntArray { + return intArrayOf(NPCs.SPIRIT_OF_SCORPIUS_492) + } +} + +class SpiritOfScorpiusDialogueFile : DialogueLabeller() { + override fun addConversation() { + assignToIds(NPCs.SPIRIT_OF_SCORPIUS_492) + + exec { player, npc -> + if (getQuestStage(player, Quests.OBSERVATORY_QUEST) == 100) { + if (!inEquipment(player, Items.GHOSTSPEAK_AMULET_552)) loadLabel(player, "noghostspeak") + else loadLabel(player, "observatoryqueststage100") + } else loadLabel(player, "observatoryquestincomplete") + } + + label("observatoryquestincomplete") + line("They seem to be ignoring you.") + + label("noghostspeak") + line("This powerful spirit seems capable of speaking to you", "even though you are not wearing an Amulet of Ghostspeak.") + exec { player, _ -> + loadLabel(player, "observatoryqueststage100") + } + + label("observatoryqueststage100") + npc("Who treads upon my grave?") + options( + DialogueOption("wisdom", "I seek your wisdom.") { player, _ -> + return@DialogueOption !getAttribute(player, attributeReceivedMould, false) + }, + DialogueOption("anothermould", "I need another unholy symbol mould.", "I need another mould for the unholy symbol.") { player, _ -> + return@DialogueOption getAttribute(player, attributeReceivedMould, false) + }, + DialogueOption("blessing", "I have come to seek your blessing."), + DialogueOption("killyou", "I have come to kill you.") + ) + + label("wisdom") + npc("Indeed, I feel you have beheld the far places in the heavens. My Lord instructs me to help you.") + item(Item(Items.UNHOLY_MOULD_1594), "An unholy mould appears in your inventory.") + exec { player, _ -> + addItemOrDrop(player, Items.UNHOLY_MOULD_1594) + setAttribute(player, attributeReceivedMould, true) + } + npc("Here is a mould to make a token for our Lord; a mould for the unholy symbol of Zamorak. Return to me when you desire my blessing.") + + label("anothermould") + exec { player, _ -> + if (inInventory(player, Items.UNHOLY_MOULD_1594)) { + loadLabel(player, "hasmould") + } + else { + addItemOrDrop(player, Items.UNHOLY_MOULD_1594) + loadLabel(player, "lostmould") + } + } + + label("hasmould") + npc("One you already have, another is not needed. Leave me be.") + + label("lostmould") + npc("A lost object is easy to replace. The loss of the affections of our Lord is impossible to forgive.") + + label("blessing") + exec { player, _ -> + if (inInventory(player, Items.UNPOWERED_SYMBOL_1722)) { + loadLabel(player, "canbless") + } + else loadLabel(player, "cannotbless") + } + + label("canbless") + npc("I see you have the unholy symbol of our Lord. I will bless it for you.") + line("The ghost mutters in a strange voice.", "The unholy symbol throbs with power.") + exec { player, _ -> + removeItem(player, Items.UNPOWERED_SYMBOL_1722) + addItemOrDrop(player, Items.UNHOLY_SYMBOL_1724) + } + npc("The symbol of our Lord has been blessed with power! My master calls.") + + + label("cannotbless") + npc("No blessing will be given to those who have no symbol of our Lord's love.") + + label("killyou") + npc("The might of mortals, to me, is as the dust to the sea.") + } +} \ No newline at end of file diff --git a/Server/src/main/core/game/activity/Cutscene.kt b/Server/src/main/core/game/activity/Cutscene.kt index 5cf9584ec..a75dcb4b5 100644 --- a/Server/src/main/core/game/activity/Cutscene.kt +++ b/Server/src/main/core/game/activity/Cutscene.kt @@ -166,6 +166,18 @@ abstract class Cutscene(val player: Player) { player.dialogueInterpreter.addAction {_,_ -> onContinue.invoke()} } + /** + * Sends a non-NPC dialogue to the player, which updates the cutscene stage by default when continued + * @param message the message to send + * @param onContinue (optional) a method that runs when the dialogue is "continued." Increments the cutscene stage by default. + */ + fun dialogueLinesUpdate(vararg message: String, onContinue: () -> Unit = {incrementStage()}) + { + logCutscene("Sending standard dialogue lines update.") + sendDialogueLines(player, *message) + player.dialogueInterpreter.addAction {_,_ -> onContinue.invoke()} + } + /** * Sends a player dialogue, which updates the cutscene stage by default when continued * @param expression the FacialExpression to use From 846d55365a535d48fa536727741e52ef45736254 Mon Sep 17 00:00:00 2001 From: oftheshire Date: Thu, 27 Nov 2025 12:01:33 +0000 Subject: [PATCH 109/117] Fixed Wyson's dialogue so that he properly respects the choice of turning in mole parts Corrected dialogue to authentic source --- .../dialogue/WysonTheGardenerDialogue.kt | 213 ++++++++++-------- 1 file changed, 125 insertions(+), 88 deletions(-) diff --git a/Server/src/main/content/region/asgarnia/falador/dialogue/WysonTheGardenerDialogue.kt b/Server/src/main/content/region/asgarnia/falador/dialogue/WysonTheGardenerDialogue.kt index 1d2592ad8..b26643bba 100644 --- a/Server/src/main/content/region/asgarnia/falador/dialogue/WysonTheGardenerDialogue.kt +++ b/Server/src/main/content/region/asgarnia/falador/dialogue/WysonTheGardenerDialogue.kt @@ -3,12 +3,12 @@ package content.region.asgarnia.falador.dialogue import content.data.tables.BirdNest import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player -import core.game.node.entity.player.link.diary.DiaryType -import core.game.node.item.GroundItemManager import core.game.node.item.Item import core.plugin.Initializable import org.rs09.consts.Items -import core.game.diary.DiaryLevel +import core.api.* +import core.game.dialogue.DialoguePlugin +import core.game.node.entity.player.link.diary.* /** * Represents the Wyson the gardener dialogue. @@ -16,46 +16,53 @@ import core.game.diary.DiaryLevel * @version 1.0 */ @Initializable -class WysonTheGardenerDialogue : core.game.dialogue.DialoguePlugin { - /** - * If its a bird nest reward. - */ - private var birdNest = false - - /** - * Constructs a new `WysonTheGardenerDialogue` `Object`. - */ - constructor() { - /** - * empty. - */ - } +class WysonTheGardenerDialogue(player: Player? = null) : DialoguePlugin(player) { /** * Constructs a new `WysonTheGardenerDialogue` `Object`. + * Mole part dialogue source: https://www.youtube.com/watch?v=Dw-P9T7EhZk and https://www.youtube.com/watch?v=krZiIRupKbs * @param player the player. */ - constructor(player: Player?) : super(player) {} + //constructor(player: Player?) : super(player) {} - override fun newInstance(player: Player): core.game.dialogue.DialoguePlugin { + override fun newInstance(player: Player): DialoguePlugin { return WysonTheGardenerDialogue(player) } + /** + * Choose greeting. Either you have mole parts or just the normal greeting. + */ override fun open(vararg args: Any): Boolean { npc = args[0] as NPC - birdNest = player.inventory.containsItem(MOLE_CLAW) || player.inventory.containsItem(MOLE_SKIN) - if (birdNest) { - npc("If I'm not mistaken, you've got some mole bits there!", "I'll trade 'em for bird nest if ye likes.") + if (inInventory(player, Items.MOLE_CLAW_7416, 1) && inInventory(player, Items.MOLE_SKIN_7418, 1)) { + npc("If I'm not mistaken, you've got some claws and skin", " from a big mole there! I'll trade 'em for bird nests if ye", "likes. Or was ye wantin' some woad leaves instead?") + stage = 102 + return true + } + if (inInventory(player, Items.MOLE_SKIN_7418, 1)) { + npc("If I'm not mistaken, you've got some skin from a big", "mole there! I'll trade it for bird nests if ye likes. Or", "was ye wantin' some woad leaves instead?") stage = 100 return true } + if (inInventory(player, Items.MOLE_CLAW_7416, 1)) { + npc("If I'm not mistaken, you've got some claws from a big", "mole there! I'll trade it for bird nests if ye likes. Or", "was ye wantin' some woad leaves instead?") + stage = 101 + return true + } npc("I'm the head gardener around here.", "If you're looking for woad leaves, or if you need help", "with owt, I'm yer man.") stage = 0 return true } + /** + * Dialogue. + */ override fun handle(interfaceId: Int, buttonId: Int): Boolean { when (stage) { + + /** + * Dialogue options: woad leaves. + */ 0 -> { options("Yes please, I need woad leaves.", "Sorry, but I'm not interested.") stage = 1 @@ -105,15 +112,15 @@ class WysonTheGardenerDialogue : core.game.dialogue.DialoguePlugin { npc("Mmmm... ok, that sounds fair.") stage = 131 } - 131 -> if (player.inventory.contains(995, 15)) { - player.inventory.remove(COINS[0]) - player.inventory.add(WOAD_LEAF) + 131 -> if (removeItem(player,Item(Items.COINS_995, 15) ,Container.INVENTORY)) { + addItemOrDrop(player, Items.WOAD_LEAF_1793, 1) + player("Thanks.") - player.packetDispatch.sendMessage("You buy a woad leaf from Wyson.") + sendMessage(player, "You buy a woad leaf from Wyson.") stage = 132 } else { end() - player.packetDispatch.sendMessage("You need 15 cold coins to buy a woad leaf.") + sendMessage(player, "You need 15 gold coins to buy a woad leaf.") } 132 -> { npc("I'll be around if you have any more gardening needs.") @@ -124,32 +131,67 @@ class WysonTheGardenerDialogue : core.game.dialogue.DialoguePlugin { npc("Thanks for being generous", "here's an extra woad leaf.") stage = 141 } - 141 -> if (player.inventory.contains(995, 20)) { - player.inventory.remove(COINS[1]) - var i = 0 - while (i < 2) { - player.inventory.add(WOAD_LEAF, player) - i++ - } + 141 -> if (removeItem(player,Item(Items.COINS_995, 20) ,Container.INVENTORY)) { + addItemOrDrop(player, Items.WOAD_LEAF_1793, 2) player("Thanks.") - player.packetDispatch.sendMessage("You buy two woad leaves from Wyson.") + sendMessage(player, "You buy two woad leaves from Wyson.") stage = 132 } else { end() - player.packetDispatch.sendMessage("You need 15 cold coins to buy a woad leaf.") + sendMessage(player, "You need 20 gold coins to buy a woad leaf.") } 200 -> { npc("Fair enough.") stage = 201 } 201 -> end() + + /** + * Dialogue options: mole parts. + */ 100 -> { - options("Yes, I will trade the mole claws.", "Okay, I will trade the mole skin.", "I'd like to trade both.", "No, thanks.") + options("Ok, I will trade the mole skin.", "Yes please, I need woad leaves.", "Sorry, but I'm not interested.") stage = 900 } + 101 -> { + options("Yeah, I will trade the mole claws.", "Yes please, I need woad leaves.", "Sorry, but I'm not interested.") + stage = 901 + } + 102 -> { + options("Yeah, I will trade the mole claws.", "Okay, I will trade the mole skin.", "Alright, I'll trade the claws and skin.", "Yes please, I need woad leaves.", "Sorry, but I'm not interested.") + stage = 902 + } 900 -> when (buttonId) { 1 -> { - player("Yes, I will trade the mole claws.") + player("Ok, I will trade the mole skin.") + stage = 920 + } + 2 -> { + player("Yes please, I need woad leaves.") + stage = 10 + } + 3 -> { + player("Sorry, but I'm not interested.") + stage = 200 + } + } + 901 -> when (buttonId) { + 1 -> { + player("Yeah, I will trade the mole claws.") + stage = 910 + } + 2 -> { + player("Yes please, I need woad leaves.") + stage = 10 + } + 3 -> { + player("Sorry, but I'm not interested.") + stage = 200 + } + } + 902 -> when (buttonId) { + 1 -> { + player("Yeah, I will trade the mole claws.") stage = 910 } 2 -> { @@ -157,37 +199,48 @@ class WysonTheGardenerDialogue : core.game.dialogue.DialoguePlugin { stage = 920 } 3 -> { - player("I'd like to trade both.") + player("Alright, I'll trade the claws and skin.") stage = 930 } 4 -> { - player("No, thanks.") - stage = 999 + player("Yes please, I need woad leaves.") + stage = 10 + } + 5 -> { + player("Sorry, but I'm not interested.") + stage = 200 } } 910 -> { - if (!player.inventory.containsItem(MOLE_CLAW)) { + if (!inInventory(player, Items.MOLE_CLAW_7416, 1)) { player("Sorry, I don't have any mole claws.") stage = 999 + } else { + addClawRewards() + npc("Pleasure doing business with ya.") + stage = 999 } - end() - addRewards() } 920 -> { - if (!player.inventory.containsItem(MOLE_SKIN)) { + if (!inInventory(player, Items.MOLE_SKIN_7418, 1)) { player("Sorry, I don't have any mole skins.") stage = 999 + } else { + addSkinRewards() + npc("Pleasure doing business with ya.") + stage = 999 } - end() - addRewards() } 930 -> { - if (!player.inventory.containsItem(MOLE_CLAW) && !player.inventory.containsItem(MOLE_SKIN)) { + if (!inInventory(player, Items.MOLE_CLAW_7416, 1) || !inInventory(player, Items.MOLE_SKIN_7418, 1)) { player("Sorry, I don't have any.") stage = 999 + } else { + addClawRewards() + addSkinRewards() + npc("Pleasure doing business with ya.") + stage = 999 } - addRewards() - end() } 999 -> end() } @@ -196,26 +249,31 @@ class WysonTheGardenerDialogue : core.game.dialogue.DialoguePlugin { /** * Adds nests. - * @param nestAmount the amount. */ - private fun addRewards() { - val moleClaws = player.inventory.getAmount(Items.MOLE_CLAW_7416) - val moleSkin = player.inventory.getAmount(Items.MOLE_SKIN_7418) - val nestAmount = moleClaws + moleSkin - - //Remove claws and skins - player.inventory.remove(Item(Items.MOLE_CLAW_7416,moleClaws)) - player.inventory.remove(Item(Items.MOLE_SKIN_7418, moleSkin)) - - //Add white lily seeds if the player has the hard diary done - if(moleSkin > 0 && player.achievementDiaryManager.getDiary(DiaryType.FALADOR).checkComplete(DiaryLevel.HARD)) { - player.inventory.add(Item(14589, moleSkin), player) + private fun addClawRewards() { + // count the number of claws + val nestAmount = amountInInventory(player, Items.MOLE_CLAW_7416) + // remove the counted number of skins + if(removeItem(player, Item(Items.MOLE_CLAW_7416, nestAmount), Container.INVENTORY)){ + // add the counted number of nests. one by one so they each have random contents + for (i in 0 until nestAmount) { + addItemOrDrop(player, BirdNest.getRandomNest(true).nest.id, 1) + } } + } - //Add nests - for (i in 0 until nestAmount) { - if(!player.inventory.add(Item(BirdNest.getRandomNest(true).nest.id, 1), player)){ - GroundItemManager.create(Item(BirdNest.getRandomNest(true).nest.id,1),player.location,player) + private fun addSkinRewards() { + // count the number of skins + val nestAmount = amountInInventory(player, Items.MOLE_SKIN_7418) + // remove the counted number of skins + if(removeItem(player, Item(Items.MOLE_SKIN_7418, nestAmount), Container.INVENTORY)) { + // add the counted number of nests. one by one so they each have random contents + // if Falador Hard diary is complete, add a white lilly seed + for (i in 0 until nestAmount) { + addItemOrDrop(player, BirdNest.getRandomNest(true).nest.id, 1) + if (player.achievementDiaryManager.getDiary(DiaryType.FALADOR).isComplete(2)) { + addItemOrDrop(player, Items.WHITE_LILY_SEED_14589, 1) + } } } } @@ -224,25 +282,4 @@ class WysonTheGardenerDialogue : core.game.dialogue.DialoguePlugin { return intArrayOf(36) } - companion object { - /** - * Represents the coins item that can be used. - */ - private val COINS = arrayOf(Item(995, 15), Item(995, 20)) - - /** - * Represents the woad leaf item. - */ - private val WOAD_LEAF = Item(1793, 1) - - /** - * The mole claw item. - */ - private val MOLE_CLAW = Item(7416) - - /** - * The mole skin. - */ - private val MOLE_SKIN = Item(7418) - } } \ No newline at end of file From 5533a2f78a1bb6d06695125d2cbfd3e31b8cfdb3 Mon Sep 17 00:00:00 2001 From: Player Name Date: Thu, 27 Nov 2025 12:03:16 +0000 Subject: [PATCH 110/117] Fixed discord mod message logging Fixed a typo when attempting to sell an item on the GE that is blacklisted Implemented grinding of suqah teeth --- .../item/withitem/GrindToothListener.kt | 24 +++++++++++++++++++ .../main/core/net/packet/PacketProcessor.kt | 4 +++- 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 Server/src/main/content/global/handlers/item/withitem/GrindToothListener.kt diff --git a/Server/src/main/content/global/handlers/item/withitem/GrindToothListener.kt b/Server/src/main/content/global/handlers/item/withitem/GrindToothListener.kt new file mode 100644 index 000000000..93a6989da --- /dev/null +++ b/Server/src/main/content/global/handlers/item/withitem/GrindToothListener.kt @@ -0,0 +1,24 @@ +package content.global.handlers.item.withitem + +import core.api.* +import org.rs09.consts.Items +import core.game.interaction.InteractionListener +import core.game.interaction.IntType +import core.game.node.entity.player.info.LogType +import core.game.node.entity.player.info.PlayerMonitor +import core.game.node.item.Item + +class GrindToothListener : InteractionListener { + override fun defineListeners() { + onUseWith(IntType.ITEM, Items.PESTLE_AND_MORTAR_233, Items.SUQAH_TOOTH_9079) { player, _, with -> + val item = with as Item + val res = replaceSlot(player, item.slot, Item(Items.GROUND_TOOTH_9082)) + if (res?.id == Items.SUQAH_TOOTH_9079) { + sendMessage(player, "You grind the suqah tooth to dust.") //https://www.youtube.com/watch?v=RdIcNH50v7I + } else { + PlayerMonitor.log(player, LogType.DUPE_ALERT, "Player ground item ${res?.name} instead of a suqah tooth - potential slot-based manipulation attempt") + } + return@onUseWith true + } + } +} \ No newline at end of file diff --git a/Server/src/main/core/net/packet/PacketProcessor.kt b/Server/src/main/core/net/packet/PacketProcessor.kt index 820c6798c..d76b82db8 100644 --- a/Server/src/main/core/net/packet/PacketProcessor.kt +++ b/Server/src/main/core/net/packet/PacketProcessor.kt @@ -188,7 +188,7 @@ object PacketProcessor { offer.itemID = pkt.itemId offer.sell = false if (!PriceIndex.canTrade(pkt.itemId)) { - sendMessage(pkt.player, "That item is blacklisted from the grand exchange.") + sendMessage(pkt.player, "That item is blacklisted from the Grand Exchange.") return } offer.player = pkt.player @@ -215,6 +215,7 @@ object PacketProcessor { val messages = splitChatMessage(pkt.message.substring(2), pkt.player.name.length + 3, false) for (message in messages) { if (message.isNotBlank()) + PlayerMonitor.logChat(pkt.player, "global", message) GlobalChat.process(pkt.player.username, message, Rights.getChatIcon(pkt.player)) } return @@ -228,6 +229,7 @@ object PacketProcessor { builder.clanName = pkt.player.communication.clan.owner.lowercase().replace(" ", "_") builder.message = message builder.rank = Rights.getChatIcon(pkt.player) + PlayerMonitor.logChat(pkt.player, "clan", message) ManagementEvents.publish(builder.build()) } return From 1765f3a3b6e2dec568b3e9950ce9e194025f41b1 Mon Sep 17 00:00:00 2001 From: dam <27978131-real_damighty@users.noreply.gitlab.com> Date: Thu, 27 Nov 2025 14:04:25 +0200 Subject: [PATCH 111/117] Fixed ice spell casts allowing frozen victim movement for 1 tick Fixed ice barrage animations Players who get successfully frozen by an ice spell now receive the message "You have been frozen!" --- .../global/skill/magic/ancient/IceSpells.java | 322 +++++++++--------- .../core/game/system/timer/impl/Frozen.kt | 97 +++--- 2 files changed, 210 insertions(+), 209 deletions(-) diff --git a/Server/src/main/content/global/skill/magic/ancient/IceSpells.java b/Server/src/main/content/global/skill/magic/ancient/IceSpells.java index e4f287574..4b0e29ff4 100644 --- a/Server/src/main/content/global/skill/magic/ancient/IceSpells.java +++ b/Server/src/main/content/global/skill/magic/ancient/IceSpells.java @@ -1,162 +1,160 @@ -package content.global.skill.magic.ancient; - -import java.util.List; - -import core.game.node.entity.combat.spell.Runes; -import core.game.node.Node; -import core.game.node.entity.Entity; -import core.game.node.entity.combat.BattleState; -import core.game.node.entity.combat.spell.CombatSpell; -import core.game.node.entity.combat.spell.SpellType; -import core.game.node.entity.impl.Projectile; -import core.game.node.entity.impl.Animator.Priority; -import core.game.node.entity.player.link.SpellBookManager.SpellBook; -import core.game.node.item.Item; -import core.game.world.update.flag.context.Animation; -import core.game.world.update.flag.context.Graphics; -import core.plugin.Initializable; -import core.plugin.Plugin; -import org.rs09.consts.Sounds; - -import static core.api.ContentAPIKt.*; - -/** - * Handles the Ice spells from the Ancient spellbook. - * @author Emperor - * @version 1.0 - */ -@Initializable -public final class IceSpells extends CombatSpell { - - /** - * The barrage orb GFX. - */ - private static final Graphics BARRAGE_ORB = new Graphics(1677, 96); //119 - - /** - * The projectile for Ice rush. - */ - private static final Projectile RUSH_PROJECTILE = Projectile.create((Entity) null, null, 360, 40, 36, 52, 75, 15, 11); - - /** - * The end graphic for Ice rush. - */ - private static final Graphics RUSH_END = new Graphics(361, 96); - - /** - * The projectile for Ice barrage. - */ - private static final Projectile BURST_PROJECTILE = Projectile.create((Entity) null, null, 362, 40, 36, 52, 75, 15, 11); - - /** - * The end graphic for Ice barrage. - */ - private static final Graphics BURST_END = new Graphics(363, 0); - - /** - * The start graphic for Ice rush. - */ - private static final Graphics BLITZ_START = new Graphics(366, 96); - - /** - * The end graphic for Ice rush. - */ - private static final Graphics BLITZ_END = new Graphics(367, 96); - - /** - * The projectile for Ice barrage. - */ - private static final Projectile BARRAGE_PROJECTILE = Projectile.create((Entity) null, null, 368, 40, 36, 52, 75, 15, 11); - - /** - * The end graphic for Ice barrage. - */ - private static final Graphics BARRAGE_END = new Graphics(369, 0); - - /** - * Constructs a new {@code IceSpells} {@code Object}. - */ - public IceSpells() { - /* - * empty. - */ - } - - /** - * Constructs a new {@code IceSpells} {@Code Object} - * @param type The spell type. - * @param impactSound The impact sound id. - * @param anim The animation. - * @param start The start graphics. - * @param projectile The projectile. - * @param end The end graphics. - */ - private IceSpells(SpellType type, int level, double baseExperience, int impactSound, Animation anim, Graphics start, Projectile projectile, Graphics end, Item... runes) { - super(type, SpellBook.ANCIENT, level, baseExperience, Sounds.ICE_CAST_171, impactSound, anim, start, projectile, end, runes); - } - - @Override - public void visualize(Entity entity, Node target) { - entity.graphics(graphic); - if (projectile != null) { - projectile.transform(entity, (Entity) target, false, 58, 10).send(); - } - entity.animate(animation); - playGlobalAudio(entity.getLocation(), audio.id, 20); - - } - - @Override - public void visualizeImpact(Entity entity, Entity target, BattleState state) { - if (state.isFrozen()) { - playGlobalAudio(target.getLocation(), impactAudio, 20); - target.graphics(BARRAGE_ORB); - return; - } - super.visualizeImpact(entity, target, state); - } - - @Override - public Plugin newInstance(SpellType arg) throws Throwable { - SpellBook.ANCIENT.register(0, new IceSpells(SpellType.RUSH, 58, 34.0, Sounds.ICE_RUSH_IMPACT_173, new Animation(1978, Priority.HIGH), null, RUSH_PROJECTILE, RUSH_END, Runes.DEATH_RUNE.getItem(2), Runes.CHAOS_RUNE.getItem(2), Runes.WATER_RUNE.getItem(2))); - SpellBook.ANCIENT.register(2, new IceSpells(SpellType.BURST, 70, 40.0, Sounds.ICE_BURST_IMPACT_170, new Animation(1979, Priority.HIGH), null, BURST_PROJECTILE, BURST_END, Runes.DEATH_RUNE.getItem(2), Runes.CHAOS_RUNE.getItem(4), Runes.WATER_RUNE.getItem(4))); - SpellBook.ANCIENT.register(1, new IceSpells(SpellType.BLITZ, 82, 46.0, Sounds.ICE_BLITZ_IMPACT_169, new Animation(1978, Priority.HIGH), BLITZ_START, null, BLITZ_END, Runes.BLOOD_RUNE.getItem(2), Runes.DEATH_RUNE.getItem(2), Runes.WATER_RUNE.getItem(3))); - SpellBook.ANCIENT.register(3, new IceSpells(SpellType.BARRAGE, 94, 52.0, Sounds.ICE_BARRAGE_IMPACT_168, new Animation(1979, Priority.HIGH), null, BARRAGE_PROJECTILE, BARRAGE_END, Runes.BLOOD_RUNE.getItem(2), Runes.DEATH_RUNE.getItem(4), Runes.WATER_RUNE.getItem(6))); - return this; - } - - @Override - public void fireEffect(Entity entity, Entity victim, BattleState state) { - if (state.getEstimatedHit() == -1) { - return; - } - int ticks = (1 + (type.ordinal() - SpellType.RUSH.ordinal())) * 8; - if (state.getEstimatedHit() > -1) { - if (!hasTimerActive(victim, "frozen:immunity")) { - registerTimer(victim, spawnTimer("frozen", ticks, true)); - } else if (type == SpellType.BARRAGE) { - state.setFrozen(true); - } - } - } - - @Override - public BattleState[] getTargets(Entity entity, Entity target) { - if (animation.getId() == 1978 || !entity.getProperties().isMultiZone() || !target.getProperties().isMultiZone()) { - return super.getTargets(entity, target); - } - List list = getMultihitTargets(entity, target, 9); - BattleState[] targets = new BattleState[list.size()]; - int index = 0; - for (Entity e : list) { - targets[index++] = new BattleState(entity, e); - } - return targets; - } - - @Override - public int getMaximumImpact(Entity entity, Entity victim, BattleState state) { - return getType().getImpactAmount(entity, victim, 4); - } - -} +package content.global.skill.magic.ancient; + +import java.util.List; + +import core.game.node.entity.combat.spell.Runes; +import core.game.node.Node; +import core.game.node.entity.Entity; +import core.game.node.entity.combat.BattleState; +import core.game.node.entity.combat.spell.CombatSpell; +import core.game.node.entity.combat.spell.SpellType; +import core.game.node.entity.impl.Projectile; +import core.game.node.entity.impl.Animator.Priority; +import core.game.node.entity.player.link.SpellBookManager.SpellBook; +import core.game.node.item.Item; +import core.game.world.update.flag.context.Animation; +import core.game.world.update.flag.context.Graphics; +import core.plugin.Initializable; +import core.plugin.Plugin; +import org.rs09.consts.Sounds; + +import static core.api.ContentAPIKt.*; + +/** + * Handles the Ice spells from the Ancient spellbook. + * @author Emperor + * @version 1.0 + */ +@Initializable +public final class IceSpells extends CombatSpell { + + /** + * The barrage orb GFX. + */ + private static final Graphics BARRAGE_ORB = new Graphics(1677, 96); //119 + + /** + * The projectile for Ice rush. + */ + private static final Projectile RUSH_PROJECTILE = Projectile.create((Entity) null, null, 360, 40, 36, 52, 75, 15, 11); + + /** + * The end graphic for Ice rush. + */ + private static final Graphics RUSH_END = new Graphics(361, 96); + + /** + * The projectile for Ice barrage. + */ + private static final Projectile BURST_PROJECTILE = Projectile.create((Entity) null, null, 362, 40, 36, 52, 75, 15, 11); + + /** + * The end graphic for Ice barrage. + */ + private static final Graphics BURST_END = new Graphics(363, 0); + + /** + * The start graphic for Ice rush. + */ + private static final Graphics BLITZ_START = new Graphics(366, 96); + + /** + * The end graphic for Ice rush. + */ + private static final Graphics BLITZ_END = new Graphics(367, 96); + + /** + * The projectile for Ice barrage. + */ + private static final Projectile BARRAGE_PROJECTILE = Projectile.create((Entity) null, null, 368, 40, 36, 52, 75, 15, 11); + + /** + * The end graphic for Ice barrage. + */ + private static final Graphics BARRAGE_END = new Graphics(369, 0); + + /** + * Constructs a new {@code IceSpells} {@code Object}. + */ + public IceSpells() { + /* + * empty. + */ + } + + /** + * Constructs a new {@code IceSpells} {@Code Object} + * @param type The spell type. + * @param impactSound The impact sound id. + * @param anim The animation. + * @param start The start graphics. + * @param projectile The projectile. + * @param end The end graphics. + */ + private IceSpells(SpellType type, int level, double baseExperience, int impactSound, Animation anim, Graphics start, Projectile projectile, Graphics end, Item... runes) { + super(type, SpellBook.ANCIENT, level, baseExperience, Sounds.ICE_CAST_171, impactSound, anim, start, projectile, end, runes); + } + + @Override + public void visualize(Entity entity, Node target) { + entity.graphics(graphic); + if (projectile != null) { + projectile.transform(entity, (Entity) target, false, 58, 10).send(); + } + entity.animate(animation); + playGlobalAudio(entity.getLocation(), audio.id, 20); + + } + + @Override + public void visualizeImpact(Entity entity, Entity target, BattleState state) { + if (state.isFrozen()) { + playGlobalAudio(target.getLocation(), impactAudio, 20); + target.graphics(BARRAGE_ORB); + return; + } + super.visualizeImpact(entity, target, state); + } + + @Override + public Plugin newInstance(SpellType arg) throws Throwable { + SpellBook.ANCIENT.register(0, new IceSpells(SpellType.RUSH, 58, 34.0, Sounds.ICE_RUSH_IMPACT_173, new Animation(1978, Priority.HIGH), null, RUSH_PROJECTILE, RUSH_END, Runes.DEATH_RUNE.getItem(2), Runes.CHAOS_RUNE.getItem(2), Runes.WATER_RUNE.getItem(2))); + SpellBook.ANCIENT.register(2, new IceSpells(SpellType.BURST, 70, 40.0, Sounds.ICE_BURST_IMPACT_170, new Animation(1979, Priority.HIGH), null, BURST_PROJECTILE, BURST_END, Runes.DEATH_RUNE.getItem(2), Runes.CHAOS_RUNE.getItem(4), Runes.WATER_RUNE.getItem(4))); + SpellBook.ANCIENT.register(1, new IceSpells(SpellType.BLITZ, 82, 46.0, Sounds.ICE_BLITZ_IMPACT_169, new Animation(1978, Priority.HIGH), BLITZ_START, null, BLITZ_END, Runes.BLOOD_RUNE.getItem(2), Runes.DEATH_RUNE.getItem(2), Runes.WATER_RUNE.getItem(3))); + SpellBook.ANCIENT.register(3, new IceSpells(SpellType.BARRAGE, 94, 52.0, Sounds.ICE_BARRAGE_IMPACT_168, new Animation(1979, Priority.HIGH), null, BARRAGE_PROJECTILE, BARRAGE_END, Runes.BLOOD_RUNE.getItem(2), Runes.DEATH_RUNE.getItem(4), Runes.WATER_RUNE.getItem(6))); + return this; + } + + @Override + public void fireEffect(Entity entity, Entity victim, BattleState state) { + if (state.getEstimatedHit() == -1) { + return; + } + int ticks = (type.ordinal() - 4) * 8; + if (hasTimerActive(victim, "frozen") || hasTimerActive(victim, "frozen:immunity")) { + if (type == SpellType.BARRAGE) { state.setFrozen(true); } + return; + } + registerTimer(victim, spawnTimer("frozen", ticks, true)); + } + + @Override + public BattleState[] getTargets(Entity entity, Entity target) { + if (animation.getId() == 1978 || !entity.getProperties().isMultiZone() || !target.getProperties().isMultiZone()) { + return super.getTargets(entity, target); + } + List list = getMultihitTargets(entity, target, 9); + BattleState[] targets = new BattleState[list.size()]; + int index = 0; + for (Entity e : list) { + targets[index++] = new BattleState(entity, e); + } + return targets; + } + + @Override + public int getMaximumImpact(Entity entity, Entity victim, BattleState state) { + return getType().getImpactAmount(entity, victim, 4); + } + +} diff --git a/Server/src/main/core/game/system/timer/impl/Frozen.kt b/Server/src/main/core/game/system/timer/impl/Frozen.kt index 97fd002f8..23a2fa7e5 100644 --- a/Server/src/main/core/game/system/timer/impl/Frozen.kt +++ b/Server/src/main/core/game/system/timer/impl/Frozen.kt @@ -1,47 +1,50 @@ -package core.game.system.timer.impl - -import core.game.system.timer.* -import core.api.* -import core.game.node.entity.Entity -import core.game.node.entity.player.Player -import core.game.world.repository.Repository -import org.json.simple.* - -class Frozen : PersistTimer (1, "frozen", flags = arrayOf(TimerFlag.ClearOnDeath)) { - var shouldApplyImmunity = false - - override fun save (root: JSONObject, entity: Entity) { - root["ticksLeft"] = (nextExecution - getWorldTicks()).toString() - root["applyImmunity"] = shouldApplyImmunity - } - - override fun parse (root: JSONObject, entity: Entity) { - runInterval = root["ticksLeft"].toString().toInt() - shouldApplyImmunity = root["applyImmunity"] as? Boolean ?: false - } - - override fun beforeRegister (entity: Entity) { - if (hasTimerActive(entity)) { - removeTimer(entity, this) - return - } - if (hasTimerActive(entity)) { - removeTimer(entity, this) - return - } - } - - override fun run (entity: Entity) : Boolean { - if (shouldApplyImmunity) { - registerTimer (entity, spawnTimer(7)) - } else (entity as? Player)?.debug ("Can't apply immunity") - return false - } - - override fun getTimer (vararg args: Any) : RSTimer { - val inst = Frozen() - inst.runInterval = args.getOrNull(0) as? Int ?: 10 - inst.shouldApplyImmunity = args.getOrNull(1) as? Boolean ?: false - return inst - } -} +package core.game.system.timer.impl + +import core.game.system.timer.* +import core.api.* +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import core.game.world.repository.Repository +import org.json.simple.* + +class Frozen : PersistTimer (1, "frozen", flags = arrayOf(TimerFlag.ClearOnDeath)) { + var shouldApplyImmunity = false + + override fun save (root: JSONObject, entity: Entity) { + root["ticksLeft"] = (nextExecution - getWorldTicks()).toString() + root["applyImmunity"] = shouldApplyImmunity + } + + override fun parse (root: JSONObject, entity: Entity) { + runInterval = root["ticksLeft"].toString().toInt() + shouldApplyImmunity = root["applyImmunity"] as? Boolean ?: false + } + + override fun beforeRegister (entity: Entity) { + if (hasTimerActive(entity)) { + removeTimer(entity, this) + return + } + if (hasTimerActive(entity)) { + removeTimer(entity, this) + return + } + if (entity is Player) { + sendMessage(entity as Player, "You have been frozen!") + } + } + + override fun run (entity: Entity) : Boolean { + if (shouldApplyImmunity) { + registerTimer (entity, spawnTimer(7)) + } else (entity as? Player)?.debug ("Can't apply immunity") + return false + } + + override fun getTimer (vararg args: Any) : RSTimer { + val inst = Frozen() + inst.runInterval = args.getOrNull(0) as? Int ?: 10 + inst.shouldApplyImmunity = args.getOrNull(1) as? Boolean ?: false + return inst + } +} From 96c42c18d995825bcc9ce4b8ef310c8807d7bdb7 Mon Sep 17 00:00:00 2001 From: Player Name Date: Thu, 27 Nov 2025 12:08:58 +0000 Subject: [PATCH 112/117] Added a new server config option, shooting_star_ring, enabling whether the inauthentic ancient blueprint gets rolled --- .../global/activity/shootingstar/ShootingStarMiningPulse.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Server/src/main/content/global/activity/shootingstar/ShootingStarMiningPulse.kt b/Server/src/main/content/global/activity/shootingstar/ShootingStarMiningPulse.kt index 9d6591ca6..5c5ecf033 100644 --- a/Server/src/main/content/global/activity/shootingstar/ShootingStarMiningPulse.kt +++ b/Server/src/main/content/global/activity/shootingstar/ShootingStarMiningPulse.kt @@ -6,6 +6,7 @@ import core.game.node.entity.player.Player import core.game.node.entity.skill.SkillPulse import core.game.node.entity.skill.Skills import content.data.skill.SkillingTool +import core.ServerConstants import core.game.node.item.Item import core.tools.RandomFunction import org.rs09.consts.Items @@ -103,7 +104,7 @@ class ShootingStarMiningPulse(player: Player?, node: Scenery?, val star: Shootin if (ShootingStarPlugin.getStarDust(player) < 200) { player.inventory.add(Item(ShootingStarPlugin.STAR_DUST, 1)) } - if(!inInventory(player, Items.ANCIENT_BLUEPRINT_14651) && !inBank(player, Items.ANCIENT_BLUEPRINT_14651)){ + if (ServerConstants.SHOOTING_STAR_RING && hasAnItem(player, Items.ANCIENT_BLUEPRINT_14651).container == null) { rollBlueprint(player) } From a38d3734bd1a40e635266c6476843f465a63b529 Mon Sep 17 00:00:00 2001 From: Player Name Date: Thu, 27 Nov 2025 12:18:26 +0000 Subject: [PATCH 113/117] Global news announcements no longer overrun the chatbox Global news announcements are now locked behind the enable_global_chat server config setting PvP and brawler drops from the Chaos Elemental now refer to it as "the Chaos Elemental", rather than "a Chaos Elemental" PvP and brawler drops from revenants now lowercase the revenant's name --- .../world/map/zone/impl/WildernessZone.java | 6 ++++-- .../core/game/world/repository/Repository.kt | 19 ++++--------------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/Server/src/main/core/game/world/map/zone/impl/WildernessZone.java b/Server/src/main/core/game/world/map/zone/impl/WildernessZone.java index e4886e5f6..623f2f080 100644 --- a/Server/src/main/core/game/world/map/zone/impl/WildernessZone.java +++ b/Server/src/main/core/game/world/map/zone/impl/WildernessZone.java @@ -108,7 +108,8 @@ public final class WildernessZone extends MapZone { byte glove = (byte) RandomFunction.random(1, 14); Item reward = new Item(BrawlingGloves.forIndicator(glove).getId()); GroundItemManager.create(reward, e.asNpc().getDropLocation(), killer.asPlayer()); - Repository.sendNews(killer.getUsername() + " has received " + reward.getName().toLowerCase() + " from a " + e.asNpc().getName() + "!"); + String npcString = e.getId() == NPCs.CHAOS_ELEMENTAL_3200 ? "the Chaos Elemental" : ("a " + e.asNpc().getName().toLowerCase()); + Repository.sendNews(killer.getUsername() + " has received " + reward.getName().toLowerCase() + " from " + npcString + "!"); } for (int j : PVP_GEAR) { boolean chance = RandomFunction.roll(pvpGearRate); @@ -120,7 +121,8 @@ public final class WildernessZone extends MapZone { reward = new Item(j); } GroundItemManager.create(reward, ((NPC) e).getDropLocation(), killer.asPlayer()); - Repository.sendNews(killer.asPlayer().getUsername() + " has received a " + reward.getName() + " from a " + e.asNpc().getName() + "!"); + String npcString = e.getId() == NPCs.CHAOS_ELEMENTAL_3200 ? "the Chaos Elemental" : ("a " + e.asNpc().getName().toLowerCase()); + Repository.sendNews(killer.asPlayer().getUsername() + " has received a " + reward.getName() + " from " + npcString + "!"); } } } diff --git a/Server/src/main/core/game/world/repository/Repository.kt b/Server/src/main/core/game/world/repository/Repository.kt index 575bcb0f2..81d44d5f4 100644 --- a/Server/src/main/core/game/world/repository/Repository.kt +++ b/Server/src/main/core/game/world/repository/Repository.kt @@ -6,6 +6,7 @@ import core.game.node.entity.player.Player import core.game.world.map.Location import core.game.world.map.RegionManager import core.ServerConstants +import core.api.sendMessage import core.game.world.update.UpdateSequence import java.util.* import java.util.concurrent.CopyOnWriteArrayList @@ -55,20 +56,7 @@ object Repository { */ @JvmStatic val disconnectionQueue = DisconnectionQueue() - /** - * Sends a market update message to all players. - * @param string The string. - * @param color The color. - */ - @JvmOverloads - fun sendMarketUpdate(string: String, icon: Int = 12, color: String = "") { - val players: Array = playerNames.values.toTypedArray() - val size = players.size - for (i in 0 until size) { - val player = players[i] as Player ?: continue - player.sendMessage("" + color + "Market Update: " + string) - } - } + /** * Send a news message to all players. * @param string The string. @@ -76,11 +64,12 @@ object Repository { */ @JvmStatic fun sendNews(string: String, icon: Int = 12, color: String = "CC6600") { + if (!ServerConstants.ENABLE_GLOBAL_CHAT) return val players: Array = playerNames.values.toTypedArray() val size = players.size for (i in 0 until size) { val player = players[i] as Player ?: continue - player.sendMessage("News: $string") + sendMessage(player, "News: $string") } } From 0bf2f7d0ecb80ceb04e211dce84cb67dbd4cdc0f Mon Sep 17 00:00:00 2001 From: dam <27978131-real_damighty@users.noreply.gitlab.com> Date: Fri, 28 Nov 2025 14:35:26 +0200 Subject: [PATCH 114/117] Corrected destination coordinates of the Carrallangar teleport --- .../global/skill/magic/ancient/AncientTeleportPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/src/main/content/global/skill/magic/ancient/AncientTeleportPlugin.java b/Server/src/main/content/global/skill/magic/ancient/AncientTeleportPlugin.java index f212f3877..8b8e2c693 100644 --- a/Server/src/main/content/global/skill/magic/ancient/AncientTeleportPlugin.java +++ b/Server/src/main/content/global/skill/magic/ancient/AncientTeleportPlugin.java @@ -98,7 +98,7 @@ public final class AncientTeleportPlugin extends MagicSpell { // dareeyak teleport SpellBook.ANCIENT.register(24, new AncientTeleportPlugin(78, 88, Location.create(2966, 3696, 0), new Item(Runes.LAW_RUNE.getId(), 2), new Item(Runes.FIRE_RUNE.getId(), 3), new Item(Runes.AIR_RUNE.getId(), 2))); // carralangar teleport - SpellBook.ANCIENT.register(25, new AncientTeleportPlugin(84, 82, Location.create(3163, 3664, 0), new Item(Runes.SOUL_RUNE.getId(), 2), new Item(Runes.LAW_RUNE.getId(), 2))); + SpellBook.ANCIENT.register(25, new AncientTeleportPlugin(84, 82, Location.create(3217, 3676, 0), new Item(Runes.SOUL_RUNE.getId(), 2), new Item(Runes.LAW_RUNE.getId(), 2))); // annakarl teleport SpellBook.ANCIENT.register(26, new AncientTeleportPlugin(90, 100, Location.create(3287, 3883, 0), new Item(Runes.BLOOD_RUNE.getId(), 2), new Item(Runes.LAW_RUNE.getId(), 2))); // ghorrock teleport From 07e48d91a8440afbad9659a0e22ae306f414838a Mon Sep 17 00:00:00 2001 From: dam <27978131-real_damighty@users.noreply.gitlab.com> Date: Fri, 28 Nov 2025 14:37:26 +0200 Subject: [PATCH 115/117] Fixed the price of spirit shards and pouches --- Server/src/main/core/game/shops/Shop.kt | 60 +++++++++++++++++------- Server/src/main/core/game/shops/Shops.kt | 3 +- 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/Server/src/main/core/game/shops/Shop.kt b/Server/src/main/core/game/shops/Shop.kt index d19a4c2ec..1d041cf8a 100644 --- a/Server/src/main/core/game/shops/Shop.kt +++ b/Server/src/main/core/game/shops/Shop.kt @@ -167,7 +167,12 @@ class Shop(val title: String, val stock: Array, val general: Boolean = Items.TOKKUL_6529 -> item.definition.getConfiguration(ItemConfigParser.TOKKUL_PRICE, 1) Items.ARCHERY_TICKET_1464 -> item.definition.getConfiguration(ItemConfigParser.ARCHERY_TICKET_PRICE, 1) Items.CASTLE_WARS_TICKET_4067 -> item.definition.getConfiguration(ItemConfigParser.CASTLE_WARS_TICKET_PRICE, 1) - else -> getGPCost(Item(item.id, 1), if (isMainStock) stock[item.slot].amount else playerStock[slot].amount, if (isMainStock) item.amount else playerStock[slot].amount) + else -> { + val fixedPrice = fixedPriceItems[item.id] + fixedPrice ?: getGPCost(Item(item.id, 1), + if (isMainStock) stock[item.slot].amount else playerStock[slot].amount, + if (isMainStock) item.amount else playerStock[slot].amount) + } } return Item(currency, price) @@ -210,13 +215,16 @@ class Shop(val title: String, val stock: Array, val general: Boolean = } } - val price = when(currency) - { - Items.TOKKUL_6529 -> (item.definition.getConfiguration(ItemConfigParser.TOKKUL_PRICE, 1) / 10.0).toInt() // selling items authentically return 10x less tokkul (floored/truncated) than the item's shop price - Items.ARCHERY_TICKET_1464 -> item.definition.getConfiguration(ItemConfigParser.ARCHERY_TICKET_PRICE, 1) - Items.CASTLE_WARS_TICKET_4067 -> item.definition.getConfiguration(ItemConfigParser.CASTLE_WARS_TICKET_PRICE, 1) - else -> getGPSell(Item(shopItemId, 1), stockAmt, currentAmt) - } + val price = when (currency) { + // selling items authentically return 10x less tokkul (floored/truncated) than the item's shop price + Items.TOKKUL_6529 -> (item.definition.getConfiguration(ItemConfigParser.TOKKUL_PRICE, 1) / 10.0).toInt() + Items.ARCHERY_TICKET_1464 -> item.definition.getConfiguration(ItemConfigParser.ARCHERY_TICKET_PRICE, 1) + Items.CASTLE_WARS_TICKET_4067 -> item.definition.getConfiguration(ItemConfigParser.CASTLE_WARS_TICKET_PRICE, 1) + else -> { + val fixedPrice = fixedPriceItems[shopItemId] + fixedPrice ?: getGPSell(Item(shopItemId, 1), stockAmt, currentAmt) + } + } if(!general && stockAmt == 0 && shopSlot == -1) { @@ -293,10 +301,17 @@ class Shop(val title: String, val stock: Array, val general: Boolean = if(cost.id == -1) sendMessage(player, "This shop cannot sell that item.").also { return TransactionStatus.Failure("Shop cannot sell this item") } if(currency == Items.COINS_995){ - var amt = item.amount - var inStockAmt = inStock.amount - while(amt-- > 1) - cost.amount += getGPCost(Item(item.id, 1), if (isMainStock) stock[slot].amount else playerStock[slot].amount, --inStockAmt) + val fixedPrice = fixedPriceItems[item.id] + if (fixedPrice != null) { + // Fixed price items: simple multiplication + cost.amount = fixedPrice * item.amount + } else { + // Dynamic pricing: calculate cost for each item as stock depletes + var amt = item.amount + var inStockAmt = inStock.amount + while(amt-- > 1) + cost.amount += getGPCost(Item(item.id, 1), if (isMainStock) stock[slot].amount else playerStock[slot].amount, --inStockAmt) + } } else { cost.amount = cost.amount * item.amount } @@ -379,7 +394,10 @@ class Shop(val title: String, val stock: Array, val general: Boolean = return TransactionStatus.Failure("Attempt to sell to full shop.") } - if(currency == Items.COINS_995 && item.amount > 1){ + val fixedPrice = fixedPriceItems[id] + if (fixedPrice != null) { + profit.amount = fixedPrice * item.amount + } else if(currency == Items.COINS_995 && item.amount > 1){ var amt = item.amount var inStockAmt = container!![shopSlot]?.amount ?: playerStock.getAmount(id) while(amt-- > 1) @@ -446,11 +464,17 @@ class Shop(val title: String, val stock: Array, val general: Boolean = return Pair(isPlayerStock, shopSlot) } - companion object { - //General stores globally share player stock (weird quirk, right?) - val generalPlayerStock = Container(40, ContainerType.SHOP) - val listenerInstances = HashMap() - } + companion object { + //General stores globally share player stock (weird quirk, right?) + val generalPlayerStock = Container(40, ContainerType.SHOP) + val listenerInstances = HashMap() + + // Items with fixed prices that don't fluctuate based on stock (both buy & sell price) + val fixedPriceItems = mapOf( + Items.SPIRIT_SHARDS_12183 to 25, + Items.POUCH_12155 to 1 + ) + } sealed class TransactionStatus { class Success : TransactionStatus() diff --git a/Server/src/main/core/game/shops/Shops.kt b/Server/src/main/core/game/shops/Shops.kt index 0a649a891..b87f82d21 100644 --- a/Server/src/main/core/game/shops/Shops.kt +++ b/Server/src/main/core/game/shops/Shops.kt @@ -68,7 +68,8 @@ class Shops : StartupListener, TickListener, InteractionListener, InterfaceListe return@map }} } else { - items.add(ShopItem(item, amount.toInt(), tokens.getOrNull(2)?.toIntOrNull() ?: 100)) + val restockRate = tokens.getOrNull(2)?.toIntOrNull() ?: 100 + items.add(ShopItem(item, amount.toInt(), restockRate)) idsInStock[item] = true } } catch (e: Exception) { From 12f217f8f3d942bcbbd0449fa7d5d69969a1d71e Mon Sep 17 00:00:00 2001 From: oftheshire Date: Fri, 28 Nov 2025 12:42:59 +0000 Subject: [PATCH 116/117] Tortoise no longer drop 3 regular-sized bones on death Tortoises have corrected HP (101 for level 79, 121 for level 92) Added stats and animations for Gnome Driver, Gnome Mage, and Gnome Archer Gnome-Mounted Tortoise now attacks with its mounted gnomes, and the gnomes spawn on the ground upon tortoise death --- Server/data/configs/drop_tables.json | 6 - Server/data/configs/npc_configs.json | 45 +-- .../kandarin/handlers/GnomeTortoiseNPC.kt | 284 ++++++++++++++++++ 3 files changed, 310 insertions(+), 25 deletions(-) create mode 100644 Server/src/main/content/region/kandarin/handlers/GnomeTortoiseNPC.kt diff --git a/Server/data/configs/drop_tables.json b/Server/data/configs/drop_tables.json index 076cf6b80..474c5bbe7 100644 --- a/Server/data/configs/drop_tables.json +++ b/Server/data/configs/drop_tables.json @@ -36458,12 +36458,6 @@ "weight": "100.0", "id": "532", "maxAmount": "1" - }, - { - "minAmount": "3", - "weight": "100.0", - "id": "526", - "maxAmount": "3" } ], "charm": [], diff --git a/Server/data/configs/npc_configs.json b/Server/data/configs/npc_configs.json index 2c705af7f..d4efc299f 100644 --- a/Server/data/configs/npc_configs.json +++ b/Server/data/configs/npc_configs.json @@ -36019,7 +36019,7 @@ "name": "Tortoise", "defence_level": "36", "safespot": null, - "lifepoints": "51", + "lifepoints": "121", "strength_level": "36", "id": "3808", "range_level": "1", @@ -36093,61 +36093,68 @@ "examine": "A Gnome Arrow-chucker", "combat_style": "1", "melee_animation": "190", - "range_animation": "0", + "range_animation": "190", "respawn_delay": "60", - "defence_animation": "0", + "defence_animation": "193", "weakness": "2", "magic_animation": "0", "death_animation": "196", "name": "Gnome Archer", - "defence_level": "30", + "defence_level": "1", "safespot": null, - "lifepoints": "42", + "lifepoints": "10", "strength_level": "1", "id": "3814", "aggressive": "true", - "range_level": "30", - "attack_level": "1" + "range_level": "5", + "projectile": "10", + "attack_level": "1", + "prj_height": "30" }, { "examine": "Yee haa!", "melee_animation": "3969", "range_animation": "0", "respawn_delay": "60", - "defence_animation": "0", + "defence_animation": "193", "weakness": "9", "magic_animation": "0", "death_animation": "196", "name": "Gnome Driver", - "defence_level": "33", + "defence_level": "1", "safespot": null, - "lifepoints": "47", - "strength_level": "33", + "lifepoints": "10", + "strength_level": "1", "id": "3815", "aggressive": "true", "range_level": "1", - "attack_level": "33" + "attack_level": "5" }, { "examine": "A battle mage of the gnomish variety.", "combat_style": "2", + "start_gfx": "93", + "start_height": "80", "melee_animation": "3968", "range_animation": "0", - "magic_level": "34", + "magic_level": "5", + "spell_id": "", "respawn_delay": "60", - "defence_animation": "0", + "defence_animation": "193", "weakness": "5", - "magic_animation": "0", + "magic_animation": "200", "death_animation": "196", "name": "Gnome Mage", - "defence_level": "34", + "defence_level": "1", "safespot": null, - "lifepoints": "48", + "lifepoints": "10", "strength_level": "1", "id": "3816", "aggressive": "true", "range_level": "1", - "attack_level": "1" + "projectile": "94", + "attack_level": "1", + "prj_height": "30" }, { "examine": "The cruel tortoise trainer. Boo!", @@ -36176,7 +36183,7 @@ "name": "Tortoise", "defence_level": "36", "safespot": null, - "lifepoints": "51", + "lifepoints": "101", "strength_level": "36", "id": "3819", "range_level": "1", diff --git a/Server/src/main/content/region/kandarin/handlers/GnomeTortoiseNPC.kt b/Server/src/main/content/region/kandarin/handlers/GnomeTortoiseNPC.kt new file mode 100644 index 000000000..84d32fdbc --- /dev/null +++ b/Server/src/main/content/region/kandarin/handlers/GnomeTortoiseNPC.kt @@ -0,0 +1,284 @@ +package content.region.kandarin.handlers + +import core.api.* +import core.game.node.entity.Entity +import core.game.node.entity.combat.CombatStyle +import core.game.node.entity.combat.CombatSwingHandler +import core.game.node.entity.combat.MultiSwingHandler +import core.game.node.entity.combat.equipment.SwitchAttack +import core.game.node.entity.impl.Animator.Priority +import core.game.node.entity.impl.Projectile +import core.game.node.entity.npc.AbstractNPC +import core.game.node.entity.npc.NPC +import core.game.node.entity.npc.NPCBehavior +import core.game.node.entity.npc.agg.AggressiveBehavior +import core.game.node.entity.npc.agg.AggressiveHandler +import core.game.node.entity.player.Player +import core.game.world.GameWorld +import core.game.world.map.Direction +import core.game.world.map.Location +import core.game.world.update.flag.context.Animation +import core.plugin.Initializable +import org.rs09.consts.NPCs + +/* + * Behavior is based on the following sources: + * https://www.youtube.com/watch?v=u2A-_ihV_2w (november 2008) + * https://www.youtube.com/watch?v=O0EIZu7-iys (august 2009) + * https://runescape.wiki/w/Tortoise?oldid=815524 https://web.archive.org/web/20090308094532/http://runescape.wikia.com/wiki/Tortoise + * + * todo fix att, str, def? I'm not sure what is the typical way to calculate these when they are unknown. + * level 79: HP 101, max hit 12, "low attack but good defence" + * level 92: HP 121, max hit 12, "low attack but good defence" + * + * https://runescape.wiki/w/Gnome_Archer?oldid=949978 https://web.archive.org/web/20090929124104/http://runescape.wikia.com/wiki/Gnome_archer + * https://runescape.wiki/w/Gnome_Driver?oldid=1235409 https://web.archive.org/web/20090929124109/http://runescape.wikia.com:80/wiki/Gnome_driver + * https://runescape.wiki/w/Gnome_Mage?oldid=1425756 https://web.archive.org/web/20090928131013/http://runescape.wikia.com:80/wiki/Gnome_mage + */ + +@Initializable +class GnomeTortoiseNPC(id: Int = 0, location: Location? = null) : AbstractNPC(id, location) { + + override fun construct(id: Int, location: Location, vararg objects: Any): AbstractNPC { + return GnomeTortoiseNPC(id, location) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.TORTOISE_3808) + } + + fun spawnGnomes(location: Location, direction: Direction) { + + //todo: sometimes the child spawns or direction is wrong on death. does a move trigger right before it dies? + + var archerLoc = location + var driverLoc = location + var mageLoc = location + + // X D X 3x3 turtle, C center + // M C A D driver, M mage, A archer + // X X X + + // If I was smart I could probably do this with vectors, but I'm dumb so just doing the possibilities by hand. + if(direction == Direction.NORTH) { + archerLoc = location.transform(1,0,0) + driverLoc = location.transform(0,1,0) + mageLoc = location.transform(-1,0,0) + }else if(direction == Direction.NORTH_EAST) { + archerLoc = location.transform(1,-1,0) + driverLoc = location.transform(1,1,0) + mageLoc = location.transform(-1,1,0) + }else if(direction == Direction.EAST) { + archerLoc = location.transform(0,-1,0) + driverLoc = location.transform(1,0,0) + mageLoc = location.transform(0,1,0) + }else if(direction == Direction.SOUTH_EAST) { + archerLoc = location.transform(-1,-1,0) + driverLoc = location.transform(1,-1,0) + mageLoc = location.transform(1,1,0) + }else if(direction == Direction.SOUTH) { + archerLoc = location.transform(-1,0,0) + driverLoc = location.transform(0,-1,0) + mageLoc = location.transform(1,0,0) + }else if(direction == Direction.SOUTH_WEST) { + archerLoc = location.transform(-1,1,0) + driverLoc = location.transform(-1,-1,0) + mageLoc = location.transform(1,-1,0) + }else if(direction == Direction.WEST) { + archerLoc = location.transform(0,1,0) + driverLoc = location.transform(-1,0,0) + mageLoc = location.transform(0,-1,0) + }else if(direction == Direction.NORTH_WEST) { + archerLoc = location.transform(1,1,0) + driverLoc = location.transform(-1,1,0) + mageLoc = location.transform(-1,-1,0) + } + + val npcArcher = GnomeArcherNPC(NPCs.GNOME_ARCHER_3814, archerLoc) + npcArcher.sendChat("Argh!") + npcArcher.init() + + val npcDriver = GnomeDriverNPC(NPCs.GNOME_DRIVER_3815, driverLoc) + npcDriver.sendChat("Nooooo! Dobbie's dead!") + npcDriver.init() + + val npcMage = GnomeMageNPC(NPCs.GNOME_MAGE_3816, mageLoc) + npcMage.sendChat("Kill the infidel!") + npcMage.init() + } + + override fun finalizeDeath(killer: Entity?) { + val turtleLoc = this.centerLocation + val turtleDir = this.direction + spawnGnomes(turtleLoc, turtleDir) + super.finalizeDeath(killer) + // todo remove this debug if not needed. It's just telling me the "direction" the tortoise dies in so I can verify the direction that the child NPCs should spawn. + if (killer is Player) { + killer.debug(direction.toString()) + } + } +} + +//handles the attack switching +class GnomeTortoiseBehavior : NPCBehavior(NPCs.TORTOISE_3808) { + + private val combatHandler = MultiSwingHandler( + true, + // per wiki source, melee has a max hit of 12 + SwitchAttack( + CombatStyle.MELEE.swingHandler, + Animation(3953, Priority.HIGH) + ), + // todo correct the projectile locations (they should originate from the range or mage gnome, not the center). not sure how to do this. + SwitchAttack( + CombatStyle.RANGE.swingHandler, + Animation(3954, Priority.HIGH), + null, + null, + Projectile.create( + null as Entity?, + null, + 10, //bronze arrow + 35, + 30, + 10, + 50, + 14, + 255 + ) + ), + // per wiki source above, spell should be water strike with the sounds of ice barrage + SwitchAttack( + CombatStyle.MAGIC.swingHandler, + Animation(3955, Priority.HIGH), + null, + null, + Projectile.create( + null as Entity?, + null, + 94, //water strike + 35, + 30, + 10, + 50, + 14, + 255 + ) + ) + ) + + override fun getSwingHandlerOverride(self: NPC, original: CombatSwingHandler): CombatSwingHandler { + return combatHandler + } +} + +// Handles Gnome Archer behavior +class GnomeArcherNPC(id: Int = 0, location: Location? = null) : AbstractNPC(id, location) { + + override fun construct(id: Int, location: Location, vararg objects: Any): AbstractNPC { + return GnomeArcherNPC(id, location) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.GNOME_ARCHER_3814) + } + + override fun init() { + super.init() + this.isRespawn = false + this.isAggressive = true + this.aggressiveHandler = AggressiveHandler(this, object : AggressiveBehavior() { + override fun ignoreCombatLevelDifference(): Boolean { + return true + } + }) + } +} + +class GnomeArcherBehavior : NPCBehavior(NPCs.GNOME_ARCHER_3814) { + override fun onCreation(self: NPC) { + // stops the entity from instantly moving. + delayEntity(self, 1) + setAttribute(self, "despawn-time", GameWorld.ticks + 25) + } + + override fun tick(self: NPC): Boolean { + if (!self.inCombat() && (getAttribute(self, "despawn-time", 0) <= GameWorld.ticks)) + self.clear() + return true + } +} + +// Handles Gnome Mage behavior +class GnomeMageNPC(id: Int = 0, location: Location? = null) : AbstractNPC(id, location) { + + override fun construct(id: Int, location: Location, vararg objects: Any): AbstractNPC { + return GnomeMageNPC(id, location) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.GNOME_MAGE_3816) + } + + override fun init() { + super.init() + this.isRespawn = false + this.isAggressive = true + this.aggressiveHandler = AggressiveHandler(this, object : AggressiveBehavior() { + override fun ignoreCombatLevelDifference(): Boolean { + return true + } + }) + } +} + +class GnomeMageBehavior : NPCBehavior(NPCs.GNOME_MAGE_3816) { + override fun onCreation(self: NPC) { + // stops the entity from instantly moving. + delayEntity(self, 1) + setAttribute(self, "despawn-time", GameWorld.ticks + 25) + } + + override fun tick(self: NPC): Boolean { + if (!self.inCombat() && (getAttribute(self, "despawn-time", 0) <= GameWorld.ticks)) + self.clear() + return true + } +} + +// Handles Gnome Driver behavior +class GnomeDriverNPC(id: Int = 0, location: Location? = null) : AbstractNPC(id, location) { + + override fun construct(id: Int, location: Location, vararg objects: Any): AbstractNPC { + return GnomeDriverNPC(id, location) + } + + override fun getIds(): IntArray { + return intArrayOf(NPCs.GNOME_DRIVER_3815) + } + + override fun init() { + super.init() + this.isRespawn = false + this.isAggressive = true + this.aggressiveHandler = AggressiveHandler(this, object : AggressiveBehavior() { + override fun ignoreCombatLevelDifference(): Boolean { + return true + } + }) + } +} + +class GnomeDriverBehavior : NPCBehavior(NPCs.GNOME_DRIVER_3815) { + override fun onCreation(self: NPC) { + // stops the entity from instantly moving. + delayEntity(self, 1) + setAttribute(self, "despawn-time", GameWorld.ticks + 25) + } + + override fun tick(self: NPC): Boolean { + if (!self.inCombat() && (getAttribute(self, "despawn-time", 0) <= GameWorld.ticks)) + self.clear() + return true + } +} \ No newline at end of file From 8bdfc43fba8cf97957f1b97964bf2ce9074d23dd Mon Sep 17 00:00:00 2001 From: sirdabalot Date: Fri, 28 Nov 2025 13:16:55 +0000 Subject: [PATCH 117/117] Rewrote fletching --- .../global/bots/FletchingBankstander.kt | 10 +- .../global/skill/crafting/PotteryPlugin.java | 4 +- .../skill/crafting/SnakeSkinPlugin.java | 2 +- .../AchievementDiaryAttributeKeys.kt | 8 + .../global/skill/fletching/Feathers.kt | 16 + .../global/skill/fletching/Fletching.java | 355 ------------------ .../skill/fletching/FletchingListeners.kt | 197 ---------- .../skill/fletching/FletchingPlugin.java | 131 ------- .../skill/fletching/FletchingPulse.java | 145 ------- .../global/skill/fletching/GemBoltListener.kt | 69 ---- .../content/global/skill/fletching/Zones.kt | 21 ++ .../skill/fletching/arrow/ArrowCraftInfo.kt | 24 ++ .../skill/fletching/arrow/ArrowListeners.kt | 112 ++++++ .../arrow/FlightedOgreArrowCraftScript.kt | 88 +++++ .../arrow/HeadlessArrowCraftScript.kt | 76 ++++ .../fletching/arrow/TippedArrowCraftScript.kt | 79 ++++ .../skill/fletching/bolts/BoltCraftInfo.kt | 39 ++ .../skill/fletching/bolts/BoltCraftScript.kt | 79 ++++ .../skill/fletching/bolts/BoltListeners.kt | 65 ++++ .../fletching/crossbow/LimbingListener.kt | 80 ++++ .../skill/fletching/crossbow/LimbingScript.kt | 56 +++ .../crossbow/UnfinishedCrossbowCraftInfo.kt | 55 +++ .../skill/fletching/darts/DartCraftInfo.kt | 36 ++ .../skill/fletching/darts/DartCraftScript.kt | 78 ++++ .../skill/fletching/darts/DartListeners.kt | 59 +++ .../fletching/gem/AttachGemTipToBoltScript.kt | 77 ++++ .../gem/CutGemsIntoBoltTipsScript.kt | 79 ++++ .../skill/fletching/gem/GemBoltListeners.kt | 90 +++++ .../skill/fletching/gem/GemBoltsCraftInfo.kt | 106 ++++++ .../fletching/grapple/GrappleListeners.kt | 67 ++++ .../fletching/items/arrow/ArrowHead.java | 123 ------ .../fletching/items/arrow/ArrowHeadPulse.java | 110 ------ .../items/arrow/HeadlessArrowPulse.java | 145 ------- .../items/arrow/HeadlessOgreArrowPulse.java | 131 ------- .../skill/fletching/items/bolts/Bolt.java | 123 ------ .../fletching/items/bolts/BoltPulse.java | 130 ------- .../skill/fletching/items/bow/StringBow.java | 136 ------- .../fletching/items/bow/StringPulse.java | 103 ----- .../items/crossbow/CrossbowPulse.java | 97 ----- .../skill/fletching/items/crossbow/Limb.java | 128 ------- .../fletching/items/crossbow/LimbPulse.kt | 52 --- .../fletching/items/crossbow/StringCross.java | 139 ------- .../skill/fletching/items/darts/Dart.java | 96 ----- .../fletching/items/darts/DartPulse.java | 105 ------ .../global/skill/fletching/items/gem/Gem.java | 101 ----- .../skill/fletching/items/gem/GemBolt.java | 144 ------- .../fletching/items/gem/GemBoltCutPulse.kt | 68 ---- .../fletching/items/gem/GemBoltPulse.java | 91 ----- .../fletching/items/grapple/GrapplePulse.java | 85 ----- .../fletching/log/CraftItemWithLogScript.kt | 113 ++++++ .../skill/fletching/log/GrammarHelpers.kt | 20 + .../skill/fletching/log/LogCraftInfo.kt | 63 ++++ .../fletching/log/LogCraftableListeners.kt | 83 ++++ .../fletching/stringing/StringItemScript.kt | 71 ++++ .../stringing/StringableCraftInfo.kt | 66 ++++ .../fletching/stringing/StringingListeners.kt | 69 ++++ .../global/skill/herblore/GrindItemPlugin.kt | 3 +- .../global/skill/magic/SpellListeners.kt | 10 +- Server/src/main/core/api/ContentAPI.kt | 37 +- .../game/dialogue/SkillDialogueHandler.kt | 60 +-- .../game/interaction/InteractionListeners.kt | 4 + .../core/game/interaction/ScriptProcessor.kt | 10 +- .../main/core/game/node/entity/Entity.java | 2 +- .../game/node/entity/impl/PulseManager.java | 12 + .../command/sets/AnimationCommandSet.kt | 34 +- 65 files changed, 1983 insertions(+), 3084 deletions(-) create mode 100644 Server/src/main/content/global/skill/fletching/AchievementDiaryAttributeKeys.kt create mode 100644 Server/src/main/content/global/skill/fletching/Feathers.kt delete mode 100644 Server/src/main/content/global/skill/fletching/Fletching.java delete mode 100644 Server/src/main/content/global/skill/fletching/FletchingListeners.kt delete mode 100644 Server/src/main/content/global/skill/fletching/FletchingPlugin.java delete mode 100644 Server/src/main/content/global/skill/fletching/FletchingPulse.java delete mode 100644 Server/src/main/content/global/skill/fletching/GemBoltListener.kt create mode 100644 Server/src/main/content/global/skill/fletching/Zones.kt create mode 100644 Server/src/main/content/global/skill/fletching/arrow/ArrowCraftInfo.kt create mode 100644 Server/src/main/content/global/skill/fletching/arrow/ArrowListeners.kt create mode 100644 Server/src/main/content/global/skill/fletching/arrow/FlightedOgreArrowCraftScript.kt create mode 100644 Server/src/main/content/global/skill/fletching/arrow/HeadlessArrowCraftScript.kt create mode 100644 Server/src/main/content/global/skill/fletching/arrow/TippedArrowCraftScript.kt create mode 100644 Server/src/main/content/global/skill/fletching/bolts/BoltCraftInfo.kt create mode 100644 Server/src/main/content/global/skill/fletching/bolts/BoltCraftScript.kt create mode 100644 Server/src/main/content/global/skill/fletching/bolts/BoltListeners.kt create mode 100644 Server/src/main/content/global/skill/fletching/crossbow/LimbingListener.kt create mode 100644 Server/src/main/content/global/skill/fletching/crossbow/LimbingScript.kt create mode 100644 Server/src/main/content/global/skill/fletching/crossbow/UnfinishedCrossbowCraftInfo.kt create mode 100644 Server/src/main/content/global/skill/fletching/darts/DartCraftInfo.kt create mode 100644 Server/src/main/content/global/skill/fletching/darts/DartCraftScript.kt create mode 100644 Server/src/main/content/global/skill/fletching/darts/DartListeners.kt create mode 100644 Server/src/main/content/global/skill/fletching/gem/AttachGemTipToBoltScript.kt create mode 100644 Server/src/main/content/global/skill/fletching/gem/CutGemsIntoBoltTipsScript.kt create mode 100644 Server/src/main/content/global/skill/fletching/gem/GemBoltListeners.kt create mode 100644 Server/src/main/content/global/skill/fletching/gem/GemBoltsCraftInfo.kt create mode 100644 Server/src/main/content/global/skill/fletching/grapple/GrappleListeners.kt delete mode 100644 Server/src/main/content/global/skill/fletching/items/arrow/ArrowHead.java delete mode 100644 Server/src/main/content/global/skill/fletching/items/arrow/ArrowHeadPulse.java delete mode 100644 Server/src/main/content/global/skill/fletching/items/arrow/HeadlessArrowPulse.java delete mode 100644 Server/src/main/content/global/skill/fletching/items/arrow/HeadlessOgreArrowPulse.java delete mode 100644 Server/src/main/content/global/skill/fletching/items/bolts/Bolt.java delete mode 100644 Server/src/main/content/global/skill/fletching/items/bolts/BoltPulse.java delete mode 100644 Server/src/main/content/global/skill/fletching/items/bow/StringBow.java delete mode 100644 Server/src/main/content/global/skill/fletching/items/bow/StringPulse.java delete mode 100644 Server/src/main/content/global/skill/fletching/items/crossbow/CrossbowPulse.java delete mode 100644 Server/src/main/content/global/skill/fletching/items/crossbow/Limb.java delete mode 100644 Server/src/main/content/global/skill/fletching/items/crossbow/LimbPulse.kt delete mode 100644 Server/src/main/content/global/skill/fletching/items/crossbow/StringCross.java delete mode 100644 Server/src/main/content/global/skill/fletching/items/darts/Dart.java delete mode 100644 Server/src/main/content/global/skill/fletching/items/darts/DartPulse.java delete mode 100644 Server/src/main/content/global/skill/fletching/items/gem/Gem.java delete mode 100644 Server/src/main/content/global/skill/fletching/items/gem/GemBolt.java delete mode 100644 Server/src/main/content/global/skill/fletching/items/gem/GemBoltCutPulse.kt delete mode 100644 Server/src/main/content/global/skill/fletching/items/gem/GemBoltPulse.java delete mode 100644 Server/src/main/content/global/skill/fletching/items/grapple/GrapplePulse.java create mode 100644 Server/src/main/content/global/skill/fletching/log/CraftItemWithLogScript.kt create mode 100644 Server/src/main/content/global/skill/fletching/log/GrammarHelpers.kt create mode 100644 Server/src/main/content/global/skill/fletching/log/LogCraftInfo.kt create mode 100644 Server/src/main/content/global/skill/fletching/log/LogCraftableListeners.kt create mode 100644 Server/src/main/content/global/skill/fletching/stringing/StringItemScript.kt create mode 100644 Server/src/main/content/global/skill/fletching/stringing/StringableCraftInfo.kt create mode 100644 Server/src/main/content/global/skill/fletching/stringing/StringingListeners.kt diff --git a/Server/src/main/content/global/bots/FletchingBankstander.kt b/Server/src/main/content/global/bots/FletchingBankstander.kt index 201dd6f00..dd60a4e3c 100644 --- a/Server/src/main/content/global/bots/FletchingBankstander.kt +++ b/Server/src/main/content/global/bots/FletchingBankstander.kt @@ -1,12 +1,12 @@ package content.global.bots +import content.global.skill.fletching.log.LogCraftInfo +import content.global.skill.fletching.log.CraftItemWithLogScript +import core.game.bots.Script +import core.game.bots.SkillingBotAssembler import core.game.node.entity.skill.Skills -import content.global.skill.fletching.Fletching -import content.global.skill.fletching.FletchingPulse import core.game.node.item.Item import org.rs09.consts.Items -import core.game.bots.SkillingBotAssembler -import core.game.bots.Script class FletchingBankstander : Script(){ var state = State.FLETCHING @@ -17,7 +17,7 @@ class FletchingBankstander : Script(){ State.FLETCHING -> { bot.inventory.add(Item(Items.KNIFE_946)) bot.inventory.add(Item(Items.LOGS_1511,27)) - bot.pulseManager.run(FletchingPulse(bot, Item(Items.LOGS_1511),27, Fletching.FletchingItems.ARROW_SHAFT)) + CraftItemWithLogScript(bot, LogCraftInfo.ARROW_SHAFT, 27).invoke() State.BANKING } diff --git a/Server/src/main/content/global/skill/crafting/PotteryPlugin.java b/Server/src/main/content/global/skill/crafting/PotteryPlugin.java index f7fa44e3a..23ddac860 100644 --- a/Server/src/main/content/global/skill/crafting/PotteryPlugin.java +++ b/Server/src/main/content/global/skill/crafting/PotteryPlugin.java @@ -56,7 +56,7 @@ public final class PotteryPlugin extends UseWithHandler { @Override public boolean handle(final NodeUsageEvent event) { final Player player = event.getPlayer(); - new SkillDialogueHandler(player, SkillDialogue.FIVE_OPTION, (Object[]) getPottery(false)) { + new SkillDialogueHandler(player, SkillDialogue.FIVE_OPTION, getPottery(false)) { @Override public void create(final int amount, int index) { @@ -145,7 +145,7 @@ public final class PotteryPlugin extends UseWithHandler { * @return the dialogue handler. */ public SkillDialogueHandler getSkillHandler(final Player player) { - return new SkillDialogueHandler(player, SkillDialogue.FIVE_OPTION, (Object[]) getPottery(true)) { + return new SkillDialogueHandler(player, SkillDialogue.FIVE_OPTION, getPottery(true)) { @Override public void create(final int amount, final int index) { diff --git a/Server/src/main/content/global/skill/crafting/SnakeSkinPlugin.java b/Server/src/main/content/global/skill/crafting/SnakeSkinPlugin.java index f4929248e..09c2ed605 100644 --- a/Server/src/main/content/global/skill/crafting/SnakeSkinPlugin.java +++ b/Server/src/main/content/global/skill/crafting/SnakeSkinPlugin.java @@ -34,7 +34,7 @@ public class SnakeSkinPlugin extends UseWithHandler { @Override public boolean handle(final NodeUsageEvent event) { final Player player = event.getPlayer(); - new SkillDialogueHandler(player, SkillDialogue.FIVE_OPTION, (Object[]) getSkins()) { + new SkillDialogueHandler(player, SkillDialogue.FIVE_OPTION, getSkins()) { @Override public void create(final int amount, int index) { diff --git a/Server/src/main/content/global/skill/fletching/AchievementDiaryAttributeKeys.kt b/Server/src/main/content/global/skill/fletching/AchievementDiaryAttributeKeys.kt new file mode 100644 index 000000000..8e3d7fe33 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/AchievementDiaryAttributeKeys.kt @@ -0,0 +1,8 @@ +package content.global.skill.fletching + +object AchievementDiaryAttributeKeys { + /** + * This attribute signifies that the fletch of the magic shortbow is complete which is the first stage of getting the achievement + */ + const val FLETCHED_UNSTRUNG_MAGIC_SHORTBOW = "diary:seers:fletch-magic-short-bow" +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/Feathers.kt b/Server/src/main/content/global/skill/fletching/Feathers.kt new file mode 100644 index 000000000..f76b00a72 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/Feathers.kt @@ -0,0 +1,16 @@ +package content.global.skill.fletching + +import org.rs09.consts.Items + +object Feathers { + val all = intArrayOf( + Items.FEATHER_314, + Items.STRIPY_FEATHER_10087, + Items.RED_FEATHER_10088, + Items.BLUE_FEATHER_10089, + Items.YELLOW_FEATHER_10090, + Items.ORANGE_FEATHER_10091 + ) + + const val STANDARD = Items.FEATHER_314 +} diff --git a/Server/src/main/content/global/skill/fletching/Fletching.java b/Server/src/main/content/global/skill/fletching/Fletching.java deleted file mode 100644 index 3a6c6906e..000000000 --- a/Server/src/main/content/global/skill/fletching/Fletching.java +++ /dev/null @@ -1,355 +0,0 @@ -package content.global.skill.fletching; - -import core.game.node.item.Item; -import core.game.world.update.flag.context.Animation; - -import java.util.HashMap; -public class Fletching { - public static HashMaplogMap = new HashMap<>(); - public static HashMap boltMap = new HashMap<>(); - public static HashMap dartMap = new HashMap<>(); - public static HashMap arrowHeadMap = new HashMap<>(); - public static HashMap gemMap = new HashMap<>(); - public static HashMap tipMap = new HashMap<>(); - public static HashMap stringMap = new HashMap<>(); - public static HashMap limbMap = new HashMap<>(); - static{ - Items[] itemsArray = Items.values(); - int thisLength = itemsArray.length; - for(int x = 0; x < thisLength; x++){ - Items item = itemsArray[x]; - logMap.putIfAbsent(item.id, item.items); - } - Bolts[] boltArray = Bolts.values(); - thisLength = boltArray.length; - for(int x = 0; x < thisLength; x++){ - Bolts bolt = boltArray[x]; - boltMap.putIfAbsent(bolt.unfinished,bolt); - } - Darts[] dartArray = Darts.values(); - thisLength = dartArray.length; - for(int x = 0; x < thisLength; x++){ - Darts dart = dartArray[x]; - dartMap.putIfAbsent(dart.unfinished,dart); - } - ArrowHeads[] ahArray = ArrowHeads.values(); - thisLength = ahArray.length; - for(int x = 0; x < thisLength; x++){ - ArrowHeads arrowhead = ahArray[x]; - arrowHeadMap.putIfAbsent(arrowhead.unfinished,arrowhead); - } - GemBolts[] gbArray = GemBolts.values(); - thisLength = gbArray.length; - for(int x = 0; x < thisLength; x++){ - GemBolts gem = gbArray[x]; - gemMap.putIfAbsent(gem.gem,gem); - tipMap.putIfAbsent(gem.tip,gem); - } - String[] stringArray = String.values(); - thisLength = stringArray.length; - for(int x = 0; x < thisLength; x++){ - String bow = stringArray[x]; - stringMap.putIfAbsent(bow.unfinished,bow); - } - Limb[] limbsArray = Limb.values(); - thisLength = limbsArray.length; - for(int x = 0; x < thisLength; x++){ - Limb limb = limbsArray[x]; - limbMap.putIfAbsent(limb.stock, limb); - } - } - public static FletchingItems[] getEntries(int id){ - return logMap.get(id); - } - public static boolean isLog(int id){ - return logMap.get(id) != null; - } - public static boolean isBolt(int id){ - return boltMap.get(id) != null; - } - public static boolean isDart(int id){ - return dartMap.get(id) != null; - } - public static boolean isArrowHead(int id){ - return arrowHeadMap.get(id) != null; - } - public static boolean isGemTip(int id){ - return tipMap.get(id) != null; - } - public static Item[] getItems(int id){ - FletchingItems[] entry = getEntries(id); - Item items[] = {}; - switch(entry.length){ - case 1: - items = new Item[] {new Item(entry[0].id)}; - break; - case 2: - items = new Item[] {new Item(entry[0].id), new Item(entry[1].id)}; - break; - case 3: - items = new Item[] {new Item(entry[0].id), new Item(entry[1].id), new Item(entry[2].id)}; - break; - case 4: - items = new Item[] {new Item(entry[0].id), new Item(entry[1].id), new Item(entry[2].id), new Item(entry[3].id)}; - break; - } - return items; - } - public enum Limb { - WOODEN_STOCK(9440,9420,9454,9, 12, new Animation(4436)), - OAK_STOCK(9442,9422,9456,24, 32, new Animation(4437)), - WILLOW_STOCK(9444,9423,9457,39, 44, new Animation(4438)), - TEAK_STOCK(9446,9425,9459,46, 54, new Animation(4439)), - MAPLE_STOCK(9448,9427,9461,54, 64, new Animation(4440)), - MAHOGANY_STOCK(9450,9429,9463,61, 82, new Animation(4441)), - YEW_STOCK(9452,9431,9465,69, 100, new Animation(4442)); - - - public int stock, limb, product,level; - public double experience; - public Animation animation; - - Limb(int stock, int limb, int product, int level, double experience, Animation animation) { - this.stock = stock; - this.limb = limb; - this.product = product; - this.level = level; - this.experience = experience; - this.animation = animation; - } - } - public enum String{ - //Bows - SHORT_BOW((byte) 1,50,841,5, 5, new Animation(6678)), - LONG_BOW((byte) 1,48,839,10, 10, new Animation(6684)), - OAK_SHORTBOW((byte) 1,54,843,20, 16.5, new Animation(6679)), - OAK_LONGBOW((byte) 1,56,845,25, 25, new Animation(6685)), - WILLOW_SHORTBOW((byte) 1,60,849,35, 33.3, new Animation(6680)), - WILLOW_LONGBOW((byte) 1,58,847,40, 41.5, new Animation(6686)), - MAPLE_SHORTBOW((byte) 1,64,853,50, 50, new Animation(6681)), - MAPLE_LONGBOW((byte) 1,62,851,55, 58.3, new Animation(6687)), - YEW_SHORTBOW((byte) 1,68,857,65, 67.5, new Animation(6682)), - YEW_LONGBOW((byte) 1,66,855,70, 75, new Animation(6688)), - MAGIC_SHORTBOW((byte) 1,72,861,80, 83.3, new Animation(6683)), - MAGIC_LONGBOW((byte) 1,70,859,85, 91.5, new Animation(6689)), - OGRE_COMP_BOW((byte) 1,4825,4827,30, 45, new Animation(-1)), - - //crossbows - BRONZE_CBOW((byte) 2,9454,9174,9, 6, new Animation(6671)), - BLURITE_CBOW((byte) 2,9456,9176,24, 16, new Animation(6672)), - IRON_CBOW((byte) 2,9457,9177,39, 22, new Animation(6673)), - STEEL_CBOW((byte) 2,9459,9179,46, 27, new Animation(6674)), - MITHIRIL_CBOW((byte) 2,9461,9181,54, 32, new Animation(6675)), - ADAMANT_CBOW((byte) 2,9463,9183,61, 41, new Animation(6676)), - RUNITE_CBOW((byte) 2,9465,9185,69, 50, new Animation(6677)); - - - public int unfinished,product,string,level; - public final double experience; - public final Animation animation; - String(byte indicator, final int unfinished, final int product, final int level, final double experience, final Animation animation) { - this.unfinished = unfinished; - this.product = product; - this.level = level; - this.experience = experience; - this.animation = animation; - switch(indicator & 0xFF){ - case 1: - this.string = org.rs09.consts.Items.BOW_STRING_1777; - break; - case 2: - this.string = org.rs09.consts.Items.CROSSBOW_STRING_9438; - break; - default: - break; - } - } - } - public enum GemBolts { - OPAL(877, org.rs09.consts.Items.OPAL_1609, 45, 879, 11, 1.6), - PEARL(9140, org.rs09.consts.Items.OYSTER_PEARL_411, 46, 880, 41, 3.2), - PEARLS(9140, org.rs09.consts.Items.OYSTER_PEARLS_413, 46, 880, 41, 3.2), - JADE(9139, org.rs09.consts.Items.JADE_1611, 9187, 9335, 26, 2.4), - RED_TOPAZ(9141, org.rs09.consts.Items.RED_TOPAZ_1613, 9188, 9336, 48, 3.9), - SAPPHIRE(9142, org.rs09.consts.Items.SAPPHIRE_1607, 9189, 9337, 56, 4.7), - EMERALD(9142, org.rs09.consts.Items.EMERALD_1605, 9190, 9338, 58, 5.5), - RUBY(9143, org.rs09.consts.Items.RUBY_1603, 9191, 9339, 63, 6.3), - DIAMOND(9143, org.rs09.consts.Items.DIAMOND_1601, 9192, 9340, 65, 7), - DRAGONSTONE(9144, org.rs09.consts.Items.DRAGONSTONE_1615, 9193, 9341, 71, 8.2), - ONYX(9144, org.rs09.consts.Items.ONYX_6573, 9194, 9342, 73, 9.4); - - public int gem,tip,base,product,level; - public double experience; - GemBolts(int base, int gem, int tip, int product, int level, double experience){ - this.gem = gem; - this.tip = tip; - this.base = base; - this.product = product; - this.level = level; - this.experience = experience; - } - - } - public enum ArrowHeads { - BRONZE_ARROW(39, 882, 1, 1.3), - IRON_ARROW(40, 884, 15, 2.5), - STEEL_ARROW(41, 886, 30, 5), - MITHRIL_ARROW(42, 888, 45, 7.5), - ADAMANT_ARROW(43, 890, 60, 10), - RUNE_ARROW(44, 892, 75, 12.5), - DRAGON_ARROW(11237, 11212, 90, 15), - BROAD_ARROW(13278, 4160, 52, 15); - - public int unfinished,finished,level; - public double experience; - ArrowHeads(int unfinished, int finished, int level, double experience){ - this.unfinished = unfinished; - this.finished = finished; - this.level = level; - this.experience = experience; - } - public Item getFinished(){ - return new Item(finished); - } - public Item getUnfinished(){ - return new Item(unfinished); - } - } - public enum Darts{ - BRONZE_DART(819, 806, 1, 1.8), - IRON_DART(820, 807, 22, 3.8), - STEEL_DART(821, 808, 37, 7.5), - MITHRIL_DART(822, 809, 52, 11.2), - ADAMANT_DART(823, 810, 67, 15), - RUNE_DART(824, 811, 81, 18.8), - DRAGON_DART(11232, 11230, 95, 25); - - public int unfinished, finished, level; - public double experience; - Darts(int unfinished, int finished, int level, double experience){ - this.unfinished = unfinished; - this.finished = finished; - this.level = level; - this.experience = experience; - } - public Item getFinished(){ - return new Item(finished); - } - public Item getUnfinished(){ - return new Item(unfinished); - } - } - public enum Bolts{ - BRONZE_BOLT(9375, 877, 9, 0.5), - BLURITE_BOLT(9376, 9139, 24, 1), - IRON_BOLT(9377, 9140, 39, 1.5), - SILVER_BOLT(9382, 9145, 43, 2.5), - STEEL_BOLT(9378, 9141, 46, 3.5), - MITHRIL_BOLT(9379, 9142, 54, 5), - ADAMANTITE_BOLT(9380, 9143, 61, 7), - RUNITE_BOLT(9381, 9144, 69, 10), - BROAD_BOLT(13279, 13280, 55, 3); - - public int unfinished, finished, level; - public double experience; - Bolts(int unfinished, int finished, int level, double experience){ - this.unfinished = unfinished; - this.finished = finished; - this.level = level; - this.experience = experience; - } - public Item getFinished(){ - return new Item(finished); - } - public Item getUnfinished(){ - return new Item(unfinished); - } - } - private enum Items{ - STANDARD(1511,FletchingItems.ARROW_SHAFT, FletchingItems.SHORT_BOW, FletchingItems.LONG_BOW, FletchingItems.WOODEN_STOCK), - ACHEY(2862, FletchingItems.OGRE_ARROW_SHAFT, FletchingItems.OGRE_COMP_BOW), - OAK(1521, FletchingItems.OAK_SHORTBOW, FletchingItems.OAK_LONGBOW, FletchingItems.OAK_STOCK), - WILLOW(1519, FletchingItems.WILLOW_SHORTBOW, FletchingItems.WILLOW_LONGBOW, FletchingItems.WILLOW_STOCK), - MAPLE(1517, FletchingItems.MAPLE_SHORTOW, FletchingItems.MAPLE_LONGBOW, FletchingItems.MAPLE_STOCK), - YEW(1515, FletchingItems.YEW_SHORTBOW, FletchingItems.YEW_LONGBOW, FletchingItems.YEW_STOCK), - MAGIC(1513, FletchingItems.MAGIC_SHORTBOW, FletchingItems.MAGIC_LONGBOW), - TEAK(6333, FletchingItems.TEAK_STOCK), - MAHOGANY(6332, FletchingItems.MAHOGANY_STOCK); - - - FletchingItems[] items; - int id; - Items(int id, FletchingItems item_1, FletchingItems item_2, FletchingItems item_3, FletchingItems item_4){ - items = new FletchingItems[] {item_1, item_2, item_3, item_4}; - this.id = id; - } - Items(int id, FletchingItems item_1, FletchingItems item_2, FletchingItems item_3){ - items = new FletchingItems[] {item_1, item_2, item_3}; - this.id = id; - } - Items(int id, FletchingItems item_1, FletchingItems item_2){ - items = new FletchingItems[] {item_1, item_2}; - this.id = id; - } - Items(int id, FletchingItems item_1){ - items = new FletchingItems[] {item_1}; - this.id = id; - } - } - public enum FletchingItems { - //Standard logs - ARROW_SHAFT(52, 5, 1, 15), - SHORT_BOW(50, 5, 5, 1), - LONG_BOW(48, 10, 10, 1), - WOODEN_STOCK(9440, 6, 9, 1), - - //Achey logs - OGRE_ARROW_SHAFT(2864, 6.4, 5, 4), - OGRE_COMP_BOW(4825, 45, 30, 1), - - //Oak logs - OAK_SHORTBOW(54, 16.5, 20, 1), - OAK_LONGBOW(56,25,25,1), - OAK_STOCK(9442, 16, 24, 1), - - //Willow logs - WILLOW_SHORTBOW(60, 33.3, 35, 1), - WILLOW_LONGBOW(58, 41.5, 40, 1), - WILLOW_STOCK(9444, 22, 39, 1), - - //Maple logs - MAPLE_SHORTOW(64, 50, 50, 1), - MAPLE_LONGBOW(62, 58.3, 55, 1), - MAPLE_STOCK(9448, 32, 54, 1), - - //Yew logs - YEW_SHORTBOW(68, 67.5, 65, 1), - YEW_LONGBOW(66, 75, 70, 1), - YEW_STOCK(9452, 50, 69, 1), - - //Magic logs - MAGIC_SHORTBOW(72, 83.3, 80,1), - MAGIC_LONGBOW(70, 91.5, 85, 1), - - //Teak - TEAK_STOCK(9446, 27, 46,1), - - //Mahogany - MAHOGANY_STOCK(9450, 41.0, 61, 1); - - - int id,level,amount,logId; - double experience; - FletchingItems(int id, double experience, int level, int amount){ - this.id = id; - this.level = level; - this.amount = amount; - this.experience = experience; - } - - public Item getItem(){ - return new Item(id); - } - } - -} diff --git a/Server/src/main/content/global/skill/fletching/FletchingListeners.kt b/Server/src/main/content/global/skill/fletching/FletchingListeners.kt deleted file mode 100644 index 464a0b6ff..000000000 --- a/Server/src/main/content/global/skill/fletching/FletchingListeners.kt +++ /dev/null @@ -1,197 +0,0 @@ -package content.global.skill.fletching - -import content.data.Quests -import content.global.skill.fletching.items.arrow.ArrowHeadPulse -import content.global.skill.fletching.items.arrow.HeadlessArrowPulse -import content.global.skill.fletching.items.arrow.HeadlessOgreArrowPulse -import content.global.skill.fletching.items.bow.StringPulse -import content.global.skill.fletching.items.crossbow.LimbPulse -import core.api.* -import core.game.node.entity.skill.Skills -import core.game.node.item.Item -import core.net.packet.PacketRepository -import core.net.packet.context.ChildPositionContext -import core.net.packet.out.RepositionChild -import org.rs09.consts.Components -import org.rs09.consts.Items -import org.rs09.consts.Items.BLUE_FEATHER_10089 -import org.rs09.consts.Items.FEATHER_314 -import org.rs09.consts.Items.ORANGE_FEATHER_10091 -import org.rs09.consts.Items.RED_FEATHER_10088 -import org.rs09.consts.Items.STRIPY_FEATHER_10087 -import org.rs09.consts.Items.YELLOW_FEATHER_10090 -import core.game.dialogue.SkillDialogueHandler -import core.game.interaction.InteractionListener -import core.game.interaction.IntType -import core.game.node.entity.player.Player - -class FletchingListeners : InteractionListener { - - val LIMBIDs = Fletching.Limb.values().map(Fletching.Limb::limb).toIntArray() - val STOCKIDs = Fletching.Limb.values().map(Fletching.Limb::stock).toIntArray() - val MITHRIL_BOLT = Items.MITHRIL_BOLTS_9142 - val MITH_GRAPPLE_TIP = Items.MITH_GRAPPLE_TIP_9416 - val ROPE = Items.ROPE_954 - val MITH_GRAPPLE = Items.MITH_GRAPPLE_9418 - val ROPE_GRAPPLE = Items.MITH_GRAPPLE_9419 - val ARROW_SHAFT = Items.ARROW_SHAFT_52 - val OGRE_ARROW_SHAFT = Items.OGRE_ARROW_SHAFT_2864 - val FLETCHED_SHAFT = Items.HEADLESS_ARROW_53 - val FLIGHTED_OGRE_ARROW = Items.FLIGHTED_OGRE_ARROW_2865 - val UNFINISHED_ARROWS = Fletching.ArrowHeads.values().map(Fletching.ArrowHeads::unfinished).toIntArray() - val FEATHERS = intArrayOf(FEATHER_314,STRIPY_FEATHER_10087,RED_FEATHER_10088,BLUE_FEATHER_10089,YELLOW_FEATHER_10090,ORANGE_FEATHER_10091) - val UNSTRUNG_BOWS = Fletching.String.values().map(Fletching.String::unfinished).toIntArray() - val STRINGS = intArrayOf(Items.BOW_STRING_1777,Items.CROSSBOW_STRING_9438) - - override fun defineListeners() { - - onUseWith(IntType.ITEM,STRINGS,*UNSTRUNG_BOWS){ player, string, bow -> - val enum = Fletching.stringMap[bow.id] ?: return@onUseWith false - - if (bow.id == Items.UNSTRUNG_COMP_BOW_4825) { - // You shouldn't be able to string a bow - if (getQuestStage(player, Quests.ZOGRE_FLESH_EATERS) < 8) { - player.packetDispatch.sendMessage("You must have started Zogre Flesh Eaters and asked Grish to string this.") - return@onUseWith true - } - } - if(enum.string != string.id){ - player.sendMessage("That's not the right kind of string for this.") - return@onUseWith true - } - val handler: SkillDialogueHandler = - object : SkillDialogueHandler(player, SkillDialogue.ONE_OPTION, Item(enum.product)) { - override fun create(amount: Int, index: Int) { - player.pulseManager.run(StringPulse(player, string.asItem(), enum, amount)) - } - - override fun getAll(index: Int): Int { - return player.inventory.getAmount(string.asItem()) - } - } - handler.open() - PacketRepository.send(RepositionChild::class.java, ChildPositionContext(player, Components.SKILL_MULTI1_309, 2, 215, 10)) - return@onUseWith true - } - - onUseWith(IntType.ITEM,ARROW_SHAFT,*FEATHERS){ player, shaft, feather -> - val handler: SkillDialogueHandler = - object : SkillDialogueHandler(player, SkillDialogue.MAKE_SET_ONE_OPTION, Item(FLETCHED_SHAFT)) { - override fun create(amount: Int, index: Int) { - player.pulseManager.run(HeadlessArrowPulse(player, shaft.asItem(), Item(feather.id), amount)) - } - - override fun getAll(index: Int): Int { - return player.inventory.getAmount(FLETCHED_SHAFT) - } - } - handler.open() - return@onUseWith true - } - - onUseWith(IntType.ITEM,OGRE_ARROW_SHAFT,*FEATHERS){ player, shaft, feather -> - val handler: SkillDialogueHandler = - object : SkillDialogueHandler(player, SkillDialogue.MAKE_SET_ONE_OPTION, Item(FLIGHTED_OGRE_ARROW)) { - override fun create(amount: Int, index: Int) { - player.pulseManager.run(HeadlessOgreArrowPulse(player, shaft.asItem(), Item(feather.id, 4), amount)) - } - - override fun getAll(index: Int): Int { - return player.inventory.getAmount(FLIGHTED_OGRE_ARROW) - } - } - handler.open() - return@onUseWith true - } - - onUseWith(IntType.ITEM,FLETCHED_SHAFT,*UNFINISHED_ARROWS){ player, shaft, unfinished -> - val head = Fletching.arrowHeadMap[unfinished.id] ?: return@onUseWith false - val handler: SkillDialogueHandler = - object : SkillDialogueHandler(player, SkillDialogue.MAKE_SET_ONE_OPTION, head.getFinished()) { - override fun create(amount: Int, index: Int) { - player.pulseManager.run(ArrowHeadPulse(player, shaft.asItem(), head, amount)) - } - - override fun getAll(index: Int): Int { - return player.inventory.getAmount(head.getUnfinished()) - } - } - handler.open() - return@onUseWith true - } - - onUseWith(IntType.ITEM,MITHRIL_BOLT,MITH_GRAPPLE_TIP){ player, bolt, tip -> - if(player.skills.getLevel(Skills.FLETCHING) < 59){ - player.sendMessage("You need a fletching level of 59 to make this.") - return@onUseWith true - } - if(player.inventory.remove(Item(MITHRIL_BOLT,1),tip.asItem())){ - player.inventory.add(Item(MITH_GRAPPLE)) - } - return@onUseWith true - } - - onUseWith(IntType.ITEM,ROPE,MITH_GRAPPLE){ player, rope, grapple -> - if(player.skills.getLevel(Skills.FLETCHING) < 59){ - player.sendMessage("You need a fletching level of 59 to make this.") - return@onUseWith true - } - if(player.inventory.remove(rope.asItem(),grapple.asItem())){ - player.inventory.add(Item(ROPE_GRAPPLE)) - } - return@onUseWith true - } - - onUseWith(IntType.ITEM,LIMBIDs,*STOCKIDs){ player, limb, stock -> - val limbEnum = Fletching.limbMap[stock.id] ?: return@onUseWith false - if(limbEnum.limb != limb.id){ - player.sendMessage("That's not the right limb to attach to that stock.") - return@onUseWith true - } - val handler: SkillDialogueHandler = object : SkillDialogueHandler(player, SkillDialogue.ONE_OPTION, Item(limbEnum.product)){ - override fun create(amount: Int, index: Int) { - player.pulseManager.run(LimbPulse(player, stock.asItem(), limbEnum, amount)) - } - - override fun getAll(index: Int): Int { - return player.inventory.getAmount(stock.asItem()) - } - } - handler.open() - PacketRepository.send(RepositionChild::class.java, ChildPositionContext(player, Components.SKILL_MULTI1_309, 2, 210, 10)) - return@onUseWith true - } - - /** - * (Long) Kebbit bolts don't need feathers and go 6 at a time so use their own interaction - */ - fun makeKebbitBolt(player : Player, ingredient : Item) : Boolean{ - val longBolts = when(ingredient.id){ - Items.KEBBIT_SPIKE_10105 -> false - Items.LONG_KEBBIT_SPIKE_10107 -> true - else -> return false - } - val level = if(longBolts) 42 else 26 - if (getDynLevel(player, Skills.FLETCHING) < level){ - sendMessage(player, "You need a fletching level of $level to create ${if (longBolts) "long " else ""}kebbit bolts.") - return true - } - val finalProduct = if(longBolts) Items.LONG_KEBBIT_BOLTS_10159 else Items.KEBBIT_BOLTS_10158 - val xp = if(longBolts) 47.7 else 28.6 // source https://runescape.wiki/w/Fletching?oldid=1069981#Bolts_2 - if(removeItem(player, ingredient.id)){ - addItem(player, finalProduct, 6) - player.skills.addExperience(Skills.FLETCHING, xp) - animate(player, 885) - } - return true - } - onUseWith(IntType.ITEM, Items.CHISEL_1755, Items.KEBBIT_SPIKE_10105) { player, used, with -> - return@onUseWith makeKebbitBolt(player, with as Item) - } - - onUseWith(IntType.ITEM, Items.CHISEL_1755, Items.LONG_KEBBIT_SPIKE_10107) { player, used, with -> - return@onUseWith makeKebbitBolt(player, with as Item) - } - } - -} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/FletchingPlugin.java b/Server/src/main/content/global/skill/fletching/FletchingPlugin.java deleted file mode 100644 index 3ec122866..000000000 --- a/Server/src/main/content/global/skill/fletching/FletchingPlugin.java +++ /dev/null @@ -1,131 +0,0 @@ -package content.global.skill.fletching; - -import content.global.skill.fletching.items.bolts.BoltPulse; -import content.global.skill.fletching.items.darts.DartPulse; -import org.rs09.consts.Items; -import core.game.dialogue.SkillDialogueHandler; -import core.game.dialogue.SkillDialogueHandler.SkillDialogue; -import core.game.interaction.NodeUsageEvent; -import core.game.interaction.UseWithHandler; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; -import core.net.packet.PacketRepository; -import core.net.packet.context.ChildPositionContext; -import core.net.packet.out.RepositionChild; -import core.plugin.Initializable; -import core.plugin.Plugin; - -/** - * Represents the plugin used to open the fletching dialogue. - * @author Ceikry - */ -@Initializable -public class FletchingPlugin extends UseWithHandler { - - public FletchingPlugin() { - super(819,820,821,822,823,824,11232,9375,9376,9377,9382,9378,9379,9380,9381,13279,1511,1521,1519,1517,1515,1513,2862,6332,6333, Items.MAHOGANY_LOGS_6332); - } - - @Override - public Plugin newInstance(Object arg) throws Throwable { - // Knife - addHandler(946, ITEM_TYPE, this); - - // Feathers plus colored feathers - addHandler(314, ITEM_TYPE,this); - addHandler(10087, ITEM_TYPE, this); - addHandler(10088, ITEM_TYPE, this); - addHandler(10089, ITEM_TYPE, this); - addHandler(10090, ITEM_TYPE, this); - addHandler(10091, ITEM_TYPE, this); - - return this; - } - - @Override - public boolean handle(final NodeUsageEvent event) { - final Player player = event.getPlayer(); - - //handle darts - if(Fletching.isDart(event.getUsedItem().getId())){ - final Fletching.Darts dart = Fletching.dartMap.get(event.getUsedItem().getId()); - SkillDialogueHandler handler = new SkillDialogueHandler(player, SkillDialogue.MAKE_SET_ONE_OPTION, dart.getFinished()) { - @Override - public void create(final int amount, int index) { - player.getPulseManager().run(new DartPulse(player, event.getUsedItem(), dart, amount)); - } - @Override - public int getAll(int index) { - return player.getInventory().getAmount(event.getUsedItem()); - } - }; - handler.open(); - return true; - } - - //handle bolts - if(Fletching.isBolt(event.getUsedItem().getId()) || Fletching.isBolt(event.getUsedWith().getId())){ - // figure out which of the used items is a bolt, and which is potentially a feather - final Fletching.Bolts bolt - = Fletching.isBolt(event.getUsedItem().getId()) - ? Fletching.boltMap.get(event.getUsedItem().getId()) - : Fletching.boltMap.get(event.getUsedWith().getId()); - final int featherId - = Fletching.isBolt(event.getUsedItem().getId()) - ? event.getUsedWith().getId() - : event.getUsedItem().getId(); - final boolean hasFeather = (featherId == 314 || (featherId >= 10087 && featherId <= 10091)); - - if (hasFeather) { - SkillDialogueHandler handler = new SkillDialogueHandler(player, SkillDialogue.MAKE_SET_ONE_OPTION, bolt.getFinished()) { - @Override - public void create(final int amount, int index) { - player.getPulseManager().run(new BoltPulse(player, event.getUsedItem(), bolt, new Item(featherId), amount)); - } - @Override - public int getAll(int index) { - return player.getInventory().getAmount(event.getUsedItem()); - } - }; - handler.open(); - return true; - } - return false; - } - - //handle logs - if(Fletching.isLog(event.getUsedItem().getId()) && event.getUsedWith().getId() == 946) { - final Item log = event.getUsedItem(); - Item[] items = Fletching.getItems(log.getId()); - SkillDialogue dialLength = SkillDialogue.ONE_OPTION; - switch (items.length) { - case 2: - dialLength = SkillDialogue.TWO_OPTION; - break; - case 3: - dialLength = SkillDialogue.THREE_OPTION; - break; - case 4: - dialLength = SkillDialogue.FOUR_OPTION; - break; - } - SkillDialogueHandler handler = new SkillDialogueHandler(player, dialLength, items) { - - @Override - public void create(final int amount, int index) { - final Fletching.FletchingItems item = Fletching.getEntries(log.getId())[index]; - player.getPulseManager().run(new FletchingPulse(player, log, amount, item)); - } - - @Override - public int getAll(int index) { - return player.getInventory().getAmount(log); - } - - }; - handler.open(); - return true; - } - return false; - } -} diff --git a/Server/src/main/content/global/skill/fletching/FletchingPulse.java b/Server/src/main/content/global/skill/fletching/FletchingPulse.java deleted file mode 100644 index b02e40572..000000000 --- a/Server/src/main/content/global/skill/fletching/FletchingPulse.java +++ /dev/null @@ -1,145 +0,0 @@ -package content.global.skill.fletching; - -import core.tools.RandomFunction; -import core.game.node.entity.player.link.diary.DiaryType; -import core.game.world.map.zone.ZoneBorders; -import core.game.node.entity.skill.SkillPulse; -import core.game.node.entity.skill.Skills; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; -import core.game.world.update.flag.context.Animation; -import core.tools.StringUtils; -import content.data.Quests; - -/** - * fletching skill pulse - * @author ceik - */ -public final class FletchingPulse extends SkillPulse { - - /** - * Seers bank zone borders for the diary task - */ - private static final ZoneBorders bankZone = new ZoneBorders(2721,3493,2730,3487); - - /** - * Represents the animation used in this generic pulse. - */ - private static final Animation ANIMATION = new Animation(1248); - - /** - * Represents the item we are fletching. - */ - private Fletching.FletchingItems fletch; - - /** - * Represents the amount to fletch. - */ - private int amount = 0; - - /** - * Represents the amount to arrows fletched (for ogre arrow shafts which is a random number from 2-6). - */ - private int finalAmount = 0; - - /** - * Constructs a new {@code FletchingPulse.java} {@code Object}. - * @param player - * @param node - */ - public FletchingPulse(final Player player, final Item node, final int amount, final Fletching.FletchingItems fletch) { - super(player, node); - this.amount = amount; - this.fletch = fletch; - } - - @Override - public boolean checkRequirements() { - if (player.getSkills().getLevel(Skills.FLETCHING) < fletch.level) { - player.getDialogueInterpreter().sendDialogue("You need a Fletching skill of " + fletch.level + " or above to make " + (StringUtils.isPlusN(fletch.getItem().getName().replace("(u)", "").trim()) ? "an" : "a") + " " + fletch.getItem().getName().replace("(u)", "").trim()); - return false; - } - if (amount > player.getInventory().getAmount(node)) { - amount = player.getInventory().getAmount(node); - } - if (fletch == Fletching.FletchingItems.OGRE_ARROW_SHAFT) { - if (player.getQuestRepository().getQuest(Quests.BIG_CHOMPY_BIRD_HUNTING).getStage(player) == 0) { - player.getPacketDispatch().sendMessage("You must have started Big Chompy Bird Hunting to make those."); - return false; - } - } - if (fletch == Fletching.FletchingItems.OGRE_COMP_BOW) { - // Technically, this isn't supposed to show up till you've asked Grish. - if (player.getQuestRepository().getQuest(Quests.ZOGRE_FLESH_EATERS).getStage(player) < 8) { - player.getPacketDispatch().sendMessage("You must have started Zogre Flesh Eaters and asked Grish to make this."); - return false; - } - if (!player.getInventory().contains(2859, 1)) { - player.getPacketDispatch().sendMessage("You need to have Wolf Bones in order to make this."); - return false; - } - } - return true; - } - - @Override - public void animate() { - player.animate(ANIMATION); - } - - @Override - public boolean reward() { - if(bankZone.insideBorder(player) && fletch == Fletching.FletchingItems.MAGIC_SHORTBOW) { - player.getAchievementDiaryManager().finishTask(player, DiaryType.SEERS_VILLAGE, 2, 2); - } - if (getDelay() == 1) { - super.setDelay(4); - return false; - } - if (player.getInventory().remove(node)) { - final Item item = new Item(fletch.id,fletch.amount); - if ( fletch == Fletching.FletchingItems.OGRE_ARROW_SHAFT ) { - // The amount of shafts given is random; between two and six will be made. - finalAmount = RandomFunction.random(2,6); - item.setAmount(finalAmount); - } - if ( fletch == Fletching.FletchingItems.OGRE_COMP_BOW ) { - if (!player.getInventory().contains(2859, 1)) { - return false; - } else { - player.getInventory().remove(new Item(2859)); - } - } - player.getInventory().add(item); - player.getSkills().addExperience(Skills.FLETCHING, fletch.experience, true); - String message = getMessage(); - player.getPacketDispatch().sendMessage(message); - - if (fletch.id == Fletching.FletchingItems.MAGIC_SHORTBOW.id - && (new ZoneBorders(2721, 3489, 2724, 3493, 0).insideBorder(player) - || new ZoneBorders(2727, 3487, 2730, 3490, 0).insideBorder(player)) - && !player.getAchievementDiaryManager().hasCompletedTask(DiaryType.SEERS_VILLAGE, 2, 2)) { - player.setAttribute("/save:diary:seers:fletch-magic-short-bow", true); - } - } else { - return true; - } - amount--; - return amount == 0; - } - - /** - * Method used to get the message of the fletch. - * @return the message. - */ - public String getMessage() { - switch (fletch) { - case ARROW_SHAFT: - return "You carefully cut the wood into 15 arrow shafts."; - case OGRE_ARROW_SHAFT: - return "You carefully cut the wood into " + finalAmount + " arrow shafts."; - default: - return "You carefully cut the wood into " + (StringUtils.isPlusN(fletch.getItem().getName()) ? "an" : "a") + " " + fletch.getItem().getName().replace("(u)", "").trim() + "."; - } - } -} diff --git a/Server/src/main/content/global/skill/fletching/GemBoltListener.kt b/Server/src/main/content/global/skill/fletching/GemBoltListener.kt deleted file mode 100644 index 94c09c65b..000000000 --- a/Server/src/main/content/global/skill/fletching/GemBoltListener.kt +++ /dev/null @@ -1,69 +0,0 @@ -package content.global.skill.fletching - -import content.global.skill.fletching.Fletching.GemBolts -import content.global.skill.fletching.items.gem.GemBoltCutPulse -import content.global.skill.fletching.items.gem.GemBoltPulse -import core.api.amountInInventory -import core.game.dialogue.SkillDialogueHandler -import core.game.interaction.IntType -import core.game.interaction.InteractionListener -import core.game.node.item.Item -import core.net.packet.PacketRepository -import core.net.packet.context.ChildPositionContext -import core.net.packet.out.RepositionChild -import org.rs09.consts.Items -import kotlin.math.min - -class GemBoltListener : InteractionListener { - val gems = intArrayOf( - Items.OYSTER_PEARL_411, - Items.OYSTER_PEARLS_413, - Items.OPAL_1609, - Items.JADE_1611, - Items.RED_TOPAZ_1613, - Items.SAPPHIRE_1607, - Items.EMERALD_1605, - Items.RUBY_1603, - Items.DIAMOND_1601, - Items.DRAGONSTONE_1615, - Items.ONYX_6573 - ) - val boltBases = GemBolts.values().map { it.base }.toIntArray() - val boltTips = GemBolts.values().map { it.tip }.toIntArray() - - override fun defineListeners() { - onUseWith(IntType.ITEM, Items.CHISEL_1755, *gems) { player, used, with -> - val gem = Fletching.gemMap[with.id] ?: return@onUseWith true - - object : SkillDialogueHandler(player, SkillDialogue.ONE_OPTION, Item(gem.gem)) { - override fun create(amount: Int, index: Int) { - player.pulseManager.run(GemBoltCutPulse(player, used as? Item, gem, amount)) - } - - override fun getAll(index: Int): Int { - return player.inventory.getAmount(gem.gem) - } - }.open() - return@onUseWith true - } - - onUseWith(IntType.ITEM, boltBases, *boltTips) {player, used, with -> - val bolt = Fletching.tipMap[with.id] ?: return@onUseWith true - if (used.id != bolt.base || with.id != bolt.tip) return@onUseWith true - - - val handler: SkillDialogueHandler = - object : SkillDialogueHandler(player, SkillDialogue.MAKE_SET_ONE_OPTION, Item(bolt.product)) { - override fun create(amount: Int, index: Int) { - player.pulseManager.run(GemBoltPulse(player, used as? Item, bolt, amount)) - } - - override fun getAll(index: Int): Int { - return min(amountInInventory(player, used.id), amountInInventory(player, with.id)) - } - } - handler.open() - return@onUseWith true - } - } -} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/Zones.kt b/Server/src/main/content/global/skill/fletching/Zones.kt new file mode 100644 index 000000000..8c10f0c4f --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/Zones.kt @@ -0,0 +1,21 @@ +package content.global.skill.fletching + +import core.game.node.Node +import core.game.world.map.zone.ZoneBorders + +object Zones { + val seersMagicShortbowAchievementZones = arrayOf( + ZoneBorders(2721, 3489, 2724, 3493, 0), + ZoneBorders(2727, 3487, 2730, 3490, 0), + ZoneBorders(2721, 3493, 2730, 3487) + ) + + fun inAnyZone(node: Node, zones: Array): Boolean { + for (zone in zones) { + if (zone.insideBorder(node)) { + return true + } + } + return false + } +} diff --git a/Server/src/main/content/global/skill/fletching/arrow/ArrowCraftInfo.kt b/Server/src/main/content/global/skill/fletching/arrow/ArrowCraftInfo.kt new file mode 100644 index 000000000..087c688a9 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/arrow/ArrowCraftInfo.kt @@ -0,0 +1,24 @@ +package content.global.skill.fletching.arrow + +import org.rs09.consts.Items + +enum class ArrowCraftInfo(val tipItemId: Int, val arrowItemId: Int, val level: Int, val experience: Double) { + BRONZE_ARROW(Items.BRONZE_ARROWTIPS_39, Items.BRONZE_ARROW_882, 1, 1.3), + IRON_ARROW(Items.IRON_ARROWTIPS_40, Items.IRON_ARROW_884, 15, 2.5), + STEEL_ARROW(Items.STEEL_ARROWTIPS_41, Items.STEEL_ARROW_886, 30, 5.0), + MITHRIL_ARROW(Items.MITHRIL_ARROWTIPS_42, Items.MITHRIL_ARROW_888, 45, 7.5), + ADAMANT_ARROW(Items.ADAMANT_ARROWTIPS_43, Items.ADAMANT_ARROW_890, 60, 10.0), + RUNE_ARROW(Items.RUNE_ARROWTIPS_44, Items.RUNE_ARROW_892, 75, 12.5), + DRAGON_ARROW(Items.DRAGON_ARROWTIPS_11237,Items.DRAGON_ARROW_11212, 90, 15.0), + BROAD_ARROW(Items.BROAD_ARROW_HEADS_13278, Items.BROAD_ARROW_4160, 52, 15.0); + + companion object { + private val arrowCraftInfoByTipId = values().associateBy { it.tipItemId } + + val arrowTipIds: IntArray = values().map { arrowCraftInfo: ArrowCraftInfo -> arrowCraftInfo.tipItemId }.toIntArray() + + fun fromTipId(tipId: Int) : ArrowCraftInfo? { + return arrowCraftInfoByTipId[tipId] + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/arrow/ArrowListeners.kt b/Server/src/main/content/global/skill/fletching/arrow/ArrowListeners.kt new file mode 100644 index 000000000..2c061b3cb --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/arrow/ArrowListeners.kt @@ -0,0 +1,112 @@ +package content.global.skill.fletching.arrow + +import content.global.skill.fletching.Feathers +import core.api.* +import core.game.dialogue.SkillDialogueHandler +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import org.rs09.consts.Items + +@Suppress("unused") // Reflectively loaded +class ArrowListeners : InteractionListener { + private val arrowShaft = Items.ARROW_SHAFT_52 + val headlessArrow = Items.HEADLESS_ARROW_53 + + companion object { + const val FLIGHTED_OGRE_ARROW_LEVEL = 5 + fun sendLevelCheckFailDialog(player: Player, level: Int) { + sendDialogue( + player, + "You need a fletching level of $level to do this." + ) + } + } + + override fun defineListeners() { + onUseWith(IntType.ITEM, arrowShaft, *Feathers.all) { player, shaft, feather -> + val handler: SkillDialogueHandler = + object : SkillDialogueHandler(player, SkillDialogue.MAKE_SET_ONE_OPTION, Item(headlessArrow)) { + override fun create(amount: Int, index: Int) { + if (!hasSpaceFor(player, Item(headlessArrow))) { + sendDialogue(player, "You do not have enough inventory space.") + return + } + HeadlessArrowCraftScript(player, feather.id, amount).invoke() + } + + override fun getAll(index: Int): Int { + return amountInInventory(player, headlessArrow) + } + } + handler.open() + return@onUseWith true + } + + onUseWith(IntType.ITEM, headlessArrow, *ArrowCraftInfo.arrowTipIds) { player, headlessArrow, arrowTip -> + val arrowCraftInfo = ArrowCraftInfo.fromTipId(arrowTip.id) ?: return@onUseWith false + val handler: SkillDialogueHandler = + object : SkillDialogueHandler(player, SkillDialogue.MAKE_SET_ONE_OPTION, Item(arrowCraftInfo.arrowItemId)) { + override fun create(amount: Int, index: Int) { + if (!playerMeetsInitialRequirements()) return + TippedArrowCraftScript(player, arrowCraftInfo, amount).invoke() + } + + private fun playerMeetsInitialRequirements(): Boolean { + if (arrowCraftInfo == ArrowCraftInfo.BROAD_ARROW && !getSlayerFlags(player).isBroadsUnlocked()) { + sendDialogue(player, "You need to unlock the ability to create broad arrows.") + return false + } + + if (getDynLevel(player, Skills.FLETCHING) < arrowCraftInfo.level) { + sendLevelCheckFailDialog(player, arrowCraftInfo.level) + return false + } + + if (!hasSpaceFor(player, Item(arrowCraftInfo.arrowItemId))) { + sendDialogue(player, "You do not have enough inventory space.") + return false + } + return true + } + + override fun getAll(index: Int): Int { + return amountInInventory(player, arrowTip.id) + } + } + handler.open() + return@onUseWith true + } + + onUseWith(IntType.ITEM, Items.OGRE_ARROW_SHAFT_2864, *Feathers.all) { player, ogreArrowShaft, feather -> + val handler: SkillDialogueHandler = + object : SkillDialogueHandler(player, SkillDialogue.MAKE_SET_ONE_OPTION, Item(Items.FLIGHTED_OGRE_ARROW_2865)) { + override fun create(amount: Int, index: Int) { + if (!playerMeetsInitialRequirements()) return + FlightedOgreArrowCraftScript(player, feather.id, amount).invoke() + } + + private fun playerMeetsInitialRequirements(): Boolean { + if (getDynLevel(player, Skills.FLETCHING) < FLIGHTED_OGRE_ARROW_LEVEL) { + sendLevelCheckFailDialog(player, FLIGHTED_OGRE_ARROW_LEVEL) + return false + } + + if (!hasSpaceFor(player, Item(Items.FLIGHTED_OGRE_ARROW_2865))) { + sendDialogue(player, "You do not have enough inventory space.") + return false + } + return true + } + + override fun getAll(index: Int): Int { + return amountInInventory(player, Items.OGRE_ARROW_SHAFT_2864) + } + } + handler.open() + return@onUseWith true + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/arrow/FlightedOgreArrowCraftScript.kt b/Server/src/main/content/global/skill/fletching/arrow/FlightedOgreArrowCraftScript.kt new file mode 100644 index 000000000..126b72aee --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/arrow/FlightedOgreArrowCraftScript.kt @@ -0,0 +1,88 @@ +package content.global.skill.fletching.arrow + +import core.api.* +import core.game.interaction.Clocks +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import org.rs09.consts.Items +import kotlin.math.min + +/** + * Defines the queueScript for creating a flighted ogre arrow + * @author 'Vexia + * @param player the player. + * @param idOfFeatherUsed the feather to attach to the shaft. + * @param sets the amount of sets to complete. + */ +class FlightedOgreArrowCraftScript( + private val player: Player, + private val idOfFeatherUsed: Int, + private val sets: Int +) { + + private val flightedOgreArrow = Items.FLIGHTED_OGRE_ARROW_2865 + private val ogreArrowShaft = Items.OGRE_ARROW_SHAFT_2864 + private val maximumFlightedOgreArrowsCraftableInOneStage = 6 + + private val feathersPerArrow = 4 + + private val initialDelay = 1 + private val subsequentDelay = 3 + + private fun getAmountToCraftForThisStage(shaftsInInventory: Int, feathersInInventory: Int): Int { + val limitOfCraftableArrowsDueToFeathers = feathersInInventory / feathersPerArrow + + val totalCraftableArrows = min(shaftsInInventory, limitOfCraftableArrowsDueToFeathers) + return if (totalCraftableArrows > maximumFlightedOgreArrowsCraftableInOneStage) { + maximumFlightedOgreArrowsCraftableInOneStage + } else { + totalCraftableArrows + } + } + + fun invoke() { + queueScript(player, initialDelay) { stage -> + if (!clockReady(player, Clocks.SKILLING)) return@queueScript keepRunning(player) + + if (getDynLevel(player, Skills.FLETCHING) < ArrowListeners.FLIGHTED_OGRE_ARROW_LEVEL) { + ArrowListeners.sendLevelCheckFailDialog(player, ArrowListeners.FLIGHTED_OGRE_ARROW_LEVEL) + return@queueScript stopExecuting(player) // Check each iteration since dynLevel can change (status effects ending, skill assist session end...) + } + + val featherAmount = amountInInventory(player, idOfFeatherUsed) + val shaftAmount = amountInInventory(player, ogreArrowShaft) + + val amountToCraft = getAmountToCraftForThisStage(shaftAmount, featherAmount) + + if ( + removeItemsIfPlayerHasEnough( + player, + Item(ogreArrowShaft, amountToCraft), + Item(idOfFeatherUsed, amountToCraft * feathersPerArrow) + ) + ) { + when (amountToCraft) { + 1 -> sendMessage(player, "You attach $feathersPerArrow feathers to a shaft.") + else -> { + sendMessage( + player, + "You attach ${amountToCraft * feathersPerArrow} feathers to $amountToCraft arrow shafts." + ) + } + } + + rewardXP(player, Skills.FLETCHING, amountToCraft.toDouble()) + addItem(player, flightedOgreArrow, amountToCraft) + } else { + return@queueScript stopExecuting(player) + } + + if (stage >= sets - 1) { + return@queueScript stopExecuting(player) + } + + return@queueScript delayClock(player, Clocks.SKILLING, subsequentDelay, true) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/arrow/HeadlessArrowCraftScript.kt b/Server/src/main/content/global/skill/fletching/arrow/HeadlessArrowCraftScript.kt new file mode 100644 index 000000000..40ef4c235 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/arrow/HeadlessArrowCraftScript.kt @@ -0,0 +1,76 @@ +package content.global.skill.fletching.arrow + +import core.api.* +import core.game.interaction.Clocks +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import org.rs09.consts.Items +import kotlin.math.min + +/** + * Defines the queueScript for creating a headless arrow + * @author 'Vexia + * @param player the player. + * @param idOfFeatherUsed the feather to attach to the shaft. + * @param sets the amount of sets to complete. + */ +class HeadlessArrowCraftScript( + private val player: Player, + private val idOfFeatherUsed: Int, + private val sets: Int +) { + + private val headlessArrow = Items.HEADLESS_ARROW_53 + private val arrowShaft = Items.ARROW_SHAFT_52 + private val maximumHeadlessArrowsCraftableInOneStage = 15 + + private val initialDelay = 1 + private val subsequentDelay = 3 + + private fun getAmountToCraftForThisStage(shaftsInInventory: Int, feathersInInventory: Int): Int { + val smallerItemAmount = min(shaftsInInventory, feathersInInventory) + return if (smallerItemAmount > maximumHeadlessArrowsCraftableInOneStage) { + maximumHeadlessArrowsCraftableInOneStage + } else { + smallerItemAmount + } + } + + fun invoke() { + queueScript(player, initialDelay) { stage -> + if (!clockReady(player, Clocks.SKILLING)) return@queueScript keepRunning(player) + + val featherAmount = amountInInventory(player, idOfFeatherUsed) + val shaftAmount = amountInInventory(player, arrowShaft) + + val amountToCraft = getAmountToCraftForThisStage(shaftAmount, featherAmount) + + if ( + removeItemsIfPlayerHasEnough( + player, + Item(arrowShaft, amountToCraft), + Item(idOfFeatherUsed, amountToCraft) + ) + ) { + when (amountToCraft) { + 1 -> sendMessage(player, "You attach a feather to a shaft.") + else -> { + sendMessage(player, "You attach feathers to $amountToCraft arrow shafts.") + } + } + + rewardXP(player, Skills.FLETCHING, amountToCraft.toDouble()) + addItem(player, headlessArrow, amountToCraft) + } else { + return@queueScript stopExecuting(player) + } + + if (stage >= sets - 1) { + return@queueScript stopExecuting(player) + } + + return@queueScript delayClock(player, Clocks.SKILLING, subsequentDelay, true) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/arrow/TippedArrowCraftScript.kt b/Server/src/main/content/global/skill/fletching/arrow/TippedArrowCraftScript.kt new file mode 100644 index 000000000..d4249b660 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/arrow/TippedArrowCraftScript.kt @@ -0,0 +1,79 @@ +package content.global.skill.fletching.arrow + +import core.api.* +import core.game.interaction.Clocks +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import org.rs09.consts.Items +import kotlin.math.min + +/** + * Represents the arrow head pulse to complete the headless arrow. + * @author 'Vexia + * @param player the player. + * @param arrowCraftInfo provides information about the arrow we're crafting. + * @param sets the amount of sets to complete. + */ +class TippedArrowCraftScript( + private val player: Player, + private val arrowCraftInfo: ArrowCraftInfo, + private val sets: Int +) { + + private val headlessArrow = Items.HEADLESS_ARROW_53 + private val maximumArrowsCraftableInOneStage = 15 + private val initialDelay = 1 + private val subsequentDelay = 3 + + private fun getAmountToCraftForThisStage(headlessArrowsInInventory: Int, tipsInInventory: Int): Int { + val smallerItemAmount = min(headlessArrowsInInventory, tipsInInventory) + return if (smallerItemAmount > maximumArrowsCraftableInOneStage) { + maximumArrowsCraftableInOneStage + } else { + smallerItemAmount + } + } + + fun invoke() { + queueScript(player, initialDelay) { stage -> + if (!clockReady(player, Clocks.SKILLING)) return@queueScript keepRunning(player) + + if (getDynLevel(player, Skills.FLETCHING) < arrowCraftInfo.level) { + ArrowListeners.sendLevelCheckFailDialog(player, arrowCraftInfo.level) + return@queueScript stopExecuting(player) // Check each iteration since dynLevel can change (status effects ending, skill assist session end...) + } + + val tipsInInventory = amountInInventory(player, arrowCraftInfo.tipItemId) + val headlessArrowsInInventory = amountInInventory(player, headlessArrow) + + val amountToCraft = getAmountToCraftForThisStage(headlessArrowsInInventory, tipsInInventory) + + if ( + removeItemsIfPlayerHasEnough( + player, + Item(headlessArrow, amountToCraft), + Item(arrowCraftInfo.tipItemId, amountToCraft) + ) + ) { + when (amountToCraft) { + 1 -> sendMessage(player, "You attach an arrow head to an arrow shaft.") + else -> { + sendMessage(player, "You attach arrow heads to $amountToCraft arrow shafts.") + } + } + + rewardXP(player, Skills.FLETCHING, arrowCraftInfo.experience * amountToCraft) + addItem(player, arrowCraftInfo.arrowItemId, amountToCraft) + } else { + return@queueScript stopExecuting(player) + } + + if (stage >= sets - 1) { + return@queueScript stopExecuting(player) + } + + return@queueScript delayClock(player, Clocks.SKILLING, subsequentDelay, true) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/bolts/BoltCraftInfo.kt b/Server/src/main/content/global/skill/fletching/bolts/BoltCraftInfo.kt new file mode 100644 index 000000000..497332a59 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/bolts/BoltCraftInfo.kt @@ -0,0 +1,39 @@ +package content.global.skill.fletching.bolts + +import org.rs09.consts.Items + +/** + * Provides information pertaining to crafting different bolts + * @property unfinishedBoltItemId the unfinished bolt (unf) Item id used to craft the dart. + * @property finishedItemId the resulting finished bolt Item id. + * @property level the level required to craft. + * @property experience the experience gained by crafting. + */ +enum class BoltCraftInfo( + val unfinishedBoltItemId: Int, + val finishedItemId: Int, + val level: Int, + val experience: Double +) { + BRONZE_BOLT(Items.BRONZE_BOLTS_UNF_9375, Items.BRONZE_BOLTS_877, 9, 0.5), + BLURITE_BOLT(Items.BLURITE_BOLTS_UNF_9376, Items.BLURITE_BOLTS_9139, 24, 1.0), + IRON_BOLT(Items.IRON_BOLTS_UNF_9377, Items.IRON_BOLTS_9140, 39, 1.5), + SILVER_BOLT(Items.SILVER_BOLTS_UNF_9382, Items.SILVER_BOLTS_9145, 43, 2.5), + STEEL_BOLT(Items.STEEL_BOLTS_UNF_9378, Items.STEEL_BOLTS_9141, 46, 3.5), + MITHRIL_BOLT(Items.MITHRIL_BOLTS_UNF_9379, Items.MITHRIL_BOLTS_9142, 54, 5.0), + ADAMANTITE_BOLT(Items.ADAMANT_BOLTSUNF_9380, Items.ADAMANT_BOLTS_9143, 61, 7.0), + RUNITE_BOLT(Items.RUNITE_BOLTS_UNF_9381, Items.RUNE_BOLTS_9144, 69, 10.0), + BROAD_BOLT(Items.UNFINISHED_BROAD_BOLTS_13279, Items.BROAD_TIPPED_BOLTS_13280, 55, 3.0); + + companion object { + private val boltCraftInfoByUnfinishedBoltIds = values().associateBy { it.unfinishedBoltItemId } + + val unfinishedBoltIds: IntArray = BoltCraftInfo.values() + .map { unfinishedBoltToBoltMapping: BoltCraftInfo -> unfinishedBoltToBoltMapping.unfinishedBoltItemId } + .toIntArray() + + fun fromUnfinishedBoltId(unfinishedBoltId: Int): BoltCraftInfo? { + return boltCraftInfoByUnfinishedBoltIds[unfinishedBoltId] + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/bolts/BoltCraftScript.kt b/Server/src/main/content/global/skill/fletching/bolts/BoltCraftScript.kt new file mode 100644 index 000000000..e525e7055 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/bolts/BoltCraftScript.kt @@ -0,0 +1,79 @@ +package content.global.skill.fletching.bolts + +import core.api.* +import core.game.interaction.Clocks +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import kotlin.math.min + +/** + * Represents the bolt pulse class to make bolts. + * @author ceik + * @param player the player. + * @param boltCraftInfo contains information about what bolt we're crafting + * @param feather the feather we're using to craft + * @param sets the amount of sets to craft + */ +class BoltCraftScript( + private val player: Player, + private val boltCraftInfo: BoltCraftInfo, + private val feather: Item, + private val sets: Int +) { + + private val maximumBoltsCraftableInOneStage = 10 + private val initialDelay = 1 + private val subsequentDelay = 3 + + private fun getAmountToCraftForThisSet(feathersInInventory: Int, unfinishedBoltsInInventory: Int): Int { + val smallerItemAmount = min(feathersInInventory, unfinishedBoltsInInventory) + return if (smallerItemAmount > maximumBoltsCraftableInOneStage) { + maximumBoltsCraftableInOneStage + } else { + smallerItemAmount + } + } + + fun invoke() { + queueScript(player, initialDelay) { stage -> + if (!clockReady(player, Clocks.SKILLING)) return@queueScript keepRunning(player) + + if (getDynLevel(player, Skills.FLETCHING) < boltCraftInfo.level) { + BoltListeners.sendLevelCheckFailDialog(player, boltCraftInfo.level) + return@queueScript stopExecuting(player) // Check each iteration since dynLevel can change (status effects ending, skill assist session end...) + } + + val featherAmount = amountInInventory(player, feather.id) + val unfinishedBoltAmount = amountInInventory(player, boltCraftInfo.unfinishedBoltItemId) + + val amountToCraft = getAmountToCraftForThisSet(featherAmount, unfinishedBoltAmount) + + if ( + removeItemsIfPlayerHasEnough( + player, + Item(feather.id, amountToCraft), + Item(boltCraftInfo.unfinishedBoltItemId, amountToCraft) + ) + ) { + when (amountToCraft) { + 1 -> sendMessage(player, "You attach a feather to a bolt.") + else -> { + sendMessage(player, "You fletch $amountToCraft bolts") + } + } + + rewardXP(player, Skills.FLETCHING, boltCraftInfo.experience * amountToCraft) + addItem(player, boltCraftInfo.finishedItemId, amountToCraft) + } else { + return@queueScript stopExecuting(player) + } + + if (stage >= sets - 1) { + return@queueScript stopExecuting(player) + } + + return@queueScript delayClock(player, Clocks.SKILLING, subsequentDelay, true) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/bolts/BoltListeners.kt b/Server/src/main/content/global/skill/fletching/bolts/BoltListeners.kt new file mode 100644 index 000000000..ad9d806d8 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/bolts/BoltListeners.kt @@ -0,0 +1,65 @@ +package content.global.skill.fletching.bolts + +import content.global.skill.fletching.Feathers +import core.api.* +import core.game.dialogue.SkillDialogueHandler +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import kotlin.math.min + +@Suppress("unused") // Reflectively loaded +class BoltListeners : InteractionListener { + override fun defineListeners() { + onUseWith(IntType.ITEM, Feathers.all, *BoltCraftInfo.unfinishedBoltIds) { player, feather, unfinishedBolt -> + val boltCraftInfo = BoltCraftInfo.fromUnfinishedBoltId(unfinishedBolt.id) ?: return@onUseWith false + val handler: SkillDialogueHandler = + object : SkillDialogueHandler( + player, + SkillDialogue.MAKE_SET_ONE_OPTION, + Item(boltCraftInfo.finishedItemId) + ) { + override fun create(amount: Int, index: Int) { + if (!playerMeetsInitialRequirements()) return + BoltCraftScript(player, boltCraftInfo, feather.asItem(), amount).invoke() + } + + private fun playerMeetsInitialRequirements(): Boolean { + if (boltCraftInfo == BoltCraftInfo.BROAD_BOLT && !getSlayerFlags(player).isBroadsUnlocked()) { + sendDialogue(player, "You need to unlock the ability to create broad bolts.") + return false + } + if (getDynLevel(player, Skills.FLETCHING) < boltCraftInfo.level) { + sendLevelCheckFailDialog(player, boltCraftInfo.level) + return false + } + if (!hasSpaceFor(player, Item(boltCraftInfo.finishedItemId))) { + sendDialogue(player, "You do not have enough inventory space.") + return false + } + + return true + } + + override fun getAll(index: Int): Int { + return min( + amountInInventory(player, feather.id), + amountInInventory(player, unfinishedBolt.id) + ) + } + } + handler.open() + return@onUseWith true + } + } + companion object { + fun sendLevelCheckFailDialog(player: Player, level: Int) { + sendDialogue( + player, + "You need a fletching level of $level in order to do this." + ) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/crossbow/LimbingListener.kt b/Server/src/main/content/global/skill/fletching/crossbow/LimbingListener.kt new file mode 100644 index 000000000..0876d54c1 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/crossbow/LimbingListener.kt @@ -0,0 +1,80 @@ +package content.global.skill.fletching.crossbow + +import core.api.getDynLevel +import core.api.hasSpaceFor +import core.api.sendDialogue +import core.api.sendMessage +import core.game.dialogue.SkillDialogueHandler +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import core.net.packet.PacketRepository +import core.net.packet.context.ChildPositionContext +import core.net.packet.out.RepositionChild +import org.rs09.consts.Components + +@Suppress("unused") // Reflectively loaded +class LimbingListener : InteractionListener { + override fun defineListeners() { + onUseWith( + IntType.ITEM, + UnfinishedCrossbowCraftInfo.limbIds, + *UnfinishedCrossbowCraftInfo.stockIds + ) { player, limb, stock -> + val unfinishedCrossbowCraftInfo = UnfinishedCrossbowCraftInfo.forStockId(stock.id) ?: return@onUseWith false + if (unfinishedCrossbowCraftInfo.limbItemId != limb.id) { + sendMessage(player, "That's not the right limb to attach to that stock.") + return@onUseWith true + } + val handler: SkillDialogueHandler = object : SkillDialogueHandler( + player, + SkillDialogue.ONE_OPTION, + Item(unfinishedCrossbowCraftInfo.unstrungCrossbowItemId) + ) { + override fun create(amount: Int, index: Int) { + if (!playerMeetsInitialRequirements()) return + LimbingScript(player, unfinishedCrossbowCraftInfo, amount).invoke() + } + + private fun playerMeetsInitialRequirements(): Boolean { + if (getDynLevel(player, Skills.FLETCHING) < unfinishedCrossbowCraftInfo.level) { + sendLevelCheckFailDialog(player, unfinishedCrossbowCraftInfo.level) + return false + } + if (!hasSpaceFor(player, Item(unfinishedCrossbowCraftInfo.unstrungCrossbowItemId))) { + sendDialogue(player, "You do not have enough inventory space.") + return false + } + + return true + } + + override fun getAll(index: Int): Int { + return player.inventory.getAmount(stock.asItem()) + } + } + handler.open() + fixTextOverlappingTheCrossbowIcon(player) + return@onUseWith true + } + } + + + private fun fixTextOverlappingTheCrossbowIcon(player: Player) { + PacketRepository.send( + RepositionChild::class.java, + ChildPositionContext(player, Components.SKILL_MULTI1_309, 2, 210, 10) + ) + } + + companion object { + fun sendLevelCheckFailDialog(player: Player, level: Int) { + sendDialogue( + player, + "You need a fletching level of $level to attach these limbs." + ) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/crossbow/LimbingScript.kt b/Server/src/main/content/global/skill/fletching/crossbow/LimbingScript.kt new file mode 100644 index 000000000..07fb8ac43 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/crossbow/LimbingScript.kt @@ -0,0 +1,56 @@ +package content.global.skill.fletching.crossbow + +import core.api.* +import core.game.interaction.Clocks +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item + +/** + * The queue script for attaching limbs. + * @author Ceikry + * @param player The player + * @param unfinishedCrossbowCraftInfo info about the unfinished (unstrung) crossbow we're crafting + * @param amount to create + */ +class LimbingScript( + private val player: Player, + private val unfinishedCrossbowCraftInfo: UnfinishedCrossbowCraftInfo, + private val amount: Int +) { + + private val initialDelay = 1 + private val subsequentDelay = 6 + + fun invoke() { + queueScript(player, initialDelay) { stage -> + if (!clockReady(player, Clocks.SKILLING)) return@queueScript keepRunning(player) + + if (getDynLevel(player, Skills.FLETCHING) < unfinishedCrossbowCraftInfo.level) { + LimbingListener.sendLevelCheckFailDialog(player, unfinishedCrossbowCraftInfo.level) + return@queueScript stopExecuting(player) // Check each iteration since dynLevel can change (status effects ending, skill assist session end...) + } + + if ( + removeItemsIfPlayerHasEnough( + player, + Item(unfinishedCrossbowCraftInfo.stockItemId), + Item(unfinishedCrossbowCraftInfo.limbItemId) + ) + ) { + addItem(player, unfinishedCrossbowCraftInfo.unstrungCrossbowItemId, 1) + rewardXP(player, Skills.FLETCHING, unfinishedCrossbowCraftInfo.experience) + sendMessage(player, "You attach the metal limbs to the stock.") + animate(player, unfinishedCrossbowCraftInfo.animation) + } else { + return@queueScript stopExecuting(player) + } + + if (stage >= amount - 1) { + return@queueScript stopExecuting(player) + } + + return@queueScript delayClock(player, Clocks.SKILLING, subsequentDelay, true) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/crossbow/UnfinishedCrossbowCraftInfo.kt b/Server/src/main/content/global/skill/fletching/crossbow/UnfinishedCrossbowCraftInfo.kt new file mode 100644 index 000000000..7250b02e4 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/crossbow/UnfinishedCrossbowCraftInfo.kt @@ -0,0 +1,55 @@ +package content.global.skill.fletching.crossbow + +import core.game.world.update.flag.context.Animation +import org.rs09.consts.Items + +/** + * Provides information pertaining to crafting different unfinished (unstrung) crossbows + * @author 'Vexia + * @param stockItemId item id of the stock. + * @param limbItemId item id of the limb. + * @param unstrungCrossbowItemId item id of the product. + * @param level the level. + * @param experience the experience. + * @param animation the animation. + */ +enum class UnfinishedCrossbowCraftInfo + ( + val stockItemId: Int, + val limbItemId: Int, + val unstrungCrossbowItemId: Int, + val level: Int, + val experience: Double, + val animation: Animation +) { + WOODEN_STOCK(Items.WOODEN_STOCK_9440, Items.BRONZE_LIMBS_9420, Items.BRONZE_CBOW_U_9454, 9, 12.0, Animation(4436)), + OAK_STOCK(Items.OAK_STOCK_9442, Items.BLURITE_LIMBS_9422, Items.BLURITE_CROSSBOW_9176, 24, 32.0, Animation(4437)), + WILLOW_STOCK(Items.WILLOW_STOCK_9444, Items.IRON_LIMBS_9423, Items.IRON_CBOW_U_9457, 39, 44.0, Animation(4438)), + TEAK_STOCK(Items.TEAK_STOCK_9446, Items.STEEL_LIMBS_9425, Items.STEEL_CBOW_U_9459, 46, 54.0, Animation(4439)), + MAPLE_STOCK(Items.MAPLE_STOCK_9448, Items.MITHRIL_LIMBS_9427, Items.MITHRIL_CBOW_U_9461, 54, 64.0, Animation(4440)), + MAHOGANY_STOCK( + Items.MAHOGANY_STOCK_9450, + Items.ADAMANTITE_LIMBS_9429, + Items.ADAMANT_CBOW_U_9463, + 61, + 82.0, + Animation(4441) + ), + YEW_STOCK(Items.YEW_STOCK_9452, Items.RUNITE_LIMBS_9431, Items.RUNITE_CBOW_U_9465, 69, 100.0, Animation(4442)); + + companion object { + private val unfinishedCrossbowCraftInfoByStockId = values().associateBy { it.stockItemId } + + val limbIds: IntArray = + values().map { unfinishedCrossbowCraftInfo: UnfinishedCrossbowCraftInfo -> unfinishedCrossbowCraftInfo.limbItemId } + .toIntArray() + + val stockIds: IntArray = + values().map { unfinishedCrossbowCraftInfo: UnfinishedCrossbowCraftInfo -> unfinishedCrossbowCraftInfo.stockItemId } + .toIntArray() + + fun forStockId(stockId: Int): UnfinishedCrossbowCraftInfo? { + return unfinishedCrossbowCraftInfoByStockId[stockId] + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/darts/DartCraftInfo.kt b/Server/src/main/content/global/skill/fletching/darts/DartCraftInfo.kt new file mode 100644 index 000000000..488075b80 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/darts/DartCraftInfo.kt @@ -0,0 +1,36 @@ +package content.global.skill.fletching.darts + +import org.rs09.consts.Items + +/** + * Provides information pertaining to crafting different darts + * @author 'Vexia + * @property tipItemId the tip Item id used to craft the dart. + * @property dartItemId the resulting dart Item id. + * @property level the level required to craft. + * @property experience the experience gained by crafting. + */ +enum class DartCraftInfo( + val tipItemId: Int, + val dartItemId: Int, + val level: Int, + val experience: Double) { + + BRONZE_DART(Items.BRONZE_DART_TIP_819, Items.BRONZE_DART_806, 1, 1.8), + IRON_DART(Items.IRON_DART_TIP_820, Items.IRON_DART_807, 22, 3.8), + STEEL_DART(Items.STEEL_DART_TIP_821, Items.STEEL_DART_808, 37, 7.5), + MITHRIL_DART(Items.MITHRIL_DART_TIP_822, Items.MITHRIL_DART_809, 52, 11.2), + ADAMANT_DART(Items.ADAMANT_DART_TIP_823, Items.ADAMANT_DART_810, 67, 15.0), + RUNE_DART(Items.RUNE_DART_TIP_824, Items.RUNE_DART_811, 81, 18.8), + DRAGON_DART(Items.DRAGON_DART_TIP_11232, Items.DRAGON_DART_11230, 95, 25.0); + + companion object { + private val dartCraftInfoByTipIds = values().associateBy { it.tipItemId } + + val tipIDs: IntArray = values().map { dartCraftInfo: DartCraftInfo -> dartCraftInfo.tipItemId }.toIntArray() + + fun fromTipID(dartTipId: Int): DartCraftInfo? { + return dartCraftInfoByTipIds[dartTipId] + } + } +} diff --git a/Server/src/main/content/global/skill/fletching/darts/DartCraftScript.kt b/Server/src/main/content/global/skill/fletching/darts/DartCraftScript.kt new file mode 100644 index 000000000..baecff08c --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/darts/DartCraftScript.kt @@ -0,0 +1,78 @@ +package content.global.skill.fletching.darts + +import content.global.skill.fletching.Feathers +import core.api.* +import core.game.interaction.Clocks +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import kotlin.math.min + +/** + * Represents the queueScript to craft a dart. + * @author ceikry + * @param player the player. + * @param dartCraftInfo contains info about the dart we're crafting. + * @param sets count of sets to make + */ +class DartCraftScript( + private val player: Player, + private val dartCraftInfo: DartCraftInfo, + private var sets: Int +) { + + private val initialDelay = 1 + private val subsequentDelay = 3 + private val maximumDartsCraftableInOneStage = 10 + + private fun getAmountToCraftForThisStage(feathersInInventory: Int, dartTipsInInventory: Int): Int { + val smallerItemAmount = min(feathersInInventory, dartTipsInInventory) + return if (smallerItemAmount > maximumDartsCraftableInOneStage) { + maximumDartsCraftableInOneStage + } else { + smallerItemAmount + } + } + + fun invoke() { + queueScript(player, initialDelay) { stage -> + if (!clockReady(player, Clocks.SKILLING)) return@queueScript keepRunning(player) + + if (getDynLevel(player, Skills.FLETCHING) < dartCraftInfo.level) { + DartListeners.sendLevelCheckFailDialog(player, dartCraftInfo.level) + return@queueScript stopExecuting(player) // Check each iteration since dynLevel can change (status effects ending, skill assist session end...) + } + + val dartTipsInInventory = amountInInventory(player, dartCraftInfo.tipItemId) + val feathersInInventory = amountInInventory(player, Feathers.STANDARD) + + val amountToCraft = getAmountToCraftForThisStage(feathersInInventory, dartTipsInInventory) + + if ( + removeItemsIfPlayerHasEnough( + player, + Item(Feathers.STANDARD, amountToCraft), + Item(dartCraftInfo.tipItemId, amountToCraft) + ) + ) { + when (amountToCraft) { + 1 -> sendMessage(player, "You attach a feather to a dart.") + else -> { + sendMessage(player, "You attach feathers to $amountToCraft darts.") + } + } + + rewardXP(player, Skills.FLETCHING, dartCraftInfo.experience * amountToCraft) + addItem(player, dartCraftInfo.dartItemId, amountToCraft) + } else { + return@queueScript stopExecuting(player) + } + + if (stage >= sets - 1) { + return@queueScript stopExecuting(player) + } + + return@queueScript delayClock(player, Clocks.SKILLING, subsequentDelay, true) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/darts/DartListeners.kt b/Server/src/main/content/global/skill/fletching/darts/DartListeners.kt new file mode 100644 index 000000000..93b96d2ab --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/darts/DartListeners.kt @@ -0,0 +1,59 @@ +package content.global.skill.fletching.darts + +import content.data.Quests +import content.global.skill.fletching.Feathers +import core.api.* +import core.game.dialogue.SkillDialogueHandler +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import kotlin.math.min + +@Suppress("unused") // Reflectively loaded +class DartListeners : InteractionListener { + override fun defineListeners() { + onUseWith(IntType.ITEM, Feathers.STANDARD, *DartCraftInfo.tipIDs) { player, feather, dartTip -> + val dartCraftInfo = DartCraftInfo.fromTipID(dartTip.id) ?: return@onUseWith false + val handler: SkillDialogueHandler = + object : + SkillDialogueHandler(player, SkillDialogue.MAKE_SET_ONE_OPTION, Item(dartCraftInfo.dartItemId)) { + override fun create(amount: Int, index: Int) { + if (!playerMeetsInitialRequirements()) return + DartCraftScript(player, dartCraftInfo, amount).invoke() + } + + private fun playerMeetsInitialRequirements(): Boolean { + if (getDynLevel(player, Skills.FLETCHING) < dartCraftInfo.level) { + sendLevelCheckFailDialog(player, dartCraftInfo.level) + return false + } + if (!isQuestComplete(player, Quests.THE_TOURIST_TRAP)) { + sendDialogue(player, "You need to have completed Tourist Trap to fletch darts.") + return false + } + if (!hasSpaceFor(player, Item(dartCraftInfo.dartItemId))) { + sendDialogue(player, "You do not have enough inventory space.") + return false + } + return true + } + + override fun getAll(index: Int): Int { + return min( + amountInInventory(player, feather.id), + amountInInventory(player, dartTip.id) + ) + } + } + handler.open() + return@onUseWith true + } + } + companion object { + fun sendLevelCheckFailDialog(player: Player, level: Int) { + sendDialogue(player, "You need a fletching level of $level to do this.") + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/gem/AttachGemTipToBoltScript.kt b/Server/src/main/content/global/skill/fletching/gem/AttachGemTipToBoltScript.kt new file mode 100644 index 000000000..c6b1cfdfd --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/gem/AttachGemTipToBoltScript.kt @@ -0,0 +1,77 @@ +package content.global.skill.fletching.gem + +import core.api.* +import core.game.interaction.Clocks +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import kotlin.math.min + +/** + * Represents the attaching of a gem tip to a premade bolt. + * @author Ceikry + * @param player the player. + * @param gemBoltCraftInfo crafting info for adding the gem to the bolt + * @param sets the number of sets to craft. + */ +class AttachGemTipToBoltScript( + private val player: Player, + private val gemBoltCraftInfo: GemBoltsCraftInfo, + private val sets: Int +) { + + private val initialDelay = 1 + private val subsequentDelay = 3 + private val maximumGemBoltsCraftableInOneStage = 10 + + private fun getAmountToCraftForThisPulse(untippedBoltsInInventory: Int, tipsInInventory: Int): Int { + val smallerItemAmount = min(untippedBoltsInInventory, tipsInInventory) + return if (smallerItemAmount > maximumGemBoltsCraftableInOneStage) { + maximumGemBoltsCraftableInOneStage + } else { + smallerItemAmount + } + } + + fun invoke() { + queueScript(player, initialDelay) { stage -> + if (!clockReady(player, Clocks.SKILLING)) return@queueScript keepRunning(player) + + if (getDynLevel(player, Skills.FLETCHING) < gemBoltCraftInfo.level) { + GemBoltListeners.sendGemTipAttachLevelCheckFailDialog(player, gemBoltCraftInfo.level) + return@queueScript stopExecuting(player) // Check each iteration since dynLevel can change (status effects ending, skill assist session end...) + } + + val untippedBoltsInInventory = amountInInventory(player, gemBoltCraftInfo.untippedBoltItemId) + val tipsInInventory = amountInInventory(player, gemBoltCraftInfo.tipItemId) + + val amountToCraft = getAmountToCraftForThisPulse(untippedBoltsInInventory, tipsInInventory) + + if ( + removeItemsIfPlayerHasEnough( + player, + Item(gemBoltCraftInfo.untippedBoltItemId, amountToCraft), + Item(gemBoltCraftInfo.tipItemId, amountToCraft) + ) + ) { + when (amountToCraft) { + 1 -> sendMessage(player, "You attach the tip to the bolt.") + else -> { + sendMessage(player, "You fletch $amountToCraft bolts.") + } + } + + rewardXP(player, Skills.FLETCHING, gemBoltCraftInfo.experience * amountToCraft) + addItem(player, gemBoltCraftInfo.tippedBoltItemId, amountToCraft) + } else { + return@queueScript stopExecuting(player) + } + + if (stage >= sets - 1) { + return@queueScript stopExecuting(player) + } + + return@queueScript delayClock(player, Clocks.SKILLING, subsequentDelay, true) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/gem/CutGemsIntoBoltTipsScript.kt b/Server/src/main/content/global/skill/fletching/gem/CutGemsIntoBoltTipsScript.kt new file mode 100644 index 000000000..55ea058d0 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/gem/CutGemsIntoBoltTipsScript.kt @@ -0,0 +1,79 @@ +package content.global.skill.fletching.gem + +import core.api.* +import core.game.interaction.Clocks +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item + +/** + * Represents the queue script for cutting gems into bolt tips + * @author Ceikry + * @param player the player. + * @param gemBoltCraftInfo represents the crafting info for the gem we're cutting. + * @param amount the amount to make. + */ +class CutGemsIntoBoltTipsScript( + private val player: Player, + private val gemBoltCraftInfo: GemBoltsCraftInfo, + private val amount: Int +) { + + private val initialDelay = 1 + private val craftDelay = 5 + private val animationDelay = 6 + + private var craftingFinished = false + + private fun invokeAnimationLoop() { + queueScript(player, 0) { + if (craftingFinished) { + return@queueScript stopExecuting(player) + } + animate(player, gemBoltCraftInfo.gemCutAnimationId) + return@queueScript delayScript(player, animationDelay) + } + } + + private fun invokeCraftLoop() { + queueScript(player, 0) { stage -> + if (!clockReady(player, Clocks.SKILLING)) return@queueScript keepRunning(player) + + if (getDynLevel(player, Skills.FLETCHING) < gemBoltCraftInfo.level) { + craftingFinished = true + GemBoltListeners.sendGemTipCutLevelCheckFailDialog(player, gemBoltCraftInfo.level) + return@queueScript stopExecuting(player) // Check each iteration since dynLevel can change (status effects ending, skill assist session end...) + } + + val amountOfTipsToCraft = when (gemBoltCraftInfo) { + GemBoltsCraftInfo.PEARLS -> 24 + GemBoltsCraftInfo.PEARL -> 6 + GemBoltsCraftInfo.ONYX -> 24 + else -> 12 + } + + if (removeItem(player, Item(gemBoltCraftInfo.gemItemId))) { + addItem(player, gemBoltCraftInfo.tipItemId, amountOfTipsToCraft) + rewardXP(player, Skills.FLETCHING, gemBoltCraftInfo.experience) + } else { + craftingFinished = true + return@queueScript stopExecuting(player) + } + + if (stage >= amount - 1) { + craftingFinished = true + return@queueScript stopExecuting(player) + } + + return@queueScript delayClock(player, Clocks.SKILLING, craftDelay, true) + } + } + + fun invoke() { + queueScript(player, initialDelay) { + invokeAnimationLoop() + invokeCraftLoop() + return@queueScript stopExecuting(player) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/gem/GemBoltListeners.kt b/Server/src/main/content/global/skill/fletching/gem/GemBoltListeners.kt new file mode 100644 index 000000000..15ef1d38f --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/gem/GemBoltListeners.kt @@ -0,0 +1,90 @@ +package content.global.skill.fletching.gem + +import core.api.amountInInventory +import core.api.getDynLevel +import core.api.hasSpaceFor +import core.api.sendDialogue +import core.game.dialogue.SkillDialogueHandler +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import org.rs09.consts.Items +import kotlin.math.min + +@Suppress("unused") // Reflectively loaded +class GemBoltListeners : InteractionListener { + + override fun defineListeners() { + onUseWith(IntType.ITEM, Items.CHISEL_1755, *GemBoltsCraftInfo.gemIds) { player, _, gem -> + val gemBoltCraftInfo = GemBoltsCraftInfo.forGemId(gem.id) ?: return@onUseWith true + + object : SkillDialogueHandler(player, SkillDialogue.ONE_OPTION, Item(gemBoltCraftInfo.gemItemId)) { + override fun create(amount: Int, index: Int) { + if (getDynLevel(player, Skills.FLETCHING) < gemBoltCraftInfo.level) { + sendGemTipCutLevelCheckFailDialog(player, gemBoltCraftInfo.level) + return + } + CutGemsIntoBoltTipsScript(player, gemBoltCraftInfo, amount).invoke() + } + + override fun getAll(index: Int): Int { + return amountInInventory(player, gemBoltCraftInfo.gemItemId) + } + }.open() + return@onUseWith true + } + + onUseWith( + IntType.ITEM, + GemBoltsCraftInfo.untippedBoltIds, + *GemBoltsCraftInfo.boltTipIds + ) { player, untippedBolt, boltTip -> + val bolt = GemBoltsCraftInfo.forTipId(boltTip.id) ?: return@onUseWith false + if (untippedBolt.id != bolt.untippedBoltItemId) return@onUseWith false + + val handler: SkillDialogueHandler = + object : SkillDialogueHandler(player, SkillDialogue.MAKE_SET_ONE_OPTION, Item(bolt.tippedBoltItemId)) { + override fun create(amount: Int, index: Int) { + if (!playerMeetsInitialRequirements()) return + AttachGemTipToBoltScript(player, bolt, amount).invoke() + } + + private fun playerMeetsInitialRequirements(): Boolean { + if (getDynLevel(player, Skills.FLETCHING) < bolt.level) { + sendGemTipAttachLevelCheckFailDialog(player, bolt.level) + return false + } + if (!hasSpaceFor(player, Item(bolt.tippedBoltItemId))) { + sendDialogue(player, "You do not have enough inventory space.") + return false + } + return true + } + + override fun getAll(index: Int): Int { + return min(amountInInventory(player, untippedBolt.id), amountInInventory(player, boltTip.id)) + } + } + handler.open() + return@onUseWith true + } + } + + companion object { + fun sendGemTipCutLevelCheckFailDialog(player: Player, level: Int) { + sendDialogue( + player, + "You need a Fletching level of $level or above to do that." + ) + } + + fun sendGemTipAttachLevelCheckFailDialog(player: Player, level: Int) { + sendDialogue( + player, + "You need a Fletching level of $level or above to do that." + ) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/gem/GemBoltsCraftInfo.kt b/Server/src/main/content/global/skill/fletching/gem/GemBoltsCraftInfo.kt new file mode 100644 index 000000000..d0e2b0f11 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/gem/GemBoltsCraftInfo.kt @@ -0,0 +1,106 @@ +package content.global.skill.fletching.gem + +import org.rs09.consts.Items + +/** + * Provides information pertaining to crafting different gem tipped bolts + * @property untippedBoltItemId the base untipped bolt item id + * @property gemItemId the gem item id used to craft the bolt tips + * @property gemCutAnimationId the animation to play when the player cuts the gem + * @property tipItemId the item id of the resulting bolt tips + * @property tippedBoltItemId the item id of the finished gem-tipped bolt + * @property level the required level to craft the gem tipped bolt or tips themselves + * @property experience gained creating one set of gem tips, or per bolt tipped + */ +enum class GemBoltsCraftInfo( + var untippedBoltItemId: Int, + var gemItemId: Int, + var gemCutAnimationId: Int, + var tipItemId: Int, + var tippedBoltItemId: Int, + var level: Int, + var experience: Double +) { + OPAL(Items.BRONZE_BOLTS_877, Items.OPAL_1609, 890, Items.OPAL_BOLT_TIPS_45, Items.OPAL_BOLTS_879, 11, 1.6), + JADE(Items.BLURITE_BOLTS_9139, Items.JADE_1611, 891, Items.JADE_BOLT_TIPS_9187, Items.JADE_BOLTS_9335, 26, 2.4), + PEARL(Items.IRON_BOLTS_9140, Items.OYSTER_PEARL_411, 4470, Items.PEARL_BOLT_TIPS_46, Items.PEARL_BOLTS_880, 41, 3.2), + PEARLS( + Items.IRON_BOLTS_9140, + Items.OYSTER_PEARLS_413, + 4470, + Items.PEARL_BOLT_TIPS_46, + Items.PEARL_BOLTS_880, + 41, + 3.2 + ), + RED_TOPAZ( + Items.STEEL_BOLTS_9141, + Items.RED_TOPAZ_1613, + 892, + Items.TOPAZ_BOLT_TIPS_9188, + Items.TOPAZ_BOLTS_9336, + 48, + 3.9 + ), + SAPPHIRE( + Items.MITHRIL_BOLTS_9142, + Items.SAPPHIRE_1607, + 888, + Items.SAPPHIRE_BOLT_TIPS_9189, + Items.SAPPHIRE_BOLTS_9337, + 56, + 4.7 + ), + EMERALD( + Items.MITHRIL_BOLTS_9142, + Items.EMERALD_1605, + 889, + Items.EMERALD_BOLT_TIPS_9190, + Items.EMERALD_BOLTS_9338, + 58, + 5.5 + ), + RUBY(Items.ADAMANT_BOLTS_9143, Items.RUBY_1603, 887, Items.RUBY_BOLT_TIPS_9191, Items.RUBY_BOLTS_9339, 63, 6.3), + DIAMOND( + Items.ADAMANT_BOLTS_9143, + Items.DIAMOND_1601, + 886, + Items.DIAMOND_BOLT_TIPS_9192, + Items.DIAMOND_BOLTS_9340, + 65, + 7.0 + ), + DRAGONSTONE( + Items.RUNE_BOLTS_9144, + Items.DRAGONSTONE_1615, + 885, + Items.DRAGON_BOLT_TIPS_9193, + Items.DRAGON_BOLTS_9341, + 71, + 8.2 + ), + ONYX(Items.RUNE_BOLTS_9144, Items.ONYX_6573, 2717, Items.ONYX_BOLT_TIPS_9194, Items.ONYX_BOLTS_9342, 73, 9.4); + + companion object { + private val gemBoltCraftInfoByGemId = values().associateBy { it.gemItemId } + private val gemBoltCraftInfoByBoltTipId = values().associateBy { it.tipItemId } + + val untippedBoltIds: IntArray = + values().map { gemBoltsCraftInfo: GemBoltsCraftInfo -> gemBoltsCraftInfo.untippedBoltItemId }.distinct() + .toIntArray() + + val gemIds: IntArray = + values().map { gemBoltsCraftInfo: GemBoltsCraftInfo -> gemBoltsCraftInfo.gemItemId }.toIntArray() + + val boltTipIds: IntArray = + values().map { gemBoltsCraftInfo: GemBoltsCraftInfo -> gemBoltsCraftInfo.tipItemId }.toIntArray() + + fun forGemId(gemId: Int): GemBoltsCraftInfo? { + return gemBoltCraftInfoByGemId[gemId] + } + + fun forTipId(tipId: Int): GemBoltsCraftInfo? { + return gemBoltCraftInfoByBoltTipId[tipId] + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/grapple/GrappleListeners.kt b/Server/src/main/content/global/skill/fletching/grapple/GrappleListeners.kt new file mode 100644 index 000000000..46373cda0 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/grapple/GrappleListeners.kt @@ -0,0 +1,67 @@ +package content.global.skill.fletching.grapple + +import core.api.* +import core.game.interaction.Clocks +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import org.rs09.consts.Items + +@Suppress("unused") // Reflectively loaded +class GrappleListeners : InteractionListener { + private val mithrilBolt = Items.MITHRIL_BOLTS_9142 + private val mithrilGrappleTip = Items.MITH_GRAPPLE_TIP_9416 + private val rope = Items.ROPE_954 + private val mithrilGrapple = Items.MITH_GRAPPLE_9418 + private val mithrilGrappleWithRope = Items.MITH_GRAPPLE_9419 + + override fun defineListeners() { + onUseWith(IntType.ITEM, mithrilBolt, mithrilGrappleTip) { player, bolt, tip -> + if (getDynLevel(player, Skills.FLETCHING) < 59) { + sendMessage(player, "You need a fletching level of 59 to make this.") + return@onUseWith true + } + queueScript(player, 0) { _ -> + if (!clockReady(player, Clocks.SKILLING)) return@queueScript keepRunning(player) + if (removeItemsIfPlayerHasEnough( + player, + Item(mithrilBolt, 1), + tip.asItem() + ) + ) { + addItem(player, mithrilGrapple, 1) + } + + delayClock(player, Clocks.SKILLING, 3) + return@queueScript stopExecuting(player) + } + + + return@onUseWith true + } + + onUseWith(IntType.ITEM, rope, mithrilGrapple) { player, rope, grapple -> + if (getDynLevel(player, Skills.FLETCHING) < 59) { + sendMessage(player, "You need a fletching level of 59 to make this.") + return@onUseWith true + } + queueScript(player, 0) { _ -> + if (!clockReady(player, Clocks.SKILLING)) return@queueScript keepRunning(player) + if (removeItemsIfPlayerHasEnough( + player, + rope.asItem(), + grapple.asItem() + ) + ) { + addItem(player, mithrilGrappleWithRope, 1) + } + delayClock(player, Clocks.SKILLING, 3) + return@queueScript stopExecuting(player) + } + return@onUseWith true + } + + } + +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/items/arrow/ArrowHead.java b/Server/src/main/content/global/skill/fletching/items/arrow/ArrowHead.java deleted file mode 100644 index c7ab5ec7c..000000000 --- a/Server/src/main/content/global/skill/fletching/items/arrow/ArrowHead.java +++ /dev/null @@ -1,123 +0,0 @@ -/* -package core.game.node.entity.skill.fletching.items.arrow; - -import org.crandor.game.node.item.Item; - -*/ -/** - * Represents the enum storing the arrow head information. - * @author 'Vexia - * @note brutal arrows after quest. - *//* - -public enum ArrowHead { - BRONZE_ARROW(new Item(39), new Item(882), 1, 2.6), - IRON_ARROW(new Item(40), new Item(884), 15, 3.8), - STEEL_ARROW(new Item(41), new Item(886), 30, 6.3), - MITHRIL_ARROW(new Item(42), new Item(888), 45, 8.8), - ADAMANT_ARROW(new Item(43), new Item(890), 60, 10), - RUNE_ARROW(new Item(44), new Item(892), 75, 13.8), - DRAGON_ARROW(new Item(11237), new Item(11212), 90, 16.3), - BROAD_ARROW(new Item(13278), new Item(4160), 52, 10); - - */ -/** - * Constructs a new {@code ArrowHead.java} {@code Object}. - * @param item the item. - * @param product the product. - * @param level the level. - * @param experience the experience. - *//* - - ArrowHead(Item item, Item product, int level, double experience) { - this.item = item; - this.product = product; - this.level = level; - this.experience = experience; - } - - */ -/** - * Represents the arrow tip. - *//* - - private final Item item; - - */ -/** - * Represents the product item. - *//* - - private final Item product; - - */ -/** - * Represents the level required. - *//* - - private final int level; - - */ -/** - * Represents the experience gained. - *//* - - private final double experience; - - */ -/** - * Gets the item. - * @return The item. - *//* - - public Item getTips() { - return item; - } - - */ -/** - * Gets the product. - * @return The product. - *//* - - public Item getProduct() { - return product; - } - - */ -/** - * Gets the level. - * @return The level. - *//* - - public int getLevel() { - return level; - } - - */ -/** - * Gets the experience. - * @return The experience. - *//* - - public double getExperience() { - return experience; - } - - */ -/** - * Gets the arrow head. - * @param item the item. - * @return the arrow head. - *//* - - public static ArrowHead forItem(final Item item) { - for (ArrowHead arrow : ArrowHead.values()) { - if (arrow.getTips().getId() == item.getId()) { - return arrow; - } - } - return null; - } -} -*/ diff --git a/Server/src/main/content/global/skill/fletching/items/arrow/ArrowHeadPulse.java b/Server/src/main/content/global/skill/fletching/items/arrow/ArrowHeadPulse.java deleted file mode 100644 index 5ea27c2cc..000000000 --- a/Server/src/main/content/global/skill/fletching/items/arrow/ArrowHeadPulse.java +++ /dev/null @@ -1,110 +0,0 @@ -package content.global.skill.fletching.items.arrow; - -import content.global.skill.slayer.SlayerManager; -import core.game.node.entity.skill.SkillPulse; -import core.game.node.entity.skill.Skills; -import content.global.skill.fletching.Fletching; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; - -import static core.api.ContentAPIKt.*; - -/** - * Represents the arrow head pulse to complete the headless arrow. - * @author 'Vexia - */ -public class ArrowHeadPulse extends SkillPulse { - - /** - * Represents the headless arrow item. - */ - private static final Item HEADLESS_ARROW = new Item(53); - - /** - * Represents the arrow head. - */ - private final Fletching.ArrowHeads arrow; - - /** - * Represents the sets to do. - */ - private int sets; - - /** - * Constructs a new {@code ArrowHeadPulse.java} {@code Object}. - * @param player the player. - * @param node the node. - * @param arrow the arrow. - * @param sets the sets. - */ - public ArrowHeadPulse(final Player player, final Item node, final Fletching.ArrowHeads arrow, final int sets) { - super(player, node); - this.arrow = arrow; - this.sets = sets; - } - - @Override - public boolean checkRequirements() { - if (arrow.unfinished == 4160) { - if (!SlayerManager.getInstance(player).flags.isBroadsUnlocked()) { - player.getDialogueInterpreter().sendDialogue("You need to unlock the ability to create broad arrows."); - return false; - } - } - if (player.getSkills().getLevel(Skills.FLETCHING) < arrow.level) { - player.getDialogueInterpreter().sendDialogue("You need a fletching level of " + arrow.level + " to do this."); - return false; - } - if (!hasSpaceFor(player, arrow.getFinished())) { - sendDialogue(player, "You do not have enough inventory space."); - return false; - } - return true; - } - - @Override - public void animate() { - } - - @Override - public boolean reward() { - if (getDelay() == 1) { - super.setDelay(3); - } - Item tip = arrow.getUnfinished(); - int tipAmount = player.getInventory().getAmount(arrow.unfinished); - int shaftAmount = player.getInventory().getAmount(HEADLESS_ARROW); - if (tipAmount >= 15 && shaftAmount >= 15) { - HEADLESS_ARROW.setAmount(15); - tip.setAmount(15); - player.getPacketDispatch().sendMessage("You attach arrow heads to 15 arrow shafts."); - } else { - int amount = tipAmount > shaftAmount ? shaftAmount : tipAmount; - HEADLESS_ARROW.setAmount(amount); - tip.setAmount(amount); - player.getPacketDispatch().sendMessage(amount == 1 ? "You attach an arrow head to an arrow shaft." : "You attach arrow heads to " + amount + " arrow shafts."); - } - if (player.getInventory().remove(HEADLESS_ARROW, tip)) { - player.getSkills().addExperience(Skills.FLETCHING, arrow.experience * tip.getAmount(), true); - Item product = arrow.getFinished(); - product.setAmount(tip.getAmount()); - player.getInventory().add(product); - } - HEADLESS_ARROW.setAmount(1); - tip.setAmount(1); - if (!player.getInventory().containsItem(HEADLESS_ARROW)) { - return true; - } - if (!player.getInventory().containsItem(tip)) { - return true; - } - sets--; - return sets == 0; - } - - @Override - public void message(int type) { - - } - -} diff --git a/Server/src/main/content/global/skill/fletching/items/arrow/HeadlessArrowPulse.java b/Server/src/main/content/global/skill/fletching/items/arrow/HeadlessArrowPulse.java deleted file mode 100644 index 3125b5669..000000000 --- a/Server/src/main/content/global/skill/fletching/items/arrow/HeadlessArrowPulse.java +++ /dev/null @@ -1,145 +0,0 @@ -package content.global.skill.fletching.items.arrow; - -import org.rs09.consts.Items; -import core.game.node.entity.skill.SkillPulse; -import core.game.node.entity.skill.Skills; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; - -import static core.api.ContentAPIKt.*; - -/** - * Represents the arrow pulse for creating unfinished arrows. - * @author 'Vexia - */ -public final class HeadlessArrowPulse extends SkillPulse { - - /** - * Represents the headless arrow item. - */ - private final Item HEADLESS_ARROW = new Item(Items.HEADLESS_ARROW_53); - - /** - * Represents the arrow shaft item. - */ - private final Item ARROW_SHAFT = new Item(Items.ARROW_SHAFT_52); - - /** - * Represents the feather items. - */ - private static final Item[] FEATHER = new Item[] { - new Item(Items.FEATHER_314), - new Item(Items.STRIPY_FEATHER_10087), - new Item(Items.RED_FEATHER_10088), - new Item(Items.BLUE_FEATHER_10089), - new Item(Items.YELLOW_FEATHER_10090), - new Item(Items.ORANGE_FEATHER_10091) - }; - - /** - * The feather being used. - */ - private Item feather; - - /** - * Represents the amount to make. - */ - private int sets; - - /** - * Represents if we should use sets, meaning we have 15 & 15 arrow shafts and feathers. - */ - private boolean useSets = false; - - /** - * Constructs a new {@code ArrowPulse.java} {@code Object}. - * @param player the player. - * @param node the node. - */ - public HeadlessArrowPulse(Player player, Item node, Item feather, int sets) { - super(player, node); - this.sets = sets; - this.feather = feather; - } - - @Override - public boolean checkRequirements() { - if (!player.getInventory().containsItem(ARROW_SHAFT)) { - player.getDialogueInterpreter().sendDialogue("You don't have any arrow shafts."); - return false; - } - if (feather == null || !player.getInventory().containsItem(feather)) { - player.getDialogueInterpreter().sendDialogue("You don't have any feathers."); - return false; - } - if (player.getInventory().contains(ARROW_SHAFT.getId(), 15) && player.getInventory().contains(feather.getId(), 15)) { - useSets = true; - } else { - useSets = false; - } - if (!hasSpaceFor(player, HEADLESS_ARROW.asItem())) { - sendDialogue(player, "You do not have enough inventory space."); - return false; - } - return true; - } - - @Override - public void animate() { - } - - @Override - public boolean reward() { - int featherAmount = player.getInventory().getAmount(feather); - int shaftAmount = player.getInventory().getAmount(ARROW_SHAFT); - if (getDelay() == 1) { - super.setDelay(3); - } - if (featherAmount >= 15 && shaftAmount >= 15) { - feather.setAmount(15); - ARROW_SHAFT.setAmount(15); - player.getPacketDispatch().sendMessage("You attach feathers to 15 arrow shafts."); - } else { - int amount = Math.min(featherAmount, shaftAmount); - feather.setAmount(amount); - ARROW_SHAFT.setAmount(amount); - player.getPacketDispatch().sendMessage(amount == 1 - ? "You attach a feathers to a shaft." : "You attach feathers to " + amount + " arrow shafts."); - } - if (player.getInventory().remove(feather, ARROW_SHAFT)) { - HEADLESS_ARROW.setAmount(feather.getAmount()); - player.getSkills().addExperience(Skills.FLETCHING, HEADLESS_ARROW.getAmount(), true); - player.getInventory().add(HEADLESS_ARROW); - } - HEADLESS_ARROW.setAmount(1); - feather.setAmount(1); - ARROW_SHAFT.setAmount(1); - if (!player.getInventory().containsItem(ARROW_SHAFT)) { - return true; - } - if (!player.getInventory().containsItem(feather)) { - return true; - } - sets--; - return sets <= 0; - } - - @Override - public void message(int type) { - } - - /** - * Gets the feather item. - * @return the item. - */ - private Item getFeather() { - int length = FEATHER.length; - for (int i = 0; i < length; i++) { - Item f = FEATHER[i]; - if (player.getInventory().containsItem(f)) { - return f; - } - } - return null; - } -} diff --git a/Server/src/main/content/global/skill/fletching/items/arrow/HeadlessOgreArrowPulse.java b/Server/src/main/content/global/skill/fletching/items/arrow/HeadlessOgreArrowPulse.java deleted file mode 100644 index fd7af6731..000000000 --- a/Server/src/main/content/global/skill/fletching/items/arrow/HeadlessOgreArrowPulse.java +++ /dev/null @@ -1,131 +0,0 @@ -package content.global.skill.fletching.items.arrow; - -import core.game.node.entity.player.Player; -import core.game.node.entity.skill.SkillPulse; -import core.game.node.entity.skill.Skills; -import core.game.node.item.Item; -import org.rs09.consts.Items; - -import static core.api.ContentAPIKt.hasSpaceFor; -import static core.api.ContentAPIKt.sendDialogue; - -/** - * Represents the arrow pulse for creating unfinished ogre arrows. - * @author 'Vexia - */ -public final class HeadlessOgreArrowPulse extends SkillPulse { - - /** - * Represents the headless ogre arrow item. - */ - private final Item HEADLESS_ARROW = new Item(Items.FLIGHTED_OGRE_ARROW_2865); - - /** - * Represents the ogre arrow shaft item. - */ - private final Item ARROW_SHAFT = new Item(Items.OGRE_ARROW_SHAFT_2864); - - /** - * Represents the feather items. - */ - private static final Item[] FEATHER = new Item[] { - new Item(Items.FEATHER_314, 4), - }; - - /** - * The feather being used. - */ - private Item feather; - - /** - * Represents the amount to make. - */ - private int sets; - - /** - * Constructs a new {@code ArrowPulse.java} {@code Object}. - * @param player the player. - * @param node the node. - */ - public HeadlessOgreArrowPulse(Player player, Item node, Item feather, int sets) { - super(player, node); - this.sets = sets; - this.feather = feather; - } - - @Override - public boolean checkRequirements() { - if (!player.getInventory().containsItem(ARROW_SHAFT)) { - player.getDialogueInterpreter().sendDialogue("You don't have any arrow shafts."); - return false; - } - if (feather == null || !player.getInventory().containsItem(feather)) { - player.getDialogueInterpreter().sendDialogue("You don't have any feathers."); - return false; - } - if (!hasSpaceFor(player, HEADLESS_ARROW.asItem())) { - sendDialogue(player, "You do not have enough inventory space."); - return false; - } - return true; - } - - @Override - public void animate() { - } - - @Override - public boolean reward() { - int featherAmount = player.getInventory().getAmount(feather); - int shaftAmount = player.getInventory().getAmount(ARROW_SHAFT); - if (getDelay() == 1) { - super.setDelay(3); - } - if (featherAmount >= 24 && shaftAmount >= 6) { - feather.setAmount(24); - ARROW_SHAFT.setAmount(6); - player.getPacketDispatch().sendMessage("You attach 24 feathers to 6 ogre arrow shafts."); - } else { - int amount = Math.min(featherAmount / 4, shaftAmount); - feather.setAmount(amount*4); - ARROW_SHAFT.setAmount(amount); - player.getPacketDispatch().sendMessage(amount == 1 - ? "You attach a feathers to a shaft." : "You attach " + amount * 4 + " feathers to " + amount + " ogre arrow shafts."); - } - if (player.getInventory().remove(feather, ARROW_SHAFT)) { - HEADLESS_ARROW.setAmount(ARROW_SHAFT.getAmount()); - player.getSkills().addExperience(Skills.FLETCHING, HEADLESS_ARROW.getAmount(), true); - player.getInventory().add(HEADLESS_ARROW); - } - HEADLESS_ARROW.setAmount(1); - feather.setAmount(1); - ARROW_SHAFT.setAmount(1); - if (!player.getInventory().containsItem(ARROW_SHAFT)) { - return true; - } - if (!player.getInventory().containsItem(feather)) { - return true; - } - sets--; - return sets <= 0; - } - - @Override - public void message(int type) { - } - - /** - * Gets the feather item. - * @return the item. - */ - private Item getFeather() { - int length = FEATHER.length; - for (int i = 0; i < length; i++) { - Item f = FEATHER[i]; - if (player.getInventory().containsItem(f)) { - return f; - } - } - return null; - } -} diff --git a/Server/src/main/content/global/skill/fletching/items/bolts/Bolt.java b/Server/src/main/content/global/skill/fletching/items/bolts/Bolt.java deleted file mode 100644 index f877050db..000000000 --- a/Server/src/main/content/global/skill/fletching/items/bolts/Bolt.java +++ /dev/null @@ -1,123 +0,0 @@ -/* -package core.game.node.entity.skill.fletching.items.bolts; - -import org.crandor.game.node.item.Item; - -*/ -/** - * Represents an enum of bolts. - * @author 'Vexia - *//* - -public enum Bolt { - BRONZE_BOLT(new Item(9375), new Item(877), 9, 0.5), - BLURITE_BOLT(new Item(9376), new Item(9139), 24, 1), - IRON_BOLT(new Item(9377), new Item(9140), 39, 1.5), - SILVER_BOLT(new Item(9382), new Item(9145), 43, 2.5), - STEEL_BOLT(new Item(9378), new Item(9141), 46, 3.5), - MITHRIL_BOLT(new Item(9379), new Item(9142), 54, 5), - ADAMANTITE_BOLT(new Item(9380), new Item(9143), 61, 7), - RUNITE_BOLT(new Item(9381), new Item(9144), 69, 10), - BROAD_BOLT(new Item(13279), new Item(13280), 55, 3); - - */ -/** - * The item required. - *//* - - private final Item item; - - */ -/** - * The product recieved. - *//* - - private final Item product; - - */ -/** - * The level required. - *//* - - private final int level; - - */ -/** - * The experience gained. - *//* - - private final double experience; - - */ -/** - * Constructs a new {@code Bolt} {@code Object}. - * @param item the item. - * @param product the product. - * @param level the level. - * @param experience the experienece. - *//* - - Bolt(Item item, Item product, int level, double experience) { - this.item = item; - this.product = product; - this.level = level; - this.experience = experience; - } - - */ -/** - * Gets the item. - * @return The item. - *//* - - public Item getItem() { - return item; - } - - */ -/** - * Gets the product. - * @return The product. - *//* - - public Item getProduct() { - return product; - } - - */ -/** - * Gets the level. - * @return The level. - *//* - - public int getLevel() { - return level; - } - - */ -/** - * Gets the experience. - * @return The experience. - *//* - - public double getExperience() { - return experience; - } - - */ -/** - * Method used to get the bolt for the item. - * @param item the item. - * @return the bolt. - *//* - - public static Bolt forItem(final Item item) { - for (Bolt bolt : Bolt.values()) { - if (bolt.getItem().getId() == item.getId()) { - return bolt; - } - } - return null; - } -} -*/ diff --git a/Server/src/main/content/global/skill/fletching/items/bolts/BoltPulse.java b/Server/src/main/content/global/skill/fletching/items/bolts/BoltPulse.java deleted file mode 100644 index b345c4276..000000000 --- a/Server/src/main/content/global/skill/fletching/items/bolts/BoltPulse.java +++ /dev/null @@ -1,130 +0,0 @@ -package content.global.skill.fletching.items.bolts; - -import content.global.skill.slayer.SlayerManager; -import org.rs09.consts.Items; -import core.game.node.entity.skill.SkillPulse; -import core.game.node.entity.skill.Skills; -import content.global.skill.fletching.Fletching; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; - -/** - * Represents the bolt pulse class to make bolts. - * @author ceik - */ -public final class BoltPulse extends SkillPulse { - - /** - * Represents the feather item. - */ - private Item feather; - - /** - * Represents possible feather Items - */ - private static final Item[] FEATHER = new Item[] { - new Item(Items.FEATHER_314), - new Item(Items.STRIPY_FEATHER_10087), - new Item(Items.RED_FEATHER_10088), - new Item(Items.BLUE_FEATHER_10089), - new Item(Items.YELLOW_FEATHER_10090), - new Item(Items.ORANGE_FEATHER_10091) - }; - - /** - * Represents the bolt. - */ - private final Fletching.Bolts bolt; - - /** - * Represents the sets to do. - */ - private int sets; - - /** - * Represents if we're using sets. - */ - private boolean useSets = false; - - /** - * Constructs a new {@code BoltPulse.java} {@code Object}. - * @param player the player. - * @param node the node. - */ - public BoltPulse(Player player, Item node, final Fletching.Bolts bolt, final Item feather, final int sets) { - super(player, node); - this.bolt = bolt; - this.sets = sets; - this.feather = feather; - } - - @Override - public boolean checkRequirements() { - if (bolt.getUnfinished().getId() == 13279) { - if (!SlayerManager.getInstance(player).flags.isBroadsUnlocked()) { - player.getDialogueInterpreter().sendDialogue("You need to unlock the ability to create broad bolts."); - return false; - } - } - if (player.getSkills().getLevel(Skills.FLETCHING) < bolt.level) { - player.getDialogueInterpreter().sendDialogue("You need a fletching level of " + bolt.level + " in order to do this."); - return false; - } - if (!player.getInventory().containsItem(feather)) { - return false; - } - if (!player.getInventory().containsItem(bolt.getUnfinished())) { - return false; - } - if (!player.getInventory().hasSpaceFor(bolt.getFinished())) { - player.getDialogueInterpreter().sendDialogue("You do not have enough inventory space."); - return false; - } - - return true; - } - - @Override - public void animate() { - } - - @Override - public boolean reward() { - int featherAmount = player.getInventory().getAmount(feather); - int boltAmount = player.getInventory().getAmount(bolt.unfinished); - if (getDelay() == 1) { - super.setDelay(3); - } - final Item unfinished = bolt.getUnfinished(); - if (featherAmount >= 10 && boltAmount >= 10) { - feather.setAmount(10); - unfinished.setAmount(10); - player.getPacketDispatch().sendMessage("You fletch 10 bolts."); - } else { - int amount = featherAmount > boltAmount ? boltAmount : featherAmount; - feather.setAmount(amount); - unfinished.setAmount(amount); - player.getPacketDispatch().sendMessage(amount == 1 ? "You attach a feather to a bolt." : "You fletch " + amount + " bolts"); - } - if (player.getInventory().remove(feather, unfinished)) { - Item product = bolt.getFinished(); - product.setAmount(feather.getAmount()); - player.getSkills().addExperience(Skills.FLETCHING, product.getAmount() * bolt.experience, true); - player.getInventory().add(product); - } - feather.setAmount(1); - if (!player.getInventory().containsItem(feather)) { - return true; - } - if (!player.getInventory().containsItem(bolt.getUnfinished())) { - return true; - } - sets--; - return sets <= 0; - } - - @Override - public void message(int type) { - } - -} diff --git a/Server/src/main/content/global/skill/fletching/items/bow/StringBow.java b/Server/src/main/content/global/skill/fletching/items/bow/StringBow.java deleted file mode 100644 index 0187f17bb..000000000 --- a/Server/src/main/content/global/skill/fletching/items/bow/StringBow.java +++ /dev/null @@ -1,136 +0,0 @@ -/* -package core.game.node.entity.skill.fletching.items.bow; - -import org.crandor.game.node.item.Item; -import org.crandor.game.world.update.flag.context.Animation; - -*/ -/** - * Represents the enum of stringing bows. - * @author 'Vexia - *//* - -public enum StringBow { - SHORT_BOW(new Item(50), new Item(841), 5, 5, new Animation(6678)), - LONG_BOW(new Item(48), new Item(839), 10, 10, new Animation(6684)), - OAK_SHORTBOW(new Item(54), new Item(843), 20, 16.5, new Animation(6679)), - OAK_LONGBOW(new Item(56), new Item(845), 25, 25, new Animation(6685)), - WILLOW_SHORTBOW(new Item(60), new Item(849), 35, 33.3, new Animation(6680)), - WILLOW_LONGBOW(new Item(58), new Item(847), 40, 41.5, new Animation(6686)), - MAPLE_SHORTBOW(new Item(64), new Item(853), 50, 50, new Animation(6681)), - MAPLE_LONGBOW(new Item(62), new Item(851), 55, 58.3, new Animation(6687)), - YEW_SHORTBOW(new Item(68), new Item(857), 65, 66, new Animation(6682)), - YEW_LONGBOW(new Item(66), new Item(855), 70, 75, new Animation(6688)), - MAGIC_SHORTBOW(new Item(72), new Item(861), 80, 83.3, new Animation(6683)), - MAGIC_LONGBOW(new Item(70), new Item(859), 85, 91.5, new Animation(6689)); - - - StringBow(final Item item, final Item product, final int level, final double experience, final Animation animation) { - this.item = item; - this.product = product; - this.level = level; - this.experience = experience; - this.animation = animation; - } - - */ -/** - * The item required. - *//* - - private final Item item; - - */ -/** - * The item product. - *//* - - private final Item product; - - */ -/** - * The level required. - *//* - - private final int level; - - */ -/** - * The experience required. - *//* - - private final double experience; - - */ -/** - * The animation of stringing. - *//* - - private final Animation animation; - - */ -/** - * Gets the item. - * @return The item. - *//* - - public Item getItem() { - return item; - } - - */ -/** - * Gets the product. - * @return The product. - *//* - - public Item getProduct() { - return product; - } - - */ -/** - * Gets the level. - * @return The level. - *//* - - public int getLevel() { - return level; - } - - */ -/** - * Gets the experience. - * @return The experience. - *//* - - public double getExperience() { - return experience; - } - - */ -/** - * Method used to get the animation. - * @return the animation. - *//* - - public Animation getAnimation() { - return animation; - } - - */ -/** - * Method used to get the string bow for the item. - * @param item the item. - * @return the string bow. - *//* - - public static StringBow forItem(final int id) { - for (StringBow bw : StringBow.values()) { - if (bw.getItem().getId() == id) { - return bw; - } - } - return null; - } -}*/ diff --git a/Server/src/main/content/global/skill/fletching/items/bow/StringPulse.java b/Server/src/main/content/global/skill/fletching/items/bow/StringPulse.java deleted file mode 100644 index 0d8645e44..000000000 --- a/Server/src/main/content/global/skill/fletching/items/bow/StringPulse.java +++ /dev/null @@ -1,103 +0,0 @@ -package content.global.skill.fletching.items.bow; - -import core.game.node.entity.player.info.LogType; -import core.game.node.entity.player.info.PlayerMonitor; -import core.game.node.entity.player.link.diary.DiaryType; -import core.game.world.map.zone.ZoneBorders; -import core.game.node.entity.skill.SkillPulse; -import core.game.node.entity.skill.Skills; -import content.global.skill.fletching.Fletching; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; - -import static core.api.ContentAPIKt.amountInInventory; - -/** - * Represents the skill pulse of stringing. - * - * @author Ceikry - */ -public class StringPulse extends SkillPulse { - - /** - * Represents the string bow. - */ - private final Fletching.String bow; - - /** - * The amount. - */ - private int amount; - - private int initialAmount; - private int processedAmount; - - /** - * Constructs a new {@code StringbowPlugin.java} {@code Object}. - * - * @param player the player. - * @param node the node. - */ - public StringPulse(Player player, Item node, final Fletching.String bow, int amount) { - super(player, node); - this.bow = bow; - this.amount = amount; - this.initialAmount = amountInInventory(player, node.getId()); - this.processedAmount = 0; - } - - @Override - public boolean checkRequirements() { - if (getDelay() == 1) { - setDelay(2); - } - if (player.getSkills().getLevel(Skills.FLETCHING) < bow.level) { - player.getDialogueInterpreter().sendDialogue("You need a fletching level of " + bow.level + " to string this bow."); - return false; - } - if (!player.getInventory().containsItem(new Item(bow.unfinished))) { - return false; - } - if (!player.getInventory().containsItem(new Item(bow.string))) { - player.getDialogueInterpreter().sendDialogue("You seem to have run out of bow strings."); - return false; - } - animate(); - return true; - } - - @Override - public void animate() { - player.animate(bow.animation); - } - - @Override - public boolean reward() { - if (player.getInventory().remove(new Item(bow.unfinished), new Item(bow.string))) { - player.getInventory().add(new Item(bow.product)); - player.getSkills().addExperience(Skills.FLETCHING, bow.experience, true); - player.getPacketDispatch().sendMessage("You add a string to the bow."); - processedAmount++; - if (processedAmount > initialAmount) { - PlayerMonitor.log(player, LogType.DUPE_ALERT, "fletched item (" + player.getName() + ", " + bow.unfinished + "): initialAmount " + initialAmount + ", processedAmount " + processedAmount); - } - - if (bow == Fletching.String.MAGIC_SHORTBOW - && (new ZoneBorders(2721, 3489, 2724, 3493, 0).insideBorder(player) - || new ZoneBorders(2727, 3487, 2730, 3490, 0).insideBorder(player)) - && player.getAttribute("diary:seers:fletch-magic-short-bow", false)) { - player.getAchievementDiaryManager().finishTask(player, DiaryType.SEERS_VILLAGE, 2, 2); - } - } - if (!player.getInventory().containsItem(new Item(bow.string)) || !player.getInventory().containsItem(new Item(bow.unfinished))) { - return true; - } - amount--; - return amount == 0; - } - - @Override - public void message(int type) { - } - -} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/items/crossbow/CrossbowPulse.java b/Server/src/main/content/global/skill/fletching/items/crossbow/CrossbowPulse.java deleted file mode 100644 index cfe0cf53b..000000000 --- a/Server/src/main/content/global/skill/fletching/items/crossbow/CrossbowPulse.java +++ /dev/null @@ -1,97 +0,0 @@ -/* -package core.game.node.entity.skill.fletching.items.crossbow; - -import core.game.node.entity.skill.SkillPulse; -import core.game.node.entity.skill.Skills; -import org.crandor.game.node.entity.player.Player; -import org.crandor.game.node.item.Item; - -*/ -/** - * Represents the skill pulse of stringing. - * @author 'Vexia - *//* - -public class CrossbowPulse extends SkillPulse { - - */ -/** - * Represents the bow string item. - *//* - - private final Item BOW_STRING = new Item(9438); - - */ -/** - * Represents the string bow. - *//* - - private final StringCross bow; - - */ -/** - * Represents the amount. - *//* - - private int amount; - - */ -/** - * Constructs a new {@code StringcrossbowPlugin.java} {@code Object}. - * @param player the player. - * @param node the node. - *//* - - public CrossbowPulse(Player player, Item node, final StringCross bow, int amount) { - super(player, node); - this.bow = bow; - this.amount = amount; - } - - @Override - public boolean checkRequirements() { - if (player.getSkills().getLevel(Skills.FLETCHING) < bow.getLevel()) { - player.getDialogueInterpreter().sendDialogue("You need a fletching level of " + bow.getLevel() + " to string this crossbow."); - return false; - } - if (!player.getInventory().containsItem(BOW_STRING)) { - player.getDialogueInterpreter().sendDialogue("You seem to have run out of bow strings."); - return false; - } - return true; - } - - @Override - public void animate() { - player.animate(bow.getAnimation()); - } - - @Override - public boolean reward() { - if (getDelay() == 1) { - super.setDelay(5); - return false; - } - if (player.getInventory().remove(bow.getItem(), BOW_STRING)) { - player.getInventory().add(bow.getProduct()); - player.getSkills().addExperience(Skills.FLETCHING, bow.getExperience(), true); - player.getPacketDispatch().sendMessage("You add a string to the crossbow."); - } - if (!player.getInventory().containsItem(BOW_STRING) || !player.getInventory().containsItem(bow.getItem())) { - return true; - } - amount--; - return amount == 0; - } - - @Override - public void message(int type) { - switch (type) { - case 0: - break; - case 1: - break; - } - } - -}*/ diff --git a/Server/src/main/content/global/skill/fletching/items/crossbow/Limb.java b/Server/src/main/content/global/skill/fletching/items/crossbow/Limb.java deleted file mode 100644 index 6ffc01413..000000000 --- a/Server/src/main/content/global/skill/fletching/items/crossbow/Limb.java +++ /dev/null @@ -1,128 +0,0 @@ -package content.global.skill.fletching.items.crossbow; - -import core.game.node.item.Item; -import core.game.world.update.flag.context.Animation; - -/** - * Represents the enum for limbs. - * @author 'Vexia - */ -public enum Limb { - WOODEN_STOCK(new Item(9440), new Item(9420), new Item(9454), 9, 12, new Animation(4436)), - OAK_STOCK(new Item(9442), new Item(9422), new Item(9176), 24, 32, new Animation(4437)), - WILLOW_STOCK(new Item(9444), new Item(9423), new Item(9457), 39, 44, new Animation(4438)), - TEAK_STOCK(new Item(9446), new Item(9425), new Item(9459), 46, 54, new Animation(4439)), - MAPLE_STOCK(new Item(9448), new Item(9427), new Item(9461), 54, 64, new Animation(4440)), - MAHOGANY_STOCK(new Item(9450), new Item(9429), new Item(9463), 61, 82, new Animation(4441)), - YEW_STOCK(new Item(9452), new Item(9431), new Item(9465), 69, 100, new Animation(4442)); - - /** - * Constructs a new {@code StringcrosbowPlugin.java} {@code Object}. - * @param stock the stock. - * @param limb the limb. - * @param product the product. - * @param level the level. - * @param experience the experience. - * @param animation the animation. - */ - Limb(Item stock, Item limb, Item product, int level, double experience, Animation animation) { - this.stock = stock; - this.limb = limb; - this.product = product; - this.level = level; - this.experience = experience; - this.animation = animation; - } - - /** - * The stock. - */ - private final Item stock; - - /** - * The limb. - */ - private final Item limb; - - /** - * The product. - */ - private final Item product; - - /** - * The level. - */ - private final int level; - - /** - * The experience. - */ - private final double experience; - - /** - * The animation. - */ - private final Animation animation; - - /** - * Gets the stock. - * @return The stock. - */ - public Item getStock() { - return stock; - } - - /** - * Gets the limb. - * @return The limb. - */ - public Item getLimb() { - return limb; - } - - /** - * Gets the product. - * @return The product. - */ - public Item getProduct() { - return product; - } - - /** - * Gets the level. - * @return The level. - */ - public int getLevel() { - return level; - } - - /** - * Gets the experience. - * @return The experience. - */ - public double getExperience() { - return experience; - } - - /** - * Gets the animation. - * @return The animation. - */ - public Animation getAnimation() { - return animation; - } - - /** - * Method used to get the {@link Limb} for the item. - * @param item the item. - * @return the limb. - */ - public static Limb forItems(final Item item, final Item second) { - for (Limb l : Limb.values()) { - if (l.getLimb().getId() == item.getId() && l.getStock().getId() == second.getId() || l.getLimb().getId() == second.getId() && l.getStock().getId() == item.getId()) { - return l; - } - } - return null; - } -} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/items/crossbow/LimbPulse.kt b/Server/src/main/content/global/skill/fletching/items/crossbow/LimbPulse.kt deleted file mode 100644 index 2d6d35562..000000000 --- a/Server/src/main/content/global/skill/fletching/items/crossbow/LimbPulse.kt +++ /dev/null @@ -1,52 +0,0 @@ -package content.global.skill.fletching.items.crossbow - -import core.game.node.entity.player.Player -import core.game.node.entity.skill.SkillPulse -import core.game.node.entity.skill.Skills -import content.global.skill.fletching.Fletching -import core.game.node.item.Item - -/** - * Represents the skill pulse of attaching limbs. - * @author Ceikry - */ -class LimbPulse(player: Player?, node: Item, private val limb: Fletching.Limb, private var amount: Int) : SkillPulse(player, node) { - override fun checkRequirements(): Boolean { - if (player.skills.getLevel(Skills.FLETCHING) < limb.level) { - player.dialogueInterpreter.sendDialogue("You need a fletching level of " + limb.level + " to attach these limbs.") - return false - } - if (!player.inventory.containsItem(Item(limb.limb))) { - player.dialogueInterpreter.sendDialogue("That's not the correct limb to attach.") - return false - } - if(!player.inventory.containsItem(Item(limb.stock))){ - player.dialogueInterpreter.sendDialogue("That's not the correct stock for that limb.") - return false - } - return player.inventory.containsItem(Item(limb.stock)) - } - - override fun animate() { - player.animate(limb.animation) - } - - override fun reward(): Boolean { - if (delay == 1) { - super.setDelay(6) - return false - } - if (player.inventory.remove(Item(limb.stock), Item(limb.limb))) { - player.inventory.add(Item(limb.product)) - player.skills.addExperience(Skills.FLETCHING, limb.experience, true) - player.packetDispatch.sendMessage("You attach the metal limbs to the stock.") - } - if (!player.inventory.containsItem(Item(limb.limb))) { - return true - } - amount-- - return amount == 0 - } - - override fun message(type: Int) {} -} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/items/crossbow/StringCross.java b/Server/src/main/content/global/skill/fletching/items/crossbow/StringCross.java deleted file mode 100644 index 32671c480..000000000 --- a/Server/src/main/content/global/skill/fletching/items/crossbow/StringCross.java +++ /dev/null @@ -1,139 +0,0 @@ -/* -package core.game.node.entity.skill.fletching.items.crossbow; - -import org.crandor.game.node.item.Item; -import org.crandor.game.world.update.flag.context.Animation; - -*/ -/** - * Represents the enum of stringing crossbows. - * @author 'Vexia - *//* - -public enum StringCross { - BRONZE_CBOW(new Item(9454), new Item(9174), 9, 6, new Animation(6671)), - BLURITE_CBOW(new Item(9456), new Item(9176), 24, 16, new Animation(6672)), - IRON_CBOW(new Item(9457), new Item(9177), 39, 22, new Animation(6673)), - STEEL_CBOW(new Item(9459), new Item(9179), 46, 27, new Animation(6674)), - MITHIRIL_CBOW(new Item(9461), new Item(9181), 54, 32, new Animation(6675)), - ADAMANT_CBOW(new Item(9463), new Item(9183), 61, 41, new Animation(6676)), - RUNITE_CBOW(new Item(9465), new Item(9185), 69, 50, new Animation(6677)); - */ -/** - * Constructs a new {@code StringcrossbowPlugin.java} {@code Object}. - * @param item the item. - * @param product the product. - * @param level the level. - * @param experience the experience. - *//* - - StringCross(final Item item, final Item product, final int level, final double experience, final Animation animation) { - this.item = item; - this.product = product; - this.level = level; - this.experience = experience; - this.animation = animation; - } - - */ -/** - * The item required. - *//* - - private final Item item; - - */ -/** - * The item product. - *//* - - private final Item product; - - */ -/** - * The level required. - *//* - - private final int level; - - */ -/** - * The experience required. - *//* - - private final double experience; - - */ -/** - * The animation of stringing. - *//* - - private final Animation animation; - - */ -/** - * Gets the item. - * @return The item. - *//* - - public Item getItem() { - return item; - } - - */ -/** - * Gets the product. - * @return The product. - *//* - - public Item getProduct() { - return product; - } - - */ -/** - * Gets the level. - * @return The level. - *//* - - public int getLevel() { - return level; - } - - */ -/** - * Gets the experience. - * @return The experience. - *//* - - public double getExperience() { - return experience; - } - - */ -/** - * Method used to get the animation. - * @return the animation. - *//* - - public Animation getAnimation() { - return animation; - } - - */ -/** - * Method used to get the string bow for the item. - * @param item the item. - * @return the string bow. - *//* - - public static StringCross forItem(final Item item) { - for (StringCross bw : StringCross.values()) { - if (bw.getItem().getId() == item.getId()) { - return bw; - } - } - return null; - } -} -*/ diff --git a/Server/src/main/content/global/skill/fletching/items/darts/Dart.java b/Server/src/main/content/global/skill/fletching/items/darts/Dart.java deleted file mode 100644 index 235849496..000000000 --- a/Server/src/main/content/global/skill/fletching/items/darts/Dart.java +++ /dev/null @@ -1,96 +0,0 @@ -package content.global.skill.fletching.items.darts; - -import core.game.node.item.Item; - -/** - * Represents the enum to hold dart info. - * @author 'Vexia - */ -public enum Dart { - BRONZE_DART(new Item(819), new Item(806), 1, 1.8), - IRON_DART(new Item(820), new Item(807), 22, 3.8), - STEEL_DART(new Item(821), new Item(808), 37, 7.5), - MITHRIL_DART(new Item(822), new Item(809), 52, 11.2), - ADAMANT_DART(new Item(823), new Item(810), 67, 15), - RUNE_DART(new Item(824), new Item(811), 81, 18.8), - DRAGON_DART(new Item(11232), new Item(11230), 95, 25); - /** - * Constructs a new {@code Dart} {@code Object}. - * @param item the item. - * @param product the product. - * @param level the level. - * @param experience the experience. - */ - Dart(final Item item, final Item product, final int level, final double experience) { - this.item = item; - this.product = product; - this.level = level; - this.experience = experience; - } - - /** - * Represents the item required. - */ - private final Item item; - - /** - * Represents the product gained. - */ - private final Item product; - - /** - * Represents the level required. - */ - private final int level; - - /** - * Represents the experience gained. - */ - private final double experience; - - /** - * Gets the item. - * @return The item. - */ - public Item getItem() { - return item; - } - - /** - * Gets the product. - * @return The product. - */ - public Item getProduct() { - return product; - } - - /** - * Gets the level. - * @return The level. - */ - public int getLevel() { - return level; - } - - /** - * Gets the experience. - * @return The experience. - */ - public double getExperience() { - return experience; - } - - /** - * Method used to get the dart for the item. - * @param item the item. - * @return the dart. - */ - public static Dart forItem(final Item item) { - for (Dart dart : Dart.values()) { - if (dart.getItem().getId() == item.getId()) { - return dart; - } - } - return null; - } -} diff --git a/Server/src/main/content/global/skill/fletching/items/darts/DartPulse.java b/Server/src/main/content/global/skill/fletching/items/darts/DartPulse.java deleted file mode 100644 index d65fac185..000000000 --- a/Server/src/main/content/global/skill/fletching/items/darts/DartPulse.java +++ /dev/null @@ -1,105 +0,0 @@ -package content.global.skill.fletching.items.darts; - -import core.game.node.entity.skill.SkillPulse; -import core.game.node.entity.skill.Skills; -import content.global.skill.fletching.Fletching; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; - -import static core.api.ContentAPIKt.*; -import content.data.Quests; - -/** - * Represents the dart pulse. - * @author ceikry - */ -public final class DartPulse extends SkillPulse { - - /** - * Represents the feather item. - */ - private static final Item FEATHER = new Item(314); - - /** - * Represents the dart. - */ - private final Fletching.Darts dart; - - /** - * Represents the sets to make. - */ - private int sets; - - /** - * Constructs a new {@code DartPulse.java} {@code Object}. - * @param player the player. - * @param node the node. - */ - public DartPulse(Player player, Item node, Fletching.Darts dart, int sets) { - super(player, node); - this.dart = dart; - this.sets = sets; - } - - @Override - public boolean checkRequirements() { - if (player.getSkills().getLevel(Skills.FLETCHING) < dart.level) { - player.getDialogueInterpreter().sendDialogue("You need a fletching level of " + dart.level + " to do this."); - return false; - } - if (!player.getQuestRepository().isComplete(Quests.THE_TOURIST_TRAP)){ - player.getDialogueInterpreter().sendDialogue("You need to have completed Tourist Trap to fletch darts."); - return false; - } - if (!hasSpaceFor(player, dart.getFinished())) { - sendDialogue(player, "You do not have enough inventory space."); - return false; - } - return true; - } - - @Override - public void animate() { - - } - - @Override - public boolean reward() { - if (getDelay() == 1) { - super.setDelay(3); - } - final Item unfinished = dart.getUnfinished(); - final int dartAmount = player.getInventory().getAmount(unfinished); - final int featherAmount = player.getInventory().getAmount(FEATHER); - if (dartAmount >= 10 && featherAmount >= 10) { - FEATHER.setAmount(10); - unfinished.setAmount(10); - player.getPacketDispatch().sendMessage("You attach feathers to 10 darts."); - } else { - int amount = featherAmount > dartAmount ? dartAmount : featherAmount; - FEATHER.setAmount(amount); - unfinished.setAmount(amount); - player.getPacketDispatch().sendMessage(amount == 1 ? "You attach a feather to a dart." : "You attach feathers to " + amount + " darts."); - } - if (player.getInventory().remove(FEATHER, unfinished)) { - Item product = dart.getFinished(); - product.setAmount(FEATHER.getAmount()); - player.getSkills().addExperience(Skills.FLETCHING, dart.experience * product.getAmount(), true); - player.getInventory().add(product); - } - FEATHER.setAmount(1); - if (!player.getInventory().containsItem(FEATHER)) { - return true; - } - if (!player.getInventory().containsItem(dart.getUnfinished())) { - return true; - } - sets--; - return sets == 0; - } - - @Override - public void message(int type) { - } - -} diff --git a/Server/src/main/content/global/skill/fletching/items/gem/Gem.java b/Server/src/main/content/global/skill/fletching/items/gem/Gem.java deleted file mode 100644 index 6a3377aa4..000000000 --- a/Server/src/main/content/global/skill/fletching/items/gem/Gem.java +++ /dev/null @@ -1,101 +0,0 @@ -package content.global.skill.fletching.items.gem; - -import core.game.node.item.Item; - -/** - * Represents gems to cut into bolt tips. - * @author 'Vexia - * @date 01/12/2013 - */ -public enum Gem { - OPAL(new Item(1609), new Item(45, 12), 11, 1.5), - JADE(new Item(1611), new Item(9187, 12), 26, 2.4), - RED_TOPAZ(new Item(1613), new Item(9188, 12), 48, 3.9), - SAPPHIRE(new Item(1607), new Item(9189, 12), 56, 4), - EMERALD(new Item(1605), new Item(9190, 12), 58, 5.5), - RUBY(new Item(1603), new Item(9191, 12), 63, 6.3), - DIAMOND(new Item(1601), new Item(9192, 12), 65, 7), - DRAGONSTONE(new Item(1615), new Item(9193, 12), 71, 8.2), - ONYX(new Item(6573), new Item(9194, 24), 73, 9.4); - - /** - * Constructs a new {@code Gem.java} {@code Object}. - * @param gem the gem. - * @param bolt the bolt. - * @param level the level. - * @param experience the experience. - */ - Gem(Item gem, Item bolt, int level, double experience) { - this.gem = gem; - this.bolt = bolt; - this.level = level; - this.experience = experience; - } - - /** - * Represents the gem. - */ - private final Item gem; - - /** - * Represents the bolt. - */ - private final Item bolt; - - /** - * Represents the level required. - */ - private final int level; - - /** - * Represents the experience gained. - */ - private final double experience; - - /** - * Gets the gem. - * @return The gem. - */ - public Item getGem() { - return gem; - } - - /** - * Gets the bolt. - * @return The bolt. - */ - public Item getBolt() { - return bolt; - } - - /** - * Gets the level. - * @return The level. - */ - public int getLevel() { - return level; - } - - /** - * Gets the experience. - * @return The experience. - */ - public double getExperience() { - return experience; - } - - /** - * Method used to get a gem for the item. - * @param item the item. - * @return the gem. - */ - public static Gem forItem(final Item item) { - for (Gem gem : values()) { - if (gem.getGem().getId() == item.getId()) { - return gem; - } - } - return null; - } - -} diff --git a/Server/src/main/content/global/skill/fletching/items/gem/GemBolt.java b/Server/src/main/content/global/skill/fletching/items/gem/GemBolt.java deleted file mode 100644 index b21e6a164..000000000 --- a/Server/src/main/content/global/skill/fletching/items/gem/GemBolt.java +++ /dev/null @@ -1,144 +0,0 @@ -/* -package core.game.node.entity.skill.fletching.items.gem; - -import org.crandor.game.node.item.Item; - -*/ -/** - * Represents a gem bolt. - * @author 'Vexia - * @date 01/12/2013 - *//* - -public enum GemBolt { - OPAL(new Item(877, 10), new Item(45, 10), new Item(879, 10), 11, 1.5), - PEARL(new Item(9140, 10), new Item(46, 10), new Item(880, 10), 41, 3.2), - JADE(new Item(9139, 10), new Item(9187, 10), new Item(9335, 10), 26, 2.4), - RED_TOPAZ(new Item(9141, 10), new Item(9188, 10), new Item(9336, 10), 48, 3.9), - SAPPHIRE(new Item(9142, 10), new Item(9189, 10), new Item(9337, 10), 56, 4), - EMERALD(new Item(9142, 10), new Item(9190, 10), new Item(9338, 10), 58, 5.5), - RUBY(new Item(9143, 10), new Item(9191, 10), new Item(9339, 10), 63, 6.3), - DIAMOND(new Item(9143, 10), new Item(9192, 10), new Item(9340, 10), 65, 7), - DRAGONSTONE(new Item(9144, 10), new Item(9193, 10), new Item(9341, 10), 71, 8.2), - ONYX(new Item(9144, 10), new Item(9194, 10), new Item(9342, 10), 73, 9.4); - - */ -/** - * Constructs a new {@code GemBolt} {@code Object}. - * @param base the base. - * @param tip the tip. - * @param level the level. - * @param experience the experience. - *//* - - GemBolt(Item base, Item tip, Item product, int level, double experience) { - this.base = base; - this.tip = tip; - this.product = product; - this.level = level; - this.experience = experience; - } - - */ -/** - * Represents the base item. - *//* - - private final Item base; - - */ -/** - * Represents the tip to attach. - *//* - - private final Item tip; - - */ -/** - * Represents the product. - *//* - - private final Item product; - - */ -/** - * Represents the level. - *//* - - private final int level; - - */ -/** - * Represents the experience. - *//* - - private final double experience; - - */ -/** - * Gets the base. - * @return The base. - *//* - - public Item getBase() { - return base; - } - - */ -/** - * Gets the tip. - * @return The tip. - *//* - - public Item getTip() { - return tip; - } - - */ -/** - * Gets the product. - * @return The product. - *//* - - public Item getProduct() { - return product; - } - - */ -/** - * Gets the level. - * @return The level. - *//* - - public int getLevel() { - return level; - } - - */ -/** - * Gets the experience. - * @return The experience. - *//* - - public double getExperience() { - return experience; - } - - */ -/** - * Method used to get the gem bolt from the id. - * @param boltt the boltt. - * @param tip the tip. - * @return the bolt. - *//* - - public static GemBolt forItems(final Item boltt, final Item tip) { - for (GemBolt bolt : values()) { - if (bolt.getBase().getId() == boltt.getId() && bolt.getTip().getId() == tip.getId() || bolt.getBase().getId() == tip.getId() && bolt.getTip().getId() == boltt.getId()) { - return bolt; - } - } - return null; - } -} -*/ diff --git a/Server/src/main/content/global/skill/fletching/items/gem/GemBoltCutPulse.kt b/Server/src/main/content/global/skill/fletching/items/gem/GemBoltCutPulse.kt deleted file mode 100644 index e03781529..000000000 --- a/Server/src/main/content/global/skill/fletching/items/gem/GemBoltCutPulse.kt +++ /dev/null @@ -1,68 +0,0 @@ -package content.global.skill.fletching.items.gem - -import core.game.node.entity.player.Player -import core.game.node.entity.skill.SkillPulse -import core.game.node.entity.skill.Skills -import content.global.skill.fletching.Fletching.GemBolts -import core.game.node.item.Item -import core.game.world.update.flag.context.Animation -import org.rs09.consts.Items - -/** - * Represents the gem cutting pulse(gem to bolt). - * @author Ceikry - */ -class GemBoltCutPulse -/** - * Constructs a new `GemCutPulse.java` `Object`. - * @param player the player. - * @param node the node. - * @param amount the amount. - */(player: Player?, node: Item?, - /** - * Represents the gem we're cutting. - */ - private val gem: GemBolts, - /** - * Represents the amount to make. - */ - private var amount: Int) : SkillPulse(player, node) { - /** - * Represents the ticks passed. - */ - private var ticks = 0 - - override fun checkRequirements(): Boolean { - if (player.skills.getLevel(Skills.FLETCHING) < gem.level) { - player.dialogueInterpreter.sendDialogue("You need a Fletching level of " + gem.level + " or above to do that.") - return false - } - return player.inventory.containsItem(Item(gem.gem)) - } - - override fun animate() { - if (ticks % 6 == 0) { - player.animate(ANIMATION) - } - } - - override fun reward(): Boolean { - if (++ticks % 5 != 0) { - return false - } - val reward = if (gem.gem == Items.OYSTER_PEARLS_413) Item(gem.tip, 24) else Item(gem.tip, 12) - if (player.inventory.remove(Item(gem.gem))) { - player.inventory.add(reward) - player.skills.addExperience(Skills.FLETCHING, gem.experience, true) - } - amount-- - return amount <= 0 - } - - companion object { - /** - * Represents the cutting animation. - */ - private val ANIMATION = Animation(6702) - } -} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/items/gem/GemBoltPulse.java b/Server/src/main/content/global/skill/fletching/items/gem/GemBoltPulse.java deleted file mode 100644 index 4cd856e8a..000000000 --- a/Server/src/main/content/global/skill/fletching/items/gem/GemBoltPulse.java +++ /dev/null @@ -1,91 +0,0 @@ -package content.global.skill.fletching.items.gem; - -import core.game.node.entity.skill.SkillPulse; -import core.game.node.entity.skill.Skills; -import content.global.skill.fletching.Fletching; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; - -/** - * Represents the attaching of a gem bolt to a premade bolt. - * @author Ceikry - */ -public final class GemBoltPulse extends SkillPulse { - - /** - * Represents the gem bolt being made. - */ - private Fletching.GemBolts bolt; - - /** - * Represents the sets to make. - */ - private int sets = 0; - - /** - * Represents the ticks passed. - */ - private int ticks; - - /** - * Constructs a new {@code GemBoltPulse} {@code Object}. - * @param player the player. - * @param node the node. - * @param sets the sets. - */ - public GemBoltPulse(Player player, Item node, Fletching.GemBolts bolt, int sets) { - super(player, node); - this.bolt = bolt; - this.sets = sets; - } - - @Override - public boolean checkRequirements() { - if (player.getSkills().getLevel(Skills.FLETCHING) < bolt.level) { - player.getDialogueInterpreter().sendDialogue("You need a Fletching level of " + bolt.level + " or above to do that."); - return false; - } - if (!player.getInventory().containsItem(new Item(bolt.base)) || !player.getInventory().containsItem(new Item(bolt.tip))) { - return false; - } - if (!player.getInventory().hasSpaceFor(new Item(bolt.product))){ - player.getDialogueInterpreter().sendDialogue("You do not have enough inventory space."); - return false; - } - return true; - } - - @Override - public void animate() { - } - - @Override - public boolean reward() { - if (++ticks % 3 != 0) { - return false; - } - int baseAmount = player.getInventory().getAmount(bolt.base); - int tipAmount = player.getInventory().getAmount(bolt.tip); - Item base = new Item(bolt.base); - Item tip = new Item(bolt.tip); - Item product = new Item(bolt.product); - if(baseAmount >= 10 && tipAmount >= 10){ - base.setAmount(10); - tip.setAmount(10); - product.setAmount(10); - } else { - int amount = baseAmount > tipAmount ? tipAmount : baseAmount; - base.setAmount(amount); - tip.setAmount(amount); - product.setAmount(amount); - } - if (player.getInventory().remove(base,tip)) { - player.getInventory().add(product); - player.getSkills().addExperience(Skills.FLETCHING, bolt.experience * product.getAmount(), true); - player.getPacketDispatch().sendMessage(product.getAmount() == 1 ? "You attach the tip to the bolt." : "You fletch " + product.getAmount() + " bolts."); - } - sets--; - return sets <= 0; - } - -} diff --git a/Server/src/main/content/global/skill/fletching/items/grapple/GrapplePulse.java b/Server/src/main/content/global/skill/fletching/items/grapple/GrapplePulse.java deleted file mode 100644 index cbba3820f..000000000 --- a/Server/src/main/content/global/skill/fletching/items/grapple/GrapplePulse.java +++ /dev/null @@ -1,85 +0,0 @@ -package content.global.skill.fletching.items.grapple; - -import core.game.node.entity.skill.SkillPulse; -import core.game.node.entity.skill.Skills; -import core.game.node.entity.player.Player; -import core.game.node.item.Item; - -/** - * Represents the skill pulse used to create a mith grapple. - * @author 'Vexia - * @date 21/12/2013 - */ -public final class GrapplePulse extends SkillPulse { - - /** - * Represents the mith grapple tip. - */ - private static final Item MITH_GRAPPLE = new Item(9418); - - /** - * Represents the mithril grapple tio. - */ - private static final Item GRAPPLE_TIP = new Item(9416); - - /** - * Represents the mithril bolt. - */ - private static final Item MITHRIL_BOLT = new Item(9142); - - /** - * Represents the amount of the grapple to make. - */ - private int amount; - - /** - * Constructs a new {@code GrapplePulse} {@code Object}. - * @param player the player. - * @param node the node. - * @param amount the amount. - */ - public GrapplePulse(Player player, Item node, int amount) { - super(player, node); - this.amount = amount; - } - - @Override - public boolean checkRequirements() { - int inventoryAmount = player.getInventory().getAmount(GRAPPLE_TIP); - if (amount > inventoryAmount) { - amount = inventoryAmount; - } - if (!player.getInventory().containsItem(GRAPPLE_TIP) || !player.getInventory().containsItem(MITHRIL_BOLT)) { - return false; - } - if (player.getSkills().getLevel(Skills.FLETCHING) < 59) { - player.getDialogueInterpreter().sendDialogue("You need a fletching level of at least 59 in order to do this."); - return false; - } - return true; - } - - @Override - public void animate() { - } - - @Override - public boolean reward() { - if (getDelay() == 1) { - setDelay(3); - return false; - } - if (player.getInventory().remove(GRAPPLE_TIP) && player.getInventory().remove(MITHRIL_BOLT)) { - player.getInventory().add(MITH_GRAPPLE); - player.getSkills().addExperience(Skills.FLETCHING, 5, true); - } - amount--; - return amount < 1; - } - - @Override - public void message(int type) { - - } - -} diff --git a/Server/src/main/content/global/skill/fletching/log/CraftItemWithLogScript.kt b/Server/src/main/content/global/skill/fletching/log/CraftItemWithLogScript.kt new file mode 100644 index 000000000..33e6c5524 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/log/CraftItemWithLogScript.kt @@ -0,0 +1,113 @@ +package content.global.skill.fletching.log + +import content.global.skill.fletching.AchievementDiaryAttributeKeys +import content.global.skill.fletching.Zones +import content.global.skill.fletching.log.GrammarHelpers.aOrAn +import content.global.skill.fletching.log.GrammarHelpers.makeFriendlyName +import core.api.* +import core.game.interaction.Clocks +import core.game.node.entity.player.Player +import core.game.node.entity.player.link.diary.DiaryType +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import core.game.world.update.flag.context.Animation +import core.tools.RandomFunction +import org.rs09.consts.Items + +/** + * Represents the queueScript used to craft things by carving a log + * @author ceik + * @param player the player. + * @param logCraftInfo crafting info about what we're making out of the log + * @param amount iterations of craft to run + */ +class CraftItemWithLogScript( + private val player: Player, + private val logCraftInfo: LogCraftInfo, + private val amount: Int +) { + + private val initialDelay = 1 + + // src https://gitlab.com/2009scape/2009scape/-/merge_requests/1960#note_2702231552 + private val subsequentDelay = 3 + + private val carveLogAnimation = Animation(1248) + + fun invoke() { + queueScript(player, initialDelay) { stage -> + if (!clockReady(player, Clocks.SKILLING)) return@queueScript keepRunning(player) + + if (getDynLevel(player, Skills.FLETCHING) < logCraftInfo.level) { + LogCraftableListeners.sendLevelCheckFailDialog(player, logCraftInfo) + return@queueScript stopExecuting(player) // Check each iteration since dynLevel can change (status effects ending, skill assist session end...) + } + + if (removeItem(player, logCraftInfo.logItemId.asItem())) { + player.animate(carveLogAnimation) + + val finishedItemName = makeFriendlyName(Item(logCraftInfo.finishedItemId).name) + + val amountToCraft = when (logCraftInfo) { + LogCraftInfo.OGRE_ARROW_SHAFT -> RandomFunction.random(2, 6) + LogCraftInfo.ARROW_SHAFT -> 15 + else -> 1 + } + + if (logCraftInfo == LogCraftInfo.OGRE_COMP_BOW) { + if (!removeItem(player, Items.WOLF_BONES_2859)) { + return@queueScript stopExecuting(player) + } + } + + sendCraftMessageToPlayer(amountToCraft, finishedItemName) + + addItem(player, logCraftInfo.finishedItemId, amountToCraft) + rewardXP(player, Skills.FLETCHING, logCraftInfo.experience) + + if (logCraftInfo == LogCraftInfo.MAGIC_SHORTBOW) { + handleSeersMagicShortbowAchievement() + } + } else { + return@queueScript stopExecuting(player) + } + + if (stage >= amount - 1) { + return@queueScript stopExecuting(player) + } + + return@queueScript delayClock(player, Clocks.SKILLING, subsequentDelay, true) + } + } + + private fun handleSeersMagicShortbowAchievement() { + if (Zones.inAnyZone(player, Zones.seersMagicShortbowAchievementZones) && + !player.achievementDiaryManager.hasCompletedTask(DiaryType.SEERS_VILLAGE, 2, 2) + ) { + setAttribute( + player, + "/save:${AchievementDiaryAttributeKeys.FLETCHED_UNSTRUNG_MAGIC_SHORTBOW}", + true + ) + } + } + + private fun sendCraftMessageToPlayer(amountToCraft: Int, finishedItemName: String) { + when (logCraftInfo) { + LogCraftInfo.ARROW_SHAFT -> sendMessage( + player, + "You carefully cut the wood into $amountToCraft arrow shafts." + ) + + LogCraftInfo.OGRE_ARROW_SHAFT -> sendMessage( + player, + "You carefully cut the wood into $amountToCraft arrow shafts." + ) + + else -> sendMessage( + player, + "You carefully cut the wood into ${aOrAn(finishedItemName)} $finishedItemName." + ) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/log/GrammarHelpers.kt b/Server/src/main/content/global/skill/fletching/log/GrammarHelpers.kt new file mode 100644 index 000000000..5863c51ea --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/log/GrammarHelpers.kt @@ -0,0 +1,20 @@ +package content.global.skill.fletching.log + +import core.tools.StringUtils + +object GrammarHelpers { + fun makeFriendlyName(name: String): String { + return name.replace("(u)", "").trim { it <= ' ' } + } + + /** + * Returns "a" or "an" depending on whether the word begins with a vowel + */ + @Suppress("GrazieInspection") + fun aOrAn(word: String): String { + return when (StringUtils.isPlusN(word)) { + true -> "an" + false -> "a" + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/log/LogCraftInfo.kt b/Server/src/main/content/global/skill/fletching/log/LogCraftInfo.kt new file mode 100644 index 000000000..2111606dc --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/log/LogCraftInfo.kt @@ -0,0 +1,63 @@ +package content.global.skill.fletching.log + +import org.rs09.consts.Items + +/** + * Provides information pertaining to crafting different items from logs + * @property logItemId the log item id that can be used to craft this item + * @property finishedItemId the resulting finished bolt Item id. + * @property level the level required to craft. + * @property experience the experience gained by crafting. + */ +enum class LogCraftInfo(val logItemId: Int, val finishedItemId: Int, val experience: Double, val level: Int) { + ARROW_SHAFT(Items.LOGS_1511, Items.ARROW_SHAFT_52, 5.0, 1), + SHORT_BOW(Items.LOGS_1511, Items.SHORTBOW_U_50, 5.0, 5), + LONG_BOW(Items.LOGS_1511, Items.LONGBOW_U_48, 10.0, 10), + WOODEN_STOCK(Items.LOGS_1511, Items.WOODEN_STOCK_9440, 6.0, 9), + + OGRE_ARROW_SHAFT(Items.ACHEY_TREE_LOGS_2862, Items.OGRE_ARROW_SHAFT_2864, 6.4, 5), + OGRE_COMP_BOW(Items.ACHEY_TREE_LOGS_2862, Items.UNSTRUNG_COMP_BOW_4825, 45.0, 30), + + OAK_SHORTBOW(Items.OAK_LOGS_1521, Items.OAK_SHORTBOW_U_54, 16.5, 20), + OAK_LONGBOW(Items.OAK_LOGS_1521, Items.OAK_LONGBOW_U_56, 25.0, 25), + OAK_STOCK(Items.OAK_LOGS_1521, Items.OAK_STOCK_9442, 16.0, 24), + + WILLOW_SHORTBOW(Items.WILLOW_LOGS_1519, Items.WILLOW_SHORTBOW_U_60, 33.3, 35), + WILLOW_LONGBOW(Items.WILLOW_LOGS_1519, Items.WILLOW_LONGBOW_U_58, 41.5, 40), + WILLOW_STOCK(Items.WILLOW_LOGS_1519, Items.WILLOW_STOCK_9444, 22.0, 39), + + MAPLE_SHORTBOW(Items.MAPLE_LOGS_1517, Items.MAPLE_SHORTBOW_U_64, 50.0, 50), + MAPLE_LONGBOW(Items.MAPLE_LOGS_1517, Items.MAPLE_LONGBOW_U_62, 58.3, 55), + MAPLE_STOCK(Items.MAPLE_LOGS_1517, Items.MAPLE_STOCK_9448, 32.0, 54), + + YEW_SHORTBOW(Items.YEW_LOGS_1515, Items.YEW_SHORTBOW_U_68, 67.5, 65), + YEW_LONGBOW(Items.YEW_LOGS_1515, Items.YEW_LONGBOW_U_66, 75.0, 70), + YEW_STOCK(Items.YEW_LOGS_1515, Items.YEW_STOCK_9452, 50.0, 69), + + MAGIC_SHORTBOW(Items.MAGIC_LOGS_1513, Items.MAGIC_SHORTBOW_U_72, 83.3, 80), + MAGIC_LONGBOW(Items.MAGIC_LOGS_1513, Items.MAGIC_LONGBOW_U_70, 91.5, 85), + + TEAK_STOCK(Items.TEAK_LOGS_6333, Items.TEAK_STOCK_9446, 27.0, 46), + + MAHOGANY_STOCK(Items.MAHOGANY_LOGS_6332, Items.MAHOGANY_STOCK_9450, 41.0, 61); + + + companion object { + val logIds: IntArray = LogCraftInfo.values().map { logCraftInfo: LogCraftInfo -> logCraftInfo.logItemId }.distinct().toIntArray() + + private val logCraftablesByLogId = associateCraftInfoByLogIds() + private fun associateCraftInfoByLogIds(): Map> { + val map = HashMap>() + + for (logId in logIds) { + map[logId] = LogCraftInfo.values().filter { logCraftInfo: LogCraftInfo -> logCraftInfo.logItemId == logId } + } + + return map + } + + fun forLogId(logId: Int): List? { + return logCraftablesByLogId[logId] + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/log/LogCraftableListeners.kt b/Server/src/main/content/global/skill/fletching/log/LogCraftableListeners.kt new file mode 100644 index 000000000..fde0d5ff4 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/log/LogCraftableListeners.kt @@ -0,0 +1,83 @@ +package content.global.skill.fletching.log + +import content.data.Quests +import content.global.skill.fletching.log.GrammarHelpers.aOrAn +import content.global.skill.fletching.log.GrammarHelpers.makeFriendlyName +import core.api.* +import core.game.dialogue.SkillDialogueHandler +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import org.rs09.consts.Items + +@Suppress("unused") // Reflectively loaded +class LogCraftableListeners : InteractionListener { + override fun defineListeners() { + onUseWith(IntType.ITEM, Items.KNIFE_946, *LogCraftInfo.logIds) { player, knife, log -> + val applicableCraftInfosForLog = LogCraftInfo.forLogId(log.id) ?: return@onUseWith false + val craftableItems = + applicableCraftInfosForLog.map { logCraftable -> Item(logCraftable.finishedItemId) }.toTypedArray() + + val handler: SkillDialogueHandler = object : SkillDialogueHandler( + player, + SkillDialogue.forLength(applicableCraftInfosForLog.size), + *craftableItems + ) { + override fun create(amount: Int, index: Int) { + if (!playerMeetsInitialRequirements(applicableCraftInfosForLog[index])) return + CraftItemWithLogScript(player, applicableCraftInfosForLog[index], amount).invoke() + } + + private fun playerMeetsInitialRequirements(logCraftInfo: LogCraftInfo): Boolean { + if (getDynLevel(player, Skills.FLETCHING) < logCraftInfo.level) { + sendLevelCheckFailDialog(player, logCraftInfo) + return false + } + if (logCraftInfo == LogCraftInfo.OGRE_ARROW_SHAFT && + !isQuestStarted(player, Quests.BIG_CHOMPY_BIRD_HUNTING) + ) { + sendDialogue(player, "You must have started ${Quests.BIG_CHOMPY_BIRD_HUNTING} to make those.") + return false + } + if (logCraftInfo == LogCraftInfo.OGRE_COMP_BOW) { + if (getQuestStage(player, Quests.ZOGRE_FLESH_EATERS) < 8) { + sendMessage( + player, + "You must have started Zogre Flesh Eaters and asked Grish to string this." + ) + return false + } + if (amountInInventory(player, Items.WOLF_BONES_2859) < 1) { + sendMessage(player, "You need to have Wolf Bones in order to make this.") + return false + } + } + return true + } + + override fun getAll(index: Int): Int { + return amountInInventory(player, log.id) + } + } + handler.open() + return@onUseWith true + } + } + + companion object { + fun sendLevelCheckFailDialog(player: Player, logCraftInfo: LogCraftInfo) { + val finishedItem = Item(logCraftInfo.finishedItemId) + val friendlyCraftedItemName = makeFriendlyName(finishedItem.name) + sendDialogue( + player, + "You need a Fletching skill of ${logCraftInfo.level} or above to make ${ + aOrAn( + friendlyCraftedItemName + ) + } $friendlyCraftedItemName." + ) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/stringing/StringItemScript.kt b/Server/src/main/content/global/skill/fletching/stringing/StringItemScript.kt new file mode 100644 index 000000000..633e29667 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/stringing/StringItemScript.kt @@ -0,0 +1,71 @@ +package content.global.skill.fletching.stringing + +import content.global.skill.fletching.AchievementDiaryAttributeKeys +import content.global.skill.fletching.Zones +import content.global.skill.fletching.stringing.StringableCraftInfo.Companion.applicableStringId +import core.api.* +import core.game.interaction.Clocks +import core.game.node.entity.player.Player +import core.game.node.entity.player.link.diary.DiaryType +import core.game.node.entity.skill.Skills +import core.game.node.item.Item + +/** + * Represents queueScript to string a bow/crossbow + * @author Ceikry + * @param player the player. + * @param stringableCraftInfo contains crafting information about what we're stringing + * @param amount the amount of items to string + */ +class StringItemScript( + private val player: Player, + private val stringableCraftInfo: StringableCraftInfo, + private var amount: Int +) { + private val initialDelay = 1 + private val subsequentDelay = 2 + + fun invoke() { + queueScript(player, initialDelay) { stage -> + if (!clockReady(player, Clocks.SKILLING)) return@queueScript keepRunning(player) + + if (getDynLevel(player, Skills.FLETCHING) < stringableCraftInfo.level) { + StringingListeners.sendLevelCheckFailDialogue(player, stringableCraftInfo.level) + return@queueScript stopExecuting(player) // Check each iteration since dynLevel can change (status effects ending, skill assist session end...) + } + + if ( + removeItemsIfPlayerHasEnough( + player, + Item(stringableCraftInfo.unstrungItemId), + Item(stringableCraftInfo.applicableStringId) + ) + ) { + player.animate(stringableCraftInfo.animation) + + addItem(player, stringableCraftInfo.strungItemId, 1) + rewardXP(player, Skills.FLETCHING, stringableCraftInfo.experience) + sendMessage(player, "You add a string to the bow.") + if (stringableCraftInfo == StringableCraftInfo.MAGIC_SHORTBOW) { + handleSeersMagicShortbowAchievement() + } + } else { + return@queueScript stopExecuting(player) + } + + if (stage >= amount - 1) { + return@queueScript stopExecuting(player) + } + + return@queueScript delayClock(player, Clocks.SKILLING, subsequentDelay, true) + } + } + + private fun handleSeersMagicShortbowAchievement() { + if (Zones.inAnyZone(player, Zones.seersMagicShortbowAchievementZones) && + getAttribute(player, AchievementDiaryAttributeKeys.FLETCHED_UNSTRUNG_MAGIC_SHORTBOW, false) + ) { + player.achievementDiaryManager.finishTask(player, DiaryType.SEERS_VILLAGE, 2, 2) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/stringing/StringableCraftInfo.kt b/Server/src/main/content/global/skill/fletching/stringing/StringableCraftInfo.kt new file mode 100644 index 000000000..38c089621 --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/stringing/StringableCraftInfo.kt @@ -0,0 +1,66 @@ +package content.global.skill.fletching.stringing + +import core.game.world.update.flag.context.Animation +import org.rs09.consts.Items + +/** + * Provides information pertaining to crafting different strung items + * @property isCrossbow is this enum for a crossbow? + * @property unstrungItemId the unstrung item Id + * @property strungItemId the resulting strung item Id + * @property level the level required to string the item + * @property experience experience gained from stringing + * @property animation the stringing animation to play + */ +enum class StringableCraftInfo( + val isCrossbow: Boolean, + val unstrungItemId: Int, + val strungItemId: Int, + val level: Int, + val experience: Double, + val animation: Animation +) { + SHORT_BOW(false, Items.SHORTBOW_U_50, Items.SHORTBOW_841, 5, 5.0, Animation(6678)), + LONG_BOW(false, Items.LONGBOW_U_48, Items.LONGBOW_839, 10, 10.0, Animation(6684)), + OAK_SHORTBOW(false, Items.OAK_SHORTBOW_U_54, Items.OAK_SHORTBOW_843, 20, 16.5, Animation(6679)), + OAK_LONGBOW(false, Items.OAK_LONGBOW_U_56, Items.OAK_LONGBOW_845, 25, 25.0, Animation(6685)), + OGRE_COMP_BOW(false, Items.UNSTRUNG_COMP_BOW_4825, Items.COMP_OGRE_BOW_4827, 30, 45.0, Animation(-1)), + WILLOW_SHORTBOW(false, Items.WILLOW_SHORTBOW_U_60, Items.WILLOW_SHORTBOW_849, 35, 33.3, Animation(6680)), + WILLOW_LONGBOW(false, Items.WILLOW_LONGBOW_U_58, Items.WILLOW_LONGBOW_847, 40, 41.5, Animation(6686)), + MAPLE_SHORTBOW(false, Items.MAPLE_SHORTBOW_U_64, Items.MAPLE_SHORTBOW_853, 50, 50.0, Animation(6681)), + MAPLE_LONGBOW(false, Items.MAPLE_LONGBOW_U_62, Items.MAPLE_LONGBOW_851, 55, 58.3, Animation(6687)), + YEW_SHORTBOW(false, Items.YEW_SHORTBOW_U_68, Items.YEW_SHORTBOW_857, 65, 67.5, Animation(6682)), + YEW_LONGBOW(false, Items.YEW_LONGBOW_U_66, Items.YEW_LONGBOW_855, 70, 75.0, Animation(6688)), + MAGIC_SHORTBOW(false, Items.MAGIC_SHORTBOW_U_72, Items.MAGIC_SHORTBOW_861, 80, 83.3, Animation(6683)), + MAGIC_LONGBOW(false, Items.MAGIC_LONGBOW_U_70, Items.MAGIC_LONGBOW_859, 85, 91.5, Animation(6689)), + + BRONZE_CBOW(true, Items.BRONZE_CBOW_U_9454, Items.BRONZE_CROSSBOW_9174, 9, 6.0, Animation(6671)), + BLURITE_CBOW(true, Items.BLURITE_CBOW_U_9456, Items.BLURITE_CROSSBOW_9176, 24, 16.0, Animation(6672)), + IRON_CBOW(true, Items.IRON_CBOW_U_9457, Items.IRON_CROSSBOW_9177, 39, 22.0, Animation(6673)), + STEEL_CBOW(true, Items.STEEL_CBOW_U_9459, Items.STEEL_CROSSBOW_9179, 46, 27.0, Animation(6674)), + MITHIRIL_CBOW(true, Items.MITHRIL_CBOW_U_9461, Items.MITH_CROSSBOW_9181, 54, 32.0, Animation(6675)), + ADAMANT_CBOW(true, Items.ADAMANT_CBOW_U_9463, Items.ADAMANT_CROSSBOW_9183, 61, 41.0, Animation(6676)), + RUNITE_CBOW(true, Items.RUNITE_CBOW_U_9465, Items.RUNE_CROSSBOW_9185, 69, 50.0, Animation(6677)); + + companion object { + val unstrungBowIds: IntArray = values().map { stringableCraftInfo: StringableCraftInfo -> stringableCraftInfo.unstrungItemId }.toIntArray() + val stringItemIds: IntArray = intArrayOf(Items.BOW_STRING_1777,Items.CROSSBOW_STRING_9438) + + private val stringableCraftInfoByUnstrungItemId = values().associateBy { it.unstrungItemId } + + fun forUnstrungBowItemId(unstrungItemId: Int): StringableCraftInfo? { + return stringableCraftInfoByUnstrungItemId[unstrungItemId] + } + + /** + * Returns the item id that should be used for stringing this enums unstrung bow + */ + val StringableCraftInfo.applicableStringId: Int + get() { + return when { + this.isCrossbow -> Items.CROSSBOW_STRING_9438 + else -> Items.BOW_STRING_1777 + } + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/fletching/stringing/StringingListeners.kt b/Server/src/main/content/global/skill/fletching/stringing/StringingListeners.kt new file mode 100644 index 000000000..051f8102b --- /dev/null +++ b/Server/src/main/content/global/skill/fletching/stringing/StringingListeners.kt @@ -0,0 +1,69 @@ +package content.global.skill.fletching.stringing + +import content.data.Quests +import content.global.skill.fletching.stringing.StringableCraftInfo.Companion.applicableStringId +import core.api.* +import core.game.dialogue.SkillDialogueHandler +import core.game.interaction.IntType +import core.game.interaction.InteractionListener +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.Item +import core.net.packet.PacketRepository +import core.net.packet.context.ChildPositionContext +import core.net.packet.out.RepositionChild +import org.rs09.consts.Components + +@Suppress("unused") // Reflectively loaded +class StringingListeners : InteractionListener { + override fun defineListeners() { + onUseWith( + IntType.ITEM, + StringableCraftInfo.stringItemIds, + *StringableCraftInfo.unstrungBowIds + ) { player, string, unstrungBow -> + val stringableCraftInfo = StringableCraftInfo.forUnstrungBowItemId(unstrungBow.id) ?: return@onUseWith false + if (string.id != stringableCraftInfo.applicableStringId) { + sendMessage(player, "That's not the right kind of string for this.") + return@onUseWith true + } + if (stringableCraftInfo == StringableCraftInfo.OGRE_COMP_BOW && getQuestStage(player, Quests.ZOGRE_FLESH_EATERS) < 8) { + sendMessage(player, "You must have started Zogre Flesh Eaters and asked Grish to string this.") + return@onUseWith true + } + val handler: SkillDialogueHandler = + object : + SkillDialogueHandler(player, SkillDialogue.ONE_OPTION, Item(stringableCraftInfo.strungItemId)) { + override fun create(amount: Int, index: Int) { + if (getDynLevel(player, Skills.FLETCHING) < stringableCraftInfo.level) { + sendLevelCheckFailDialogue(player, stringableCraftInfo.level) + return + } + StringItemScript(player, stringableCraftInfo, amount).invoke() + } + + override fun getAll(index: Int): Int { + return amountInInventory(player, string.id) + } + } + handler.open() + fixSpacingBetweenTextAndCraftedItemIcon(player) + return@onUseWith true + } + } + + private fun fixSpacingBetweenTextAndCraftedItemIcon(player: Player) { + PacketRepository.send( + RepositionChild::class.java, + ChildPositionContext(player, Components.SKILL_MULTI1_309, 2, 215, 10) + ) + } + companion object { + fun sendLevelCheckFailDialogue(player: Player, level: Int) { + sendDialogue( + player, + "You need a fletching level of $level to string this bow." + ) + } + } +} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/herblore/GrindItemPlugin.kt b/Server/src/main/content/global/skill/herblore/GrindItemPlugin.kt index ec9cd70c4..b87ead3f4 100644 --- a/Server/src/main/content/global/skill/herblore/GrindItemPlugin.kt +++ b/Server/src/main/content/global/skill/herblore/GrindItemPlugin.kt @@ -61,8 +61,7 @@ class GrindItemPlugin : UseWithHandler(233) { override fun reward(): Boolean { if (node.id == Items.FISHING_BAIT_313) { - var quantity = 0 - quantity = if (amountInInventory(player, FISHING_BAIT) >= 10) { + val quantity: Int = if (amountInInventory(player, FISHING_BAIT) >= 10) { 10 } else { amountInInventory(player, FISHING_BAIT) diff --git a/Server/src/main/content/global/skill/magic/SpellListeners.kt b/Server/src/main/content/global/skill/magic/SpellListeners.kt index 95ff686a0..e390516e3 100644 --- a/Server/src/main/content/global/skill/magic/SpellListeners.kt +++ b/Server/src/main/content/global/skill/magic/SpellListeners.kt @@ -1,14 +1,14 @@ package content.global.skill.magic +import core.api.hasLineOfSight +import core.api.log import core.game.event.SpellCastEvent -import core.api.* +import core.game.interaction.MovementPulse import core.game.node.Node import core.game.node.entity.player.Player import core.game.node.entity.player.link.SpellBookManager -import core.tools.Log -import core.tools.SystemLogger -import core.game.interaction.* import core.game.world.map.path.Pathfinder +import core.tools.Log object SpellListeners { val castMap = HashMap Unit>() @@ -44,7 +44,7 @@ object SpellListeners { range = next.first method = next.second ?: return } - + player.scripts.removeWeakScripts() if (type in intArrayOf (SpellListener.NPC, SpellListener.OBJECT, SpellListener.PLAYER, SpellListener.GROUND_ITEM)) { player.pulseManager.run (object : MovementPulse (player, node, Pathfinder.SMART) { override fun pulse() : Boolean { diff --git a/Server/src/main/core/api/ContentAPI.kt b/Server/src/main/core/api/ContentAPI.kt index 813c355dc..8185d5079 100644 --- a/Server/src/main/core/api/ContentAPI.kt +++ b/Server/src/main/core/api/ContentAPI.kt @@ -6,6 +6,7 @@ import content.data.consumables.* import content.data.skill.SkillingTool import content.global.handlers.iface.ge.StockMarket import content.global.skill.slayer.SlayerEquipmentFlags +import content.global.skill.slayer.SlayerFlags import content.global.skill.slayer.SlayerManager import content.global.skill.slayer.Tasks import content.global.skill.summoning.familiar.BurdenBeast @@ -343,6 +344,24 @@ fun removeItem(player: Player, item: T, container: Container = Container.INV } } +/** + * Remove items in a players inventory + * Checks that the player has enough first + * @param player + * @param items the items to remove + */ +fun removeItemsIfPlayerHasEnough(player: Player, vararg items: Item): Boolean { + for (item in items) { + if (amountInInventory(player, item.id) >= 1) continue + return false + } + for (item in items) { + if (removeItem(player, item)) continue + return false + } + return true +} + /** * Add an item to the given player's given container * @param player the player whose container to modify @@ -1948,12 +1967,16 @@ fun setQuestStage(player: Player, quest: Quests, stage: Int) { } /** - * Check if a quest is in progress + * Check if a quests' stage for a given player is (inclusively) between the provided start and end stage */ fun isQuestInProgress(player: Player, quest: Quests, startStage: Int, endStage: Int): Boolean { return player.questRepository.getStage(quest) in startStage..endStage } +fun isQuestStarted(player: Player, quest: Quests): Boolean { + return getQuestStage(player, quest) > 0 +} + /** * Check if a quest is complete */ @@ -2454,7 +2477,11 @@ fun getSlayerTip(player: Player): Array { * @return the task flags as Int. */ fun getSlayerTaskFlags(player: Player): Int { - return SlayerManager.getInstance(player).flags.taskFlags + return getSlayerFlags(player).taskFlags +} + +fun getSlayerFlags(player: Player): SlayerFlags { + return SlayerManager.getInstance(player).flags } /** @@ -2982,10 +3009,14 @@ fun queueScript(entity: Entity, delay: Int = 1, strength: QueueStrength = QueueS * @param entity the entity whose clock we are updating * @param clock the clock we are updating. Please use [core.game.interaction.Clocks] for this argument. * @param ticks the number of ticks to delay by + * @param delayScript whether to delay the script by the number of ticks passed in. * @return always returns false so this can be used as a script return value. **/ -fun delayClock(entity: Entity, clock: Int, ticks: Int) : Boolean { +fun delayClock(entity: Entity, clock: Int, ticks: Int, delayScript: Boolean = false) : Boolean { entity.clocks[clock] = getWorldTicks() + ticks + if (delayScript) { + return delayScript(entity, ticks) + } return false } diff --git a/Server/src/main/core/game/dialogue/SkillDialogueHandler.kt b/Server/src/main/core/game/dialogue/SkillDialogueHandler.kt index 58e692b23..acc07905c 100644 --- a/Server/src/main/core/game/dialogue/SkillDialogueHandler.kt +++ b/Server/src/main/core/game/dialogue/SkillDialogueHandler.kt @@ -19,25 +19,11 @@ open class SkillDialogueHandler( /** * Represents the skill dialogue type. */ - val type: SkillDialogue?, vararg data: Any) { - /** - * Gets the player. - * @return The player. - */ - - /** - * Gets the type. - * @return The type. - */ - - /** - * Gets the passed data. - * @return the data. - */ - /** - * Represents the object data passed through. - */ - val data: Array + val type: SkillDialogue?, + /** + * The items that can be created through this dialog. + */ + vararg val items: Item) { /** * Method used to open a skill dialogue. @@ -70,7 +56,7 @@ open class SkillDialogueHandler( * @return the amount. */ open fun getAll(index: Int): Int { - return player.inventory.getAmount(data[0] as Item) + return player.inventory.getAmount(items[0]) } /** @@ -107,7 +93,7 @@ open class SkillDialogueHandler( private val length: Int) { ONE_OPTION(309, 5, 1) { override fun display(player: Player, handler: SkillDialogueHandler) { - val item = handler.data[0] as Item + val item = handler.items[0] player.packetDispatch.sendString("



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



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



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



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



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



" + handler.getName(item), 306, 10 + 4 * i) player.packetDispatch.sendItemZoomOnInterface(item.id, 160, 306, 2 + i) PacketRepository.send(RepositionChild::class.java, ChildPositionContext(player, 306, 2 + i, positions[i][0], positions[i][1])) @@ -361,14 +347,4 @@ open class SkillDialogueHandler( */ const val SKILL_DIALOGUE = 3 shl 16 } - - /** - * Constructs a new `SkillDialogueHandler` `Object`. - * @param player the player. - * @param type the type. - * @param data the data. - */ - init { - this.data = data as Array - } } \ No newline at end of file diff --git a/Server/src/main/core/game/interaction/InteractionListeners.kt b/Server/src/main/core/game/interaction/InteractionListeners.kt index c28113500..e3cab3e34 100644 --- a/Server/src/main/core/game/interaction/InteractionListeners.kt +++ b/Server/src/main/core/game/interaction/InteractionListeners.kt @@ -164,6 +164,7 @@ object InteractionListeners { @JvmStatic fun run(id: Int, player: Player, node: Node, isEquip: Boolean): Boolean{ + player.scripts.removeWeakScripts() player.dispatch(InteractionEvent(node, if(isEquip) "equip" else "unequip")) if(isEquip){ return equipListeners["equip:$id"]?.invoke(player,node) ?: true @@ -174,6 +175,7 @@ object InteractionListeners { @JvmStatic fun run(used: Node, with: Node, type: IntType, player: Player): Boolean{ + player.scripts.removeWeakScripts() val flag = when(type){ IntType.NPC, IntType.PLAYER -> DestinationFlag.ENTITY IntType.SCENERY -> DestinationFlag.OBJECT @@ -224,6 +226,8 @@ object InteractionListeners { @JvmStatic fun run(id: Int, type: IntType, option: String, player: Player, node: Node): Boolean{ + player.scripts.removeWeakScripts() + val flag = when(type){ IntType.PLAYER -> DestinationFlag.ENTITY IntType.GROUNDITEM -> DestinationFlag.ITEM diff --git a/Server/src/main/core/game/interaction/ScriptProcessor.kt b/Server/src/main/core/game/interaction/ScriptProcessor.kt index 52c86b1e8..2c638f35f 100644 --- a/Server/src/main/core/game/interaction/ScriptProcessor.kt +++ b/Server/src/main/core/game/interaction/ScriptProcessor.kt @@ -1,6 +1,7 @@ package core.game.interaction import core.api.* +import core.game.bots.AIPlayer import core.game.node.Node import core.game.node.entity.Entity import core.game.node.entity.npc.NPC @@ -10,10 +11,10 @@ import core.game.node.scenery.Scenery import core.game.world.GameWorld import core.game.world.map.Location import core.game.world.map.path.Pathfinder -import core.game.bots.AIPlayer import core.tools.Log +import java.io.PrintWriter +import java.io.StringWriter import java.lang.Integer.max -import java.io.* class ScriptProcessor(val entity: Entity) { private var apScript: Script<*>? = null @@ -90,11 +91,8 @@ class ScriptProcessor(val entity: Entity) { } fun processQueue() : Boolean { - var strongInQueue = false - var softInQueue = false var anyExecuted = false - strongInQueue = hasTypeInQueue(QueueStrength.STRONG) - softInQueue = hasTypeInQueue(QueueStrength.SOFT) + val strongInQueue = hasTypeInQueue(QueueStrength.STRONG) if (strongInQueue) { if (entity is Player) { diff --git a/Server/src/main/core/game/node/entity/Entity.java b/Server/src/main/core/game/node/entity/Entity.java index b566cccb0..9699be83d 100644 --- a/Server/src/main/core/game/node/entity/Entity.java +++ b/Server/src/main/core/game/node/entity/Entity.java @@ -78,7 +78,7 @@ public abstract class Entity extends Node { /** * The pulse manager. */ - private final PulseManager pulseManager = new PulseManager(); + private final PulseManager pulseManager = new PulseManager(this); /** * The impact handler. diff --git a/Server/src/main/core/game/node/entity/impl/PulseManager.java b/Server/src/main/core/game/node/entity/impl/PulseManager.java index 74a78e7cc..6f93e1250 100644 --- a/Server/src/main/core/game/node/entity/impl/PulseManager.java +++ b/Server/src/main/core/game/node/entity/impl/PulseManager.java @@ -17,6 +17,12 @@ import java.util.HashMap; */ public final class PulseManager { + private final Entity entity; + + public PulseManager(Entity entity) { + this.entity = entity; + } + /** * The movement pulse. */ @@ -61,6 +67,9 @@ public final class PulseManager { } public void clear() { + entity.scripts.removeWeakScripts(); + entity.scripts.removeNormalScripts(); + currentPulses.forEach((type, pulse) -> { if (type != PulseType.STRONG && pulse != null) pulse.stop(); }); @@ -70,6 +79,9 @@ public final class PulseManager { * Clears the pulses. */ public boolean clear(PulseType pulseType) { + entity.scripts.removeWeakScripts(); + entity.scripts.removeNormalScripts(); + Pulse pulse = currentPulses.get(pulseType); if (pulse != null) { diff --git a/Server/src/main/core/game/system/command/sets/AnimationCommandSet.kt b/Server/src/main/core/game/system/command/sets/AnimationCommandSet.kt index e1de944d1..d06258333 100644 --- a/Server/src/main/core/game/system/command/sets/AnimationCommandSet.kt +++ b/Server/src/main/core/game/system/command/sets/AnimationCommandSet.kt @@ -1,5 +1,10 @@ package core.game.system.command.sets +import core.api.animate +import core.api.delayScript +import core.api.queueScript +import core.api.stopExecuting +import core.game.interaction.QueueStrength import core.game.node.entity.npc.NPC import core.game.system.task.Pulse import core.game.world.update.flag.context.Animation @@ -27,23 +32,26 @@ class AnimationCommandSet : CommandSet(Privilege.ADMIN) { player.animate(animation) } - /** - * Force the player to play animation - */ - define("anims", Privilege.ADMIN, "::anim Animation ID", "Plays the animation with the given ID."){ player, args -> + define("anims", Privilege.ADMIN, "::anims <(opt) Duration Per Animation>", "Plays animations from the From ID to the To ID, with a delay of 3 between animations, unless specified otherwise"){ player, args -> + if (args.size < 3) { - reject(player, "Syntax error: ::anim ") + reject(player, "Syntax error: ::anims <(opt) Duration Per Animation>") } - val animation = args[1].toInt() + val animationFrom = args[1].toInt() val animationTo = args[2].toInt() - GameWorld.Pulser.submit(object : Pulse(3, player) { - var someId = animation - override fun pulse(): Boolean { - player.animate(Animation.create(someId)) - someId += 1 - return someId >= animationTo + val animationDelay = (args.getOrNull(3) ?: "3").toInt() + + queueScript(player, 1, QueueStrength.STRONG) { stage: Int -> + val animationId = animationFrom + stage + animate(player, animationId, true) + notify(player, "Playing animation $animationId") + + if (animationId == animationTo) { + return@queueScript stopExecuting(player) } - }) + + return@queueScript delayScript(player, animationDelay) + } } /**