diff --git a/data/area/kandarin/feldip_hills/feldip_hills.npc-spawns.toml b/data/area/kandarin/feldip_hills/feldip_hills.npc-spawns.toml
index 2d6145d6cc..c61c73e54b 100644
--- a/data/area/kandarin/feldip_hills/feldip_hills.npc-spawns.toml
+++ b/data/area/kandarin/feldip_hills/feldip_hills.npc-spawns.toml
@@ -228,5 +228,4 @@ spawns = [
{ id = "mound_feldip_hills", x = 2465, y = 2911, members = true },
{ id = "mound_feldip_hills", x = 2466, y = 2921, members = true },
{ id = "wolf", x = 2470, y = 2865 },
- { id = "rantz", x = 2630, y = 2981 },
]
\ No newline at end of file
diff --git a/data/area/kandarin/feldip_hills/feldip_hills.npcs.toml b/data/area/kandarin/feldip_hills/feldip_hills.npcs.toml
index cb1a0a4ad2..f4704e5e15 100644
--- a/data/area/kandarin/feldip_hills/feldip_hills.npcs.toml
+++ b/data/area/kandarin/feldip_hills/feldip_hills.npcs.toml
@@ -80,6 +80,7 @@ examine = "A large and contentious lady ogre."
[rantz_feldip_hills_2]
id = 8659
+dialogue = "ogre"
[mound_feldip_hills]
id = 9466
@@ -115,11 +116,6 @@ id = 13174
id = 5073
examine = "This bird obviously doesn't believe in subtlety."
-[rantz]
-id = 3587
-wander_range = 4
-examine = "A large dim looking humanoid."
-
[ogre_boat_feldip_hills_2]
id = 3472
@@ -128,6 +124,7 @@ id = 3467
[rantz_feldip_hills_2_2]
id = 1010
+dialogue = "ogre"
[fishing_spot_big_net_harpoon_feldip_hills]
id = 7044
diff --git a/data/area/kandarin/feldip_hills/jiggig/dungeon/jiggig_dungeon.areas.toml b/data/area/kandarin/feldip_hills/jiggig/dungeon/jiggig_dungeon.areas.toml
new file mode 100644
index 0000000000..658f27ce99
--- /dev/null
+++ b/data/area/kandarin/feldip_hills/jiggig/dungeon/jiggig_dungeon.areas.toml
@@ -0,0 +1,5 @@
+[zogre_blackened_area]
+x = [2447, 2448]
+y = [9459, 9467]
+level = 2
+tags = ["dark"]
\ No newline at end of file
diff --git a/data/area/kandarin/feldip_hills/jiggig/dungeon/jiggig_dungeon.combat.toml b/data/area/kandarin/feldip_hills/jiggig/dungeon/jiggig_dungeon.combat.toml
new file mode 100644
index 0000000000..fab80f06c7
--- /dev/null
+++ b/data/area/kandarin/feldip_hills/jiggig/dungeon/jiggig_dungeon.combat.toml
@@ -0,0 +1,31 @@
+[zogre_jiggig_dungeon]
+attack_speed = 6
+defend_anim = "ogre_defend"
+defend_sound = "zogre_hit"
+death_anim = "ogre_death"
+death_sound = "zogre_death"
+
+[zogre_jiggig_dungeon.melee]
+range = 1
+anim = "ogre_attack"
+target_sound = "zogre_attack"
+target_hit = { offense = "crush", max = 50 }
+impact_disease = 10
+
+[zogre_jiggig_dungeon_2]
+clone = "zogre_jiggig_dungeon"
+
+[zogre_jiggig_dungeon_2.melee]
+clone = "zogre_jiggig_dungeon.melee"
+
+[zogre_jiggig_dungeon_3]
+clone = "zogre_jiggig_dungeon"
+
+[zogre_jiggig_dungeon_3.melee]
+clone = "zogre_jiggig_dungeon.melee"
+
+[zogre_jiggig_dungeon_4]
+clone = "zogre_jiggig_dungeon"
+
+[zogre_jiggig_dungeon_4.melee]
+clone = "zogre_jiggig_dungeon.melee"
\ No newline at end of file
diff --git a/data/area/kandarin/feldip_hills/jiggig/jiggig.combat.toml b/data/area/kandarin/feldip_hills/jiggig/jiggig.combat.toml
new file mode 100644
index 0000000000..259d3140dc
--- /dev/null
+++ b/data/area/kandarin/feldip_hills/jiggig/jiggig.combat.toml
@@ -0,0 +1,75 @@
+[zogre_jiggig]
+attack_speed = 6
+defend_anim = "ogre_defend"
+defend_sound = "zogre_hit"
+death_anim = "ogre_death"
+death_sound = "zogre_death"
+
+[zogre_jiggig.melee]
+range = 1
+anim = "ogre_attack"
+target_sound = "zogre_attack"
+target_hit = { offense = "crush", max = 50 }
+impact_disease = 10
+
+[zogre_jiggig_2]
+clone = "zogre_jiggig"
+
+[zogre_jiggig_2.melee]
+clone = "zogre_jiggig.melee"
+
+[zogre_jiggig_3]
+clone = "zogre_jiggig"
+
+[zogre_jiggig_3.melee]
+clone = "zogre_jiggig.melee"
+
+[zogre_jiggig_4]
+clone = "zogre_jiggig"
+
+[zogre_jiggig_4.melee]
+clone = "zogre_jiggig.melee"
+
+[zogre_jiggig_5]
+clone = "zogre_jiggig"
+
+[zogre_jiggig_5.melee]
+clone = "zogre_jiggig.melee"
+
+[zogre_jiggig_6]
+clone = "zogre_jiggig"
+
+[zogre_jiggig_6.melee]
+clone = "zogre_jiggig.melee"
+
+[zogre_jiggig_7]
+clone = "zogre_jiggig"
+
+[zogre_jiggig_7.melee]
+clone = "zogre_jiggig.melee"
+
+[skogre_jiggig]
+attack_speed = 6
+defend_anim = "ogre_defend"
+defend_sound = "skelly_hit"
+death_anim = "ogre_death"
+death_sound = "zogre_death"
+
+[skogre_jiggig.melee]
+range = 1
+anim = "ogre_attack"
+target_sound = "giant_attack"
+target_hit = { offense = "crush", max = 50 }
+impact_disease = 10
+
+[skogre_jiggig_2]
+clone = "skogre_jiggig"
+
+[skogre_jiggig_2.melee]
+clone = "skogre_jiggig.melee"
+
+[skogre_jiggig_3]
+clone = "skogre_jiggig"
+
+[skogre_jiggig_3.melee]
+clone = "skogre_jiggig.melee"
\ No newline at end of file
diff --git a/data/area/kandarin/feldip_hills/jiggig/jiggig.npc-spawns.toml b/data/area/kandarin/feldip_hills/jiggig/jiggig.npc-spawns.toml
index 60cf2469d9..2c799f055f 100644
--- a/data/area/kandarin/feldip_hills/jiggig/jiggig.npc-spawns.toml
+++ b/data/area/kandarin/feldip_hills/jiggig/jiggig.npc-spawns.toml
@@ -3,7 +3,7 @@ spawns = [
{ id = "uglug_nar", x = 2442, y = 3049, members = true },
{ id = "pilg", x = 2443, y = 3046, members = true },
{ id = "grug", x = 2446, y = 3049, members = true },
- { id = "ogre_guard_jiggig", x = 2454, y = 3047, members = true },
+ { id = "zogre_ogre_guard", x = 2454, y = 3047, members = true },
{ id = "ogre_guard_jiggig_2", x = 2443, y = 3038, members = true },
{ id = "ogre_guard_jiggig_2", x = 2452, y = 3030, members = true },
{ id = "zogre_jiggig", x = 2486, y = 3048, members = true },
diff --git a/data/area/kandarin/feldip_hills/jiggig/jiggig.npcs.toml b/data/area/kandarin/feldip_hills/jiggig/jiggig.npcs.toml
index 6cf4186246..4833dd0efd 100644
--- a/data/area/kandarin/feldip_hills/jiggig/jiggig.npcs.toml
+++ b/data/area/kandarin/feldip_hills/jiggig/jiggig.npcs.toml
@@ -1,24 +1,30 @@
[grish]
id = 2038
+dialogue = "ogre"
examine = "An ogre shaman"
[uglug_nar]
id = 2039
+dialogue = "ogre"
examine = "An ogre shaman"
[pilg]
id = 2040
+dialogue = "ogre"
examine = "They're done for!"
[grug]
id = 2041
+dialogue = "ogre"
examine = "They're done for!"
-[ogre_guard_jiggig]
+[zogre_ogre_guard]
id = 2042
+dialogue = "ogre"
[ogre_guard_jiggig_2]
id = 2043
+dialogue = "ogre"
[zogre_jiggig]
id = 2044
@@ -58,6 +64,7 @@ examine = "An undead skeletal ogre."
[skogre_jiggig_2]
id = 2056
+retaliates = false
clone = "skogre_jiggig"
[skogre_jiggig_3]
@@ -66,20 +73,24 @@ clone = "skogre_jiggig"
[zogre_jiggig_3]
id = 2051
+clone = "zogre_jiggig"
[zogre_jiggig_4]
id = 2052
+clone = "zogre_jiggig"
[zogre_jiggig_5]
id = 2053
+clone = "zogre_jiggig"
[zogre_jiggig_6]
id = 2054
+clone = "zogre_jiggig"
[zogre_jiggig_7]
id = 2055
-categories = ["ogres"]
-examine = "A partially decomposing zombie ogre."
+retaliates = false
+clone = "zogre_jiggig"
[ogre_cook_jiggig]
id = 791
diff --git a/data/area/kandarin/yanille/yanille.objs.toml b/data/area/kandarin/yanille/yanille.objs.toml
index 4beec11fcc..81ac7bf730 100644
--- a/data/area/kandarin/yanille/yanille.objs.toml
+++ b/data/area/kandarin/yanille/yanille.objs.toml
@@ -2,6 +2,10 @@
id = 9302
examine = "A rather strategically placed hole, which appears to be in the ground."
+[zogre_outdoor_bell]
+id = 6847
+examine = "A bell to attract the attention of the secretary."
+
[yanille_underwall_tunnel_castle_wall]
id = 9301
examine = "A well constructed castle wall."
diff --git a/data/area/morytania/braindeath_island/braindeath_island.combat.toml b/data/area/morytania/braindeath_island/braindeath_island.combat.toml
index 0d4afb1a3b..000df10208 100644
--- a/data/area/morytania/braindeath_island/braindeath_island.combat.toml
+++ b/data/area/morytania/braindeath_island/braindeath_island.combat.toml
@@ -19,7 +19,7 @@ range = 1
anim = "spider_large_attack"
target_sound = "insect_attack"
impact_regardless = true
-impact_disease = 80
+impact_disease = 8
[zombie_pirate]
attack_speed = 4
diff --git a/data/entity/npc/humanoid/jogre/jogre.combat.toml b/data/entity/npc/humanoid/jogre/jogre.combat.toml
index d8614a0b55..fde38f19b7 100644
--- a/data/entity/npc/humanoid/jogre/jogre.combat.toml
+++ b/data/entity/npc/humanoid/jogre/jogre.combat.toml
@@ -10,5 +10,5 @@ death_sound = "giant_death"
chance = 75
range = 1
anim = "ogre_attack"
-target_sound = "giant_attack"
+target_sound = "moss_giant_attack"
target_hit = { offense = "crush", max = 80 }
diff --git a/data/entity/player/human.anims.toml b/data/entity/player/human.anims.toml
index d77e236f9e..abf3bbcf2b 100644
--- a/data/entity/player/human.anims.toml
+++ b/data/entity/player/human.anims.toml
@@ -22,6 +22,9 @@ id = 422
[unarmed_kick]
id = 423
+[human_dancing]
+id = 818
+
[unarmed_block]
id = 422
diff --git a/data/quest/members/big_chompy_bird_hunting/big_chompy_bird_hunting.anims.toml b/data/quest/members/big_chompy_bird_hunting/big_chompy_bird_hunting.anims.toml
index e8911fba42..310ce63418 100644
--- a/data/quest/members/big_chompy_bird_hunting/big_chompy_bird_hunting.anims.toml
+++ b/data/quest/members/big_chompy_bird_hunting/big_chompy_bird_hunting.anims.toml
@@ -5,4 +5,41 @@ id = 6800
id = 6802
[chompy_bird_death]
-id = 6823
\ No newline at end of file
+id = 6823
+
+# Cache-canonical names from D:/Names. Some collide on id with existing aliases; that's allowed.
+[chompy_update_attack]
+id = 6761
+
+[chompy_update_death]
+id = 6762
+
+[chompy_update_fly_down]
+id = 6766
+
+[jubbly_update_attack]
+id = 6800
+
+[jubbly_update_death]
+id = 6801
+
+[jubbly_update_fly_down]
+id = 6805
+
+[human_cooking]
+id = 896
+
+[chompy_toad_inflate]
+id = 1019
+
+[human_chompybird_ogrebellows]
+id = 1026
+
+[human_ogre_fletching]
+id = 4433
+
+[human_castcurse_walkmerge]
+id = 11428
+
+[human_openchest]
+id = 536
\ No newline at end of file
diff --git a/data/quest/members/big_chompy_bird_hunting/big_chompy_bird_hunting.gfx.toml b/data/quest/members/big_chompy_bird_hunting/big_chompy_bird_hunting.gfx.toml
index 380ecd427d..4657cbcfbb 100644
--- a/data/quest/members/big_chompy_bird_hunting/big_chompy_bird_hunting.gfx.toml
+++ b/data/quest/members/big_chompy_bird_hunting/big_chompy_bird_hunting.gfx.toml
@@ -1,2 +1,8 @@
[ogre_bellows]
id = 241
+
+[chompy_toad_exploding]
+id = 240
+
+[ogre_arrow_travel]
+id = 242
\ No newline at end of file
diff --git a/data/quest/members/big_chompy_bird_hunting/big_chompy_bird_hunting.npcs.toml b/data/quest/members/big_chompy_bird_hunting/big_chompy_bird_hunting.npcs.toml
index 5918753b27..aa5e387717 100644
--- a/data/quest/members/big_chompy_bird_hunting/big_chompy_bird_hunting.npcs.toml
+++ b/data/quest/members/big_chompy_bird_hunting/big_chompy_bird_hunting.npcs.toml
@@ -8,5 +8,29 @@ combat_def = "chompy_bird"
slayer_xp = 10.0
categories = ["birds"]
immune_poison = true
-drop_table = "chompy_bird"
+# No drop_table — Java intercepts death and transforms into a pluckable carcass instead.
examine = "A large boisterous bird, a delicacy for ogres."
+
+[jubbly_bird_chompy]
+id = 3476
+hitpoints = 100
+att = 5
+str = 5
+def = 3
+combat_def = "chompy_bird"
+slayer_xp = 10.0
+categories = ["birds"]
+immune_poison = true
+examine = "A large boisterous bird, a delicacy for ogres."
+
+[plucked_chompy]
+id = 1016
+examine = "A large boisterous bird, a delicacy for ogres."
+
+[plucked_jubbly]
+id = 3477
+examine = "A large boisterous bird, a delicacy for ogres."
+
+[bloated_toad_placed]
+id = 1014
+examine = "A bloated toad."
diff --git a/data/quest/members/big_chompy_bird_hunting/big_chompy_bird_hunting.sounds.toml b/data/quest/members/big_chompy_bird_hunting/big_chompy_bird_hunting.sounds.toml
index c660b71454..f5ab4a1dcd 100644
--- a/data/quest/members/big_chompy_bird_hunting/big_chompy_bird_hunting.sounds.toml
+++ b/data/quest/members/big_chompy_bird_hunting/big_chompy_bird_hunting.sounds.toml
@@ -12,3 +12,18 @@ id = 1451
[chompy_bird_squak]
id = 1453
+
+[ogre_bow]
+id = 1452
+
+[ogre_bellows_suck]
+id = 1454
+
+[ogre_bellows]
+id = 1455
+
+[spit_roast]
+id = 1456
+
+[toad_croak]
+id = 1458
\ No newline at end of file
diff --git a/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.anims.toml b/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.anims.toml
new file mode 100644
index 0000000000..6c355aa9e7
--- /dev/null
+++ b/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.anims.toml
@@ -0,0 +1,59 @@
+[ogre_kick]
+id = 2102
+
+[slash_bash_melee]
+id = 11458
+
+[ogre_breath]
+id = 2101
+
+[human_reachforladder]
+id = 828
+
+[human_mapping]
+id = 909
+
+[regicide_stepover]
+id = 1236
+
+[zombie_update_defend_normal]
+id = 5567
+
+[zombie_update_attack_normal]
+id = 5568
+
+[zombie_update_death_normal]
+id = 5569
+
+[zogre_bell_ring]
+id = 2103
+
+[expression_ogre_neutral]
+id = 8579
+
+[expression_ogre_quiz]
+id = 8580
+
+[expression_ogre_angry]
+id = 8583
+
+[expression_ogre_mad]
+id = 8657
+
+[expression_ogre_confused]
+id = 8584
+
+[expression_ogre_happy]
+id = 8585
+
+[expression_ogre_scared]
+id = 8598
+
+[expression_ogre_sad]
+id = 8659
+
+[expression_ogre_shifty]
+id = 8661
+
+[expression_ogre_shock]
+id = 8662
diff --git a/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.combat.toml b/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.combat.toml
new file mode 100644
index 0000000000..a947dca2b8
--- /dev/null
+++ b/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.combat.toml
@@ -0,0 +1,27 @@
+[slash_bash]
+attack_speed = 6
+retreat_range = 12
+defend_anim = "ogre_defend"
+defend_sound = "zogre_hit"
+death_anim = "ogre_death"
+death_sound = "zogre_death"
+
+[slash_bash.melee]
+chance = 1
+range = 1
+anim = "slash_bash_melee"
+target_sound = "zogre_attack"
+target_hit = { offense = "crush", max = 130 }
+impact_disease = 15
+approach = false
+
+[slash_bash.range]
+chance = 1
+range = 10
+anim = "ogre_breath"
+target_sound = "ogre_bow"
+projectile = "slash_bash_projectile"
+projectile_origin_x = 1
+projectile_origin_y = 1
+target_hit = { offense = "range", max = 130 }
+impact_disease = 15
\ No newline at end of file
diff --git a/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.drops.toml b/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.drops.toml
index fe84e93d68..d423cd06da 100644
--- a/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.drops.toml
+++ b/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.drops.toml
@@ -12,3 +12,15 @@ roll = 5000
drops = [
{ id = "zombie_champions_scroll", members = true, lacks = "zombie_champions_scroll" },
]
+
+[zogre_human_brentle_vahn_drop_table]
+type = "all"
+drops = [
+ { id = "ruined_backpack" },
+]
+
+[zogre_human_brentle_vahn_tertiary]
+roll = 5000
+drops = [
+ { id = "zombie_champions_scroll", members = true, lacks = "zombie_champions_scroll" },
+]
\ No newline at end of file
diff --git a/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.gfx.toml b/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.gfx.toml
new file mode 100644
index 0000000000..a4f18d72a2
--- /dev/null
+++ b/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.gfx.toml
@@ -0,0 +1,12 @@
+[smokepuff_large]
+id = 188
+
+[slash_bash_projectile]
+id = 397
+height = 40
+end_height = 36
+delay = 41
+curve = 15
+size_offset = 11
+time_offset = 15
+multiplier = 5
\ No newline at end of file
diff --git a/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.items.toml b/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.items.toml
index d6f291c689..399dc5338b 100644
--- a/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.items.toml
+++ b/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.items.toml
@@ -61,7 +61,7 @@ weight = 0.005
examine = "A half torn necromantic page."
kept = "Wilderness"
-[ruined_backpack_zogre_flesh_eaters]
+[ruined_backpack]
id = 4810
tradeable = false
weight = 0.02
@@ -86,21 +86,21 @@ examine = "A pile of Zombie Ogre bones."
[zogre_bones_noted]
id = 4813
-[sithik_portrait]
+[zogre_sithik_portrait_good]
id = 4814
tradeable = false
weight = 0.02
examine = "A classic realist charcoal portrait of Sithik."
kept = "Wilderness"
-[sithik_portrait_bad]
+[zogre_sithik_portrait_bad]
id = 4815
tradeable = false
weight = 0.02
examine = "A semi-nihilistic, pseudo-impressionistic, half-squarist charcoal sketch of Sithik."
kept = "Wilderness"
-[signed_portrait]
+[zogre_sithik_portrait_signed]
id = 4816
tradeable = false
weight = 0.02
@@ -222,7 +222,7 @@ examine = "Ancient ogre bones from the ogre burial tomb."
[ourg_bones_noted]
id = 4835
-[strange_potion]
+[zogre_ogre_trans_potion]
id = 4836
tradeable = false
weight = 0.001
diff --git a/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.npcs.toml b/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.npcs.toml
index b5262456f7..4cf37a2483 100644
--- a/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.npcs.toml
+++ b/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.npcs.toml
@@ -14,3 +14,25 @@ slayer_xp = 100.0
categories = ["zombies"]
drop_table = "slash_bash"
examine = "A powerful looking Zogre."
+
+[zogre_sithik_man]
+id = 2061
+
+[zogre_sithik_ogre]
+id = 2062
+dialogue = "ogre"
+
+[zogre_human_brentle_vahn]
+id = 1826
+hitpoints = 500
+att = 30
+str = 30
+def = 30
+attack_speed = 6
+style = "crush"
+max_hit_melee = 40
+hunt_mode = "cowardly"
+slayer_xp = 50.0
+categories = ["zombies"]
+drop_table = "zogre_human_brentle_vahn"
+examine = "A human zombie."
\ No newline at end of file
diff --git a/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.objs.toml b/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.objs.toml
new file mode 100644
index 0000000000..f4ce32011e
--- /dev/null
+++ b/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.objs.toml
@@ -0,0 +1,62 @@
+[ogre_stairs_down]
+id = 6841
+
+[ogre_stairs]
+id = 6842
+
+[zogre_lecturn]
+id = 6846
+
+[zogre_coffin_base]
+id = 6843
+
+[zogre_coffin_special]
+id = 6844
+
+[zogre_coffin_special_searched]
+id = 6845
+
+[zogre_brentle_skeleton]
+id = 6893
+
+[zogre_stand]
+id = 6897
+
+[ogre_cavedoorr_closed]
+id = 6871
+
+[ogre_cavedoorl_closed]
+id = 6872
+
+[ogre_cavedoorr_opened]
+id = 6873
+
+[ogre_cavedoorl_opened]
+id = 6874
+
+[zogre_sithik_bed]
+id = 6889
+
+[zogre_sithik_bed_entity]
+id = 6887
+
+[ogre_bedman_loc]
+id = 6888
+
+[sithiks_drawers]
+id = 6875
+
+[sithiks_cupboard]
+id = 6876
+
+[sithiks_wardrobe]
+id = 55412
+
+[ogre_barricade_collapsed]
+id = 6879
+
+[ogre_barricade_collapsedr]
+id = 6881
+
+[ogre_barricade_collapsedl]
+id = 6882
\ No newline at end of file
diff --git a/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.sounds.toml b/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.sounds.toml
new file mode 100644
index 0000000000..9ece1937aa
--- /dev/null
+++ b/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.sounds.toml
@@ -0,0 +1,47 @@
+[ogre_destroy_barricade]
+id = 1954
+
+[drip_poison]
+id = 1955
+
+[down_stone_stairs]
+id = 1952
+
+[up_stone_stairs]
+id = 1956
+
+[disease_hitsplat]
+id = 2388
+
+[strangedoor_open]
+id = 2410
+
+[smokepuff]
+id = 1930
+
+[zogre_writing]
+id = 1958
+
+[bonewalk]
+id = 1953
+
+[zogre_death]
+id = 916
+
+[zogre_hit]
+id = 917
+
+[zogre_attack]
+id = 914
+
+[zogre_shield_hit]
+id = 915
+
+[zogre_axe_attack]
+id = 913
+
+[skelly_hit]
+id = 779
+
+[zogre_bell]
+id = 1959
\ No newline at end of file
diff --git a/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.varbits.toml b/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.varbits.toml
index b01d9fa8a1..622e7d0352 100644
--- a/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.varbits.toml
+++ b/data/quest/members/zogre_flesh_eaters/zogre_flesh_eaters.varbits.toml
@@ -3,4 +3,94 @@ id = 487
persist = true
format = "map"
default = "unstarted"
-values = { unstarted = 0, started = 1 }
+values = {
+ unstarted = 0,
+ investigate = 2,
+ barricade = 3,
+ sithik = 4,
+ potion = 6,
+ permanent_spell = 8,
+ given_key = 10,
+ killed_slash_bash = 12,
+ completed = 14,
+}
+
+[thzfe_prismsearch]
+id = 488
+persist = true
+format = "int"
+
+[thzfe_innkeepermugshown]
+id = 489
+persist = true
+format = "boolean"
+
+[thzfe_innkeeperportraitshown]
+id = 490
+persist = true
+format = "boolean"
+
+[thzfe_shownnecrobook]
+id = 491
+persist = true
+format = "boolean"
+
+[thzfe_shownhambook]
+id = 492
+persist = true
+format = "boolean"
+
+[thzfe_showntankard]
+id = 493
+persist = true
+format = "boolean"
+
+[thzfe_shownsignedportrait]
+id = 494
+persist = true
+format = "boolean"
+
+[thzfe_sithik_transformed]
+id = 495
+persist = true
+format = "int"
+
+[thzfe_blocking_barricade]
+id = 496
+persist = true
+format = "boolean"
+
+[thzfe_makecuredisease]
+id = 498
+persist = true
+format = "boolean"
+
+[thzfe_makebrutalarrow]
+id = 499
+persist = true
+format = "boolean"
+
+[thzfe_makecompozogrebow]
+id = 500
+persist = true
+format = "boolean"
+
+[thzfe_sold_balm]
+id = 502
+persist = true
+format = "boolean"
+
+[thzfe_brentle_skele]
+id = 503
+persist = true
+format = "int"
+
+[thzfe_cut_scene]
+id = 505
+persist = true
+format = "boolean"
+
+[thzfe_grish_warning_yes]
+id = 507
+persist = true
+format = "boolean"
\ No newline at end of file
diff --git a/data/quest/members/zogre_flesh_eaters/zogre_human_brentle_vahn.combat.toml b/data/quest/members/zogre_flesh_eaters/zogre_human_brentle_vahn.combat.toml
new file mode 100644
index 0000000000..986151d480
--- /dev/null
+++ b/data/quest/members/zogre_flesh_eaters/zogre_human_brentle_vahn.combat.toml
@@ -0,0 +1,13 @@
+[zogre_human_brentle_vahn]
+attack_speed = 6
+retreat_range = 16
+defend_anim = "zombie_update_defend_normal"
+defend_sound = "zombie_defend"
+death_anim = "zombie_update_death_normal"
+death_sound = "zombie_death"
+
+[zogre_human_brentle_vahn.melee]
+range = 1
+anim = "zombie_update_attack_normal"
+target_sound = "zombie_attack"
+target_hit = { offense = "crush", max = 40 }
\ No newline at end of file
diff --git a/data/quest/quests.toml b/data/quest/quests.toml
index 6a012d9bf8..e67a46e28f 100644
--- a/data/quest/quests.toml
+++ b/data/quest/quests.toml
@@ -3859,3 +3859,22 @@ req_combat = "You will need to defeat a level 58 enemy and level 36 enemy."
points = 1
reward = "3 ourg bones
2 zogre bones
Access to Jiggig
Ability to equip inoculation brace
Ability to fletch composite ogre bow and brutal arrows
Access to post-quest rewards from Zavistic Rarve or Yanni Salika, and Uglug Nar"
xp = "2,000 Fletching XP
2,000 Ranged XP
2,000 Herblore XP"
+variables = [
+ "zogre_flesh_eaters",
+ "thzfe_prismsearch",
+ "thzfe_innkeepermugshown",
+ "thzfe_innkeeperportraitshown",
+ "thzfe_shownnecrobook",
+ "thzfe_shownhambook",
+ "thzfe_showntankard",
+ "thzfe_shownsignedportrait",
+ "thzfe_sithik_transformed",
+ "thzfe_blocking_barricade",
+ "thzfe_makecuredisease",
+ "thzfe_makebrutalarrow",
+ "thzfe_makecompozogrebow",
+ "thzfe_sold_balm",
+ "thzfe_brentle_skele",
+ "thzfe_cut_scene",
+ "thzfe_grish_warning_yes",
+]
\ No newline at end of file
diff --git a/data/skill/fletching/arrowtip.recipes.toml b/data/skill/fletching/arrowtip.recipes.toml
index 258b061241..4dc688c413 100644
--- a/data/skill/fletching/arrowtip.recipes.toml
+++ b/data/skill/fletching/arrowtip.recipes.toml
@@ -92,6 +92,8 @@ skill = "fletching"
level = 7
xp = 8.4
ticks = 2
+requires = ["hammer"]
+requires_message = "You need a hammer to force these nails into the arrow shafts."
remove = [{ id = "flighted_ogre_arrow", amount = 6 }, { id = "bronze_nails", amount = 6 }]
add = [{ id = "bronze_brutal", amount = 6 }]
message = "You attach the nails to 6 arrow shafts."
@@ -103,6 +105,8 @@ skill = "fletching"
level = 18
xp = 15.6
ticks = 2
+requires = ["hammer"]
+requires_message = "You need a hammer to force these nails into the arrow shafts."
remove = [{ id = "flighted_ogre_arrow", amount = 6 }, { id = "iron_nails", amount = 6 }]
add = [{ id = "iron_brutal", amount = 6 }]
message = "You attach the nails to 6 arrow shafts."
@@ -114,6 +118,8 @@ skill = "fletching"
level = 33
xp = 30.6
ticks = 2
+requires = ["hammer"]
+requires_message = "You need a hammer to force these nails into the arrow shafts."
remove = [{ id = "flighted_ogre_arrow", amount = 6 }, { id = "steel_nails", amount = 6 }]
add = [{ id = "steel_brutal", amount = 6 }]
message = "You attach the nails to 6 arrow shafts."
@@ -125,6 +131,8 @@ skill = "fletching"
level = 38
xp = 39.0
ticks = 2
+requires = ["hammer"]
+requires_message = "You need a hammer to force these nails into the arrow shafts."
remove = [{ id = "flighted_ogre_arrow", amount = 6 }, { id = "black_nails", amount = 6 }]
add = [{ id = "black_brutal", amount = 6 }]
message = "You attach the nails to 6 arrow shafts."
@@ -136,6 +144,8 @@ skill = "fletching"
level = 49
xp = 45.0
ticks = 2
+requires = ["hammer"]
+requires_message = "You need a hammer to force these nails into the arrow shafts."
remove = [{ id = "flighted_ogre_arrow", amount = 6 }, { id = "mithril_nails", amount = 6 }]
add = [{ id = "mithril_brutal", amount = 6 }]
message = "You attach the nails to 6 arrow shafts."
@@ -147,6 +157,8 @@ skill = "fletching"
level = 62
xp = 61.2
ticks = 2
+requires = ["hammer"]
+requires_message = "You need a hammer to force these nails into the arrow shafts."
remove = [{ id = "flighted_ogre_arrow", amount = 6 }, { id = "adamant_nails", amount = 6 }]
add = [{ id = "adamant_brutal", amount = 6 }]
message = "You attach the nails to 6 arrow shafts."
@@ -158,6 +170,8 @@ skill = "fletching"
level = 77
xp = 75.0
ticks = 2
+requires = ["hammer"]
+requires_message = "You need a hammer to force these nails into the arrow shafts."
remove = [{ id = "flighted_ogre_arrow", amount = 6 }, { id = "rune_nails", amount = 6 }]
add = [{ id = "rune_brutal", amount = 6 }]
message = "You attach the nails to 6 arrow shafts."
@@ -183,6 +197,6 @@ ticks = 3
requires = ["knife"]
remove = ["achey_tree_logs", "wolf_bones"]
add = ["unstrung_comp_bow"]
-message = "You carefully cut the wood and add the bones."
+message = "You carefully cut the wood into a composite ogre bow."
question = "What would you like to fletch?"
animation = "fletching_log"
\ No newline at end of file
diff --git a/data/skill/magic/spells.tables.toml b/data/skill/magic/spells.tables.toml
index 9e3174efc7..d394a5199e 100644
--- a/data/skill/magic/spells.tables.toml
+++ b/data/skill/magic/spells.tables.toml
@@ -15,7 +15,6 @@ poison_damage = "int"
projectiles = "list"
drain_skill = "skill"
drain_percent = "int"
-npc_message = "string"
player_message = "string"
clone = "clone"
@@ -516,7 +515,6 @@ xp = 600
[.monster_examine]
xp = 660
-npc_message = ""
[.npc_contact]
xp = 630
diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/data/config/ItemOnItemDefinition.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/data/config/ItemOnItemDefinition.kt
index fbf0d9d6cc..691529a56d 100644
--- a/engine/src/main/kotlin/world/gregs/voidps/engine/data/config/ItemOnItemDefinition.kt
+++ b/engine/src/main/kotlin/world/gregs/voidps/engine/data/config/ItemOnItemDefinition.kt
@@ -44,6 +44,7 @@ data class ItemOnItemDefinition(
val sound: String = "",
val message: String = "",
val failure: String = "",
+ val requiresMessage: String = "",
val question: String = "How many would you like to $type?",
val maximum: Int = -1,
val members: Boolean = false,
diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/ItemOnItemDefinitions.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/ItemOnItemDefinitions.kt
index 3183032256..3c81d40d41 100644
--- a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/ItemOnItemDefinitions.kt
+++ b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/ItemOnItemDefinitions.kt
@@ -48,6 +48,7 @@ class ItemOnItemDefinitions {
var sound = ""
var message = ""
var failure = ""
+ var requiresMessage = ""
var question: String? = null
var maximum: Int = -1
var members: Boolean = false
@@ -72,6 +73,7 @@ class ItemOnItemDefinitions {
"sound" -> sound = string()
"message" -> message = string()
"failure" -> failure = string()
+ "requires_message" -> requiresMessage = string()
"question" -> question = string()
"maximum" -> maximum = int()
"members" -> members = boolean()
@@ -96,6 +98,7 @@ class ItemOnItemDefinitions {
sound = sound,
message = message,
failure = failure,
+ requiresMessage = requiresMessage,
question = question ?: "How many would you like to $type?",
maximum = maximum,
members = members,
diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/npc/NPCs.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/npc/NPCs.kt
index a66db9e592..9a3449e010 100644
--- a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/npc/NPCs.kt
+++ b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/npc/NPCs.kt
@@ -79,7 +79,9 @@ object NPCs : Runnable,
fun add(id: String, tile: Tile, direction: Direction = Direction.SOUTH, ticks: Int, owner: Player? = null): NPC {
val npc = add(id, tile, direction)
- npc.despawn(ticks)
+ if (ticks > 0) {
+ npc.despawn(ticks)
+ }
if (owner != null) {
npc["owner"] = owner.accountName
}
@@ -94,11 +96,11 @@ object NPCs : Runnable,
* NPC's full size. Returns `null` if the NPC id is unknown or no valid tile can be found
* (the underlying [Area.random] retries up to 100 times before giving up).
*/
- fun addRandom(id: String, area: Area, direction: Direction = Direction.SOUTH): NPC? {
+ fun addRandom(id: String, area: Area, direction: Direction = Direction.SOUTH, ticks: Int = -1, owner: Player? = null): NPC? {
val def = NPCDefinitions.getOrNull(id) ?: return null
val collision = CollisionStrategyProvider.get(def)
val tile = area.random(collision, def.size) ?: return null
- return add(id, tile, direction)
+ return add(id, tile, direction, ticks, owner)
}
fun remove(npc: NPC?): Boolean {
diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/player/Player.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/player/Player.kt
index b74823bf27..da8d448c1c 100644
--- a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/player/Player.kt
+++ b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/player/Player.kt
@@ -124,3 +124,7 @@ class Player(
private val logger = InlineLogger("Player")
}
}
+
+var Player.wearingGhostspeak: Boolean
+ get() = get("wearing_ghost_speak_amulet", false)
+ set(value) = set("wearing_ghost_speak_amulet", value)
diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/suspend/Suspend.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/suspend/Suspend.kt
index 76cc081b9a..9ebf97a172 100644
--- a/engine/src/main/kotlin/world/gregs/voidps/engine/suspend/Suspend.kt
+++ b/engine/src/main/kotlin/world/gregs/voidps/engine/suspend/Suspend.kt
@@ -7,11 +7,16 @@ import world.gregs.voidps.engine.entity.character.player.Player
fun Character.resumeSuspension(): Boolean {
val suspend = suspension ?: return false
- if (suspend is Suspension.Delay && suspend.ready()) {
- suspend.resume()
- }
- if (suspend is Suspension.Custom && suspend.ready()) {
- suspend.resume()
+ try {
+ if (suspend is Suspension.Delay && suspend.ready()) {
+ suspend.resume()
+ }
+ if (suspend is Suspension.Custom && suspend.ready()) {
+ suspend.resume()
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ suspension = null
}
return true
}
diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/suspend/Suspension.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/suspend/Suspension.kt
index e1fc0dace4..0274077646 100644
--- a/engine/src/main/kotlin/world/gregs/voidps/engine/suspend/Suspension.kt
+++ b/engine/src/main/kotlin/world/gregs/voidps/engine/suspend/Suspension.kt
@@ -1,9 +1,7 @@
package world.gregs.voidps.engine.suspend
import kotlinx.coroutines.CancellableContinuation
-import kotlinx.coroutines.suspendCancellableCoroutine
import world.gregs.voidps.engine.GameLoop
-import world.gregs.voidps.engine.entity.character.player.Player
import kotlin.coroutines.resume
sealed class Suspension {
@@ -13,21 +11,36 @@ sealed class Suspension {
* p_countdialog
*/
class IntEntry(private val continuation: CancellableContinuation) : Suspension() {
- fun resume(int: Int) = continuation.resume(int)
+ fun resume(int: Int) {
+ if (continuation.isCancelled) {
+ return
+ }
+ continuation.resume(int)
+ }
}
/**
* Wait for string entry dialogue
*/
class StringEntry(private val continuation: CancellableContinuation) : Suspension() {
- fun resume(string: String) = continuation.resume(string)
+ fun resume(string: String) {
+ if (continuation.isCancelled) {
+ return
+ }
+ continuation.resume(string)
+ }
}
/**
* Wait for name entry dialogue
*/
class NameEntry(private val continuation: CancellableContinuation) : Suspension() {
- fun resume(string: String) = continuation.resume(string)
+ fun resume(string: String) {
+ if (continuation.isCancelled) {
+ return
+ }
+ continuation.resume(string)
+ }
}
/**
@@ -35,7 +48,12 @@ sealed class Suspension {
* p_pausebutton
*/
class Continue(private val continuation: CancellableContinuation) : Suspension() {
- fun resume() = continuation.resume(Unit)
+ fun resume() {
+ if (continuation.isCancelled) {
+ return
+ }
+ continuation.resume(Unit)
+ }
}
/**
@@ -47,13 +65,23 @@ sealed class Suspension {
fun ready(): Boolean = GameLoop.tick >= tick
- fun resume() = continuation.resume(Unit)
+ fun resume() {
+ if (continuation.isCancelled) {
+ return
+ }
+ continuation.resume(Unit)
+ }
}
class Custom(private val continuation: CancellableContinuation, val block: () -> Boolean) : Suspension() {
fun ready(): Boolean = block.invoke()
- fun resume() = continuation.resume(Unit)
+ fun resume() {
+ if (continuation.isCancelled) {
+ return
+ }
+ continuation.resume(Unit)
+ }
}
}
diff --git a/game/src/main/kotlin/content/area/kandarin/feldip_hills/Grish.kt b/game/src/main/kotlin/content/area/kandarin/feldip_hills/Grish.kt
new file mode 100644
index 0000000000..4234dd7208
--- /dev/null
+++ b/game/src/main/kotlin/content/area/kandarin/feldip_hills/Grish.kt
@@ -0,0 +1,485 @@
+package content.area.kandarin.feldip_hills
+
+import content.entity.player.dialogue.Angry
+import content.entity.player.dialogue.Happy
+import content.entity.player.dialogue.Mad
+import content.entity.player.dialogue.Neutral
+import content.entity.player.dialogue.Scared
+import content.entity.player.dialogue.type.ChoiceOption
+import content.entity.player.dialogue.type.choice
+import content.entity.player.dialogue.type.item
+import content.entity.player.dialogue.type.items
+import content.entity.player.dialogue.type.npc
+import content.entity.player.dialogue.type.player
+import content.entity.player.dialogue.type.statement
+import content.entity.player.inv.item.addOrDrop
+import content.quest.quest
+import content.quest.questComplete
+import content.quest.questCompleted
+import content.quest.refreshQuestJournal
+import world.gregs.voidps.engine.Script
+import world.gregs.voidps.engine.client.message
+import world.gregs.voidps.engine.entity.character.jingle
+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.skill.Skill
+import world.gregs.voidps.engine.entity.character.player.skill.exp.exp
+import world.gregs.voidps.engine.entity.character.player.skill.level.Level.hasMax
+import world.gregs.voidps.engine.event.AuditLog
+import world.gregs.voidps.engine.inv.inventory
+import world.gregs.voidps.engine.inv.remove
+
+class Grish : Script {
+
+ init {
+ npcOperate("Talk-to", "grish") { (target) ->
+ when (val progress = quest("zogre_flesh_eaters")) {
+ "unstarted" -> {
+ if (get("thzfe_grish_warning_yes", false)) {
+ confirmQuestStart()
+ } else {
+ intro(target)
+ }
+ }
+ "given_key" -> {
+ if (inventory.contains("ogre_gate_key")) {
+ artefactReminderMenu()
+ } else {
+ lostKey()
+ }
+ }
+ "killed_slash_bash" -> {
+ if (inventory.contains("ogre_gate_key")) {
+ questFinishHandover()
+ } else {
+ lostKey()
+ }
+ }
+ "completed" -> postQuest()
+ else -> {
+ npc("Yous creature dun da fing yet? Da zogries going in da dirt full home?")
+ if (progress == "permanent_spell") {
+ foundMenu()
+ } else {
+ player("Nope, I haven't figured out why the zogres are here yet.")
+ }
+ }
+ }
+ }
+
+ itemOnNPCOperate("black_prism", "grish") { (target) ->
+ item(item = "black_prism", text = "You show the black prism to Grish.")
+ player("Hey Grish, I found this in the tomb, do you know what it is?")
+ npc("Whas you's shuvvin wizzy stuff in Grish face...is a pretty one but dat's more stuff for da wizzy's dan Grish.")
+ }
+
+ itemOnNPCOperate("torn_page", "grish") { (target) ->
+ item(item = "torn_page", text = "You show the necromantic page to Grish.")
+ player("This torn page was on a lectern in the tomb, do you know why?")
+ npc("Dat's der wizzy stuff, not Ogery stuffsies like what Grish got. Das not even big enough for empty da big blower on! No use for Grish dat creatures...you's keeps it.")
+ }
+
+ itemOnNPCOperate("dragon_inn_tankard", "grish") { (target) ->
+ item(item = "dragon_inn_tankard", text = "You show the tankard to Grish.")
+ player("I found this tankard in the tomb, have you got any suggestions?")
+ npc("Das a good drinker for da drinkies dat un is...is a small-un for Grish so yous creature keeps it yes. Yous creature keeps da fimble drinkers for da smaller drinkies.")
+ }
+ }
+
+ // ===== Progress 0: Initial intro =====
+
+ private suspend fun Player.intro(target: NPC) {
+ player("Hello there, what's going on here?")
+ npc("Hey yous creature...wha's you's doing here? Yous be cleverer to be running so da sickies from da zogres don't dead ya.")
+ introMenu(target)
+ }
+
+ suspend fun Player.introMenu(target: NPC) {
+ choice {
+ justLookingAround(target)
+ whatDoYouMeanSickies(target)
+ whatAreZogres(target)
+ sorryHaveToGo()
+ }
+ }
+
+ suspend fun Player.introMenuExpanded(target: NPC) {
+ choice {
+ justLookingAround(target)
+ whatDoYouMeanSickies(target)
+ whatAreZogres(target)
+ canIHelp()
+ sorryHaveToGo()
+ }
+ }
+
+ fun ChoiceOption.justLookingAround(target: NPC): Unit = option("I'm just looking around thanks.") {
+ npc("Yous creature won'ts see muchly in dis place...just da zogries coming wiv da sickies.")
+ introMenuExpanded(target)
+ }
+
+ fun ChoiceOption.whatDoYouMeanSickies(target: NPC): Unit = option("What do you mean sickies?") {
+ npc("Da zogries comin wiv da sickies...yous get bashed by da zogries and get da sickies...den you gonna be like da zogries.")
+ player("Sorry, I just don't understand...")
+ target.anim("ogre_fake_death")
+ npc("Da sickies is when yous creature goes like orange till green and then goes 'Urggghhhh!' ~ Grish imitates falling down with only the white of his eyes visible. ~")
+ introMenuExpanded(target)
+ }
+
+ fun ChoiceOption.whatAreZogres(target: NPC): Unit = option("What are Zogres?") {
+ npc("Da Zogres are da bigun nasties wiv da sickies, deys old pals of Grish but deys jig in Jiggig when dey's full home is deep in da dirt, dey's is not da same dead'uns like was before.")
+ npc("Dem zogries commin from da under dirt and us is lost for da Jiggie jig place.")
+ introMenuExpanded(target)
+ }
+
+ fun ChoiceOption.sorryHaveToGo(): Unit = option("Sorry, I have to go.")
+
+ fun ChoiceOption.canIHelp(): Unit = option("Can I help in any way?") {
+ npc("Yes creatures...yous does good fings for Grish and learn why Zogries at Jiggig and den get da Zogries back in da ground.")
+ player("Oh, so you want me to find out why the Zogres have appeared and then find a way of burying them?")
+ npc("Is what Grish says! But dis is da biggy danger fing yous creatures...yous be geddin' sickies most surely...yous needs be ready..wiv da foodies un da glug-glugs.")
+ player("Right, so you think there's a good chance that I can get ill from this, so I need to get some food and something to drink?")
+ npc("Yea creatures, yous just say what Grish says...not know own wordies creature?")
+ startOrDeclineMenu()
+ }
+
+ suspend fun Player.startOrDeclineMenu() {
+ choice {
+ tooDangerousOption()
+ okayCheckThings()
+ }
+ }
+
+ fun ChoiceOption.tooDangerousOption(): Unit = option("Hmm, sorry, it sounds a bit too dangerous.") {
+ npc("Yous creature is not a stoopid one...stays out of dere, like clever Grish. Yous can paint circles on chest and be da Shaman too!")
+ player("Hmm, is it too late to reconsider?")
+ }
+
+ fun ChoiceOption.okayCheckThings(): Unit = option("Ok, I'll check things out then and report back.") {
+ confirmQuestStart()
+ }
+
+ private suspend fun Player.confirmQuestStart() {
+ npc("Is yous creatures really, really sure yous wanna do dis creatures..we's got no glug-glugs for da sickies? We's knows nuffin for da going of da sickies?")
+ set("thzfe_grish_warning_yes", true)
+ choice {
+ reallySure()
+ tooDangerousOption()
+ }
+ }
+
+ fun ChoiceOption.reallySure(): Unit = option("Yes, I'm really sure!") {
+ if (!meetsZogreRequirements()) {
+ npc("Sorry, yous creatures, but yous is too green behind da ears for dis job Grish finks.")
+ player("No, I'm not!")
+ npc("Yes you are!")
+ player("No, I'm not!")
+ npc("Yes you are and that's final!")
+ statement("You do not meet all of the requirements to start the Zogre Flesh Eaters quest.")
+ return@option
+ }
+ npc("Dats da good fing yous creature...yous does Grish a good fing. But yous know dat yous get sickies and mebe get dead!")
+ player("If that's your idea of a pep talk, I have to say that it leaves a lot to be desired.")
+ npc("Yous creatures is alus says funny stuff...speaks proper like Grish!")
+ set("zogre_flesh_eaters", "investigate")
+ addOrDrop("cooked_chompy", 3)
+ addOrDrop("super_restore_3", 2)
+ items("cooked_chompy", "super_restore_3", "Grish hands you some food and two potions.")
+ npc("Der's yous go creatures...da best me's do for yous...and be back wivout da sickies.")
+ }
+
+ val Player.chompybird: Int
+ get() = get("chompy_birds", 0)
+
+ private fun Player.meetsZogreRequirements(): Boolean = hasMax(Skill.Ranged, 30) && questCompleted("jungle_potion") && chompybird == 65 // TODO
+
+ suspend fun Player.foundMenu() {
+ choice {
+ foundResponsibleOption()
+ if (get("thzfe_makebrutalarrow", false)) {
+ if (!get("thzfe_makecompozogrebow", false)) {
+ killFromDistanceOption()
+ } else {
+ easierWay()
+ }
+ }
+ if (get("thzfe_makecuredisease", false)) {
+ if (!get("thzfe_sold_balm", false)) {
+ cureDiseaseOption()
+ } else {
+ cureDisease()
+ }
+ }
+ otherQuestionsOption()
+ sorryHaveToGo()
+ }
+ }
+
+ fun ChoiceOption.foundResponsibleOption(): Unit = option("I found who's responsible for the Zogres being here.") {
+ npc("Where is da creature? Me's wants to squeeze him till he's a deadun...")
+ player("The person responsible is a wizard named 'Sithik Ints' and he's going to be in serious trouble. He told me that the spell which raised the zogres from the ground will last forever.")
+ player("I'm sorry to say, but you'll have to move the site of your ceremonial dancing somewhere else.")
+ npc("Dat is da bad fing creature...we's needs new Jiggig for da fallin' down jig.")
+ player("Yes, that's right, you'll need to create a new ceremonial dance area.")
+ npc("Urghhh...not good fing creature, yous gotta get da ogrish old fings for da making new jiggig special. You's creature needs da key for getting in da low bury place.")
+ set("zogre_flesh_eaters", "given_key")
+ set("thzfe_sithik_transformed", 2)
+ addOrDrop("ogre_gate_key")
+ message("Grish gives you a crudely crafted key.")
+ item(item = "ogre_gate_key", text = "Grish gives you a crudely crafted key.")
+ player("Oh, so you want me to go back in there and look for something for you?")
+ npc("Yeah creature, yous gotta get da ogrish old fings for da making new jiggig and proper in da special way.")
+ }
+
+ fun ChoiceOption.killFromDistanceOption(): Unit = option("I've got some information on how to kill the zogres from a distance.") {
+ player("Sithik told me how to make Brutal arrows which means I can kill these zogres from a distance!")
+ teachCompositeBow()
+ }
+
+ fun ChoiceOption.cureDiseaseOption(): Unit = option("I've found out how to cure the disease.") {
+ player("I also found out that the disease can be cured.")
+ npc("Dat's da good fing creature, yous do good fing to give un to Uglug...he gives bright pretties for da sickies glug glug.")
+ returnToProgressMenu()
+ }
+
+ /**
+ * Routes back to whichever menu fits the current quest stage — progress 8 stays
+ * in the post-Sithik review menu; progress 10/12 (after Grish has handed out the
+ * tomb key) drops into the post-no menu instead.
+ */
+ private suspend fun Player.returnToProgressMenu() {
+ if (quest("zogre_flesh_eaters") == "permanent_spell") {
+ foundMenu()
+ } else {
+ postNoMenu()
+ }
+ }
+
+ fun ChoiceOption.otherQuestionsOption(): Unit = option("I have some other questions for you.") {
+ otherQuestionsBranch()
+ }
+
+ private suspend fun Player.teachCompositeBow() {
+ npc("Uhggh, whas you's sayin' creature? Yous speakies too stupid for Grish...")
+ player("I know how to make large arrows...you know, 'big stabbers', to kill the zogres...they're bigger and apparently do a lot of damage, only thing is, the normal ogre bow I need to fire it is quite slow.")
+ npc("Why you's not say so creature...me's shows you how to make da bigger stabber chucker... ~ Grish gets a couple of items out of his back pack.~") // TODO makes too much of the line blue
+ set("thzfe_makecompozogrebow", true)
+ items(
+ "achey_tree_logs",
+ "wolf_bones",
+ "Grish shows you he has Achey tree logs and wolf bones, he starts to whittle away at them both with a knife.",
+ )
+ item(item = "unstrung_comp_bow", text = "Grish shows you his achievement, a rather powerful looking composite bow frame...")
+ items(
+ "unstrung_comp_bow",
+ "bowstring",
+ "He shows you the bow frame and the string and after some time and a great deal of effort, he strings the composite ogre bow.",
+ )
+ item(item = "comp_ogre_bow", text = "Grish shows you his proud achievement...")
+ npc("De're creature...now yous is makin' da bigga stabber chucker...")
+ player("Thanks! I think....")
+ returnToProgressMenu()
+ }
+
+ // ===== Other questions branch (lore questions) =====
+
+ suspend fun Player.otherQuestionsBranch() {
+ npc("Oh yes creatures...what's other fings yous wanna know?")
+ if (quest("zogre_flesh_eaters") == "permanent_spell") {
+ otherQuestionsLimited()
+ } else {
+ otherQuestionsFull()
+ }
+ }
+
+ suspend fun Player.otherQuestionsFull() {
+ choice {
+ shamansOption()
+ doYouKnowRantz()
+ whyDoesntRantzLive()
+ whyJiggig()
+ talkAboutQuestOption()
+ }
+ }
+
+ suspend fun Player.otherQuestionsLimited() {
+ choice {
+ doYouKnowRantz()
+ whyDoesntRantzLive()
+ whyJiggig()
+ talkAboutQuestOption()
+ }
+ }
+
+ fun ChoiceOption.shamansOption(): Unit = option("Why are you much nicer than the Shaman in Gu'Tanoth?") {
+ npc("Dey's is da big crazy one's! Dey's biggest angries wiv fings and wanna dead all fings...dey's gotten da biggies wizzy stuff...and dey's wanna eat yous creatures...Grish, not do dat...")
+ player("Oh, well that's a relief! It's good to know you don't eat humans...")
+ npc("Grish not say dat! Me's want's tasty looking creatures for yums...you's looks like da sickies chompy...not good for da gutsies...")
+ player("Gulp!")
+ // TODO: switch chathead to Uglug Nar
+ npc("Grish, you's is fright da creatures! Leave it alone!")
+ // TODO: switch chathead back to Grish
+ npc("But it's da big laffsies when it's facey goes to whiteness....ha ha ha!")
+ // TODO: switch chathead to Uglug Nar
+ npc("But it's not da big yumsies when it's gone to all frighty...")
+ npc("ha ha ha ha!")
+ player("Yeah...very funny, I'm sure.")
+ otherQuestionsFull()
+ }
+
+ fun ChoiceOption.doYouKnowRantz(): Unit = option("Do you know Rantz?") {
+ npc("Me's know's about Rantz, he's da biggun chompy hunter..he finks...ha ha ha!")
+ player("How do you mean?")
+ npc("He's da bad shot chompy sticker, no good at sneaky, sneaky part, he's more gooder at da 'noisy, noisy miss da chompy', ha ha ha! ")
+ otherQuestions()
+ }
+
+ private suspend fun Player.otherQuestions() {
+ if (quest("zogre_flesh_eaters") == "permanent") {
+ otherQuestionsLimited()
+ } else {
+ otherQuestionsFull()
+ }
+ }
+
+ fun ChoiceOption.whyDoesntRantzLive(): Unit = option("Why doesn't Rantz live with the rest of the Ogres?") {
+ npc("He's been leaving Gu 'Noth 'cos dey's peoples is da big stressy dere? All da ogries is busying all da time...not doin' no good for da healfy fing. Rantz is da brave-un tho! He's got da big secret fing for leaving Gu' Noth but me's not knowin it. But maybe's he's just want's to be da better chompy sticker?")
+ otherQuestions()
+ }
+
+ fun ChoiceOption.whyJiggig(): Unit = option("Why do you call this place Jiggig?") {
+ npc("It's da place where da Jiggig is done...we's jig at Jiggig...")
+ otherQuestions()
+ }
+
+ fun ChoiceOption.talkAboutQuestOption(): Unit = option("I want to talk about the quest.") {
+ foundMenu()
+ }
+
+ // ===== Progress 10/12: Lost key handling =====
+
+ private suspend fun Player.lostKey() {
+ npc("Yous creature got da old fings yet?")
+ player("I've lost the key you gave me!")
+ npc("Yous stupid creatures....luckily Grish has 'nother one..")
+ addOrDrop("ogre_gate_key")
+ npc("Yous creatures doesn't loosing this ones.")
+ }
+
+ // ===== Progress 10/12: Have key, ask about artefacts =====
+
+ private suspend fun Player.artefactReminderMenu() {
+ npc("Yous creature got da old fings yet?")
+ choice("Grish asks if you have the items yet.") {
+ notYet()
+ easierWay()
+ cureDisease()
+ sorryHaveToGo()
+ }
+ }
+
+ fun ChoiceOption.noSorry(): Unit = option("No sorry, I don't have them yet.") {
+ npc("Yous creatures get dem for me soon doh, yes?")
+ postNoMenu()
+ }
+
+ fun ChoiceOption.notYet(): Unit = option("Nope, not yet.") {
+ npc("Yous gets 'em quick tho, cos we'ze wonna do da new Jiggig place...")
+ postNoMenu()
+ }
+
+ fun ChoiceOption.easierWay(): Unit = option("There must be an easier way to kill these zogres!") {
+ npc("Yous creature jus makin da bigga stabber chucker like Grish shows you...")
+ postNoMenu()
+ }
+
+ fun ChoiceOption.cureDisease(): Unit = option("There must be a way to cure this disease!") {
+ npc("Did yous creature makes da sickies glug glug and putin some wiv Uglug for bright pretties? He's goodun for makin' da glug glugs...yous maken da glug-glug, den sellin' one for Uglug, he's makin' more of da sickies glug")
+ npc("glug and sellin' for bright pretties to yous creature...")
+ postNoMenu()
+ }
+
+ suspend fun Player.postNoMenu() {
+ choice {
+ if (get("thzfe_makebrutalarrow", false)) {
+ if (!get("thzfe_makecompozogrebow", false)) {
+ killFromDistanceOption()
+ } else {
+ easierWay()
+ }
+ }
+ if (get("thzfe_makecuredisease", false)) {
+ if (!get("thzfe_sold_balm", false)) {
+ cureDiseaseOption()
+ } else {
+ cureDisease()
+ }
+ }
+ otherQuestionsOption()
+ sorryHaveToGo()
+ }
+ }
+
+ private suspend fun Player.questFinishHandover() {
+ npc("Hey, you's creature got da old fings?")
+ choice {
+ if (inventory.contains("ogre_artefact")) {
+ haveThemHere()
+ } else {
+ noSorry()
+ }
+ howIsItGoing()
+ otherQuestionsOption()
+ sorryHaveToGoNow()
+ }
+ }
+
+ private suspend fun Player.postQuest() {
+ npc("Hey yous creatures da good un...")
+ postFinishMenu()
+ }
+
+ fun ChoiceOption.howIsItGoing(): Unit = option("How's everything going now?") {
+ npc("All da zogries stayin' in da oldie Jiggig, we's gonna do da new Jiggig someways else. Yous creature da good-un for geddin' da oldie fings...")
+ postFinishMenu()
+ }
+
+ fun ChoiceOption.sorryHaveToGoNow(): Unit = option("Sorry, I have to go now.") {}
+
+ suspend fun Player.postFinishMenu() {
+ choice {
+ howIsItGoing()
+ otherQuestionsOption()
+ sorryHaveToGo()
+ }
+ }
+
+ fun ChoiceOption.haveThemHere(): Unit = option("Yeah, I have them here!") {
+ npc("Dat is da goodly fing yous creature, now's we's can make da new Jiggig place away from zogries! Yous been da big helpy fing yous creature, Grish wishin' yous good stuff for da next fings for creature.")
+ npc("~ Grish seems very pleased about the return of the artefacts. ~")
+ player("Thanks, that's very nice of you!")
+ sendZogreFleshEatersReward()
+ }
+}
+
+fun Player.sendZogreFleshEatersReward() {
+ jingle("quest_complete_1")
+ inventory.remove("ogre_artefact")
+ inventory.remove("ogre_gate_key")
+ exp(Skill.Ranged, 2000.0)
+ exp(Skill.Fletching, 2000.0)
+ exp(Skill.Herblore, 2000.0)
+ inc("quest_points", 1)
+ AuditLog.event(this, "quest_completed", "zogre_flesh_eaters")
+ set("zogre_flesh_eaters", "completed")
+ refreshQuestJournal()
+ questComplete(
+ "Zogre Flesh Eaters",
+ "1 Quest Point",
+ "Can now make Brutal Arrows",
+ "and cure disease potions.",
+ "2000 Ranged, Fletching and",
+ "Herblore XP.",
+ item = "ogre_artefact",
+ )
+}
diff --git a/game/src/main/kotlin/content/area/kandarin/feldip_hills/OgreGuard.kt b/game/src/main/kotlin/content/area/kandarin/feldip_hills/OgreGuard.kt
new file mode 100644
index 0000000000..4c6d1cfdac
--- /dev/null
+++ b/game/src/main/kotlin/content/area/kandarin/feldip_hills/OgreGuard.kt
@@ -0,0 +1,64 @@
+package content.area.kandarin.feldip_hills
+
+import content.entity.player.dialogue.Neutral
+import content.entity.player.dialogue.type.npc
+import content.entity.player.dialogue.type.player
+import content.quest.quest
+import world.gregs.voidps.engine.Script
+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.sound
+import world.gregs.voidps.type.Tile
+
+class OgreGuard : Script {
+
+ init {
+ npcOperate("Talk-to", "zogre_ogre_guard") { (target) ->
+ when (quest("zogre_flesh_eaters")) {
+ "unstarted" -> warnAway()
+ "investigate" -> openBarricade(target)
+ else -> postBarricadeWarning()
+ }
+ }
+ }
+
+ // ===== Progress 0: Generic warning, player hasn't accepted quest =====
+
+ private suspend fun Player.warnAway() {
+ npc("Yous needs ta stay away from dis place...yous get da sickies and mebe yous goes to dead if yous da unlucky fing.")
+ }
+
+ // ===== Progress 2: Player has accepted quest, ready to break barricade =====
+
+ private suspend fun Player.openBarricade(guard: NPC) {
+ npc("Yous needs ta stay away from dis place...yous get da sickies and mebe yous goes to dead if yous da unlucky fing.")
+ player("But Grish has asked me to look into this place and find out why all the undead ogres are here.")
+ npc("Ok, dat is da big, big scary, danger fing!
You's sure you's wants to go in?")
+ player("Yes, I'm sure.")
+ npc("Ok, I opens da stoppa's for yous creature.")
+ breakBarricadeCutscene(guard)
+ npc("Ok der' yous goes!")
+ }
+
+ // ===== Progress 3+: Past the barricade, just a flavor warning =====
+
+ private suspend fun Player.postBarricadeWarning() {
+ npc("Hey yous tryin' not to get da sickies else yous be da sick-un and mebe get to be a dead-un if yous be da unlucky fing.")
+ player("Don't worry, I know how to take care of myself.")
+ }
+
+ // ===== Helpers - replace with project-specific implementations =====
+
+ private suspend fun Player.breakBarricadeCutscene(guard: NPC) {
+ guard.clearWatch()
+ guard.face(Tile(2458, 3049, 0))
+ delay(2)
+ guard.anim("ogre_kick")
+ sound("unarmed_kick")
+ delay(1)
+ set("zogre_flesh_eaters", "barricade")
+ set("thzfe_blocking_barricade", true)
+ sound("ogre_destroy_barricade")
+ delay(2)
+ }
+}
diff --git a/game/src/main/kotlin/content/area/kandarin/feldip_hills/UglugNar.kt b/game/src/main/kotlin/content/area/kandarin/feldip_hills/UglugNar.kt
new file mode 100644
index 0000000000..01abdeda30
--- /dev/null
+++ b/game/src/main/kotlin/content/area/kandarin/feldip_hills/UglugNar.kt
@@ -0,0 +1,106 @@
+package content.area.kandarin.feldip_hills
+
+import content.entity.npc.shop.openShop
+import content.entity.player.dialogue.Happy
+import content.entity.player.dialogue.Neutral
+import content.entity.player.dialogue.type.ChoiceOption
+import content.entity.player.dialogue.type.choice
+import content.entity.player.dialogue.type.item
+import content.entity.player.dialogue.type.items
+import content.entity.player.dialogue.type.npc
+import content.entity.player.dialogue.type.player
+import content.entity.player.inv.item.addOrDrop
+import content.quest.quest
+import world.gregs.voidps.engine.Script
+import world.gregs.voidps.engine.entity.character.player.Player
+import world.gregs.voidps.engine.inv.inventory
+import world.gregs.voidps.engine.inv.remove
+
+class UglugNar : Script {
+ init {
+ npcOperate("Talk-to", "uglug_nar") { (target) ->
+ when (quest("zogre_flesh_eaters")) {
+ "unstarted" -> firstMeetingMenu()
+ "investigate", "barricade", "sithik" -> repeatMeetingMenu()
+ else -> repeatMeetingMenu()
+ }
+ }
+
+ npcOperate("Trade", "uglug_nar") { (target) ->
+ if (get("thzfe_sold_balm", false)) {
+ openShop("uglugs_stuffsies")
+ } else {
+ npc("Me's not got no glug-glugs to sell, yous bring me da sickies glug-glug den me's open da stufsies for ya.")
+ }
+ }
+
+ registerSale("relicyms_balm_4", price = 1000)
+ registerSale("relicyms_balm_3", price = 650)
+ registerSale("relicyms_balm_2", price = 300)
+ registerSale("relicyms_balm_1", price = 100)
+ }
+
+ // ===== Talk-to: First-time meeting (progress 0) =====
+
+ suspend fun Player.firstMeetingMenu() {
+ choice {
+ whatsGoingOn()
+ whatAreYouSelling()
+ okayThanks()
+ }
+ }
+
+ fun ChoiceOption.whatsGoingOn(): Unit = option("Hey, what's going on here?") {
+ npc("Dem's dead ogre's come out of da ground...dey's makin' da rest of us into sick-uns ...and dead-uns.")
+ player("That doesn't sound good!")
+ npc("Grish want's da person go down der - see what's what!")
+ }
+
+ fun ChoiceOption.whatAreYouSelling(): Unit = option("What are you selling?") {
+ if (get("thzfe_sold_balm", false)) {
+ npc("Me's showin' you da stufsies for yous creatures!")
+ openShop("uglugs_stuffsies")
+ } else {
+ npc("Me's not got no glug-glugs to sell, yous bring me da sickies glug-glug den me's open da stufsies for ya.")
+ }
+ }
+
+ fun ChoiceOption.okayThanks(): Unit = option("Ok, thanks.")
+
+ // ===== Talk-to: Repeat meeting (progress 2+) =====
+
+ suspend fun Player.repeatMeetingMenu() {
+ choice {
+ helloAgain()
+ whatAreYouSelling()
+ okayThanks()
+ }
+ }
+
+ fun ChoiceOption.helloAgain(): Unit = option("Hello again.") {
+ if (get("thzfe_sold_balm", false)) {
+ npc("Hey yous creature...yous did good fings gedin that glug-glugs for da sickies! All is ogries pepels are not gettin dead cos of you.")
+ } else {
+ npc("Hey yous creature...yous still here?")
+ player("Yeah, I'm going to help Grish by figuring out what went on here.")
+ npc("If yous finds somefin for da sickies, yous brings to me...and I's gives you bright pretties, den me make more for alls pepels.")
+ player("Hmm, ok, I'll try to bear that in mind.")
+ }
+ }
+
+ private fun registerSale(potion: String, price: Int) {
+ itemOnNPCOperate(potion, "uglug_nar") {
+ if (get("thzfe_sold_balm", false)) {
+ npc("Yous creatures is da funny ones...yous already solds me's ones now..and us can now sell un to yous!")
+ return@itemOnNPCOperate
+ }
+ item(item = potion, text = "You show the potion to Uglug Nar.")
+ player("Hey, here you go! I brought you some of the potion which should cure the disease. You said that you would buy some from me.")
+ npc("Yous creatures done da good fing...yous get many bright pretties for dis...!")
+ set("thzfe_sold_balm", true)
+ inventory.remove(potion)
+ addOrDrop("coins", price)
+ items(potion, "coins", "You sell the potion and get $price coins in return.")
+ }
+ }
+}
diff --git a/game/src/main/kotlin/content/area/kandarin/yanille/BartenderDragonInn.kt b/game/src/main/kotlin/content/area/kandarin/yanille/BartenderDragonInn.kt
index e2aa81f870..46180523a1 100644
--- a/game/src/main/kotlin/content/area/kandarin/yanille/BartenderDragonInn.kt
+++ b/game/src/main/kotlin/content/area/kandarin/yanille/BartenderDragonInn.kt
@@ -1,12 +1,17 @@
package content.area.kandarin.yanille
import content.entity.npc.shop.buy
+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
import content.entity.player.dialogue.type.player
+import content.entity.player.inv.item.addOrDrop
import content.quest.miniquest.alfred_grimhands_barcrawl.barCrawlDrink
import content.quest.miniquest.alfred_grimhands_barcrawl.onBarCrawl
import world.gregs.voidps.engine.Script
@@ -14,6 +19,9 @@ import world.gregs.voidps.engine.client.message
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.skill.Skill
+import world.gregs.voidps.engine.entity.character.sound
+import world.gregs.voidps.engine.inv.inventory
+import world.gregs.voidps.engine.inv.remove
class BartenderDragonInn : Script {
@@ -60,6 +68,35 @@ class BartenderDragonInn : Script {
}
barCrawl(target)
}
+
+ // ===== Tankard =====
+ itemOnNPCOperate("dragon_inn_tankard", "bartender_dragon_inn") {
+ item(item = "dragon_inn_tankard", text = "You show the tankard to the Inn Keeper.")
+ if (get("thzfe_showntankard", false)) {
+ tankardRepeat()
+ } else {
+ tankardFirstTime()
+ }
+ }
+
+ // ===== Bad portrait =====
+ itemOnNPCOperate("zogre_sithik_portrait_bad", "bartender_dragon_inn") {
+ item(item = "zogre_sithik_portrait_bad", text = "You show the sketch to the Inn keeper.")
+ npc("Who's that? I mean, I guess it's a picture of a person isn't it? Sorry...you've got me? And before you ask, you're not putting it up on my wall!")
+ player("It's a portrait of Sithik Ints...don't you recognise him?")
+ npc("I'm sorry, I really am, but I just don't see it...can you make a better picture?")
+ player("I'll try...")
+ }
+
+ // ===== Good portrait =====
+ itemOnNPCOperate("zogre_sithik_portrait_good", "bartender_dragon_inn") { (target) ->
+ item(item = "zogre_sithik_portrait_good", text = "You show the portrait to the Inn keeper.")
+ if (get("thzfe_innkeepermugshown", false)) {
+ npc("Yeah, I recognise that Geezer, he was talking to one of my customers the other day.")
+ } else {
+ signPortrait(target)
+ }
+ }
}
suspend fun Player.barCrawl(target: NPC) = barCrawlDrink(
@@ -69,4 +106,44 @@ class BartenderDragonInn : Script {
levels.drain(Skill.Defence, 6)
},
)
+
+ // ===== First-time tankard reveal =====
+
+ private suspend fun Player.tankardFirstTime() {
+ player("Hello there, I found this tankard in an ogre tomb cavern. It has the emblem of this Inn on it and I wondered if you knew anything about it?")
+ set("thzfe_showntankard", true)
+ npc("Oh yes, this is Brentle's mug...I'm surprised he left it just lying around down some cave. He's quite protective of it.")
+ player("Brentle you say? So you knew him then?")
+ npc("Yeah, this belongs to 'Brentle Vahn', he's quite a common customer, though I've not seen him in a while.")
+ npc("He was talking to some shifty looking wizard the other day. I don't know his name, but I'd recognise him if I saw him.")
+ player("Hmm, I'm sorry to tell you this, but Brentle Vahn is dead - I believe he was murdered.")
+ npc("Noooo! I'm shocked...")
+ npc("...but not surprised. He was a good customer...but I knew he would sell his sword arm and do many a dark deed if paid enough.")
+ npc("If you need help bringing the culprit to justice, you let me know.")
+ }
+
+ // ===== Repeat tankard showing =====
+
+ private suspend fun Player.tankardRepeat() {
+ player("Hello again. Can you tell me what you know about this tankard again please?")
+ npc("Oh yes, Brentle's tankard. Yeah, you've shown me this already. It belonged to Brentle Vahn, he was quite a common customer, though I've not seen him in a while.")
+ npc("He was talking to some shifty looking wizard the other day. I don't know his name, but I'd recognise him if I saw him.")
+ }
+
+ // ===== Good portrait + sign-it sequence =====
+
+ private suspend fun Player.signPortrait(npc: NPC) {
+ npc("Yeah, that's the guy who was talking to Brentle Vahn the other day! Look at those eyes, never a more shifty looking pair will you ever see!")
+ player("Hmm, you've just identified the man who I think sent Brentle Vahn to his death.")
+ player("I'm trying to bring him to justice with the Wizards' Guild grand secretary. Do you think you could sign this portrait to say that he was talking to Brentle Vahn.")
+ npc("I can and I will!")
+ npc.anim("human_mapping")
+ sound("zogre_writing")
+ inventory.remove("zogre_sithik_portrait_good")
+ addOrDrop("zogre_sithik_portrait_signed")
+ set("thzfe_innkeeperportraitshown", true)
+ item(item = "zogre_sithik_portrait_signed", text = "The Dragon Inn bartender signs the portrait.")
+ player("Many thanks for your help, it's really very good of you.")
+ npc("Not at all, just doing my part.")
+ }
}
diff --git a/game/src/main/kotlin/content/area/kandarin/yanille/SithikInts.kt b/game/src/main/kotlin/content/area/kandarin/yanille/SithikInts.kt
new file mode 100644
index 0000000000..77c9761b5d
--- /dev/null
+++ b/game/src/main/kotlin/content/area/kandarin/yanille/SithikInts.kt
@@ -0,0 +1,518 @@
+package content.area.kandarin.yanille
+
+import content.entity.player.dialogue.Angry
+import content.entity.player.dialogue.Confused
+import content.entity.player.dialogue.Expression
+import content.entity.player.dialogue.Happy
+import content.entity.player.dialogue.Neutral
+import content.entity.player.dialogue.Sad
+import content.entity.player.dialogue.Shifty
+import content.entity.player.dialogue.Shock
+import content.entity.player.dialogue.type.ChoiceOption
+import content.entity.player.dialogue.type.choice
+import content.entity.player.dialogue.type.item
+import content.entity.player.dialogue.type.items
+import content.entity.player.dialogue.type.npc
+import content.entity.player.dialogue.type.player
+import content.entity.player.dialogue.type.statement
+import content.entity.player.inv.item.addOrDrop
+import content.quest.quest
+import content.quest.questStage
+import world.gregs.voidps.engine.Script
+import world.gregs.voidps.engine.client.message
+import world.gregs.voidps.engine.entity.character.player.Player
+import world.gregs.voidps.engine.entity.character.sound
+import world.gregs.voidps.engine.inv.add
+import world.gregs.voidps.engine.inv.inventory
+import world.gregs.voidps.engine.inv.remove
+import world.gregs.voidps.type.random
+
+class SithikInts : Script {
+ init {
+ objectOperate("Talk-to", "zogre_sithik_bed_entity,ogre_bedman_loc") {
+ when (quest("zogre_flesh_eaters")) {
+ "unstarted", "started", "investigate" -> sleepyOldManIntro()
+ "barricade", "sithik" -> conversationByVarbit488()
+ "potion" -> postOgreReveal()
+ "permanent_spell" -> postQuestProgressed()
+ "given_key", "killed_slash_bash", "completed" -> backToGloat()
+ else -> sleepyOldManIntro() // catch-all preservation of other states
+ }
+ }
+
+ objectOperate("Search", "sithiks_drawers") {
+ if (noMoreSnooping()) return@objectOperate
+ if (!hasPermission()) {
+ snoopWarning()
+ return@objectOperate
+ }
+ searchDrawer()
+ }
+
+ objectOperate("Search", "sithiks_cupboard") {
+ if (noMoreSnooping()) return@objectOperate
+ if (!hasPermission()) {
+ snoopWarning()
+ return@objectOperate
+ }
+ searchCupboard()
+ }
+
+ objectOperate("Search", "sithiks_wardrobe") {
+ if (noMoreSnooping()) return@objectOperate
+ if (!hasPermission()) {
+ snoopWarning()
+ return@objectOperate
+ }
+ searchWardrobe()
+ }
+
+ itemOnObjectOperate("necromancy_book", "zogre_sithik_bed_entity,ogre_bedman_loc") {
+ player("Aha! A necromantic book! What's this doing here then?")
+ item(item = "necromancy_book", text = "You show the Necromantic book to Sithik.")
+ sithik("Oh..I'm not quite sure actually...where did you find that then?")
+ player("I found it in this cupboard! What do you have to say for yourself?")
+ sithik("Oh yes, that's right...I remember now. It's for my research, there's nothing really dangerous about it, unless it falls into the wrong hands. I'm sure it's pretty safe with me.")
+ player("Hmmm, likely story!")
+ }
+
+ // ===== HAM book =====
+ itemOnObjectOperate("book_of_ham", "zogre_sithik_bed_entity,ogre_bedman_loc") {
+ player("What's this then?")
+ item(item = "book_of_ham", text = "You show the HAM book to Sithik.")
+ sithik("What do you mean? It's a book by the respected HAM leader Johanhus Ulsbrecht, that man speaks for a lot of people who are unhappy with the current state of affairs.")
+ sithik("Can you honestly tell me that you've not had to fight for your life against the odd monster or two?")
+ player("Hmm, that may be true, but I don't universally hate all monsters, whereas I have a sneaking suspicion that you do...and ogres in particular!")
+ sithik("Hmm, that's an interesting theory, care to back it up with any facts?")
+ }
+
+ // ===== Papyrus (sketching Sithik) =====
+ itemOnObjectOperate("papyrus", "zogre_sithik_bed_entity,ogre_bedman_loc") {
+ if (questStage("zogre_flesh_eaters") >= 6) {
+ message("You have already created Sithik's portrait, you don't need another one.")
+ return@itemOnObjectOperate
+ }
+ if (!inventory.contains("charcoal")) {
+ statement("You have no charcoal with which to sketch this subject.")
+ return@itemOnObjectOperate
+ }
+ sithik("Oh lovely! You're making my portrait! Let me see it afterwards!")
+ statement("You begin sketching the irritable Sithik.")
+ anim("human_mapping")
+ sound("zogre_writing")
+ delay(2)
+ inventory.remove("papyrus")
+ val portrait = if (random.nextInt(3) == 0) "zogre_sithik_portrait_good" else "zogre_sithik_portrait_bad" // TODO use crafting level
+ inventory.add(portrait)
+ item(item = portrait, text = "You get a portrait of Sithik.")
+ }
+
+ // ===== Book of portraiture =====
+ itemOnObjectOperate("book_of_portraiture", "zogre_sithik_bed_entity,ogre_bedman_loc") {
+ player("Oh, so explain this then?")
+ item(item = "book_of_portraiture", text = "You show the book on portraiture to Sithik.")
+ sithik("It's my hobby...I'm interested in portraiture, but all art in general. It's fun, you should try it.")
+ player("How do I do it...")
+ sithik("Well...you could start by reading the book!")
+ }
+
+ // ===== Bad portrait =====
+ itemOnObjectOperate("zogre_sithik_portrait_bad", "zogre_sithik_bed_entity,ogre_bedman_loc") {
+ player("Here you go, what do you think?")
+ item(item = "zogre_sithik_portrait_bad", text = "You show the sketch...")
+ sithik("Hmmm, well it's an interesting interpretation, but not really classic realist representation is it? It's not my favourite, but I like the 'truth' of the work...well done.")
+ }
+
+ // ===== Good portrait =====
+ itemOnObjectOperate("zogre_sithik_portrait_good", "zogre_sithik_bed_entity,ogre_bedman_loc") {
+ player("Here you go, what do you think?")
+ item(item = "zogre_sithik_portrait_good", text = "You show the portrait to Sithik.")
+ sithik("Hmmm, well it's not the most flattering of portraits, but I like the 'honesty' of the work...well done.")
+ }
+
+ // ===== Strange potion =====
+ itemOnObjectOperate("zogre_ogre_trans_potion", "zogre_sithik_bed_entity,ogre_bedman_loc") {
+ player("Here, try some of this potion, it'll make you feel better!")
+ sithik("Err, yuck....no way am I taking any potions or medication off you...I don't trust you!")
+ }
+
+ // ===== Signed portrait (the bribe scene) =====
+ itemOnObjectOperate("signed_portrait", "zogre_sithik_bed_entity,ogre_bedman_loc") {
+ player("Hey, what do you think of this? I'm going to show it to Zavistic and you're going to be in trouble!")
+ item(item = "signed_portrait", text = "You show the portrait to Sithik.")
+ sithik("Hmmm, well, I've got quite a common looking face, I'm often mistaken for other wizards, you know, when I'm wearing my wizard's hat, robes and staff. There's a lot of us around here you know.")
+ player("I don't think so! This is a signed picture of you, someone recognised you, you're in deep trouble!")
+ sithik("Ok, I'll pay you to keep this secret - how much do you want for the picture?")
+ player("You can't buy me Sithik!")
+ sithik("Ok, let's say two million...two million to keep quiet and give me the picture.")
+ items("coins", "coins", "Sithik shows you a chest brimming over with coins...")
+ player("Oh...erm...well, that is a lot of money actually...er....")
+ sithik("Yes, and you deserve it, you're very clever! Now, take the money...")
+ bribeChoice()
+ }
+
+ // ===== Dragon Inn tankard =====
+ itemOnObjectOperate("dragon_inn_tankard", "zogre_sithik_bed_entity,ogre_bedman_loc") {
+ player("What about this then? Guess where I found this?")
+ item(item = "dragon_inn_tankard", text = "You show the tankard to Sithik.")
+ sithik("You probably found it at the local brewhouse! It doesn't take a genius to figure that one out.")
+ player("Aha! But I found this in an old ogre tomb! I suspect it's a clue which will lead me to the suspect.")
+ sithik("Hmmm, well that eliminates all the local people who don't actually drink at the 'Dragon Inn'. When do you think you'll start questioning the remaining population of Yanille?")
+ }
+
+ // ===== Black prism =====
+ itemOnObjectOperate("black_prism", "zogre_sithik_bed_entity,ogre_bedman_loc") {
+ player("Hey, what's this then, can you explain it?!")
+ item(item = "black_prism", text = "You show the black prism to Sithik.")
+ sithik("Err..it looks sort of familiar, did you steal it from me? Come to think of it, you have the appearance of a common thief!")
+ player("I found it in a place called Jiggig where some undead ogres happen to be wandering around.")
+ sithik("Oh, nothing to do with me then, never seen it in my life before!")
+ }
+
+ // ===== Torn page =====
+ itemOnObjectOperate("torn_page", "zogre_sithik_bed_entity,ogre_bedman_loc") {
+ player("Have you ever seen anything like this before?")
+ item(item = "torn_page", text = "You show the torn page to Sithik.")
+ sithik("It's probably a piece of rubbish someone threw away...what does it say, I can't read it?")
+ player("You should be able to read it, it's been torn from a book on necromancy and you're meant to be a specialist in the subject.")
+ sithik("Oh, no..., not really a specialist, just a hobby of mine really. Hardly know anything about it, but it does seem interesting...")
+ }
+ }
+
+ // ===== Progress 0/2: Initial encounter =====
+
+ private suspend fun Player.sleepyOldManIntro() {
+ sithik("Hey...who gave you permission to come in here! Get out, get out I say.")
+ player("Alright, alright...keep your night cap on.")
+ }
+
+ // ===== Progress 3/4: Branches by varbit 488 =====
+
+ private suspend fun Player.conversationByVarbit488() {
+ when (get("thzfe_prismsearch", 0)) {
+ 4 -> {
+ sithik("Hey...who gave you permission to come in here!")
+ zavisticIntro()
+ }
+ 5 -> {
+ sithik("What do you want now?")
+ noNeedToBeRude()
+ }
+ else -> sleepyOldManIntro()
+ }
+ }
+
+ private suspend fun Player.zavisticIntro() {
+ player("Zavistic Rarve said that I could come and talk to you and ask you a few questions.")
+ sithik("Oh, Zavistic...why...why would he send you to me?")
+ sithikQuestionsMenu()
+ }
+
+ suspend fun Player.sithikQuestionsMenu() {
+ choice {
+ askAboutUndeadOgres()
+ askWhatYouDo()
+ mindIfILookAround()
+ okThanks()
+ }
+ }
+
+ fun ChoiceOption.askAboutUndeadOgres(): Unit = option("Do you know anything about the undead ogres at Jiggig?") {
+ sithik("Er...undead ogres...no, sorry, no idea what you're talking about there.")
+ player("Hmm, is that right...")
+ sithik("Well, yes, yes it is. If I knew something, I'd tell you.")
+ sithik("Anyway, dead ogres you say? How strange? That must be a strange sight?")
+ player("Very well, if you don't know anything about it, you won't mind if I look around then?")
+ provokedLookAround()
+ }
+
+ fun ChoiceOption.askWhatYouDo(): Unit = option("What do you do?") {
+ sithik("I'm a scholarly student of the magical arts. When I was younger I used to be an adventurer, probably just like yourself. But I lost interest in the constant fighting, looting and gaining abilities.")
+ sithik("Instead I decided to focus my attention and time to study the purer form of the lost arts.")
+ player("The lost arts? What are they?")
+ sithik("Ignorant people call them the 'dark arts'. I'm talking about Necromancy, the power to bring the dead back to life - the power of the gods! Surely the most awesome power known to man.")
+ player("Hmm, well I guess I must be an ignorant person then, because bringing the dead back to life sounds very unnatural.")
+ sithikQuestionsMenu()
+ }
+
+ fun ChoiceOption.mindIfILookAround(): Unit = option("Do you mind if I look around?") {
+ if (get("thzfe_prismsearch", 0) == 5) {
+ triedAlready()
+ } else {
+ provokedLookAround()
+ }
+ }
+
+ fun ChoiceOption.okThanks(): Unit = option("Ok, thanks.")
+
+ private suspend fun Player.provokedLookAround() {
+ set("thzfe_prismsearch", 5)
+ sithik("Well, err....well, actually yes I do mind...it's my place and I don't want strangers going through my things.")
+ player("Well, I'm going to have a look around anyway, if you're not involved in this whole thing, you won't have anything to hide.")
+ sithik("Why, if I was a few years younger I'd give you a good hiding!")
+ player("I'm sure!")
+ sithikQuestionsMenu()
+ }
+
+ private suspend fun Player.triedAlready() {
+ sithik("I've already told you that I do! But you'll probably just ignore me again!")
+ player("Quite right!")
+ }
+
+ // ===== "Snooping" reaction (varbit 488 == 5) =====
+
+ private suspend fun Player.noNeedToBeRude() {
+ player("Hey there's no need to be rude!")
+ sithik("What do you expect when you just go snooping around a person's place against their express permission.")
+ snoopMenu()
+ }
+
+ suspend fun Player.snoopMenu() {
+ choice {
+ askWhatYouDo()
+ whyInBed()
+ okThanks()
+ }
+ }
+
+ fun ChoiceOption.whyInBed(): Unit = option("Why do you spend most of your time in bed?") {
+ sithik("I'm actually quite old and not so very well and I'd like to get over this illness I have, then I'll return to my very serious and important studies.")
+ sithikQuestionsMenu()
+ }
+
+ // ===== Progress 6: Player turned Sithik into an ogre =====
+
+ private suspend fun Player.postOgreReveal() {
+ if (get("thzfe_sithik_transformed", 0) >= 1) {
+ ogreFormConfession()
+ } else {
+ sithik("What do you want now?")
+ noNeedToBeRude()
+ }
+ }
+
+ private suspend fun Player.ogreFormConfession() {
+ sithik("Arghhhh..what's happened to me...you beast!")
+ player("It's your own fault, you shouldn't have lied about your involvement with the undead Ogres at Jiggig. The potion will wear off once you've told the truth!")
+ sithik("Ok, ok, I admit it, I got Brentle Vahn to cast the spell to put an end to those awful Ogres...they're just disgusting creatures...")
+ player("Ok, that's a start...now I want some answers.")
+ confessionAnswersMenu()
+ }
+
+ suspend fun Player.confessionAnswersMenu() {
+ choice {
+ removeSpellFromArea()
+ getRidOfOgres()
+ getRidOfDisease()
+ sorryHaveToGo()
+ }
+ }
+
+ fun ChoiceOption.removeSpellFromArea(): Unit = option("How do I remove the effects of the spell from the area?") {
+ player("How do I remove the effects of the spell from the area? The ogres want to get their ceremonial dance area back and can't do that with undead walking all over it.")
+ if (questStage("zogre_flesh_eaters") >= 8) {
+ sithik("Haven't I told you this already? You can't remove the spell, it's permanent, it will last forever, the only option you have is to move the ceremonial area.")
+ } else {
+ sithik("Unfortunately you can't. The spell is permanent, it will last forever, the only option you have is to move the ceremonial area.")
+ set("zogre_flesh_eaters", "permanent_spell")
+ }
+ player("You're an evil man and I'm going to make you pay for this...you can stay like that forever as far as I'm concerned.")
+ sithik("No...no, let me try to make amends...please I can help you. Just don't leave me like this.")
+ confessionAnswersMenu()
+ }
+
+ fun ChoiceOption.getRidOfOgres(): Unit = option("How do I get rid of the undead ogres?") {
+ if (get("thzfe_makebrutalarrow", false)) {
+ sithik("Haven't I already explained this to you once before?")
+ player("Humour me!")
+ explainBrutalArrows()
+ } else {
+ explainBrutalArrows()
+ }
+ }
+
+ private suspend fun Player.explainBrutalArrows() {
+ sithik("Ok, similar spells have been cast before and the only way to deal with the resulting creatures is to cordon off the area and not go in there again.")
+ sithik("The undead creatures usually manifest some sort of disease so it's best to attack them from a distance with a ranged weapon.")
+ sithik("Normal missiles like arrows and darts do very little damage to them because they're designed to destroy internal organs. This is a waste of time with undead creatures like undead ogres.")
+ player("Yeah, clearly so what should we use?")
+ set("thzfe_makebrutalarrow", true)
+ sithik("From my research it looks like a flat ended arrow was designed called a 'Brutal arrow'. This does large amounts of crushing damage to the creature. You can make them by using larger arrows. ")
+ sithik("I think some Ogre hunters make them. But instead of adding an arrow tip, you hammer a large nail into the end of the shaft.")
+ confessionAnswersMenu()
+ }
+
+ fun ChoiceOption.getRidOfDisease(): Unit = option("How do I get rid of the disease?") {
+ if (get("thzfe_makecuredisease", false)) {
+ sithik("Haven't I already explained this disease thing to you once before?")
+ val threat = if (get("thzfe_sithik_transformed", 0) == 2) {
+ "Just tell me again or else I'll turn you back into an ogre!"
+ } else {
+ "Just tell me again or else I'll never turn you back into a human!"
+ }
+ player(threat)
+ sithik("No...noo...please, I'll tell you.")
+ explainDiseaseCure()
+ } else {
+ explainDiseaseCure()
+ }
+ }
+
+ private suspend fun Player.explainDiseaseCure() {
+ set("thzfe_makecuredisease", true)
+ sithik("My research shows that two jungle based herbs can be used, one is found near river tributaries and looks like a vine, the other is found in caves and grows on the wall.")
+ sithik