From dc93c1f3f97bc3d117c7e9eb08dd7a054baae00a Mon Sep 17 00:00:00 2001 From: Jesse Date: Sun, 4 Oct 2026 22:27:03 -0400 Subject: [PATCH 1/3] implement Tree Gnome Village quest. Elkoy will gain "Follow" after starting the quest and being escorted out for the first time. Adds dialogue to naerby NPCs as well such as Bolkoy and Lieutenant Schepbur. Quest is fully working from start to finish. Added pre-quest interaction with Spirit Trees to indicate they don't want to talk. Add generic `Crates.kt` to mimic `Boxes.kt`. --- .../underground_pass.npcs.toml | 3 +- .../battlefield/battlefield.drops.toml | 6 - .../battlefield/battlefield.npcs.toml | 6 + .../tree_gnome_village.npcs.toml | 9 + data/entity/npc/hunt_modes.toml | 9 + .../tree_gnome_village.anims.toml | 14 + .../tree_gnome_village.gfx.toml | 3 + .../tree_gnome_village.objs.toml | 20 + .../tree_gnome_village.teles.toml | 9 + .../tree_gnome_village.varps.toml | 3 +- .../tree_gnome_village.vars.toml | 24 + .../battlefield/LieutenantSchepbur.kt | 20 + .../main/kotlin/content/entity/obj/Crates.kt | 13 + game/src/main/kotlin/content/quest/Quest.kt | 1 + .../quest/member/the_grand_tree/SpiritTree.kt | 21 +- .../tree_gnome_village/TreeGnomeVillage.kt | 182 ++++++ .../TreeGnomeVillageDialogue.kt | 434 +++++++++++++++ .../TreeGnomeVillageJournal.kt | 47 ++ .../TreeGnomeVillageLocals.kt | 236 ++++++++ .../TreeGnomeVillageProgress.kt | 156 ++++++ .../TreeGnomeVillageWarlord.kt | 52 ++ .../member/the_grand_tree/SpiritTreeTest.kt | 70 ++- .../TreeGnomeVillageTest.kt | 524 ++++++++++++++++++ 23 files changed, 1840 insertions(+), 22 deletions(-) create mode 100644 data/quest/members/tree_gnome_village/tree_gnome_village.anims.toml create mode 100644 data/quest/members/tree_gnome_village/tree_gnome_village.gfx.toml create mode 100644 data/quest/members/tree_gnome_village/tree_gnome_village.objs.toml create mode 100644 data/quest/members/tree_gnome_village/tree_gnome_village.teles.toml create mode 100644 data/quest/members/tree_gnome_village/tree_gnome_village.vars.toml create mode 100644 game/src/main/kotlin/content/area/kandarin/battlefield/LieutenantSchepbur.kt create mode 100644 game/src/main/kotlin/content/entity/obj/Crates.kt create mode 100644 game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillage.kt create mode 100644 game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageDialogue.kt create mode 100644 game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageJournal.kt create mode 100644 game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageLocals.kt create mode 100644 game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageProgress.kt create mode 100644 game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageWarlord.kt create mode 100644 game/src/test/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageTest.kt diff --git a/data/area/kandarin/ardougne/underground_pass/underground_pass.npcs.toml b/data/area/kandarin/ardougne/underground_pass/underground_pass.npcs.toml index 7748eda978..92008f2a4e 100644 --- a/data/area/kandarin/ardougne/underground_pass/underground_pass.npcs.toml +++ b/data/area/kandarin/ardougne/underground_pass/underground_pass.npcs.toml @@ -131,6 +131,7 @@ combat_def = "guard" [khazard_warlord_underground_pass] id = 477 +respawn_delay = 50 hitpoints = 1700 att = 75 str = 78 @@ -141,7 +142,7 @@ examine = "He looks real nasty, smells bad too." drop_table = "khazard_warlord" max_hit_crush = 100 combat_def = "khazard_warlord" -hunt_mode = "cowardly" +hunt_mode = "tree_gnome_village_warlord" [child_underground_pass_3] id = 6334 diff --git a/data/area/kandarin/battlefield/battlefield.drops.toml b/data/area/kandarin/battlefield/battlefield.drops.toml index 830d52abc8..e37a884778 100644 --- a/data/area/kandarin/battlefield/battlefield.drops.toml +++ b/data/area/kandarin/battlefield/battlefield.drops.toml @@ -2,12 +2,6 @@ type = "all" drops = [ { id = "bones" }, - { table = "khazard_commander_tertiary" }, -] - -[khazard_commander_tertiary] -drops = [ - { id = "orb_of_protection" }, ] [gnome_drop_table] diff --git a/data/area/kandarin/battlefield/battlefield.npcs.toml b/data/area/kandarin/battlefield/battlefield.npcs.toml index e3bbd03d3c..92913dae8b 100644 --- a/data/area/kandarin/battlefield/battlefield.npcs.toml +++ b/data/area/kandarin/battlefield/battlefield.npcs.toml @@ -107,14 +107,17 @@ examine = "A battle mage of the gnomish variety." [lieutenant_schepbur_battlefield] id = 3817 +dialogue = "child" examine = "The battle tortoise commander. Sir!" [commander_montai_battlefield] id = 470 +dialogue = "child" examine = "It's a tree gnome." [khazard_commander_battlefield] id = 478 +respawn_delay = 50 drop_table = "khazard_commander" examine = "It's one of General Khazard's commanders." hitpoints = 220 @@ -126,14 +129,17 @@ combat_def = "khazard_commander" [tracker_gnome_1_battlefield] id = 481 +dialogue = "child" examine = "It's a gnome who specialises in covert operations." [tracker_gnome_2_battlefield] id = 482 +dialogue = "child" examine = "It's a gnome who specialises in covert operations." [tracker_gnome_3_battlefield] id = 483 +dialogue = "child" examine = "It's a gnome who specialises in covert operations." [mounted_terrorbird_gnome_battlefield] diff --git a/data/area/kandarin/tree_gnome_village/tree_gnome_village.npcs.toml b/data/area/kandarin/tree_gnome_village/tree_gnome_village.npcs.toml index 5977b1c1f8..02c7a152f9 100644 --- a/data/area/kandarin/tree_gnome_village/tree_gnome_village.npcs.toml +++ b/data/area/kandarin/tree_gnome_village/tree_gnome_village.npcs.toml @@ -1,27 +1,34 @@ [king_bolren_tree_gnome_village] id = 469 +dialogue = "child" examine = "It's a gnome. He looks important." [bolkoy_tree_gnome_village] id = 471 +dialogue = "child" examine = "It's a tree gnome." [remsai_tree_gnome_village] id = 472 +dialogue = "child" examine = "It's a tree gnome." [elkoy_tree_gnome_village] id = 473 +dialogue = "child" [elkoy_tree_gnome_village_2] id = 474 +dialogue = "child" [local_gnome_tree_gnome_village] id = 485 +dialogue = "child" examine = "It's a young tree gnome." [kalron_tree_gnome_village] id = 486 +dialogue = "child" examine = "He looks lost." [evil_creature_tree_gnome_village] @@ -29,9 +36,11 @@ id = 6333 [elkoy_tree_gnome_village_3] id = 5179 +dialogue = "child" [elkoy_tree_gnome_village_4] id = 5182 +dialogue = "child" [evil_creature] id = 4615 diff --git a/data/entity/npc/hunt_modes.toml b/data/entity/npc/hunt_modes.toml index d8287617c2..7bc98caaaa 100644 --- a/data/entity/npc/hunt_modes.toml +++ b/data/entity/npc/hunt_modes.toml @@ -44,6 +44,15 @@ check_not_combat_self = true check_not_too_strong = true check_not_busy = true +[tree_gnome_village_warlord] +type = "player" +check_visual = "line_of_sight" +check_not_too_strong = false +check_not_combat = true +check_not_combat_self = true +check_not_busy = true +check_afk = false + # Rum Deal zombie swabs; they lose interest once the quest is over, filtered in ZombieSwab [zombie_swab] type = "player" diff --git a/data/quest/members/tree_gnome_village/tree_gnome_village.anims.toml b/data/quest/members/tree_gnome_village/tree_gnome_village.anims.toml new file mode 100644 index 0000000000..562c42b904 --- /dev/null +++ b/data/quest/members/tree_gnome_village/tree_gnome_village.anims.toml @@ -0,0 +1,14 @@ +[tree_gnome_village_ballista_fire] +id = 730 + +[tree_gnome_village_chant] +id = 197 + +[tree_gnome_village_cast_orbs] +id = 198 + +[tree_gnome_village_tree_orbs] +id = 333 + +[tree_gnome_village_tree_idle] +id = 332 diff --git a/data/quest/members/tree_gnome_village/tree_gnome_village.gfx.toml b/data/quest/members/tree_gnome_village/tree_gnome_village.gfx.toml new file mode 100644 index 0000000000..9ed9f462c4 --- /dev/null +++ b/data/quest/members/tree_gnome_village/tree_gnome_village.gfx.toml @@ -0,0 +1,3 @@ +[tree_gnome_village_orbs] +id = 79 +height = 124 diff --git a/data/quest/members/tree_gnome_village/tree_gnome_village.objs.toml b/data/quest/members/tree_gnome_village/tree_gnome_village.objs.toml new file mode 100644 index 0000000000..566d1fd561 --- /dev/null +++ b/data/quest/members/tree_gnome_village/tree_gnome_village.objs.toml @@ -0,0 +1,20 @@ +[tree_gnome_village_ballista] +id = 2181 + +[tree_gnome_village_chest_open] +id = 2182 + +[tree_gnome_village_chest] +id = 2183 + +[tree_gnome_village_railing] +id = 2186 + +[tree_gnome_village_crumbled_wall] +id = 12762 + +[tree_gnome_village_stronghold_ladder] +id = 40359 + +[tree_gnome_village_stronghold_ladder_top] +id = 40360 diff --git a/data/quest/members/tree_gnome_village/tree_gnome_village.teles.toml b/data/quest/members/tree_gnome_village/tree_gnome_village.teles.toml new file mode 100644 index 0000000000..9c04102b0d --- /dev/null +++ b/data/quest/members/tree_gnome_village/tree_gnome_village.teles.toml @@ -0,0 +1,9 @@ +[tree_gnome_village_stronghold_ladder] +option = "Climb-up" +tile = { x = 2503, y = 3252 } +delta = { level = 1 } + +[tree_gnome_village_stronghold_ladder_top] +option = "Climb-down" +tile = { x = 2503, y = 3252, level = 1 } +delta = { level = -1 } diff --git a/data/quest/members/tree_gnome_village/tree_gnome_village.varps.toml b/data/quest/members/tree_gnome_village/tree_gnome_village.varps.toml index 68cf08db8b..2537e29add 100644 --- a/data/quest/members/tree_gnome_village/tree_gnome_village.varps.toml +++ b/data/quest/members/tree_gnome_village/tree_gnome_village.varps.toml @@ -3,4 +3,5 @@ id = 111 persist = true format = "map" default = "unstarted" -values = { unstarted = 0, completed = 9 } +# Intermediate names are implementation milestones pending transcript verification. +values = { unstarted = 0, started = 1, logs_delivered = 2, coordinates_obtained = 3, stronghold_open = 4, orb_recovered = 5, hunt_warlord = 6, warlord_defeated = 7, orbs_recovered = 8, completed = 9 } diff --git a/data/quest/members/tree_gnome_village/tree_gnome_village.vars.toml b/data/quest/members/tree_gnome_village/tree_gnome_village.vars.toml new file mode 100644 index 0000000000..2835fcc297 --- /dev/null +++ b/data/quest/members/tree_gnome_village/tree_gnome_village.vars.toml @@ -0,0 +1,24 @@ +[tree_gnome_village_trackers] +persist = true +format = "int" + +[tree_gnome_village_coordinate] +persist = true +format = "int" + +[tree_gnome_village_montai_met] +persist = true +format = "boolean" + +[tree_gnome_village_briefed] +persist = true +format = "boolean" + +# 0 = orbs not handed in, 1 = ceremony can be resumed, 2 = reward ready. +[tree_gnome_village_ceremony] +persist = true +format = "int" + +[tree_gnome_village_commander_met] +persist = true +format = "boolean" diff --git a/game/src/main/kotlin/content/area/kandarin/battlefield/LieutenantSchepbur.kt b/game/src/main/kotlin/content/area/kandarin/battlefield/LieutenantSchepbur.kt new file mode 100644 index 0000000000..99d2fc0aed --- /dev/null +++ b/game/src/main/kotlin/content/area/kandarin/battlefield/LieutenantSchepbur.kt @@ -0,0 +1,20 @@ +package content.area.kandarin.battlefield + +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.type.npc +import content.entity.player.dialogue.type.player +import world.gregs.voidps.engine.Script + +class LieutenantSchepbur : Script { + init { + npcOperate("Talk-to", "lieutenant_schepbur_battlefield") { + npc("Move into position lads! eh? Who are you and what do you want?") + player("Who are you then?") + npc("Lieutenant Shepbur, commanding officer of the new Armoured Tortoise Regiment.") + player("There's only two tortoises here, that's hardly a regiment.") + npc("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") + npc("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!") + player("Oooookayy...... I'll leave you to it then....") + } + } +} diff --git a/game/src/main/kotlin/content/entity/obj/Crates.kt b/game/src/main/kotlin/content/entity/obj/Crates.kt new file mode 100644 index 0000000000..165924372e --- /dev/null +++ b/game/src/main/kotlin/content/entity/obj/Crates.kt @@ -0,0 +1,13 @@ +package content.entity.obj + +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.client.message + +class Crates : Script { + + init { + objectOperate("Search", "crate_307") { + message("You search the crates but find nothing.") + } + } +} diff --git a/game/src/main/kotlin/content/quest/Quest.kt b/game/src/main/kotlin/content/quest/Quest.kt index a7eecb55d1..2c28ed60b6 100644 --- a/game/src/main/kotlin/content/quest/Quest.kt +++ b/game/src/main/kotlin/content/quest/Quest.kt @@ -34,6 +34,7 @@ val quests = setOf( "rum_deal", "lost_city", "tears_of_guthix", + "tree_gnome_village", "the_golem", "zogre_flesh_eaters", // mini-quests diff --git a/game/src/main/kotlin/content/quest/member/the_grand_tree/SpiritTree.kt b/game/src/main/kotlin/content/quest/member/the_grand_tree/SpiritTree.kt index 689292628b..615ccb1aa4 100644 --- a/game/src/main/kotlin/content/quest/member/the_grand_tree/SpiritTree.kt +++ b/game/src/main/kotlin/content/quest/member/the_grand_tree/SpiritTree.kt @@ -24,9 +24,12 @@ import java.util.concurrent.TimeUnit class SpiritTree : Script { init { - objectOperate("Talk-to", "spirit_tree,spirit_tree_fullygrown,spirit_tree_gnome,spirit_tree_stronghold") { - if (!questCompleted("the_grand_tree")) { + objectOperate("Talk-to", "spirit_tree,spirit_tree_fullygrown,spirit_tree_gnome,spirit_tree_stronghold") { (target) -> + if (!canTravel(this, target.id)) { statement("The tree doesn't feel like talking.") + if (target.id == "spirit_tree_fullygrown") { + statement("The tree won't transport you until you've completed the Tree Gnome Village quest.") + } return@objectOperate } npc( @@ -74,8 +77,9 @@ class SpiritTree : Script { } } - objectOperate("Teleport", "spirit_tree*") { - if (!questCompleted("the_grand_tree")) { + objectOperate("Teleport", "spirit_tree*") { (target) -> + if (!canTravel(this, target.id)) { + statement(if (target.id == "spirit_tree_fullygrown") "The tree won't transport you until you've completed the Tree Gnome Village quest." else "The tree doesn't feel like talking.") return@objectOperate } updatePosition(this) @@ -87,6 +91,9 @@ class SpiritTree : Script { } interfaceOption(id = "spirit_tree:text") { (_, itemSlot) -> + if (!questCompleted("tree_gnome_village") || !questCompleted(get("spirit_tree_required_quest", "tree_gnome_village"))) { + return@interfaceOption + } val enum = EnumDefinitions.get("spirit_tree_destination_tiles") val map = enum.map ?: return@interfaceOption var count = 0 @@ -116,6 +123,12 @@ class SpiritTree : Script { } } + private fun canTravel(player: Player, tree: String): Boolean { + val requiredQuest = if (tree == "spirit_tree_stronghold") "the_grand_tree" else "tree_gnome_village" + player["spirit_tree_required_quest"] = requiredQuest + return player.questCompleted("tree_gnome_village") && player.questCompleted(requiredQuest) + } + /** * How long until the next evil tree sprouts, in plain english. */ diff --git a/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillage.kt b/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillage.kt new file mode 100644 index 0000000000..b941d99a98 --- /dev/null +++ b/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillage.kt @@ -0,0 +1,182 @@ +package content.quest.member.tree_gnome_village + +import content.entity.combat.Combat +import content.entity.combat.killer +import content.entity.obj.door.enterDoor +import content.entity.obj.door.openDoor +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.type.choice +import content.entity.player.dialogue.type.player +import content.entity.player.dialogue.type.statement +import content.quest.quest +import content.quest.questJournal +import content.quest.questStage +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.client.message +import world.gregs.voidps.engine.client.ui.close +import world.gregs.voidps.engine.client.ui.open +import world.gregs.voidps.engine.entity.character.move.tele +import world.gregs.voidps.engine.entity.character.npc.NPCs +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.entity.obj.GameObject +import world.gregs.voidps.engine.entity.obj.replace +import world.gregs.voidps.type.Direction +import world.gregs.voidps.type.Tile + +class TreeGnomeVillage : Script { + + init { + npcOperate("Follow", "elkoy_tree_gnome_village,elkoy_tree_gnome_village_2,elkoy_tree_gnome_village_4") { (target) -> + if (questStage("tree_gnome_village") == 0) { + return@npcOperate + } + statement("Elkoy escorts you through the maze...", clickToContinue = false) + open("fade_out") + delay(3) + tele(if (target.tile.y >= 3180) Tile(2514, 3160) else Tile(2504, 3192)) + delay(1) + open("fade_in") + delay(3) + close("dialogue_message_np1") + } + + objectOperate("Open", "door_39_closed") { (target) -> + if (target.tile == Tile(2502, 3250)) { + if (tile.y > target.tile.y) { + enterDoor(target) + } else { + statement("The door seems to be locked from the inside. I'll need to find another way to get in.") + } + } else { + openDoor(target) + } + } + + itemOnNPCOperate("logs", "commander_montai_battlefield") { + if (TreeGnomeVillageProgress.deliverLogs(this)) { + message("You give Commander Montai six logs.") + } else if (quest("tree_gnome_village") == "started") { + message("You need six normal logs to repair the defences.") + } + } + + objectOperate("Fire", "tree_gnome_village_ballista") { (target) -> + if (questStage("tree_gnome_village") >= 4) { + statement("The Khazard stronghold has already been breached.") + return@objectOperate + } + if (questStage("tree_gnome_village") < 2) { + statement("The ballista is damaged. It cannot be used until the gnomes have finished their repairs.") + return@objectOperate + } + if (quest("tree_gnome_village") != "coordinates_obtained") { + message("You need the trackers' coordinates before firing the ballista.") + return@objectOperate + } + player("That tracker gnome was a bit vague about the x coordinate! What could it be?") + choice("Enter the x-coordinate of the stronghold") { + for (coordinate in 1..4) { + option(coordinate.toString().padStart(4, '0')) { + statement("You enter the height and y coordinates you got from the tracker gnomes.") + target.anim("tree_gnome_village_ballista_fire") + if (TreeGnomeVillageProgress.fire(this, coordinate)) { + statement("The huge spear flies through the air and screams down directly into the Khazard stronghold.") + statement("A deafening crash echoes over the battlefield as the front entrance is reduced to rubble.") + } else { + statement("The huge spear completely misses the Khazard stronghold!") + player("Evidently that wasn't the right x coordinate.") + } + } + } + } + } + + objectOperate("Squeeze-through", "tree_gnome_village_railing") { (target) -> + tele(if (tile.x < target.tile.x) target.tile else target.tile.add(Direction.WEST)) + } + + objectOperate("Climb-over", "tree_gnome_village_crumbled_wall") { (target) -> + val entering = tile.y <= target.tile.y + if (tile.y <= target.tile.y && questStage("tree_gnome_village") < 4) { + message("You need to break through the wall with the ballista first.") + return@objectOperate + } + cross(target, Direction.NORTH, "rocks_pile_climb") + if (entering && !get("tree_gnome_village_commander_met", false)) { + val commander = NPCs.at(tile.regionLevel).firstOrNull { it.id == "khazard_commander_battlefield" && it.tile.level == 0 && !it.dead } + if (commander != null) { + commander.say("What? How did you manage to get in here.") + delay(2) + say("I've come for the orb.") + delay(2) + commander.say("I'll never let you take it.") + delay(2) + set("tree_gnome_village_commander_met", true) + Combat.combat(commander, this) + } + } + } + + objectOperate("Open", "tree_gnome_village_chest") { (target) -> + if (questStage("tree_gnome_village") < 4) { + message("You have no reason to search this chest yet.") + return@objectOperate + } + target.replace("tree_gnome_village_chest_open", ticks = 100) + val commander = NPCs.at(tile.regionLevel).firstOrNull { it.id == "khazard_commander_battlefield" && it.tile.level == 1 && !it.dead } + if (commander != null) { + commander.say("Oi! You! Get out of there.") + Combat.combat(commander, this) + } + } + + objectOperate("Search", "tree_gnome_village_chest_open") { (target) -> + // The open model is also used by a chest near the warlord. + if (target.tile != Tile(2506, 3259, 1)) { + message("You find nothing of interest.") + return@objectOperate + } + if (TreeGnomeVillageProgress.recoverOrb(this)) { + message("You search the chest. Inside you find gnomes' stolen orb of protection.") + } else { + message("You have already retrieved the orb of protection.") + } + } + + objectOperate("Close", "tree_gnome_village_chest_open") { (target) -> + if (target.tile == Tile(2506, 3259, 1)) { + target.replace("tree_gnome_village_chest") + } + } + + npcDeath("khazard_warlord_underground_pass") { + val player = killer as? Player ?: return@npcDeath + if (TreeGnomeVillageProgress.defeatWarlord(player)) { + Script.launch { + player.statement("As the warlord falls to the ground, a ghostly vapour floats upwards from his battle-worn armour. Out of sight you hear a shrill scream in the still air. You spot the orbs of protection among his remains.") + } + } else if (player.quest("tree_gnome_village") == "warlord_defeated") { + player.message("You need a free inventory space to recover the orbs.") + } + } + + questJournalOpen("tree_gnome_village") { + questJournal("Tree Gnome Village", treeGnomeVillageJournal()) + } + } + + private suspend fun Player.cross(target: GameObject, axis: Direction, animation: String) { + val direction = when (axis) { + Direction.WEST -> if (tile.x < target.tile.x) Direction.EAST else Direction.WEST + Direction.NORTH -> if (tile.y > target.tile.y) Direction.SOUTH else Direction.NORTH + else -> return + } + val start = if (direction == axis) target.tile else target.tile.minus(direction) + walkOverDelay(start) + face(direction) + delay() + anim(animation) + val destination = if (direction == axis) target.tile.add(direction) else target.tile + exactMoveDelay(destination, 30, direction = direction) + } +} diff --git a/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageDialogue.kt b/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageDialogue.kt new file mode 100644 index 0000000000..b7420cdd7a --- /dev/null +++ b/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageDialogue.kt @@ -0,0 +1,434 @@ +package content.quest.member.tree_gnome_village + +import content.entity.combat.Combat +import content.entity.player.bank.ownsItem +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.type.choice +import content.entity.player.dialogue.type.item +import content.entity.player.dialogue.type.npc +import content.entity.player.dialogue.type.player +import content.entity.player.dialogue.type.statement +import content.quest.quest +import content.quest.questComplete +import content.quest.questStage +import content.quest.refreshQuestJournal +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.entity.character.jingle +import world.gregs.voidps.engine.entity.character.mode.PauseMode +import world.gregs.voidps.engine.entity.character.move.tele +import world.gregs.voidps.engine.entity.character.npc.NPCs +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.entity.character.player.combatLevel +import world.gregs.voidps.engine.entity.obj.GameObjects +import world.gregs.voidps.engine.inv.add +import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.type.Tile + +internal const val BOLREN = "king_bolren_tree_gnome_village" +internal const val MONTAI = "commander_montai_battlefield" +internal const val ELKOY = "elkoy_tree_gnome_village_4" + +internal suspend fun Player.gnomeEscort(out: Boolean, text: String) { + // Moving happens immediately after Continue on Bolren's assistant line. + tele(if (out) Tile(2504, 3192) else Tile(2514, 3160)) + npc(ELKOY, text) +} + +class TreeGnomeVillageDialogue : Script { + init { + npcOperate("Talk-to", BOLREN) { bolren() } + npcOperate("Talk-to", MONTAI) { montai() } + npcOperate("Talk-to", "tracker_gnome_1_battlefield,tracker_gnome_3_battlefield") { (target) -> + tracker(target.id.substringAfter("tracker_gnome_").substringBefore('_').toInt(), target.id) + } + npcApproach("Talk-to", "tracker_gnome_2_battlefield") { (target) -> + approachRange(2, update = false) + tracker(2, target.id) + } + npcOperate("Talk-to", "khazard_warlord_underground_pass") { (target) -> + val stage = questStage("tree_gnome_village") + when { + stage == 5 -> { + player("Hello there.") + npc(target.id, "You think you're so clever. You know nothing!") + player("What?") + npc(target.id, "I'll crush you and those pesky little green men!") + } + stage < 6 -> { + player("Hello, how are you?") + npc(target.id, "Don't speak to me you insignificant wretch! Die in the name of Khazard!") + } + stage >= 7 -> { + player("I thought I killed you?") + npc(target.id, "Fool, warriors blessed by Khazard don't die. You can't kill that which is already dead. However I can kill you!") + if (stage == 7) TreeGnomeVillageProgress.recoverOrbs(this) + } + else -> { + player("You there, stop!") + npc(target.id, "Go back to your pesky little green friends.") + player("I've come for the orbs.") + npc(target.id, "You're out of your depth traveller. These orbs are part of a much larger picture.") + player("They're stolen goods, now give them here!") + npc(target.id, "Ha, you really think you stand a chance? I'll crush you.") + } + } + if (stage >= 6) { + target["warlord_challenger"] = index + Combat.combat(target, this) + } + } + } + + private suspend fun Player.bolren() { + when (quest("tree_gnome_village")) { + "unstarted" -> { + player("Hello.") + npc(BOLREN, "Well hello stranger. My name's Bolren, I'm the king of the tree gnomes.") + npc(BOLREN, "I'm surprised you made it in, maybe I made the maze too easy.") + player("Maybe.") + npc(BOLREN, "I'm afraid I have more serious concerns at the moment. Very serious.") + choice { + option("Can I help at all?") { + npc(BOLREN, "I'm glad you asked.") + npc(BOLREN, "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.") + npc(BOLREN, "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.") + npc(BOLREN, "We became desperate, so we took one orb of protection to the battlefield. It was a foolish move.") + npc(BOLREN, "Khazard troops seized the orb. Now we are completely defenceless.") + player("How can I help?") + npc(BOLREN, "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.") + if (combatLevel < 45) statement("Before starting this quest, be aware that your combat level is lower than the recommended level of 45.") + choice { + option("I would be glad to help.") { + player("I would be glad to help.") + if (TreeGnomeVillageProgress.start(this@bolren)) { + npc(BOLREN, "Thank you. The battlefield is to the north of the maze. Commander Montai will inform you of their current situation.") + npc(BOLREN, "That is if he's still alive.") + npc(BOLREN, "My assistant shall guide you out. Good luck friend, try your best to return the orb") + gnomeEscort(true, "We're out of the maze now. Please hurry, we must have the orb if we are to survive.") + } + } + option("I'm sorry but I won't be involved.") { + player("I'm sorry but I won't be involved.") + npc(BOLREN, "Ok then, travel safe.") + } + } + } + option("I'll leave you to it then.") { npc(BOLREN, "Ok, take care.") } + } + } + "orb_recovered" -> { + if (!inventory.contains("orb_of_protection")) { + player("Hello Bolren.") + npc(BOLREN, "Do you have the orb?") + player("No, I'm afraid not.") + npc(BOLREN, "Please, we must have the orb if we are to survive.") + return + } + player("I have the orb.") + npc(BOLREN, "Oh my... The misery, the horror!") + player("King Bolren, are you OK?") + npc(BOLREN, "Thank you traveller, but it's too late. We're all doomed.") + player("What happened?") + npc(BOLREN, "They came in the night. I don't know how many, but enough.") + player("Who?") + npc(BOLREN, "Khazard troops. They slaughtered anyone who got in their way. Women, children, my wife.") + player("I'm sorry.") + npc(BOLREN, "They took the other orbs, now we are defenceless.") + player("Where did they take them?") + npc(BOLREN, "They headed north of the stronghold. A warlord carries the orbs.") + choice { + option("I will find the warlord and bring back the orbs.") { + npc(BOLREN, "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.") + npc(BOLREN, "I will safeguard this orb and pray for your safe return. My assistant will guide you out.") + if (TreeGnomeVillageProgress.returnOrb(this)) gnomeEscort(true, "Good luck friend.") + } + option("I'm sorry but I can't help.") { npc(BOLREN, "I understand, this isn't your battle.") } + } + } + "hunt_warlord", "warlord_defeated" -> { + player("Bolren, I have returned.") + npc(BOLREN, "You made it back! Do you have the orbs?") + player("No, I'm afraid not.") + npc(BOLREN, "Please, we must have the orbs if we are to survive.") + } + "orbs_recovered" -> { + if (get("tree_gnome_village_ceremony", 0) == 2) { + if (inventory.spaces == 0) { + statement("You need a free inventory space for King Bolren's reward.") + return + } + npc(BOLREN, "Now at last my people are safe once more. We can live in peace again.") + player("I'm pleased I could help.") + npc(BOLREN, "You are modest brave traveller.") + npc(BOLREN, "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.") + player("Thank you King Bolren.") + npc(BOLREN, "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.") + if (TreeGnomeVillageProgress.complete(this)) { + jingle("quest_complete_1") + questComplete("Tree Gnome Village", "2 Quest Points", "11,450 Attack XP", "Gnome amulet", "Access to spirit tree teleportation", item = "gnome_amulet") + } + return + } + if (get("tree_gnome_village_ceremony", 0) == 0) { + player("Bolren, I have returned.") + npc(BOLREN, "You made it back! Do you have the orbs?") + if (!inventory.contains("orbs_of_protection")) { + player("No, I'm afraid not.") + npc(BOLREN, "Please, we must have the orbs if we are to survive.") + return + } + player("I have them here.") + npc(BOLREN, "Hooray, you're amazing. I didn't think it was possible but you've saved us.") + npc(BOLREN, "Once the orbs are replaced we will be safe once more. We must begin the ceremony immediately.") + player("What does the ceremony involve?") + npc(BOLREN, "The spirit tree has looked over us for centuries. Now we must pay our respects.") + } + if (TreeGnomeVillageProgress.beginCeremony(this)) { + statement("The gnomes begin to chant. Meanwhile, King Bolren holds the orbs of protection out in front of him.") + ceremony() + statement("The orbs of protection come to rest gently in the branches of the ancient spirit tree.") + TreeGnomeVillageProgress.finishCeremony(this) + } + } + "completed" -> { + if (!ownsItem("gnome_amulet")) { + if (inventory.spaces == 0) { + statement("You need a free inventory space for King Bolren's amulet.") + } else { + npc(BOLREN, "Here, I think this belongs to you.") + if (!ownsItem("gnome_amulet") && inventory.add("gnome_amulet")) { + item(item = "gnome_amulet", text = "King Bolren gives you an amulet.") + } + } + } + player("Hello again, Bolren.") + npc(BOLREN, "Hello there. It's good to see you again.") + player("How are things here?") + npc(BOLREN, "Those Khazard troops continue to be a menace, but things are much better now. All thanks to you, of course.") + statement("You do not meet all of the requirements to start The Path of Glouphrie quest.") + } + else -> { + player("Hello Bolren.") + if (get("tree_gnome_village_montai_met", false)) { + npc(BOLREN, "The orb is being held at the battlefield north of the maze.") + } else { + npc(BOLREN, "Hello traveller, we must retrieve the orb. It's being held by Khazard troops north of here.") + player("Ok, I'll try my best.") + } + } + } + } + + private fun Player.chant(north: Boolean, text: String) { + for (gnome in NPCs.at(Tile(2542, 3170).regionLevel)) { + if (gnome.id == "local_gnome_tree_gnome_village" && (gnome.tile.y >= 3170) == north) { + gnome.clearWatch() + gnome.face(Tile(2543, 3170)) + gnome.anim("tree_gnome_village_chant") + gnome.say(text) + } + } + } + + private suspend fun Player.ceremony() { + val king = NPCs.at(Tile(2542, 3170).regionLevel).firstOrNull { it.id == BOLREN } + val previousMode = king?.mode + try { + repeat(2) { + chant(true, "Su tana.") + delay(2) // Two 600 ms server ticks approximate the observed one-second gap. + chant(false, "En tania.") + delay(2) + } + if (king != null) { + king.walkTo(Tile(2542, 3169), forceWalk = true) + var ticks = 0 + while (king.tile != Tile(2542, 3169) && ticks++ < 10) delay() + king.mode = PauseMode + king.clearWatch() + king.face(Tile(2543, 3169)) + } + if (king != null) { + king.anim("tree_gnome_village_cast_orbs", delay = 20) + king.gfx("tree_gnome_village_orbs", delay = 20, height = 124) + delay(6) + val tree = GameObjects.find(Tile(2543, 3168), "spirit_tree_gnome") + tree.anim("tree_gnome_village_tree_orbs") + delay(3) // Sequence 333 lasts 73 client frames, approximately 1.5 seconds. + tree.anim("tree_gnome_village_tree_idle") + } + } finally { + if (king != null && previousMode != null) king.mode = previousMode + } + } + + private suspend fun Player.montai() { + when { + questStage("tree_gnome_village") >= 6 -> { + player("Hello Montai, how are you?") + npc(MONTAI, "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.") + player("Hang in there.") + } + quest("tree_gnome_village") == "orb_recovered" -> { + player("I have the orb of protection.") + npc(MONTAI, "Incredible, for a human you really are something.") + player("Thanks... I think!") + npc(MONTAI, "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.") + } + quest("tree_gnome_village") == "stronghold_open" -> { + player("I've breached the stronghold.") + npc(MONTAI, "I saw, that was a beautiful sight. The Khazard troops didn't know what hit them.") + npc(MONTAI, "Now is the time to retrieve the orb. It's all in your hands. I'll be praying for you.") + } + get("tree_gnome_village_briefed", false) -> { + player("Hello.") + npc(MONTAI, "Hello warrior. We need the coordinates for a direct hit from the ballista. Once you have a direct hit you will be able to enter the stronghold and retrieve the orb.") + } + quest("tree_gnome_village") == "logs_delivered" -> { + player("How are you doing Montai?") + npc(MONTAI, "We're hanging in there soldier. For the next phase of our attack we need to breach their stronghold.") + npc(MONTAI, "The ballista can break through the stronghold wall, and then we can advance and seize back the orb.") + player("So what's the problem?") + npc(MONTAI, "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.") + player("Have they returned?") + npc(MONTAI, "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.") + npc(MONTAI, "Do you think you can do it?") + TreeGnomeVillageProgress.brief(this) + choice { + option("No, I've had enough of your battle.") { npc(MONTAI, "I understand, this isn't your fight.") } + option("I'll try my best.") { + npc(MONTAI, "Thank you, you're braver than most.") + npc(MONTAI, "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.") + npc(MONTAI, "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.") + } + } + } + quest("tree_gnome_village") == "unstarted" -> { + player("Hello.") + npc(MONTAI, "I can't talk now. Can't you see we're trying to win a battle here? If we can't hold back Khazard's men we're all doomed.") + } + !get("tree_gnome_village_montai_met", false) -> { + player("Hello.") + npc(MONTAI, "Hello traveller, are you here to help or just to watch?") + player("I've been sent by King Bolren to retrieve the orb of protection.") + npc(MONTAI, "Excellent we need all the help we can get.") + npc(MONTAI, "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.") + player("What can I do?") + npc(MONTAI, "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.") + set("tree_gnome_village_montai_met", true) + refreshQuestJournal() + choice { + option("Sorry, I no longer want to be involved.") { npc(MONTAI, "That's a shame, we could have done with your help.") } + option("Ok, I'll gather some wood.") { npc(MONTAI, "Please be as quick as you can, I don't know how much longer we can hold out.") } + } + } + else -> { + player("Hello.") + if (inventory.contains("logs", 6)) { + npc(MONTAI, "Hello again, we're still desperate for wood soldier.") + player("I have some here.
(You give six loads of logs to the commander.)") + if (TreeGnomeVillageProgress.deliverLogs(this)) { + npc(MONTAI, "That's excellent, now we can make more defensive battlements. Give me a moment to organise the troops and then come speak to me. I'll inform you of our next phase of attack.") + } + } else { + npc(MONTAI, "Hello again, we're still desperate for wood soldier. We need six loads of normal logs.") + player("I'll see what I can do.") + npc(MONTAI, "Thank you.") + } + } + } + } + + private suspend fun Player.tracker(number: Int, id: String) { + val stage = questStage("tree_gnome_village") + if (stage == 0) { + player("Hello.") + npc(id, if (number == 2) "I can't talk now. If the guards catch me I'll be dead gnome meat." else "I can't talk now. Can't you see we're trying to win a battle here?") + } else if (stage >= 6) { + val text = when (number) { + 1 -> "When will this battle end? I feel like I've been fighting forever." + 2 -> "When will this battle end? I feel like I've been locked up my whole life." + else -> "I feel dizzy, where am I? Oh dear, oh dear I need some rest." + } + player("Hello.") + npc(id, text) + if (number == 3) player("I think you do.") + } else if (stage >= 4) { + if (number == 3) { + player("Hello again.") + npc(id, "Don't talk to me, you can't see me. No one can, just the demons.") + } else if (stage == 5) { + player("How are you tracker?") + npc(id, if (number == 1) "Now we have the orb I'm much better. They won't stand a chance without it." else "Now we have the orb I'm much better. Soon my comrades will come and free me.") + } else { + player("Hello again.") + npc(id, "Well done, you've broken down their defences. This battle must be ours.") + } + } else if (get("tree_gnome_village_briefed", false)) { + when (number) { + 1 -> { + player("Do you know the coordinates of the Khazard stronghold?") + npc(id, "I managed to get one, although it wasn't easy.") + statement("The gnome tells you the height coordinate.") + TreeGnomeVillageProgress.recordTracker(this, 1) + player("Well done.") + npc(id, "The other two tracker gnomes should have the other coordinates if they're still alive.") + player("OK, take care.") + } + 2 -> { + player("Are you OK?") + npc(id, "They caught me spying on the stronghold. They beat and tortured me.") + npc(id, "But I didn't crack. I told them nothing. They can't break me!") + player("I'm sorry little man.") + npc(id, "Don't be. I have the position of the stronghold!") + statement("The gnome tells you the y coordinate.") + TreeGnomeVillageProgress.recordTracker(this, 2) + player("Well done.") + npc(id, "Now leave before they find you and all is lost.") + player("Hang in there.") + npc(id, "Go!") + } + 3 -> { + player("Are you OK?") + npc(id, "OK? Who's OK? Not me! Hee hee!") + player("What's wrong?") + npc(id, "You can't see me, no one can. Monsters, demons, they're all around me!") + player("What do you mean?") + npc(id, "They're dancing, all of them, hee hee.") + statement("He's clearly lost the plot.") + player("Do you have the coordinate for the Khazard stronghold?") + npc(id, "Who holds the stronghold?") + player("What?") + val clue = when (get("tree_gnome_village_coordinate", 1)) { + 1 -> "Less than my hands." + 2 -> "More than my head, less than my fingers." + 3 -> "More than we, less than our feet." + else -> "My legs and your legs, ha ha ha!" + } + npc(id, clue) + TreeGnomeVillageProgress.recordTracker(this, 3) + player("You're mad.") + npc(id, "Dance with me, and Khazard's men are beat.") + statement("The toll of war has affected his mind.") + player("I'll pray for you little man.") + npc(id, "All day we pray in the hay, hee hee.") + } + } + } else { + when (number) { + 1 -> { + player("Hello.") + npc(id, "I can't talk now. Can't you see we're trying to win a battle here?") + } + 2 -> { + player("Hi there.") + npc(id, "The battle is far from over. If you have a pure heart you will help us win.") + } + 3 -> { + player("Hi there.") + npc(id, "I can't stand this war. The misery, the pain, it's driving me crazy! When will it end?") + } + } + } + } +} diff --git a/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageJournal.kt b/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageJournal.kt new file mode 100644 index 0000000000..efcd2342a9 --- /dev/null +++ b/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageJournal.kt @@ -0,0 +1,47 @@ +package content.quest.member.tree_gnome_village + +import content.quest.questStage +import world.gregs.voidps.engine.data.definition.FontDefinitions +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.get + +fun Player.treeGnomeVillageJournal(): List { + val stage = questStage("tree_gnome_village") + val history = mutableListOf() + if (stage >= 1) history += "I spoke to King Bolren who told me that one of their orbs of protection has been stolen by Khazard troops." + if (get("tree_gnome_village_montai_met", false)) history += "I spoke to Montai. The orb was in the Khazard stronghold North of the battlefield, but it was secure in there." + if (stage >= 2) history += "I brought Montai logs to help fortify their defences." + if (get("tree_gnome_village_briefed", false)) history += "Now their defences were secure, they were ready to use the Gnome Ballista to break through the enemy defences." + if (stage >= 4) history += "I found the three trackers and used their coordinates to fire the ballista straight into the Khazard stronghold." + if (stage >= 5) history += "With the stronghold breached by ballista fire, I was able to make my way inside and recover the orb of protection." + if (stage >= 6) history += "I returned the orb to King Bolren, but while I was busy getting it the Khazard troops invaded the village and stole the remaining two orbs before heading north." + if (stage >= 8) history += "After a fierce battle I defeated the warlord who'd stolen the orbs, and reclaimed them for the gnome people." + if (get("tree_gnome_village_ceremony", 0) == 2) history += "I returned the orbs to King Bolren and the gnomes performed their ceremony." + if (stage == 9) history += "King Bolren rewarded me with a gnome amulet and allowed me to use the spirit tree." + + val current = when { + stage == 0 -> "I can start this quest by speaking to King Bolren in Tree Gnome Village, in the centre of the maze." + stage == 9 -> "QUEST COMPLETE!" + get("tree_gnome_village_ceremony", 0) == 2 -> "I should speak to King Bolren now - hopefully he will reward me" + stage >= 8 -> "I should return them now to King Bolren where they belong" + stage >= 6 -> "I should retrieve the two remaining orbs from the warlord" + stage == 5 -> "I should return the orb of protection to King Bolren." + stage == 4 -> "With the Khazard stronghold exposed I should get inside and retrieve the gnomes' orb of protection" + stage == 3 || get("tree_gnome_village_briefed", false) -> "I need to head into the battlefield and find the three Gnome trackers who have the firing coordinates and then fire the ballista!" + stage == 2 -> "I should speak to Montai again and see what to do next." + get("tree_gnome_village_montai_met", false) -> "I should bring six normal logs to Commander Montai." + else -> "I should speak to Commander Montai at the battlefield north of the maze." + } + val font = get().get("p12_full") + val lines = mutableListOf() + for (paragraph in history) { + lines += font.splitLines(paragraph, 350).map { "$it" } + lines += "" + } + var colour = "" + for (line in font.splitLines(current, 350)) { + lines += "$colour$line" + colour = Regex("<(red|navy)>").findAll(line).lastOrNull()?.value ?: colour + } + return lines +} diff --git a/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageLocals.kt b/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageLocals.kt new file mode 100644 index 0000000000..29452c60f5 --- /dev/null +++ b/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageLocals.kt @@ -0,0 +1,236 @@ +package content.quest.member.tree_gnome_village + +import content.entity.npc.shop.openShop +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.type.choice +import content.entity.player.dialogue.type.npc +import content.entity.player.dialogue.type.player +import content.quest.questStage +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.inv.inventory + +class TreeGnomeVillageLocals : Script { + init { + npcOperate("Talk-to", "elkoy_tree_gnome_village,elkoy_tree_gnome_village_2,elkoy_tree_gnome_village_3,elkoy_tree_gnome_village_4") { (target) -> + elkoy(target.tile.y < 3180) + } + npcOperate("Talk-to", "remsai_tree_gnome_village") { remsai() } + npcOperate("Talk-to", "kalron_tree_gnome_village") { + val id = "kalron_tree_gnome_village" + when { + questStage("tree_gnome_village") == 0 -> { + player("Hello.") + npc(id, "Gotta find a way out. We built this maze for protection but I can't get used to it. I'm always getting lost.") + } + questStage("tree_gnome_village") == 9 -> { + player("Hello there, you look lost.") + npc(id, "Are you trying to be funny?") + player("No.") + npc(id, "Hmmm.") + } + questStage("tree_gnome_village") >= 8 -> { + player("Hello little man.") + npc(id, "Hello. I hope they come out and find me soon. It's getting cold.") + } + questStage("tree_gnome_village") >= 6 -> { + player("Hello there.") + npc(id, "Oh my, oh my, the village has been pillaged and I'm still lost. Oh dear.") + } + else -> { + player("Hello, how are you?") + npc(id, "Oh my. I'll never find my way back before Khazard's men come and hunt me down.") + } + } + } + npcOperate("Talk-to", "local_gnome_tree_gnome_village") { + val id = "local_gnome_tree_gnome_village" + when (questStage("tree_gnome_village")) { + 0 -> { + player("Hello.") + npc(id, "Lardi dee, lardi da.") + player("Are you alright?") + npc(id, "Hee hee, lardi da, lardi dee. (The gnome appears to be singing.)") + } + 5 -> { + player("Hello little man.") + npc(id, "Little man stronger than big man. Hee hee, lardi dee, lardi da.") + } + 6, 7 -> { + player("Hi.") + npc(id, "Must save the orbs and kill the Khazard warlord. That will be fun, hee hee.") + } + 8 -> { + player("Hello gnome.") + npc(id, "Soon we're gonna have the sacred ceremony and boy am I going to party. Lock up your daughters. Hee hee.") + } + else -> { + player("Hello.") + npc(id, "You're the best!") + player("Thanks.") + npc(id, "Well I'm better. Hee hee.") + } + } + } + npcOperate("Trade", "bolkoy_tree_gnome_village") { openShop("bolkoys_village_shop") } + npcOperate("Talk-to", "bolkoy_tree_gnome_village") { + val id = "bolkoy_tree_gnome_village" + when (questStage("tree_gnome_village")) { + 5 -> { + player("Hello.") + npc(id, "Amazing, you recovered the orb.") + npc(id, "Well I am impressed. Would you like to buy something?") + } + 6, 7 -> { + player("Hi.") + npc(id, "Oh, hello there. Have you heard? They took the other orbs, it's terrible. I suppose the show must go on.") + npc(id, "Would you like to buy something?") + } + 8 -> { + player("Hello.") + npc(id, "Hello there. You're that hero who saved the orbs. Soon we will perform the ritual and the village will be safe again.") + npc(id, "Anyway, would you like anything from my shop?") + } + 9 -> { + player("Hello.") + npc(id, "Welcome, welcome. It's good to see you again. The village is much safer now you have returned the orbs.") + npc(id, "By the way, I'm the village shop keeper. Would you like to buy something?") + } + else -> { + player("Hello there.") + npc(id, "Hello stranger, new to these parts?") + npc(id, "I'm Bolkoy by the way. I'm the village shopkeeper. Would you like to buy something?") + } + } + choice { + option("What have you got?") { + npc(id, "Take a look.") + openShop("bolkoys_village_shop") + } + option("No thank you.") { npc(id, "Ok, maybe later.") } + } + } + } + + private suspend fun Player.elkoy(inside: Boolean) { + val stage = questStage("tree_gnome_village") + if (stage == 0) { + player("Hello there.") + npc(ELKOY, if (inside) "Hello, welcome to our village. I'm Elkoy the tree gnome." else "Hello, welcome to our maze. I'm Elkoy the tree gnome.") + player(if (inside) "I haven't heard of your sort before." else "I haven't heard of your sort.") + npc(ELKOY, "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.") + player("Capture by whom?") + npc(ELKOY, "Tree gnomes have been hunted for so called 'fun' since as long as I can remember.") + npc(ELKOY, "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.") + npc(ELKOY, "Have fun.") + return // The first maze traversal is required before Elkoy offers escorts. + } + when { + stage == 9 -> { + player("Hello Elkoy.") + npc(ELKOY, if (inside) "Hi there, I hope life is treating you well. Would you like me to show you the way out of the village?" else "Hi there, I hope life is treating you well. Would you like me to show you the way to the village?") + escortChoice(inside, if (inside) "Here we are. Have a safe journey." else "Here we are. Feel free to have a look around.", no = if (inside) "Not now, thanks." else "No thanks Elkoy.") + } + stage >= 8 -> { + player("Hello Elkoy.") + npc(ELKOY, "You truly are a hero.") + player("Thanks.") + npc(ELKOY, "You saved us by returning the orbs of protection. I'm humbled and wish you well.") + npc(ELKOY, if (inside) "Would you like me to show you through the maze?" else "Would you like me to show you the way to the village?") + escortChoice(inside, if (inside) "Here we are. Have a safe journey." else "Here we are. Feel free to have a look around.", no = if (inside) "Not now, thanks." else "No thanks Elkoy.") + } + stage >= 6 -> { + player("Hello Elkoy.") + npc(ELKOY, "Did you hear?") + npc(ELKOY, "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?") + if (inside) { + choice { + option("Can you show me out of the village?") { gnomeEscort(true, "Please help us find the orbs.") } + option("I'm very sorry.") { } + } + } else { + npc(ELKOY, "Would you like me to show you the way to the village?") + escortChoice(false, "Here we are. Despite what has happened here, I hope you feel welcome.", no = "No thanks Elkoy.") + } + } + stage == 5 && inventory.contains("orb_of_protection") -> { + if (inside) { + player("Hello Elkoy. I have the orb.") + npc(ELKOY, "Take it to King Bolren, I'm sure he'll be pleased to see you.") + choice { + option("Can you show me out of the village?") { gnomeEscort(true, "Please return with our orb soon.") } + option("Okay.") { } + } + } else { + player("Hello Elkoy.") + npc(ELKOY, "You're back! And the orb?") + player("I have it here.") + npc(ELKOY, "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?") + choice { + option("Yes please.") { gnomeEscort(false, "Here we are. Take the orb to King Bolren, I'm sure he'll be pleased to see you.") } + option("No thanks Elkoy.") { npc(ELKOY, "Please, we must have the orb if we are to survive.") } + } + } + } + stage == 5 -> { + player("Hello Elkoy.") + npc(ELKOY, "You're back! And the orb?") + player("No, I'm afraid not.") + npc(ELKOY, "Please, we must have the orb if we are to survive. Do you need me to show you through the maze?") + escortChoice(inside, "Please help us get our orb back.") + } + stage >= 2 -> { + player("Hello.") + npc(ELKOY, "You must retrieve the orb, or the gnome village is doomed. Do you need me to show you through the maze?") + escortChoice(inside, "Please help us get our orb back.") + } + else -> { + player("Hello Elkoy.") + npc(ELKOY, "Oh my! Oh my!") + player("What's wrong?") + npc(ELKOY, if (inside) "The orb, they have the orb. We're doomed. Do you need me to show you through the maze?" else "The orb, they have the orb. We're doomed. Do you need me to show you back to the village?") + escortChoice(inside, "Please help us get our orb back.") + } + } + } + + private suspend fun Player.escortChoice(inside: Boolean, text: String, no: String = "Not now, thanks.") { + choice { + option("Yes please.") { gnomeEscort(inside, text) } + option(no) { + if (no == "No thanks Elkoy.") npc(ELKOY, "Ok then, take care.") + } + } + } + + private suspend fun Player.remsai() { + val id = "remsai_tree_gnome_village" + when { + questStage("tree_gnome_village") >= 8 -> { + player("I've returned.") + npc(id, "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.") + } + questStage("tree_gnome_village") >= 6 -> { + player("Are you ok?") + npc(id, "Khazard's men came. Without the orb we were defenceless. They killed many and then took our last hope, the other orbs.") + npc(id, "Now surely we're all doomed. Without them the spirit tree is useless.") + } + questStage("tree_gnome_village") == 5 -> { + player("Hello Remsai.") + npc(id, "Hello, did you find the orb?") + if (inventory.contains("orb_of_protection")) { + player("I have it here.") + npc(id, "You're our saviour.") + } else { + player("No, I'm afraid not.") + npc(id, "Please, we must have the orb if we are to survive.") + } + } + else -> { + npc(id, "Oh my, oh my!") + player("What's wrong?") + npc(id, "The orb, they have the orb. It must be returned or we're doomed.") + } + } + } +} diff --git a/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageProgress.kt b/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageProgress.kt new file mode 100644 index 0000000000..198dc509bc --- /dev/null +++ b/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageProgress.kt @@ -0,0 +1,156 @@ +package content.quest.member.tree_gnome_village + +import content.entity.player.AdventurersLogs +import content.entity.player.bank.ownsItem +import content.quest.quest +import content.quest.refreshQuestJournal +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.engine.entity.character.player.skill.exp.exp +import world.gregs.voidps.engine.inv.add +import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.engine.inv.remove + +/** Dialogue calls these transitions only after the relevant conversation finishes. */ +object TreeGnomeVillageProgress { + const val QUEST = "tree_gnome_village" + + fun start(player: Player): Boolean { + if (player.quest(QUEST) != "unstarted") { + return false + } + player["tree_gnome_village_trackers"] = 0 + player["tree_gnome_village_coordinate"] = (1..4).random() + player["tree_gnome_village_montai_met"] = false + player["tree_gnome_village_briefed"] = false + player["tree_gnome_village_ceremony"] = 0 + player["tree_gnome_village_commander_met"] = false + advance(player, "started") + return true + } + + fun deliverLogs(player: Player): Boolean { + if (player.quest(QUEST) != "started" || !player["tree_gnome_village_montai_met", false] || !player.inventory.remove("logs", 6)) { + return false + } + advance(player, "logs_delivered") + return true + } + + fun brief(player: Player): Boolean { + if (player.quest(QUEST) != "logs_delivered") { + return false + } + player["tree_gnome_village_briefed"] = true + player.refreshQuestJournal() + return true + } + + fun recordTracker(player: Player, tracker: Int): Boolean { + require(tracker in 1..3) + if (player.quest(QUEST) !in setOf("logs_delivered", "coordinates_obtained") || !player["tree_gnome_village_briefed", false]) { + return false + } + val mask = player["tree_gnome_village_trackers", 0] or (1 shl (tracker - 1)) + player["tree_gnome_village_trackers"] = mask + if (mask == 7) { + advance(player, "coordinates_obtained") + } + return true + } + + fun fire(player: Player, coordinate: Int): Boolean { + if (player.quest(QUEST) != "coordinates_obtained" || player["tree_gnome_village_trackers", 0] != 7) { + return false + } + val expected = player["tree_gnome_village_coordinate", 0] + if (expected !in 1..4 || coordinate != expected) { + return false + } + advance(player, "stronghold_open") + return true + } + + fun recoverOrb(player: Player): Boolean { + if (player.quest(QUEST) !in setOf("stronghold_open", "orb_recovered") || player.ownsItem("orb_of_protection")) { + return false + } + if (!player.inventory.add("orb_of_protection")) { + return false + } + advance(player, "orb_recovered") + return true + } + + fun returnOrb(player: Player): Boolean { + if (player.quest(QUEST) != "orb_recovered" || !player.inventory.remove("orb_of_protection")) { + return false + } + advance(player, "hunt_warlord") + return true + } + + fun defeatWarlord(player: Player): Boolean { + if (player["tree_gnome_village_ceremony", 0] != 0) { + return false + } + if (player.quest(QUEST) !in setOf("hunt_warlord", "warlord_defeated", "orbs_recovered")) { + return false + } + if (player.ownsItem("orbs_of_protection")) { + return false + } + // Keep the defeated milestone if the inventory is full, so recovery can be retried. + advance(player, "warlord_defeated") + return recoverOrbs(player) + } + + fun recoverOrbs(player: Player): Boolean { + if (player.quest(QUEST) != "warlord_defeated" || player.ownsItem("orbs_of_protection")) { + return false + } + if (!player.inventory.add("orbs_of_protection")) { + return false + } + advance(player, "orbs_recovered") + return true + } + + fun complete(player: Player): Boolean { + if (player.quest(QUEST) != "orbs_recovered" || player["tree_gnome_village_ceremony", 0] != 2 || !player.inventory.add("gnome_amulet")) { + return false + } + AdventurersLogs.questCompleted(player, QUEST, points = 2) + advance(player, "completed") + player["quest_points"] = player["quest_points", 0] + 2 + player.exp(Skill.Attack, 11450.0) + return true + } + + fun beginCeremony(player: Player): Boolean { + if (player.quest(QUEST) != "orbs_recovered") { + return false + } + // A logout or interrupted ceremony can be resumed without handing in the orbs again. + if (player["tree_gnome_village_ceremony", 0] == 1) { + return true + } + if (player["tree_gnome_village_ceremony", 0] != 0 || !player.inventory.remove("orbs_of_protection")) { + return false + } + player["tree_gnome_village_ceremony"] = 1 + return true + } + + fun finishCeremony(player: Player) { + if (player.quest(QUEST) == "orbs_recovered" && player["tree_gnome_village_ceremony", 0] == 1) { + player["tree_gnome_village_ceremony"] = 2 + player.refreshQuestJournal() + } + } + + private fun advance(player: Player, stage: String) { + player[QUEST] = stage + player.refreshQuestJournal() + } +} diff --git a/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageWarlord.kt b/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageWarlord.kt new file mode 100644 index 0000000000..db3e81fc05 --- /dev/null +++ b/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageWarlord.kt @@ -0,0 +1,52 @@ +package content.quest.member.tree_gnome_village + +import content.entity.combat.Combat +import content.quest.quest +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.client.variable.hasClock +import world.gregs.voidps.engine.entity.character.mode.combat.CombatMovement +import world.gregs.voidps.engine.entity.character.npc.NPC +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.entity.character.player.Players +import world.gregs.voidps.engine.timer.Timer + +class TreeGnomeVillageWarlord : Script { + init { + npcSpawn("khazard_warlord_underground_pass") { + clear("warlord_challenger") + clear("warlord_blocked_ticks") + clear("warlord_blocked_target") + softTimers.start("tree_gnome_village_warlord_patience") + } + huntPlayer("khazard_warlord_underground_pass", "tree_gnome_village_warlord") { player -> + if (get("warlord_challenger", -1) == player.index && player.quest("tree_gnome_village") in setOf("hunt_warlord", "warlord_defeated")) { + Combat.combat(this, player) + } + } + npcTimerStart("tree_gnome_village_warlord_patience") { 1 } + npcTimerTick("tree_gnome_village_warlord_patience") { + checkPatience() + Timer.CONTINUE + } + } + + private fun NPC.checkPatience() { + val movement = mode as? CombatMovement + val player = movement?.target as? Player ?: Players.indexed(get("warlord_challenger", -1)) + val encounter = player != null && player.quest("tree_gnome_village") in setOf("hunt_warlord", "warlord_defeated") + if (dead || player == null || player.dead || player.tile.level != tile.level || (!encounter && movement == null) || hasClock("under_attack") || (movement ?: CombatMovement(this, player)).inAttackRange()) { + clear("warlord_blocked_ticks") + clear("warlord_blocked_target") + return + } + if (get("warlord_blocked_target", -1) != player.index) { + set("warlord_blocked_target", player.index) + set("warlord_blocked_ticks", 0) + } + val ticks = get("warlord_blocked_ticks", 0) + 1 + // Approximately thirty seconds, retaining the prior encounter timing. + // Complain once per blocked episode, rather than repeatedly while still stuck. + set("warlord_blocked_ticks", ticks.coerceAtMost(51)) + if (ticks == 50) say("Bah, enough of you!") + } +} diff --git a/game/src/test/kotlin/content/quest/member/the_grand_tree/SpiritTreeTest.kt b/game/src/test/kotlin/content/quest/member/the_grand_tree/SpiritTreeTest.kt index ff7a0268dd..b7042e7b9d 100644 --- a/game/src/test/kotlin/content/quest/member/the_grand_tree/SpiritTreeTest.kt +++ b/game/src/test/kotlin/content/quest/member/the_grand_tree/SpiritTreeTest.kt @@ -4,30 +4,80 @@ import WorldTest import interfaceOption import objectOption import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import skipDialogues import world.gregs.voidps.engine.client.ui.dialogue -import world.gregs.voidps.engine.client.ui.menu +import world.gregs.voidps.engine.data.Settings import world.gregs.voidps.engine.entity.obj.GameObjects import world.gregs.voidps.type.Tile import kotlin.test.assertEquals -import kotlin.test.assertNull +import kotlin.test.assertFalse +import kotlin.test.assertTrue class SpiritTreeTest : WorldTest() { - @Test - fun `Can't teleport without quest complete`() { - val player = createPlayer(Tile(2542, 3169)) - val tree = GameObjects.find(Tile(2543, 3168), "spirit_tree_gnome") + @ParameterizedTest + @ValueSource(strings = ["spirit_tree", "spirit_tree_gnome", "spirit_tree_fullygrown"]) + fun `Grand Tree completion cannot bypass the village quest for spirit tree travel`(id: String) { + val player = createPlayer(emptyTile) + player["the_grand_tree"] = "completed" + val tree = createObject(id, emptyTile.addX(1)) + player.objectOption(tree, "Talk-to") + tick(3) + player.skipDialogues() + assertEquals(null, player.dialogue) + assertFalse(player.interfaces.contains("spirit_tree")) + assertEquals(emptyTile, player.tile) player.objectOption(tree, "Teleport") - tick() + tick(3) + player.skipDialogues() + assertEquals(null, player.dialogue) + assertFalse(player.interfaces.contains("spirit_tree")) + assertEquals(emptyTile, player.tile) + player["tree_gnome_village"] = "completed" + player.objectOption(tree, "Teleport") + tick(3) + assertTrue(player.interfaces.contains("spirit_tree")) + } - assertNull(player.dialogue) - assertNull(player.menu) - assertEquals(Tile(2542, 3169), player.tile) + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun `Stronghold Spirit Tree checks Grand Tree with the missing quest bypass`(bypass: Boolean) { + val previous = Settings["quests.requirements.skipMissing", false] + Settings.load(mapOf("quests.requirements.skipMissing" to bypass.toString())) + try { + val player = createPlayer(emptyTile) + val tree = createObject("spirit_tree_stronghold", emptyTile.addX(1)) + player.objectOption(tree, "Talk-to") + tick(3) + player.skipDialogues() + assertEquals(null, player.dialogue) + assertFalse(player.interfaces.contains("spirit_tree")) + player.objectOption(tree, "Teleport") + tick(3) + player.skipDialogues() + assertFalse(player.interfaces.contains("spirit_tree")) + player["tree_gnome_village"] = "completed" + player.objectOption(tree, "Teleport") + tick(3) + assertEquals(bypass, player.interfaces.contains("spirit_tree")) + if (!bypass) { + player.skipDialogues() + player["the_grand_tree"] = "completed" + player.objectOption(tree, "Teleport") + tick(3) + assertTrue(player.interfaces.contains("spirit_tree")) + } + } finally { + Settings.load(mapOf("quests.requirements.skipMissing" to previous.toString())) + } } @Test fun `Teleport from stronghold tree to grand exchange`() { val player = createPlayer(Tile(2461, 3444)) + player["tree_gnome_village"] = "completed" player["the_grand_tree"] = "completed" val tree = GameObjects.find(Tile(2460, 3445), "spirit_tree_stronghold") player.objectOption(tree, "Teleport") diff --git a/game/src/test/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageTest.kt b/game/src/test/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageTest.kt new file mode 100644 index 0000000000..382b8c44ff --- /dev/null +++ b/game/src/test/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageTest.kt @@ -0,0 +1,524 @@ +package content.quest.member.tree_gnome_village + +import WorldTest +import content.entity.combat.Combat +import content.entity.combat.damageDealers +import content.entity.combat.target +import content.entity.player.bank.bank +import dialogueContinue +import dialogueOption +import io.mockk.mockkObject +import io.mockk.unmockkObject +import io.mockk.verify +import io.mockk.verifyOrder +import messages +import npcOption +import objectOption +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource +import org.junit.jupiter.params.provider.ValueSource +import skipDialogues +import world.gregs.voidps.engine.client.ui.dialogue +import world.gregs.voidps.engine.client.update.batch.ZoneBatchUpdates +import world.gregs.voidps.engine.client.variable.start +import world.gregs.voidps.engine.client.variable.stop +import world.gregs.voidps.engine.entity.character.mode.combat.CombatMovement +import world.gregs.voidps.engine.entity.character.mode.interact.PlayerOnNPCInteract +import world.gregs.voidps.engine.entity.character.npc.hunt.Hunt +import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.engine.entity.item.floor.FloorItems +import world.gregs.voidps.engine.entity.obj.GameObjects +import world.gregs.voidps.engine.inv.add +import world.gregs.voidps.engine.inv.clear +import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.engine.inv.remove +import world.gregs.voidps.network.login.protocol.encode.zone.ObjectAnimation +import world.gregs.voidps.network.login.protocol.encode.zone.ProjectileAddition +import world.gregs.voidps.network.login.protocol.encode.zone.ZoneUpdate +import world.gregs.voidps.type.Tile +import world.gregs.voidps.type.Zone +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class TreeGnomeVillageTest : WorldTest() { + + @Test + fun `Stronghold door lets players walk out and closes before an outside entry attempt`() { + val player = createPlayer(Tile(2502, 3252)) + val door = GameObjects.find(Tile(2502, 3250), "door_39_closed") + player.objectOption(door, "Open") + tick(10) + assertEquals(Tile(2502, 3250), player.tile) + val closed = GameObjects.find(Tile(2502, 3250), "door_39_closed") + player.objectOption(closed, "Open") + tick(3) + player.skipDialogues() + assertEquals(Tile(2502, 3250), player.tile) + assertEquals("door_39_closed", GameObjects.find(Tile(2502, 3250), "door_39_closed").id) + } + + @Test + fun `Quest starts after the player agrees before Bolren explains the battlefield`() { + val location = Tile(2542, 3168) + val player = createPlayer(location) + val king = createNPC("king_bolren_tree_gnome_village", Tile(2542, 3169)) + player.npcOption(king, "Talk-to") + tick(3) + player.dialogueContinue(5) + player.dialogueOption("line1") + player.dialogueContinue(9) // Player's question, seven replies, and low-combat warning. + player.dialogueOption("line1") + assertEquals("unstarted", player["tree_gnome_village", "unstarted"]) + player.dialogueContinue() + assertEquals("started", player["tree_gnome_village", "unstarted"]) + assertEquals(location, player.tile) + player.dialogueContinue(3) + assertEquals(Tile(2504, 3192), player.tile) + player.dialogueContinue() + assertEquals(null, player.dialogue) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun `Bolren replaces a lost amulet but does not duplicate a banked reward`(banked: Boolean) { + val player = createPlayer(Tile(2542, 3168)) + val king = createNPC("king_bolren_tree_gnome_village", Tile(2542, 3169)) + player["tree_gnome_village"] = "completed" + if (banked) player.bank.add("gnome_amulet") + repeat(2) { + player.npcOption(king, "Talk-to") + tick(3) + player.skipDialogues() + assertEquals(null, player.dialogue) + assertEquals(if (banked) 0 else 1, player.inventory.count("gnome_amulet")) + } + } + + @Test + fun `Warlord complaint survives losing pursuit outside his movement boundary`() { + val player = createPlayer(Tile(2457, 3314)) + val warlord = createNPC("khazard_warlord_underground_pass", Tile(2457, 3302)) + player["tree_gnome_village"] = "hunt_warlord" + warlord["warlord_challenger"] = player.index + Combat.combat(warlord, player) + mockkObject(warlord) + try { + tick(49) + assertFalse(warlord.mode is CombatMovement) + verify(exactly = 0) { warlord.say("Bah, enough of you!") } + tick() + verify(exactly = 1) { warlord.say("Bah, enough of you!") } + tick(20) + verify(exactly = 1) { warlord.say("Bah, enough of you!") } + } finally { + unmockkObject(warlord) + } + } + + @ParameterizedTest + @CsvSource("khazard_commander_battlefield,2509,3254,0", "khazard_commander_battlefield,2503,3255,1", "khazard_warlord_underground_pass,2457,3302,0") + fun `Stronghold commanders and warlord respawn after death`(id: String, x: Int, y: Int, level: Int) { + val location = Tile(x, y, level) + val player = createPlayer(location.addY(1)) + val npc = createNPC(id, location) + npc["warlord_challenger"] = player.index + npc.damageDealers[player] = 100 + npc.levels.set(Skill.Constitution, 0) + tick(20) + assertTrue(npc.hide) + if (id == "khazard_commander_battlefield") { + assertTrue(FloorItems.firstOrNull(location, "bones") != null) + assertEquals(null, FloorItems.firstOrNull(location, "orb_of_protection")) + } + tick(50) + assertTrue(npc.index >= 0) + assertFalse(npc.hide) + assertFalse(npc.dead) + assertEquals(level, npc.tile.level) + if (id == "khazard_warlord_underground_pass") { + assertEquals(-1, npc["warlord_challenger", -1]) + assertEquals(null, npc.target) + } + } + + @ParameterizedTest + @ValueSource(strings = ["unstarted", "orb_recovered", "hunt_warlord", "warlord_defeated"]) + fun `Warlord conversation follows the first orb and defeat milestones`(stage: String) { + val player = createPlayer(Tile(2458, 3302)) + val warlord = createNPC("khazard_warlord_underground_pass", Tile(2457, 3302)) + player["tree_gnome_village"] = stage + player.npcOption(warlord, "Talk-to") + tick(3) + assertEquals(null, warlord.target) + player.skipDialogues() + assertEquals(null, player.dialogue) + assertEquals(if (stage in setOf("hunt_warlord", "warlord_defeated")) player else null, warlord.target) + } + + @Test + fun `Warlord hunting requires a completed challenge and stops after recovering the orbs`() { + val player = createPlayer(Tile(2458, 3302)) + val warlord = createNPC("khazard_warlord_underground_pass", Tile(2457, 3302)) + player["tree_gnome_village"] = "hunt_warlord" + Hunt.hunt(warlord, player, "tree_gnome_village_warlord") + assertEquals(null, warlord.target) + player.npcOption(warlord, "Talk-to") + tick(3) + player.dialogueContinue(5) + assertEquals(null, warlord.target) + player.dialogueContinue() + assertEquals(player, warlord.target) + warlord.target = null + Hunt.hunt(warlord, player, "tree_gnome_village_warlord") + assertEquals(player, warlord.target) + warlord.target = null + player["tree_gnome_village"] = "orbs_recovered" + Hunt.hunt(warlord, player, "tree_gnome_village_warlord") + assertEquals(null, warlord.target) + val respawned = createNPC("khazard_warlord_underground_pass", Tile(2457, 3303)) + player["tree_gnome_village"] = "warlord_defeated" + Hunt.hunt(respawned, player, "tree_gnome_village_warlord") + assertEquals(null, respawned.target) + player.npcOption(respawned, "Talk-to") + tick(3) + player.dialogueContinue(2) + assertEquals(player, respawned.target) + } + + @Test + fun `Blocked warlord aggression complains once without a quest conversation`() { + val player = createPlayer(Tile(2457, 3307)) + val warlord = createNPC("khazard_warlord_underground_pass", Tile(2457, 3302)) + warlord.start("movement_delay", 1000) + Combat.combat(warlord, player) + mockkObject(warlord) + try { + tick(49) + verify(exactly = 0) { warlord.say("Bah, enough of you!") } + tick() + verify(exactly = 1) { warlord.say("Bah, enough of you!") } + tick(20) + verify(exactly = 1) { warlord.say("Bah, enough of you!") } + } finally { + unmockkObject(warlord) + } + } + + @Test + fun `Attacking the warlord resets the wait for his blocked aggression complaint`() { + val player = createPlayer(Tile(2457, 3307)) + val warlord = createNPC("khazard_warlord_underground_pass", Tile(2457, 3302)) + warlord.start("movement_delay", 1000) + Combat.combat(warlord, player) + mockkObject(warlord) + try { + tick(49) + warlord.start("under_attack", 200) + tick(60) + verify(exactly = 0) { warlord.say("Bah, enough of you!") } + warlord.stop("under_attack") + tick(49) + verify(exactly = 0) { warlord.say("Bah, enough of you!") } + tick() + verify(exactly = 1) { warlord.say("Bah, enough of you!") } + } finally { + unmockkObject(warlord) + } + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun `Unstarted Elkoy introductions end without an escort choice`(inside: Boolean) { + val tile = if (inside) Tile(2514, 3160) else Tile(2504, 3192) + val player = createPlayer(tile) + val elkoy = createNPC(if (inside) "elkoy_tree_gnome_village_2" else "elkoy_tree_gnome_village", tile.addY(-1)) + player.npcOption(elkoy, "Talk-to") + tick(3) + player.dialogueContinue(8) + assertEquals(null, player.dialogue) + assertEquals(tile, player.tile) + assertEquals("unstarted", player["tree_gnome_village", "unstarted"]) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun `Completed Elkoy dialogue escorts after continuing the chosen player line`(inside: Boolean) { + val tile = if (inside) Tile(2514, 3160) else Tile(2504, 3192) + val player = createPlayer(tile) + player["tree_gnome_village"] = "completed" + val elkoy = createNPC(if (inside) "elkoy_tree_gnome_village_2" else "elkoy_tree_gnome_village", tile.addY(-1)) + player.npcOption(elkoy, "Talk-to") + tick(3) + player.dialogueContinue(2) + player.dialogueOption("line1") + assertEquals(tile, player.tile) + player.dialogueContinue() + assertEquals(if (inside) Tile(2504, 3192) else Tile(2514, 3160), player.tile) + player.dialogueContinue() + assertEquals(null, player.dialogue) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun `Completed Elkoy refusal stays at the chosen maze endpoint`(inside: Boolean) { + val tile = if (inside) Tile(2514, 3160) else Tile(2504, 3192) + val player = createPlayer(tile) + player["tree_gnome_village"] = "completed" + val elkoy = createNPC(if (inside) "elkoy_tree_gnome_village_2" else "elkoy_tree_gnome_village", tile.addY(-1)) + player.npcOption(elkoy, "Talk-to") + tick(3) + player.dialogueContinue(2) + player.dialogueOption("line2") + player.dialogueContinue(if (inside) 1 else 2) + assertEquals(null, player.dialogue) + assertEquals(tile, player.tile) + } + + @Test + fun `Ballista strings animate while the spear message is open without a projectile`() { + val player = createPlayer(Tile(2508, 3209)) + player["tree_gnome_village"] = "coordinates_obtained" + player["tree_gnome_village_trackers"] = 7 + player["tree_gnome_village_coordinate"] = 1 + val ballista = GameObjects.find(Tile(2508, 3210), "tree_gnome_village_ballista") + mockkObject(ZoneBatchUpdates) + try { + player.objectOption(ballista, "Fire") + tick(3) + player.dialogueContinue() + player.dialogueOption("line1") + player.dialogueContinue() + assertTrue(player.dialogue?.startsWith("dialogue_message") == true) + val updates = mutableListOf() + verify { ZoneBatchUpdates.add(any(), capture(updates)) } + assertEquals(listOf(730), updates.filterIsInstance().map { it.id }) + assertTrue(updates.filterIsInstance().isEmpty()) + player.skipDialogues() + } finally { + unmockkObject(ZoneBatchUpdates) + } + } + + @Test + fun `Tracker two can give his clue through the bars without walking beside him`() { + val tile = Tile(2524, 3255) + val player = createPlayer(tile) + val tracker = createNPC("tracker_gnome_2_battlefield", Tile(2524, 3257)) + player["tree_gnome_village"] = "logs_delivered" + player["tree_gnome_village_briefed"] = true + assertFalse(PlayerOnNPCInteract(tracker, "Attack", player).arrived()) + player.npcOption(tracker, "Talk-to") + tick(3) + player.skipDialogues() + assertEquals(tile, player.tile) + assertEquals(2, player["tree_gnome_village_trackers", 0]) + } + + @Test + fun `Opening the upstairs chest starts combat without blocking the orb search`() { + val player = createPlayer(Tile(2506, 3258, 1)) + player["tree_gnome_village"] = "stronghold_open" + val commander = createNPC("khazard_commander_battlefield", Tile(2503, 3255, 1)) + player.objectOption(GameObjects.find(Tile(2506, 3259, 1), "tree_gnome_village_chest"), "Open") + tick(3) + assertEquals(player, commander.target) + player.objectOption(GameObjects.find(Tile(2506, 3259, 1), "tree_gnome_village_chest_open"), "Search") + tick(3) + assertFalse(commander.dead) + assertEquals(1, player.inventory.count("orb_of_protection")) + assertEquals("orb_recovered", player["tree_gnome_village", ""]) + } + + @Test + fun `Ceremony ends before a separate conversation awards the quest reward`() { + val player = createPlayer(Tile(2542, 3168)) + val king = createNPC("king_bolren_tree_gnome_village", Tile(2542, 3169)) + val north = createNPC("local_gnome_tree_gnome_village", Tile(2541, 3171)) + val south = createNPC("local_gnome_tree_gnome_village", Tile(2541, 3168)) + player["tree_gnome_village"] = "orbs_recovered" + player.inventory.add("orbs_of_protection") + mockkObject(north, south, king) + try { + player.npcOption(king, "Talk-to") + tick(3) + player.skipDialogues() + tick(20) + player.skipDialogues() + verifyOrder { + north.say("Su tana.") + south.say("En tania.") + north.say("Su tana.") + south.say("En tania.") + king.anim("tree_gnome_village_cast_orbs", delay = 20) + king.gfx("tree_gnome_village_orbs", delay = 20, height = 124) + } + verify(exactly = 2) { north.anim("tree_gnome_village_chant") } + verify(exactly = 2) { south.anim("tree_gnome_village_chant") } + assertEquals(2, player["tree_gnome_village_ceremony", 0]) + assertEquals("orbs_recovered", player["tree_gnome_village", ""]) + assertEquals(0, player.inventory.count("gnome_amulet")) + assertTrue(player.treeGnomeVillageJournal().joinToString(" ").contains("hopefully he will reward me")) + player.npcOption(king, "Talk-to") + tick(3) + player.skipDialogues() + assertEquals("completed", player["tree_gnome_village", ""]) + assertEquals(1, player.inventory.count("gnome_amulet")) + } finally { + unmockkObject(north, south, king) + } + } + + @Test + fun `Progression consumes quest items and awards completion only once`() { + val player = createPlayer() + val progress = TreeGnomeVillageProgress + assertFalse(progress.complete(player)) + assertTrue(progress.start(player)) + val coordinate = player["tree_gnome_village_coordinate", 0] + assertTrue(coordinate in 1..4) + assertFalse(progress.start(player)) + assertEquals(coordinate, player["tree_gnome_village_coordinate", 0]) + + player.inventory.add("logs", 5) + assertFalse(progress.deliverLogs(player)) + assertEquals(5, player.inventory.count("logs")) + player.inventory.add("logs") + player["tree_gnome_village_montai_met"] = true + assertTrue(progress.deliverLogs(player)) + assertEquals(0, player.inventory.count("logs")) + assertFalse(progress.deliverLogs(player)) + + assertFalse(progress.recordTracker(player, 3)) + progress.brief(player) + progress.recordTracker(player, 3) + progress.recordTracker(player, 3) + progress.recordTracker(player, 1) + assertFalse(progress.fire(player, coordinate)) + progress.recordTracker(player, 2) + assertFalse(progress.fire(player, 0)) + assertTrue(progress.fire(player, coordinate)) + assertFalse(progress.fire(player, coordinate)) + assertTrue(progress.recoverOrb(player)) + assertFalse(progress.recoverOrb(player)) + assertTrue(progress.returnOrb(player)) + assertFalse(progress.returnOrb(player)) + assertFalse(progress.recoverOrb(player)) + assertTrue(progress.defeatWarlord(player)) + assertFalse(progress.defeatWarlord(player)) + val experience = player.experience.get(Skill.Attack) + val points = player["quest_points", 0] + assertFalse(progress.complete(player)) + assertTrue(progress.beginCeremony(player)) + assertTrue(progress.beginCeremony(player)) // Resume without consuming a second set of orbs. + assertFalse(progress.defeatWarlord(player)) + assertFalse(progress.complete(player)) + progress.finishCeremony(player) + assertTrue(progress.complete(player)) + assertFalse(progress.complete(player)) + assertEquals("completed", player["tree_gnome_village", "unstarted"]) + assertEquals(points + 2, player["quest_points", 0]) + assertEquals(experience + 11450.0, player.experience.get(Skill.Attack)) + assertEquals(1, player.inventory.count("gnome_amulet")) + assertEquals(0, player.inventory.count("orbs_of_protection")) + assertFalse(progress.defeatWarlord(player)) + } + + @Test + fun `Chest rejects early access duplicates and full inventories but replaces a lost orb`() { + val player = createPlayer(Tile(2506, 3258, 1)) + val closed = GameObjects.find(Tile(2506, 3259, 1), "tree_gnome_village_chest") + player.objectOption(closed, "Open") + tick(3) + assertEquals(0, player.inventory.count("orb_of_protection")) + player["tree_gnome_village"] = "stronghold_open" + player.objectOption(closed, "Open") + tick(3) + val chest = GameObjects.find(Tile(2506, 3259, 1), "tree_gnome_village_chest_open") + player.inventory.add("logs", 28) + player.objectOption(chest, "Search") + tick(3) + assertEquals("stronghold_open", player["tree_gnome_village", ""]) + player.inventory.clear() + player.objectOption(chest, "Search") + tick(3) + assertEquals(1, player.inventory.count("orb_of_protection"), player.messages.toString()) + player.inventory.remove("orb_of_protection") + player.bank.add("orb_of_protection") + assertFalse(TreeGnomeVillageProgress.recoverOrb(player)) + player.bank.clear() + assertTrue(TreeGnomeVillageProgress.recoverOrb(player)) + } + + @Test + fun `A warlord kill with a full inventory preserves recovery and cannot skip the first orb`() { + val player = createPlayer() + assertFalse(TreeGnomeVillageProgress.defeatWarlord(player)) + player["tree_gnome_village"] = "hunt_warlord" + player.inventory.add("logs", 28) + assertFalse(TreeGnomeVillageProgress.defeatWarlord(player)) + assertEquals("warlord_defeated", player["tree_gnome_village", ""]) + player.inventory.remove("logs") + assertTrue(TreeGnomeVillageProgress.recoverOrbs(player)) + assertEquals("orbs_recovered", player["tree_gnome_village", ""]) + player.inventory.remove("orbs_of_protection") + assertTrue(TreeGnomeVillageProgress.defeatWarlord(player)) + } + + @Test + fun `Ballista success does not unlock another player's stronghold`() { + val player = createPlayer(Tile(2509, 3252)) + val other = createPlayer() + TreeGnomeVillageProgress.start(other) + other["tree_gnome_village"] = "coordinates_obtained" + other["tree_gnome_village_trackers"] = 7 + assertTrue(TreeGnomeVillageProgress.fire(other, other["tree_gnome_village_coordinate", 0])) + val wall = GameObjects.find(Tile(2509, 3253), "tree_gnome_village_crumbled_wall") + player.objectOption(wall, "Climb-over") + tick(5) + assertEquals(Tile(2509, 3252), player.tile) + } + + @Test + fun `Firing the actual ballista rejects a miss and accepts the stored coordinate`() { + val player = createPlayer(Tile(2508, 3209)) + TreeGnomeVillageProgress.start(player) + player["tree_gnome_village"] = "coordinates_obtained" + player["tree_gnome_village_trackers"] = 7 + val ballista = GameObjects.find(Tile(2508, 3210), "tree_gnome_village_ballista") + player.objectOption(ballista, "Fire") + tick(3) + player.dialogueContinue() + val wrong = player["tree_gnome_village_coordinate", 0] % 4 + 1 + player.dialogueOption("line$wrong") + player.skipDialogues() + tick() + assertEquals("coordinates_obtained", player["tree_gnome_village", ""]) + player.objectOption(ballista, "Fire") + tick(3) + player.dialogueContinue() + player.dialogueOption("line${player["tree_gnome_village_coordinate", 0]}") + player.skipDialogues() + tick() + assertEquals("stronghold_open", player["tree_gnome_village", ""]) + } + + @Test + fun `Started players can follow Elkoy from both maze endpoints`() { + val player = createPlayer(Tile(2504, 3192)) + TreeGnomeVillageProgress.start(player) + val outside = createNPC("elkoy_tree_gnome_village", Tile(2504, 3191)) + player.npcOption(outside, "Follow") + tick(3) + assertTrue(player.interfaces.contains("dialogue_message_np1")) + tick(8) + assertEquals(Tile(2514, 3160), player.tile) + assertFalse(player.interfaces.contains("dialogue_message_np1")) + val inside = createNPC("elkoy_tree_gnome_village_2", Tile(2514, 3159)) + player.npcOption(inside, "Follow") + tick(11) + assertEquals(Tile(2504, 3192), player.tile) + assertFalse(player.interfaces.contains("dialogue_message_np1")) + } +} From 6606d5a35d98a6c1e1f999bb3253cac4fa382835 Mon Sep 17 00:00:00 2001 From: GregHib Date: Thu, 15 Oct 2026 17:10:39 +0100 Subject: [PATCH 2/3] Add dialogues and journal --- .../tree_gnome_village/TreeGnomeVillage.kt | 94 ++++++++- .../TreeGnomeVillageDialogue.kt | 198 ++++++++++-------- .../TreeGnomeVillageJournal.kt | 47 ----- 3 files changed, 203 insertions(+), 136 deletions(-) delete mode 100644 game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageJournal.kt diff --git a/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillage.kt b/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillage.kt index b941d99a98..c183bc37c7 100644 --- a/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillage.kt +++ b/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillage.kt @@ -161,7 +161,99 @@ class TreeGnomeVillage : Script { } questJournalOpen("tree_gnome_village") { - questJournal("Tree Gnome Village", treeGnomeVillageJournal()) + val lines = mutableListOf() + val stage = questStage("tree_gnome_village") + if (stage == 0) { + lines.add("I can start this quest by speaking to Bolren at the center of the") + lines.add("Tree Gnome maze, West of Port Khazard.") + } else if (stage >= 1) { + lines.add("I spoke to King Bolren who told me that one of their orbs of") + lines.add("protection has been stolen by Khazard troops.") + lines.add("") + } + if (stage == 1) { + lines.add("Commander Montai, North of the maze, can help me get it") + } else if (stage >= 2) { + lines.add("I spoke to Montai. The orb was in the Khazard stronghold North of") + lines.add("the battlefield, but it was secure in there.") + lines.add("") + } + if (stage == 2) { + lines.add("I need to bring Montai logs to fortify their defences") + } else if (stage == 3) { + lines.add("I brought Montai logs to help fortify their defences.") + lines.add("I should speak to Montai again and see what to do next") + } else if (stage >= 4) { + lines.add("I brought Montai logs to help fortify their defences.") + lines.add("Now their defences were secure, they were ready to use the") + lines.add("Gnome Ballista to break through the enemy defences.") + lines.add("") + } + if (stage == 4) { + lines.add("I need to head into the battlefield and find the three Gnome") + lines.add("trackers who have the firing coordinates and then fire the ballista!") + } else if (stage == 5) { + lines.add("I need to head into the battlefield and find the three Gnome") + lines.add("trackers who have the firing coordinates and then fire the ballista!") + lines.add("Unfortunately the tracker who knew the x coordinate has gone") + lines.add("insane, so I'll have to guess that coordinate myself") + } else if (stage == 6) { + lines.add("I need to head into the battlefield and find the three Gnome") + lines.add("trackers who have the firing coordinates and then fire the ballista!") + lines.add("I know the height coordinate") + lines.add("Unfortunately the tracker who knew the x coordinate has gone") + lines.add("insane, so I'll have to guess that coordinate myself") + } else if (stage == 7) { + lines.add("I need to head into the battlefield and find the three Gnome") + lines.add("trackers who have the firing coordinates and then fire the ballista!") + lines.add("I know the y coordinate") + lines.add("I know the height coordinate") + lines.add("Unfortunately the tracker who knew the x coordinate has gone") + lines.add("insane, so I'll have to guess that coordinate myself") + } else if (stage == 8) { + lines.add("I found the three trackers and used their coordinates to fire the") + lines.add("ballista straight into the Khazard stronghold.") + lines.add("With the Khazard stronghold exposed I should get inside and") + lines.add("retrieve the gnomes' orb of protection") + } else if (stage >= 9) { + lines.add("I found the three trackers and used their coordinates to fire the") + lines.add("ballista straight into the Khazard stronghold.") + lines.add("With the stronghold breached by ballista fire, I was able to make") + lines.add("my way inside and recover the orb of protection.") + lines.add("") + } + if (stage == 9) { + lines.add("I should return the orb to King Bolren immediately") + } else if (stage >= 10) { + lines.add("I returned the orb to King Bolren, but while I was busy getting it") + lines.add("the Khazard troops invaded the village and stole the remaining two") + lines.add("orbs before heading north.") + lines.add("") + } + if (stage == 10) { + lines.add("After a fierce battle I defeated the warlord who'd stolen the orbs,") + lines.add("and reclaimed them for the gnome people.") + lines.add("I should return them now to King Bolren where they belong") + } else if (stage == 11) { + lines.add("After a fierce battle I defeated the warlord who'd stolen the orbs,") + lines.add("and reclaimed them for the gnome people.") + lines.add("I should speak to King Bolren now - hopefully he will reward me") + } else if (stage >= 12) { + lines.add("After a fierce battle I defeated the warlord who'd stolen the orbs,") + lines.add("and reclaimed them for the gnome people.") + lines.add("I returned them to King Bolren, and was rewarded for all of my") + lines.add("help to the Gnome people in recovering the orbs.") + lines.add("") + } + if (stage >= 12) { + lines.add("I can now use the gnome's spirit tree to teleport around the world") + lines.add("to places that are linked to the spirit tree!") + lines.add("") + } + if (stage == 12) { + lines.add("QUEST COMPLETE!") + } + questJournal("Tree Gnome Village", lines) } } diff --git a/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageDialogue.kt b/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageDialogue.kt index b7420cdd7a..619cff4dc3 100644 --- a/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageDialogue.kt +++ b/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageDialogue.kt @@ -2,7 +2,15 @@ package content.quest.member.tree_gnome_village import content.entity.combat.Combat import content.entity.player.bank.ownsItem +import content.entity.player.dialogue.Angry +import content.entity.player.dialogue.Bored +import content.entity.player.dialogue.Confused +import content.entity.player.dialogue.Happy import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.Quiz +import content.entity.player.dialogue.Sad +import content.entity.player.dialogue.Shifty +import content.entity.player.dialogue.Shock import content.entity.player.dialogue.type.choice import content.entity.player.dialogue.type.item import content.entity.player.dialogue.type.npc @@ -31,7 +39,7 @@ internal const val ELKOY = "elkoy_tree_gnome_village_4" internal suspend fun Player.gnomeEscort(out: Boolean, text: String) { // Moving happens immediately after Continue on Bolren's assistant line. tele(if (out) Tile(2504, 3192) else Tile(2514, 3160)) - npc(ELKOY, text) + npc(ELKOY, text) } class TreeGnomeVillageDialogue : Script { @@ -83,20 +91,22 @@ class TreeGnomeVillageDialogue : Script { when (quest("tree_gnome_village")) { "unstarted" -> { player("Hello.") - npc(BOLREN, "Well hello stranger. My name's Bolren, I'm the king of the tree gnomes.") - npc(BOLREN, "I'm surprised you made it in, maybe I made the maze too easy.") + npc(BOLREN, "Well hello stranger. My name's Bolren, I'm the king of the tree gnomes.") + npc(BOLREN, "I'm surprised you made it in, maybe I made the maze too easy.") player("Maybe.") - npc(BOLREN, "I'm afraid I have more serious concerns at the moment. Very serious.") + npc(BOLREN, "I'm afraid I have more serious concerns at the moment. Very serious.") choice { - option("Can I help at all?") { - npc(BOLREN, "I'm glad you asked.") - npc(BOLREN, "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.") - npc(BOLREN, "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.") - npc(BOLREN, "We became desperate, so we took one orb of protection to the battlefield. It was a foolish move.") - npc(BOLREN, "Khazard troops seized the orb. Now we are completely defenceless.") - player("How can I help?") + option("Can I help at all?") { + npc(BOLREN, "I'm glad you asked.") + npc(BOLREN, "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.") + npc(BOLREN, "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.") + npc(BOLREN, "We became desperate, so we took one orb of protection to the battlefield. It was a foolish move.") + npc(BOLREN, "Khazard troops seized the orb. Now we are completely defenceless.") + player("How can I help?") npc(BOLREN, "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.") - if (combatLevel < 45) statement("Before starting this quest, be aware that your combat level is lower than the recommended level of 45.") + if (combatLevel < 45) { + statement("Before starting this quest, be aware that your combat level is lower than the recommended level of 45.") + } choice { option("I would be glad to help.") { player("I would be glad to help.") @@ -113,36 +123,40 @@ class TreeGnomeVillageDialogue : Script { } } } - option("I'll leave you to it then.") { npc(BOLREN, "Ok, take care.") } + option("I'll leave you to it then.") { npc(BOLREN, "Ok, take care.") } } } "orb_recovered" -> { if (!inventory.contains("orb_of_protection")) { - player("Hello Bolren.") + player("Hello Bolren.") // TODO proper expressions npc(BOLREN, "Do you have the orb?") player("No, I'm afraid not.") npc(BOLREN, "Please, we must have the orb if we are to survive.") return } - player("I have the orb.") - npc(BOLREN, "Oh my... The misery, the horror!") - player("King Bolren, are you OK?") - npc(BOLREN, "Thank you traveller, but it's too late. We're all doomed.") - player("What happened?") - npc(BOLREN, "They came in the night. I don't know how many, but enough.") - player("Who?") - npc(BOLREN, "Khazard troops. They slaughtered anyone who got in their way. Women, children, my wife.") - player("I'm sorry.") - npc(BOLREN, "They took the other orbs, now we are defenceless.") - player("Where did they take them?") - npc(BOLREN, "They headed north of the stronghold. A warlord carries the orbs.") + player("I have the orb.") + npc(BOLREN, "Oh my... The misery, the horror!") + player("King Bolren, are you OK?") + npc(BOLREN, "Thank you traveller, but it's too late. We're all doomed.") + player("What happened?") + npc(BOLREN, "They came in the night. I don't know how many, but enough.") + player("Who?") + npc(BOLREN, "Khazard troops. They slaughtered anyone who got in their way. Women, children, my wife.") + player("I'm sorry.") + npc(BOLREN, "They took the other orbs, now we are defenceless.") + player("Where did they take them?") + npc(BOLREN, "They headed north of the stronghold. A warlord carries the orbs.") choice { option("I will find the warlord and bring back the orbs.") { npc(BOLREN, "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.") npc(BOLREN, "I will safeguard this orb and pray for your safe return. My assistant will guide you out.") - if (TreeGnomeVillageProgress.returnOrb(this)) gnomeEscort(true, "Good luck friend.") + if (TreeGnomeVillageProgress.returnOrb(this)) { + gnomeEscort(true, "Good luck friend.") + } + } + option("I'm sorry but I can't help.") { + npc(BOLREN, "I understand, this isn't your battle.") } - option("I'm sorry but I can't help.") { npc(BOLREN, "I understand, this isn't your battle.") } } } "hunt_warlord", "warlord_defeated" -> { @@ -264,73 +278,81 @@ class TreeGnomeVillageDialogue : Script { private suspend fun Player.montai() { when { + // TODO reorder questStage("tree_gnome_village") >= 6 -> { - player("Hello Montai, how are you?") + player("Hello Montai, how are you?") // TODO proper expressions npc(MONTAI, "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.") player("Hang in there.") } quest("tree_gnome_village") == "orb_recovered" -> { - player("I have the orb of protection.") - npc(MONTAI, "Incredible, for a human you really are something.") - player("Thanks... I think!") - npc(MONTAI, "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.") + player("I have the orb of protection.") + npc(MONTAI, "Incredible, for a human you really are something.") + player("Thanks... I think!") + npc(MONTAI, "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.") } quest("tree_gnome_village") == "stronghold_open" -> { - player("I've breached the stronghold.") - npc(MONTAI, "I saw, that was a beautiful sight. The Khazard troops didn't know what hit them.") + player("I've breached the stronghold.") + npc(MONTAI, "I saw, that was a beautiful sight. The Khazard troops didn't know what hit them.") npc(MONTAI, "Now is the time to retrieve the orb. It's all in your hands. I'll be praying for you.") } get("tree_gnome_village_briefed", false) -> { - player("Hello.") + player("Hello.") // TODO proper expressions npc(MONTAI, "Hello warrior. We need the coordinates for a direct hit from the ballista. Once you have a direct hit you will be able to enter the stronghold and retrieve the orb.") } quest("tree_gnome_village") == "logs_delivered" -> { player("How are you doing Montai?") - npc(MONTAI, "We're hanging in there soldier. For the next phase of our attack we need to breach their stronghold.") - npc(MONTAI, "The ballista can break through the stronghold wall, and then we can advance and seize back the orb.") - player("So what's the problem?") - npc(MONTAI, "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.") - player("Have they returned?") - npc(MONTAI, "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.") - npc(MONTAI, "Do you think you can do it?") + npc(MONTAI, "We're hanging in there soldier. For the next phase of our attack we need to breach their stronghold.") + npc(MONTAI, "The ballista can break through the stronghold wall, and then we can advance and seize back the orb.") + player("So what's the problem?") + npc(MONTAI, "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.") + player("Have they returned?") + npc(MONTAI, "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.") + npc(MONTAI, "Do you think you can do it?") TreeGnomeVillageProgress.brief(this) choice { - option("No, I've had enough of your battle.") { npc(MONTAI, "I understand, this isn't your fight.") } + option("No, I've had enough of your battle.") { + npc(MONTAI, "I understand, this isn't your fight.") + } option("I'll try my best.") { - npc(MONTAI, "Thank you, you're braver than most.") - npc(MONTAI, "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.") - npc(MONTAI, "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.") + npc(MONTAI, "Thank you, you're braver than most.") + npc(MONTAI, "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.") + npc(MONTAI, "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.") } } } quest("tree_gnome_village") == "unstarted" -> { - player("Hello.") + player("Hello.") // TODO proper expressions npc(MONTAI, "I can't talk now. Can't you see we're trying to win a battle here? If we can't hold back Khazard's men we're all doomed.") } !get("tree_gnome_village_montai_met", false) -> { player("Hello.") npc(MONTAI, "Hello traveller, are you here to help or just to watch?") player("I've been sent by King Bolren to retrieve the orb of protection.") - npc(MONTAI, "Excellent we need all the help we can get.") + npc(MONTAI, "Excellent we need all the help we can get.") npc(MONTAI, "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.") - player("What can I do?") + player("What can I do?") npc(MONTAI, "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.") set("tree_gnome_village_montai_met", true) refreshQuestJournal() choice { - option("Sorry, I no longer want to be involved.") { npc(MONTAI, "That's a shame, we could have done with your help.") } - option("Ok, I'll gather some wood.") { npc(MONTAI, "Please be as quick as you can, I don't know how much longer we can hold out.") } + option("Ok, I'll gather some wood.") { + npc(MONTAI, "Please be as quick as you can, I don't know how much longer we can hold out.") + } + option("Sorry, I no longer want to be involved.") { + npc(MONTAI, "That's a shame, we could have done with your help.") + } } } else -> { player("Hello.") if (inventory.contains("logs", 6)) { npc(MONTAI, "Hello again, we're still desperate for wood soldier.") - player("I have some here.
(You give six loads of logs to the commander.)") + player("I have some here.
(You give six loads of logs to the commander.)") if (TreeGnomeVillageProgress.deliverLogs(this)) { - npc(MONTAI, "That's excellent, now we can make more defensive battlements. Give me a moment to organise the troops and then come speak to me. I'll inform you of our next phase of attack.") + npc(MONTAI, "That's excellent, now we can make more defensive battlements. Give me a moment to organise the troops and then come speak to me. I'll inform you of our next phase of attack.") } } else { + // TODO proper expressions npc(MONTAI, "Hello again, we're still desperate for wood soldier. We need six loads of normal logs.") player("I'll see what I can do.") npc(MONTAI, "Thank you.") @@ -339,7 +361,7 @@ class TreeGnomeVillageDialogue : Script { } } - private suspend fun Player.tracker(number: Int, id: String) { + private suspend fun Player.tracker(number: Int, id: String) { // TODO split into individual dialogues val stage = questStage("tree_gnome_village") if (stage == 0) { player("Hello.") @@ -351,81 +373,81 @@ class TreeGnomeVillageDialogue : Script { else -> "I feel dizzy, where am I? Oh dear, oh dear I need some rest." } player("Hello.") - npc(id, text) + npc(id, text)// TODO proper expressions if (number == 3) player("I think you do.") } else if (stage >= 4) { if (number == 3) { - player("Hello again.") + player("Hello again.")// TODO proper expressions npc(id, "Don't talk to me, you can't see me. No one can, just the demons.") } else if (stage == 5) { - player("How are you tracker?") + player("How are you tracker?")// TODO proper expressions npc(id, if (number == 1) "Now we have the orb I'm much better. They won't stand a chance without it." else "Now we have the orb I'm much better. Soon my comrades will come and free me.") } else { - player("Hello again.") + player("Hello again.")// TODO proper expressions npc(id, "Well done, you've broken down their defences. This battle must be ours.") } } else if (get("tree_gnome_village_briefed", false)) { when (number) { 1 -> { - player("Do you know the coordinates of the Khazard stronghold?") - npc(id, "I managed to get one, although it wasn't easy.") + player("Do you know the coordinates of the Khazard stronghold?") + npc(id, "I managed to get one, although it wasn't easy.") statement("The gnome tells you the height coordinate.") TreeGnomeVillageProgress.recordTracker(this, 1) - player("Well done.") + player("Well done.") npc(id, "The other two tracker gnomes should have the other coordinates if they're still alive.") player("OK, take care.") } 2 -> { - player("Are you OK?") - npc(id, "They caught me spying on the stronghold. They beat and tortured me.") - npc(id, "But I didn't crack. I told them nothing. They can't break me!") - player("I'm sorry little man.") - npc(id, "Don't be. I have the position of the stronghold!") - statement("The gnome tells you the y coordinate.") + player("Are you OK?") + npc(id, "They caught me spying on the stronghold. They beat and tortured me.") + npc(id, "But I didn't crack. I told them nothing. They can't break me!") + player("I'm sorry.") + npc(id, "Don't be. I have the position of the stronghold!") + statement("The gnome tells you the y coordinate.") TreeGnomeVillageProgress.recordTracker(this, 2) - player("Well done.") - npc(id, "Now leave before they find you and all is lost.") + player("Well done.") + npc(id, "Now leave before they find you and all is lost.") player("Hang in there.") - npc(id, "Go!") + npc(id, "Go!") } 3 -> { - player("Are you OK?") - npc(id, "OK? Who's OK? Not me! Hee hee!") - player("What's wrong?") - npc(id, "You can't see me, no one can. Monsters, demons, they're all around me!") - player("What do you mean?") - npc(id, "They're dancing, all of them, hee hee.") + player("Are you OK?") + npc(id, "OK? Who's OK? Not me! Hee hee!") + player("What's wrong?") + npc(id, "You can't see me, no one can. Monsters, demons, they're all around me!") + player("What do you mean?") + npc(id, "They're dancing, all of them, hee hee.") statement("He's clearly lost the plot.") - player("Do you have the coordinate for the Khazard stronghold?") - npc(id, "Who holds the stronghold?") - player("What?") + player("Do you have the coordinate for the Khazard stronghold?") + npc(id, "Who holds the stronghold?") + player("What?") val clue = when (get("tree_gnome_village_coordinate", 1)) { 1 -> "Less than my hands." 2 -> "More than my head, less than my fingers." 3 -> "More than we, less than our feet." else -> "My legs and your legs, ha ha ha!" } - npc(id, clue) + npc(id, clue) TreeGnomeVillageProgress.recordTracker(this, 3) - player("You're mad.") - npc(id, "Dance with me, and Khazard's men are beat.") + player("You're mad.") + npc(id, "Dance with me, and Khazard's men are beat.") statement("The toll of war has affected his mind.") - player("I'll pray for you little man.") - npc(id, "All day we pray in the hay, hee hee.") + player("I'll pray for you.") + npc(id, "All day we pray in the hay, hee hee.") } } } else { when (number) { 1 -> { - player("Hello.") + player("Hello.")// TODO proper expressions npc(id, "I can't talk now. Can't you see we're trying to win a battle here?") } 2 -> { - player("Hi there.") + player("Hi there.")// TODO proper expressions npc(id, "The battle is far from over. If you have a pure heart you will help us win.") } 3 -> { - player("Hi there.") + player("Hi there.")// TODO proper expressions npc(id, "I can't stand this war. The misery, the pain, it's driving me crazy! When will it end?") } } diff --git a/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageJournal.kt b/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageJournal.kt deleted file mode 100644 index efcd2342a9..0000000000 --- a/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageJournal.kt +++ /dev/null @@ -1,47 +0,0 @@ -package content.quest.member.tree_gnome_village - -import content.quest.questStage -import world.gregs.voidps.engine.data.definition.FontDefinitions -import world.gregs.voidps.engine.entity.character.player.Player -import world.gregs.voidps.engine.get - -fun Player.treeGnomeVillageJournal(): List { - val stage = questStage("tree_gnome_village") - val history = mutableListOf() - if (stage >= 1) history += "I spoke to King Bolren who told me that one of their orbs of protection has been stolen by Khazard troops." - if (get("tree_gnome_village_montai_met", false)) history += "I spoke to Montai. The orb was in the Khazard stronghold North of the battlefield, but it was secure in there." - if (stage >= 2) history += "I brought Montai logs to help fortify their defences." - if (get("tree_gnome_village_briefed", false)) history += "Now their defences were secure, they were ready to use the Gnome Ballista to break through the enemy defences." - if (stage >= 4) history += "I found the three trackers and used their coordinates to fire the ballista straight into the Khazard stronghold." - if (stage >= 5) history += "With the stronghold breached by ballista fire, I was able to make my way inside and recover the orb of protection." - if (stage >= 6) history += "I returned the orb to King Bolren, but while I was busy getting it the Khazard troops invaded the village and stole the remaining two orbs before heading north." - if (stage >= 8) history += "After a fierce battle I defeated the warlord who'd stolen the orbs, and reclaimed them for the gnome people." - if (get("tree_gnome_village_ceremony", 0) == 2) history += "I returned the orbs to King Bolren and the gnomes performed their ceremony." - if (stage == 9) history += "King Bolren rewarded me with a gnome amulet and allowed me to use the spirit tree." - - val current = when { - stage == 0 -> "I can start this quest by speaking to King Bolren in Tree Gnome Village, in the centre of the maze." - stage == 9 -> "QUEST COMPLETE!" - get("tree_gnome_village_ceremony", 0) == 2 -> "I should speak to King Bolren now - hopefully he will reward me" - stage >= 8 -> "I should return them now to King Bolren where they belong" - stage >= 6 -> "I should retrieve the two remaining orbs from the warlord" - stage == 5 -> "I should return the orb of protection to King Bolren." - stage == 4 -> "With the Khazard stronghold exposed I should get inside and retrieve the gnomes' orb of protection" - stage == 3 || get("tree_gnome_village_briefed", false) -> "I need to head into the battlefield and find the three Gnome trackers who have the firing coordinates and then fire the ballista!" - stage == 2 -> "I should speak to Montai again and see what to do next." - get("tree_gnome_village_montai_met", false) -> "I should bring six normal logs to Commander Montai." - else -> "I should speak to Commander Montai at the battlefield north of the maze." - } - val font = get().get("p12_full") - val lines = mutableListOf() - for (paragraph in history) { - lines += font.splitLines(paragraph, 350).map { "$it" } - lines += "" - } - var colour = "" - for (line in font.splitLines(current, 350)) { - lines += "$colour$line" - colour = Regex("<(red|navy)>").findAll(line).lastOrNull()?.value ?: colour - } - return lines -} From 8e724b1a47ce62b940c6fddd5c3e244c44d5032a Mon Sep 17 00:00:00 2001 From: GregHib Date: Fri, 16 Oct 2026 20:37:55 +0100 Subject: [PATCH 3/3] Add missing dialogue expressions --- .../TreeGnomeVillageDialogue.kt | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageDialogue.kt b/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageDialogue.kt index 619cff4dc3..d5a5cf44e1 100644 --- a/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageDialogue.kt +++ b/game/src/main/kotlin/content/quest/member/tree_gnome_village/TreeGnomeVillageDialogue.kt @@ -128,10 +128,10 @@ class TreeGnomeVillageDialogue : Script { } "orb_recovered" -> { if (!inventory.contains("orb_of_protection")) { - player("Hello Bolren.") // TODO proper expressions - npc(BOLREN, "Do you have the orb?") - player("No, I'm afraid not.") - npc(BOLREN, "Please, we must have the orb if we are to survive.") + player("Hello Bolren.") + npc(BOLREN, "Do you have the orb?") + player("No, I'm afraid not.") + npc(BOLREN, "Please, we must have the orb if we are to survive.") return } player("I have the orb.") @@ -280,15 +280,15 @@ class TreeGnomeVillageDialogue : Script { when { // TODO reorder questStage("tree_gnome_village") >= 6 -> { - player("Hello Montai, how are you?") // TODO proper expressions - npc(MONTAI, "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.") - player("Hang in there.") + player("Hello Montai, how are you?") + npc(MONTAI, "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.") + player("Hang in there.") } quest("tree_gnome_village") == "orb_recovered" -> { player("I have the orb of protection.") npc(MONTAI, "Incredible, for a human you really are something.") - player("Thanks... I think!") - npc(MONTAI, "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.") + player("Thanks... I think!") + npc(MONTAI, "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.") } quest("tree_gnome_village") == "stronghold_open" -> { player("I've breached the stronghold.") @@ -296,8 +296,8 @@ class TreeGnomeVillageDialogue : Script { npc(MONTAI, "Now is the time to retrieve the orb. It's all in your hands. I'll be praying for you.") } get("tree_gnome_village_briefed", false) -> { - player("Hello.") // TODO proper expressions - npc(MONTAI, "Hello warrior. We need the coordinates for a direct hit from the ballista. Once you have a direct hit you will be able to enter the stronghold and retrieve the orb.") + player("Hello.") + npc(MONTAI, "Hello warrior. We need the coordinates for a direct hit from the ballista. Once you have a direct hit you will be able to enter the stronghold and retrieve the orb.") } quest("tree_gnome_village") == "logs_delivered" -> { player("How are you doing Montai?") @@ -321,8 +321,8 @@ class TreeGnomeVillageDialogue : Script { } } quest("tree_gnome_village") == "unstarted" -> { - player("Hello.") // TODO proper expressions - npc(MONTAI, "I can't talk now. Can't you see we're trying to win a battle here? If we can't hold back Khazard's men we're all doomed.") + player("Hello.") + npc(MONTAI, "I can't talk now. Can't you see we're trying to win a battle here? If we can't hold back Khazard's men we're all doomed.") } !get("tree_gnome_village_montai_met", false) -> { player("Hello.") @@ -352,7 +352,6 @@ class TreeGnomeVillageDialogue : Script { npc(MONTAI, "That's excellent, now we can make more defensive battlements. Give me a moment to organise the troops and then come speak to me. I'll inform you of our next phase of attack.") } } else { - // TODO proper expressions npc(MONTAI, "Hello again, we're still desperate for wood soldier. We need six loads of normal logs.") player("I'll see what I can do.") npc(MONTAI, "Thank you.") @@ -367,23 +366,24 @@ class TreeGnomeVillageDialogue : Script { player("Hello.") npc(id, if (number == 2) "I can't talk now. If the guards catch me I'll be dead gnome meat." else "I can't talk now. Can't you see we're trying to win a battle here?") } else if (stage >= 6) { - val text = when (number) { - 1 -> "When will this battle end? I feel like I've been fighting forever." - 2 -> "When will this battle end? I feel like I've been locked up my whole life." - else -> "I feel dizzy, where am I? Oh dear, oh dear I need some rest." - } player("Hello.") - npc(id, text)// TODO proper expressions - if (number == 3) player("I think you do.") + when (number) { + 1 -> npc(id, "When will this battle end? I feel like I've been fighting forever.") + 2 -> npc(id, "When will this battle end? I feel like I've been locked up my whole life.") + else -> { + npc(id, "I feel dizzy, where am I? Oh dear, oh dear I need some rest.") + player("I think you do.") + } + } } else if (stage >= 4) { if (number == 3) { - player("Hello again.")// TODO proper expressions - npc(id, "Don't talk to me, you can't see me. No one can, just the demons.") + player("Hello again.") + npc(id, "Don't talk to me, you can't see me. No one can, just the demons.") } else if (stage == 5) { - player("How are you tracker?")// TODO proper expressions + player("How are you tracker?") npc(id, if (number == 1) "Now we have the orb I'm much better. They won't stand a chance without it." else "Now we have the orb I'm much better. Soon my comrades will come and free me.") } else { - player("Hello again.")// TODO proper expressions + player("Hello again.") npc(id, "Well done, you've broken down their defences. This battle must be ours.") } } else if (get("tree_gnome_village_briefed", false)) { @@ -439,15 +439,15 @@ class TreeGnomeVillageDialogue : Script { } else { when (number) { 1 -> { - player("Hello.")// TODO proper expressions - npc(id, "I can't talk now. Can't you see we're trying to win a battle here?") + player("Hello.") + npc(id, "I can't talk now. Can't you see we're trying to win a battle here?") } 2 -> { - player("Hi there.")// TODO proper expressions + player("Hi there.") npc(id, "The battle is far from over. If you have a pure heart you will help us win.") } 3 -> { - player("Hi there.")// TODO proper expressions + player("Hi there.") npc(id, "I can't stand this war. The misery, the pain, it's driving me crazy! When will it end?") } }