From c55f7d7601ffa40d86fa254d9907dd32601ec062 Mon Sep 17 00:00:00 2001 From: GregHib Date: Sun, 5 Jul 2026 16:50:26 +0100 Subject: [PATCH 01/11] Start on pitfall traps --- data/skill/hunter/hunter.areas.toml | 9 ++ data/skill/hunter/hunter.sounds.toml | 4 + data/skill/hunter/hunter.tables.toml | 1 - .../kotlin/content/entity/combat/Combat.kt | 3 + .../kotlin/content/skill/hunter/Pitfall.kt | 129 ++++++++++++++++++ 5 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 data/skill/hunter/hunter.areas.toml create mode 100644 game/src/main/kotlin/content/skill/hunter/Pitfall.kt diff --git a/data/skill/hunter/hunter.areas.toml b/data/skill/hunter/hunter.areas.toml new file mode 100644 index 0000000000..970cd77554 --- /dev/null +++ b/data/skill/hunter/hunter.areas.toml @@ -0,0 +1,9 @@ +[pitfall_0] +x = [2762, 2763] +y = [3003, 3007] +tags = ["pitfall"] + +[pitfall_1] +x = [2769, 2773] +y = [3004, 3005] +tags = ["pitfall"] diff --git a/data/skill/hunter/hunter.sounds.toml b/data/skill/hunter/hunter.sounds.toml index c23343a1eb..3e9f1fa8ec 100644 --- a/data/skill/hunter/hunter.sounds.toml +++ b/data/skill/hunter/hunter.sounds.toml @@ -33,3 +33,7 @@ id = 739 [twitch_net] id = 2652 + +[place_branches] +id = 2639 + diff --git a/data/skill/hunter/hunter.tables.toml b/data/skill/hunter/hunter.tables.toml index 46b2754cde..56e23de1de 100644 --- a/data/skill/hunter/hunter.tables.toml +++ b/data/skill/hunter/hunter.tables.toml @@ -132,7 +132,6 @@ setup_anim = "lay_trap_small" take_down_anim = "lay_trap_small" requires = ["knife"] items = ["logs"] -max = 1 item_message = "You need some logs and a knife to set a pitfall trap." collapse_ticks = 300 collapse_message = "The pitfall trap that you constructed has collapsed." diff --git a/game/src/main/kotlin/content/entity/combat/Combat.kt b/game/src/main/kotlin/content/entity/combat/Combat.kt index 92b9b25c44..c2a43a0409 100644 --- a/game/src/main/kotlin/content/entity/combat/Combat.kt +++ b/game/src/main/kotlin/content/entity/combat/Combat.kt @@ -128,6 +128,9 @@ class Combat(val combatDefinitions: CombatDefinitions) : if (character is NPC && character.attacking && character.underAttack) { return } + if (character is NPC && character.id == "horned_graahk") { + return + } if (character is NPC) { // Retreat val definition = combatDefinitions.getOrNull(character.transformDef["combat_def", character.id]) ?: return diff --git a/game/src/main/kotlin/content/skill/hunter/Pitfall.kt b/game/src/main/kotlin/content/skill/hunter/Pitfall.kt new file mode 100644 index 0000000000..6544ed21dc --- /dev/null +++ b/game/src/main/kotlin/content/skill/hunter/Pitfall.kt @@ -0,0 +1,129 @@ +package content.skill.hunter + +import content.entity.combat.Combat +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.client.message +import world.gregs.voidps.engine.client.ui.chat.plural +import world.gregs.voidps.engine.data.definition.Rows +import world.gregs.voidps.engine.data.definition.Tables +import world.gregs.voidps.engine.entity.character.areaSound +import world.gregs.voidps.engine.entity.character.npc.NPC +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.chat.ChatType +import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.engine.entity.character.player.skill.level.Level.has +import world.gregs.voidps.engine.entity.character.sound +import world.gregs.voidps.engine.entity.obj.GameObject +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.Direction +import world.gregs.voidps.type.Tile + +class Pitfall : Script { + init { + npcOperate("Tease") { + val target = it.target + if (!has(Skill.Hunter, 99)) { // TODO + message("You need a higher hunter level") // TODO proper message + return@npcOperate + } + if (!inventory.contains("teasing_stick")) { + message("You need a teasing stick.")// TODO proper message + return@npcOperate + } + it.updateInteraction { + Combat.combat(it.character, target) + } + } + + objectOperate("Trap", "pitfall") { + layTrap(it.target) + } + + objectOperate("Trap", "pitfall_*") { + // Temp to avoid #1059 + } + + objectOperate("Jump", "pitfall_spiked") { (target) -> + val dir = if (target.rotation == 1 || target.rotation == 3) { + if (tile.x > target.tile.x) Direction.WEST else Direction.EAST + } else { + if (tile.y > target.tile.y) Direction.SOUTH else Direction.NORTH + } + anim("agility_pyramid_gap_jump") + exactMove(tile.add(dir).add(dir).add(dir), delay = 53, direction = dir) + areaSound("hunting_jump", target.tile) + } + + objectOperate("Dismantle", "pitfall_*") { (target) -> + dismantleTrap("pitfall", target, target.tile) + } + + } + + private suspend fun Player.layTrap(obj: GameObject) { + val trap = Rows.getOrNull("traps.pitfall") ?: return + val level = levels.get(Skill.Hunter) + if (!has(Skill.Hunter, trap.int("level"), message = true)) { + return + } + if (get(obj.id, "empty") != "empty") { + return + } + val max = Traps.max(level, 5) + val trapCount = get("trap_count", 0) + if (trapCount >= max) { + message("You may setup only $max ${"trap".plural(max)} at a time at your Hunter level.") + return + } + if (!inventory.contains("knife")) { + message("You need a knife to lay this trap.") // TODO proper message + return + } + if (!inventory.contains("logs")) { + message("You need logs to lay this trap.") // TODO proper message + return + } + // TODO collapse timer + arriveDelay() + anim("lay_trap_small") + inventory.remove("logs") + delay(1) + sound("place_branches") + inc("trap_count") + set(obj.id, "spiked") + } + + private suspend fun Player.dismantleTrap(trapId: String, target: GameObject, tile: Tile) { + val id = Tables.npc("traps.$trapId.npc") + val npc = NPCs.findOrNull(tile, id) ?: return + if (npc["owner", ""] != accountName) { + message("This is not your trap.") + return + } + val trap = Rows.get("traps.$trapId") + val items = trap.itemList("items") + if (inventory.spaces < items.size) { + val slots = items.size - inventory.spaces + message("You don't have enough inventory space. You need $slots more free ${"slot".plural(slots)}.") + return + } + anim(trap.anim("take_down_anim")) + sound("trap_dismantle", delay = 25) + delay(2) + removeTrap(target, npc) + for (item in items) { + inventory.add(item) + } + message("You dismantle the trap.", ChatType.Filter) + } + + private fun Player.removeTrap(target: GameObject, npc: NPC) { + dec("trap_count") + NPCs.remove(npc) + set(target.id, "empty") + } + +} \ No newline at end of file From 82907eb90651e7c0394d47060495709d17d750b2 Mon Sep 17 00:00:00 2001 From: GregHib Date: Sun, 5 Jul 2026 18:02:36 +0100 Subject: [PATCH 02/11] Update pitfall messages --- data/skill/hunter/hunter.sounds.toml | 2 + .../kotlin/content/skill/hunter/Pitfall.kt | 51 ++++--------------- 2 files changed, 13 insertions(+), 40 deletions(-) diff --git a/data/skill/hunter/hunter.sounds.toml b/data/skill/hunter/hunter.sounds.toml index 3e9f1fa8ec..58f7ee7bb0 100644 --- a/data/skill/hunter/hunter.sounds.toml +++ b/data/skill/hunter/hunter.sounds.toml @@ -37,3 +37,5 @@ id = 2652 [place_branches] id = 2639 +[take_branches] +id = 2649 diff --git a/game/src/main/kotlin/content/skill/hunter/Pitfall.kt b/game/src/main/kotlin/content/skill/hunter/Pitfall.kt index 6544ed21dc..4fd428cb31 100644 --- a/game/src/main/kotlin/content/skill/hunter/Pitfall.kt +++ b/game/src/main/kotlin/content/skill/hunter/Pitfall.kt @@ -5,32 +5,27 @@ import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.client.message import world.gregs.voidps.engine.client.ui.chat.plural import world.gregs.voidps.engine.data.definition.Rows -import world.gregs.voidps.engine.data.definition.Tables import world.gregs.voidps.engine.entity.character.areaSound -import world.gregs.voidps.engine.entity.character.npc.NPC -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.chat.ChatType import world.gregs.voidps.engine.entity.character.player.skill.Skill import world.gregs.voidps.engine.entity.character.player.skill.level.Level.has import world.gregs.voidps.engine.entity.character.sound import world.gregs.voidps.engine.entity.obj.GameObject -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.Direction -import world.gregs.voidps.type.Tile class Pitfall : Script { init { npcOperate("Tease") { val target = it.target if (!has(Skill.Hunter, 99)) { // TODO - message("You need a higher hunter level") // TODO proper message + message("You need a hunter level of at least 99 to catch a ${target.def.name.lowercase()}.") return@npcOperate } if (!inventory.contains("teasing_stick")) { - message("You need a teasing stick.")// TODO proper message + message("I don't want to lose a finger by poking it. Maybe I should get a professional teasing implement instead.") return@npcOperate } it.updateInteraction { @@ -58,7 +53,7 @@ class Pitfall : Script { } objectOperate("Dismantle", "pitfall_*") { (target) -> - dismantleTrap("pitfall", target, target.tile) + dismantleTrap(target) } } @@ -66,7 +61,8 @@ class Pitfall : Script { private suspend fun Player.layTrap(obj: GameObject) { val trap = Rows.getOrNull("traps.pitfall") ?: return val level = levels.get(Skill.Hunter) - if (!has(Skill.Hunter, trap.int("level"), message = true)) { + if (!has(Skill.Hunter, trap.int("level"), message = false)) { + message("You need a hunter level of at least 99 to set a pitfall trap here.") return } if (get(obj.id, "empty") != "empty") { @@ -78,12 +74,8 @@ class Pitfall : Script { message("You may setup only $max ${"trap".plural(max)} at a time at your Hunter level.") return } - if (!inventory.contains("knife")) { - message("You need a knife to lay this trap.") // TODO proper message - return - } - if (!inventory.contains("logs")) { - message("You need logs to lay this trap.") // TODO proper message + if (!inventory.contains("knife") || !inventory.contains("logs")) { + message("You need some logs and a knife to set a pitfall trap.") return } // TODO collapse timer @@ -96,33 +88,12 @@ class Pitfall : Script { set(obj.id, "spiked") } - private suspend fun Player.dismantleTrap(trapId: String, target: GameObject, tile: Tile) { - val id = Tables.npc("traps.$trapId.npc") - val npc = NPCs.findOrNull(tile, id) ?: return - if (npc["owner", ""] != accountName) { - message("This is not your trap.") - return - } - val trap = Rows.get("traps.$trapId") - val items = trap.itemList("items") - if (inventory.spaces < items.size) { - val slots = items.size - inventory.spaces - message("You don't have enough inventory space. You need $slots more free ${"slot".plural(slots)}.") - return - } - anim(trap.anim("take_down_anim")) - sound("trap_dismantle", delay = 25) - delay(2) - removeTrap(target, npc) - for (item in items) { - inventory.add(item) - } + private suspend fun Player.dismantleTrap(target: GameObject) { message("You dismantle the trap.", ChatType.Filter) - } - - private fun Player.removeTrap(target: GameObject, npc: NPC) { + anim("lay_trap_small") + delay(1) + sound("take_branches") dec("trap_count") - NPCs.remove(npc) set(target.id, "empty") } From 6a7e1312c16027fabc6ec89707c7f7705933d3fc Mon Sep 17 00:00:00 2001 From: GregHib Date: Sun, 5 Jul 2026 18:08:36 +0100 Subject: [PATCH 03/11] Fix bank withdraw full inventory message happening when all items were removed --- game/src/main/kotlin/content/entity/player/bank/BankWithdraw.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/src/main/kotlin/content/entity/player/bank/BankWithdraw.kt b/game/src/main/kotlin/content/entity/player/bank/BankWithdraw.kt index e2c59a9b5e..506ebe4c5a 100644 --- a/game/src/main/kotlin/content/entity/player/bank/BankWithdraw.kt +++ b/game/src/main/kotlin/content/entity/player/bank/BankWithdraw.kt @@ -65,7 +65,7 @@ class BankWithdraw : Script { } when (player.bank.transaction.error) { TransactionError.None -> { - if (moved < amount) { + if (moved < amount && !removed) { player.inventoryFull("to withdraw that many") } if (removed) { From bb9e8975925b27b35cfaf7c4bf9b6ce805eef504 Mon Sep 17 00:00:00 2001 From: GregHib Date: Sun, 5 Jul 2026 18:09:12 +0100 Subject: [PATCH 04/11] Add pitfall level checks --- data/skill/hunter/hunter.tables.toml | 9 +++++++++ .../main/kotlin/content/skill/hunter/Pitfall.kt | 14 ++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/data/skill/hunter/hunter.tables.toml b/data/skill/hunter/hunter.tables.toml index 56e23de1de..38821a26d1 100644 --- a/data/skill/hunter/hunter.tables.toml +++ b/data/skill/hunter/hunter.tables.toml @@ -290,3 +290,12 @@ xp = 1440 catch_anim = "lay_trap_small" fail_anim = "lay_trap_small" loot = ["rabbit_foot", "raw_rabbit", "bones"] + +[.spined_larupia] +level = 31 + +[.horned_graahk] +level = 41 + +[.sabre_toothed_kyatt] +level = 55 diff --git a/game/src/main/kotlin/content/skill/hunter/Pitfall.kt b/game/src/main/kotlin/content/skill/hunter/Pitfall.kt index 4fd428cb31..854cc96e01 100644 --- a/game/src/main/kotlin/content/skill/hunter/Pitfall.kt +++ b/game/src/main/kotlin/content/skill/hunter/Pitfall.kt @@ -5,6 +5,7 @@ import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.client.message import world.gregs.voidps.engine.client.ui.chat.plural import world.gregs.voidps.engine.data.definition.Rows +import world.gregs.voidps.engine.data.definition.Tables import world.gregs.voidps.engine.entity.character.areaSound import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.character.player.chat.ChatType @@ -20,8 +21,9 @@ class Pitfall : Script { init { npcOperate("Tease") { val target = it.target - if (!has(Skill.Hunter, 99)) { // TODO - message("You need a hunter level of at least 99 to catch a ${target.def.name.lowercase()}.") + val level = Tables.int("creatures.${target.id}.level") + if (!has(Skill.Hunter, level)) { + message("You need a hunter level of at least $level to catch a ${target.def.name.lowercase()}.") return@npcOperate } if (!inventory.contains("teasing_stick")) { @@ -60,15 +62,15 @@ class Pitfall : Script { private suspend fun Player.layTrap(obj: GameObject) { val trap = Rows.getOrNull("traps.pitfall") ?: return - val level = levels.get(Skill.Hunter) - if (!has(Skill.Hunter, trap.int("level"), message = false)) { - message("You need a hunter level of at least 99 to set a pitfall trap here.") + val level = trap.int("level") // TODO different pits have different levels + if (!has(Skill.Hunter, level, message = false)) { + message("You need a hunter level of at least $level to set a pitfall trap here.") return } if (get(obj.id, "empty") != "empty") { return } - val max = Traps.max(level, 5) + val max = Traps.max(levels.get(Skill.Hunter), 5) val trapCount = get("trap_count", 0) if (trapCount >= max) { message("You may setup only $max ${"trap".plural(max)} at a time at your Hunter level.") From 51b947b95db29c61cb82a88e5238fad05358a2f2 Mon Sep 17 00:00:00 2001 From: GregHib Date: Sun, 5 Jul 2026 18:44:23 +0100 Subject: [PATCH 05/11] Add pitfall collapse --- game/src/main/kotlin/content/skill/hunter/Pitfall.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/game/src/main/kotlin/content/skill/hunter/Pitfall.kt b/game/src/main/kotlin/content/skill/hunter/Pitfall.kt index 854cc96e01..4f0b5827a5 100644 --- a/game/src/main/kotlin/content/skill/hunter/Pitfall.kt +++ b/game/src/main/kotlin/content/skill/hunter/Pitfall.kt @@ -15,6 +15,7 @@ import world.gregs.voidps.engine.entity.character.sound import world.gregs.voidps.engine.entity.obj.GameObject import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.remove +import world.gregs.voidps.engine.timer.Timer import world.gregs.voidps.type.Direction class Pitfall : Script { @@ -58,6 +59,14 @@ class Pitfall : Script { dismantleTrap(target) } + for (i in 0..16) { + timerStart("collapse_pitfall_$i") { 100 } + timerTick("collapse_pitfall_$i") { + clear("pitfall_$i") + message("The pitfall trap that you constructed has collapsed.") + Timer.CANCEL + } + } } private suspend fun Player.layTrap(obj: GameObject) { @@ -80,7 +89,6 @@ class Pitfall : Script { message("You need some logs and a knife to set a pitfall trap.") return } - // TODO collapse timer arriveDelay() anim("lay_trap_small") inventory.remove("logs") @@ -88,6 +96,7 @@ class Pitfall : Script { sound("place_branches") inc("trap_count") set(obj.id, "spiked") + softTimers.start("collapse_${obj.id}") } private suspend fun Player.dismantleTrap(target: GameObject) { @@ -97,6 +106,7 @@ class Pitfall : Script { sound("take_branches") dec("trap_count") set(target.id, "empty") + softTimers.clear("collapse_${target.id}") } } \ No newline at end of file From 524ad1eb50ee3033e9889d50b9b79979f20fdffb Mon Sep 17 00:00:00 2001 From: GregHib Date: Mon, 6 Jul 2026 20:29:31 +0100 Subject: [PATCH 06/11] Add pitfall logic and tests --- .../rellekka_hunter_area.npcs.toml | 2 + .../feldip_hills/feldip_hills.npc-spawns.toml | 10 +- .../feldip_hills/feldip_hills.npcs.toml | 4 +- data/area/karamja/karamja.npcs.toml | 2 + data/skill/hunter/hunter.anims.toml | 16 +++ data/skill/hunter/hunter.areas.toml | 9 -- data/skill/hunter/hunter.combat.toml | 13 ++ data/skill/hunter/hunter.objs.toml | 34 +++--- data/skill/hunter/hunter.sounds.toml | 8 +- data/skill/hunter/hunter.tables.toml | 18 ++- data/skill/hunter/hunter.varbits.toml | 34 +++--- data/skill/hunter/hunting.gfx.toml | 11 +- .../kotlin/content/entity/combat/Combat.kt | 6 +- .../kotlin/content/entity/combat/Target.kt | 1 + .../kotlin/content/skill/hunter/Pitfall.kt | 113 +++++++++++++++--- .../main/kotlin/content/skill/melee/Weapon.kt | 1 + .../content/skill/hunter/PitfallTest.kt | 78 ++++++++++++ .../voidps/tools/map/obj/ObjectUsageFinder.kt | 7 +- 18 files changed, 289 insertions(+), 78 deletions(-) delete mode 100644 data/skill/hunter/hunter.areas.toml create mode 100644 data/skill/hunter/hunter.combat.toml create mode 100644 game/src/test/kotlin/content/skill/hunter/PitfallTest.kt diff --git a/data/area/fremennik_province/rellekka/hunter_area/rellekka_hunter_area.npcs.toml b/data/area/fremennik_province/rellekka/hunter_area/rellekka_hunter_area.npcs.toml index 13f8a93f59..ed06088936 100644 --- a/data/area/fremennik_province/rellekka/hunter_area/rellekka_hunter_area.npcs.toml +++ b/data/area/fremennik_province/rellekka/hunter_area/rellekka_hunter_area.npcs.toml @@ -14,6 +14,8 @@ examine = "A sapphire glacialis. It doesn't look as pretentious as its name soun [sabre_toothed_kyatt] id = 5103 +combat_def = "panther" +respawn_delay = 50 examine = "It's just like a big, white, furry, deadly can opener." [hunting_expert_rellekka] 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 c61c73e54b..c5b27b7ad9 100644 --- a/data/area/kandarin/feldip_hills/feldip_hills.npc-spawns.toml +++ b/data/area/kandarin/feldip_hills/feldip_hills.npc-spawns.toml @@ -174,11 +174,11 @@ spawns = [ { id = "barb_tailed_kebbit_feldip_hills", x = 2581, y = 2886 }, { id = "barb_tailed_kebbit_feldip_hills", x = 2584, y = 2912 }, { id = "barb_tailed_kebbit_feldip_hills", x = 2584, y = 2914 }, - { id = "spined_larupia_feldip_hills", x = 2544, y = 2910, members = true }, - { id = "spined_larupia_feldip_hills", x = 2550, y = 2904, members = true }, - { id = "spined_larupia_feldip_hills", x = 2556, y = 2895, members = true }, - { id = "spined_larupia_feldip_hills", x = 2563, y = 2888, members = true }, - { id = "spined_larupia_feldip_hills", x = 2573, y = 2883, members = true }, + { id = "spined_larupia", x = 2544, y = 2910, members = true }, + { id = "spined_larupia", x = 2550, y = 2904, members = true }, + { id = "spined_larupia", x = 2556, y = 2895, members = true }, + { id = "spined_larupia", x = 2563, y = 2888, members = true }, + { id = "spined_larupia", x = 2573, y = 2883, members = true }, { id = "hunting_expert_feldip_hills", x = 2525, y = 2917, members = true }, { id = "giant_spider_mid", x = 2517, y = 2976, members = true }, { id = "ogress_champion", x = 2451, y = 2857 }, diff --git a/data/area/kandarin/feldip_hills/feldip_hills.npcs.toml b/data/area/kandarin/feldip_hills/feldip_hills.npcs.toml index d02d610190..d803404045 100644 --- a/data/area/kandarin/feldip_hills/feldip_hills.npcs.toml +++ b/data/area/kandarin/feldip_hills/feldip_hills.npcs.toml @@ -47,8 +47,10 @@ examine = "A black warlock. The air seems to distort wherever it passes." id = 5088 examine = "It uses its tail to hunt and skewer fish." -[spined_larupia_feldip_hills] +[spined_larupia] id = 5104 +combat_def = "panther" +respawn_delay = 50 examine = "If you tried to ride that, you'd just impale yourself!" [hunting_expert_feldip_hills] diff --git a/data/area/karamja/karamja.npcs.toml b/data/area/karamja/karamja.npcs.toml index 52e1b7525e..b8c708aad3 100644 --- a/data/area/karamja/karamja.npcs.toml +++ b/data/area/karamja/karamja.npcs.toml @@ -72,6 +72,8 @@ examine = "A warrior from Shilo Village." [horned_graahk] id = 5105 +combat_def = "panther" +respawn_delay = 50 examine = "Get in a graahk's way and you're going to know about it...however briefly." [captain_shanks] diff --git a/data/skill/hunter/hunter.anims.toml b/data/skill/hunter/hunter.anims.toml index dc143655b4..01d91822df 100644 --- a/data/skill/hunter/hunter.anims.toml +++ b/data/skill/hunter/hunter.anims.toml @@ -58,3 +58,19 @@ id = 3067 [lay_net_trap] id = 5215 + +[tease] +id = 5236 + +[scythe_slash] +id = 2524 + +[big_cat_attack] +id = 5229 + +[big_cat_defend] +id = 5227 + +[big_cat_death] +id = 5234 +ticks = 2 diff --git a/data/skill/hunter/hunter.areas.toml b/data/skill/hunter/hunter.areas.toml deleted file mode 100644 index 970cd77554..0000000000 --- a/data/skill/hunter/hunter.areas.toml +++ /dev/null @@ -1,9 +0,0 @@ -[pitfall_0] -x = [2762, 2763] -y = [3003, 3007] -tags = ["pitfall"] - -[pitfall_1] -x = [2769, 2773] -y = [3004, 3005] -tags = ["pitfall"] diff --git a/data/skill/hunter/hunter.combat.toml b/data/skill/hunter/hunter.combat.toml new file mode 100644 index 0000000000..8eda932576 --- /dev/null +++ b/data/skill/hunter/hunter.combat.toml @@ -0,0 +1,13 @@ +[panther] +attack_speed = 4 +retreat_range = 20 +defend_anim = "big_cat_defend" +defend_sound = "dog_defend" +death_anim = "big_cat_death" +death_sound = "big_cat_death" + +[panther.melee] +range = 1 +anim = "big_cat_attack" +target_sound = "big_cat_attack" +target_hit = { offense = "slash", max = 70 } diff --git a/data/skill/hunter/hunter.objs.toml b/data/skill/hunter/hunter.objs.toml index 04fa1bce0c..7860e775e6 100644 --- a/data/skill/hunter/hunter.objs.toml +++ b/data/skill/hunter/hunter.objs.toml @@ -144,55 +144,55 @@ id = 28906 [boulder_trap_barb_tailed_kebbit] id = 19207 -[pitfall_0] +[pitfall_sabre_toothed_kyatt_0] id = 19253 -[pitfall_1] +[pitfall_sabre_toothed_kyatt_1] id = 19254 -[pitfall_2] +[pitfall_sabre_toothed_kyatt_2] id = 19255 -[pitfall_3] +[pitfall_sabre_toothed_kyatt_3] id = 19256 -[pitfall_4] +[pitfall_sabre_toothed_kyatt_4] id = 19257 -[pitfall_5] +[pitfall_sabre_toothed_kyatt_5] id = 19258 -[pitfall_6] +[pitfall_spined_larupia_6] id = 19259 -[pitfall_7] +[pitfall_spined_larupia_7] id = 19260 -[pitfall_8] +[pitfall_spined_larupia_8] id = 19261 -[pitfall_9] +[pitfall_spined_larupia_9] id = 19262 -[pitfall_10] +[pitfall_spined_larupia_10] id = 19263 -[pitfall_11] +[pitfall_horned_graahk_11] id = 19264 -[pitfall_12] +[pitfall_horned_graahk_12] id = 19265 -[pitfall_13] +[pitfall_horned_graahk_13] id = 19266 -[pitfall_14] +[pitfall_horned_graahk_14] id = 19267 -[pitfall_15] +[pitfall_horned_graahk_15] id = 19268 -[pitfall_16] +[pitfall_spined_larupia_16] id = 43614 [pitfall] diff --git a/data/skill/hunter/hunter.sounds.toml b/data/skill/hunter/hunter.sounds.toml index 58f7ee7bb0..2c426504f0 100644 --- a/data/skill/hunter/hunter.sounds.toml +++ b/data/skill/hunter/hunter.sounds.toml @@ -11,7 +11,7 @@ id = 2632 id = 2654 [pitfall_collapse] -id = 2638 # radius 5 +id = 2638 [panther_death] id = 667 # radius 5 @@ -39,3 +39,9 @@ id = 2639 [take_branches] id = 2649 + +[big_cat_attack] +id = 666 + +[big_cat_death] +id = 667 diff --git a/data/skill/hunter/hunter.tables.toml b/data/skill/hunter/hunter.tables.toml index 38821a26d1..da3c1555be 100644 --- a/data/skill/hunter/hunter.tables.toml +++ b/data/skill/hunter/hunter.tables.toml @@ -127,7 +127,7 @@ npc = "hunting_sapling_trap_npc" [.pitfall] level = 31 -trap = "pitfall_0" +trap = "pitfall_sabre_toothed_kyatt_0" setup_anim = "lay_trap_small" take_down_anim = "lay_trap_small" requires = ["knife"] @@ -136,6 +136,16 @@ item_message = "You need some logs and a knife to set a pitfall trap." collapse_ticks = 300 collapse_message = "The pitfall trap that you constructed has collapsed." +[.pitfall_spined_larupia] +level = 31 + +[.pitfall_horned_graahk] +level = 41 + +[.pitfall_sabre_toothed_kyatt] +level = 51 + + # --------------------------------------------------------------------------- # Creatures — keyed by the *caught* object id (the triggered/full-trap object) # --------------------------------------------------------------------------- @@ -293,9 +303,15 @@ loot = ["rabbit_foot", "raw_rabbit", "bones"] [.spined_larupia] level = 31 +xp = 1800 +loot = ["big_bones", "tatty_graahk_fur"] [.horned_graahk] level = 41 +xp = 2400 +loot = ["big_bones", "tatty_graahk_fur"] [.sabre_toothed_kyatt] level = 55 +xp = 3000 +loot = ["big_bones", "tatty_kyatt_fur"] diff --git a/data/skill/hunter/hunter.varbits.toml b/data/skill/hunter/hunter.varbits.toml index 7f76489be5..12ff313409 100644 --- a/data/skill/hunter/hunter.varbits.toml +++ b/data/skill/hunter/hunter.varbits.toml @@ -1,84 +1,84 @@ -[pitfall_0] +[pitfall_sabre_toothed_kyatt_0] id = 2958 format = "list" values = ["empty", "spiked", "collapsed", "caught", "inverse"] -[pitfall_1] +[pitfall_sabre_toothed_kyatt_1] id = 2959 format = "list" values = ["empty", "spiked", "collapsed", "caught", "inverse"] -[pitfall_2] +[pitfall_sabre_toothed_kyatt_2] id = 2960 format = "list" values = ["empty", "spiked", "collapsed", "caught", "inverse"] -[pitfall_3] +[pitfall_sabre_toothed_kyatt_3] id = 2961 format = "list" values = ["empty", "spiked", "collapsed", "caught", "inverse"] -[pitfall_4] +[pitfall_sabre_toothed_kyatt_4] id = 2962 format = "list" values = ["empty", "spiked", "collapsed", "caught", "inverse"] -[pitfall_5] +[pitfall_sabre_toothed_kyatt_5] id = 2963 format = "list" values = ["empty", "spiked", "collapsed", "caught", "inverse"] -[pitfall_6] +[pitfall_spined_larupia_6] id = 2964 format = "list" values = ["empty", "spiked", "collapsed", "caught", "inverse"] -[pitfall_7] +[pitfall_spined_larupia_7] id = 2965 format = "list" values = ["empty", "spiked", "collapsed", "caught", "inverse"] -[pitfall_8] +[pitfall_spined_larupia_8] id = 2966 format = "list" values = ["empty", "spiked", "collapsed", "caught", "inverse"] -[pitfall_9] +[pitfall_spined_larupia_9] id = 2967 format = "list" values = ["empty", "spiked", "collapsed", "caught", "inverse"] -[pitfall_10] +[pitfall_spined_larupia_10] id = 2968 format = "list" values = ["empty", "spiked", "collapsed", "caught", "inverse"] -[pitfall_11] +[pitfall_horned_graahk_11] id = 2969 format = "list" values = ["empty", "spiked", "collapsed", "caught", "inverse"] -[pitfall_12] +[pitfall_horned_graahk_12] id = 2970 format = "list" values = ["empty", "spiked", "collapsed", "caught", "inverse"] -[pitfall_13] +[pitfall_horned_graahk_13] id = 2971 format = "list" values = ["empty", "spiked", "collapsed", "caught", "inverse"] -[pitfall_14] +[pitfall_horned_graahk_14] id = 2972 format = "list" values = ["empty", "spiked", "collapsed", "caught", "inverse"] -[pitfall_15] +[pitfall_horned_graahk_15] id = 2973 format = "list" values = ["empty", "spiked", "collapsed", "caught", "inverse"] -[pitfall_16] +[pitfall_spined_larupia_16] id = 4189 format = "list" values = ["empty", "spiked", "collapsed", "caught", "inverse"] diff --git a/data/skill/hunter/hunting.gfx.toml b/data/skill/hunter/hunting.gfx.toml index 84dd94a867..bb57527f82 100644 --- a/data/skill/hunter/hunting.gfx.toml +++ b/data/skill/hunter/hunting.gfx.toml @@ -40,5 +40,14 @@ id = 951 [catch_fail] id = 946 -[pitfall_collapse] +[pitfall_collapse_0] +id = 933 + +[pitfall_collapse_1] +id = 934 + +[pitfall_collapse_2] id = 935 + +[pitfall_collapse_3] +id = 936 diff --git a/game/src/main/kotlin/content/entity/combat/Combat.kt b/game/src/main/kotlin/content/entity/combat/Combat.kt index c2a43a0409..aea58c79d4 100644 --- a/game/src/main/kotlin/content/entity/combat/Combat.kt +++ b/game/src/main/kotlin/content/entity/combat/Combat.kt @@ -119,6 +119,9 @@ class Combat(val combatDefinitions: CombatDefinitions) : } fun retaliate(character: Character, source: Character) { + if (source is NPC && source.def.options.contains("Tease")) { + return + } if (character.dead || character.levels.get(Skill.Constitution) <= 0 || !retaliates(character)) { return } @@ -128,9 +131,6 @@ class Combat(val combatDefinitions: CombatDefinitions) : if (character is NPC && character.attacking && character.underAttack) { return } - if (character is NPC && character.id == "horned_graahk") { - return - } if (character is NPC) { // Retreat val definition = combatDefinitions.getOrNull(character.transformDef["combat_def", character.id]) ?: return diff --git a/game/src/main/kotlin/content/entity/combat/Target.kt b/game/src/main/kotlin/content/entity/combat/Target.kt index 8f88fab7c5..bc25832042 100644 --- a/game/src/main/kotlin/content/entity/combat/Target.kt +++ b/game/src/main/kotlin/content/entity/combat/Target.kt @@ -180,6 +180,7 @@ object Target { is NPC if target.def.contains("damage_cap") -> damage.coerceAtMost(target.def["damage_cap"]) is NPC if target.def.contains("immune_death") -> damage.coerceAtMost(target.levels.get(Skill.Constitution) - 10) is NPC if target.id.endsWith("_impling") -> 0 + is NPC if (target.id == "spined_laurpia" || target.id == "horned_graahk" || target.id == "sabre_toothed_kyatt") -> 0 else -> damage } } diff --git a/game/src/main/kotlin/content/skill/hunter/Pitfall.kt b/game/src/main/kotlin/content/skill/hunter/Pitfall.kt index 4f0b5827a5..48fee15539 100644 --- a/game/src/main/kotlin/content/skill/hunter/Pitfall.kt +++ b/game/src/main/kotlin/content/skill/hunter/Pitfall.kt @@ -1,22 +1,33 @@ package content.skill.hunter -import content.entity.combat.Combat +import content.entity.combat.attacker +import content.entity.combat.dead +import content.entity.combat.target +import content.entity.combat.underAttack +import content.skill.melee.weapon.attackSpeed import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.client.message import world.gregs.voidps.engine.client.ui.chat.plural +import world.gregs.voidps.engine.client.variable.start import world.gregs.voidps.engine.data.definition.Rows import world.gregs.voidps.engine.data.definition.Tables import world.gregs.voidps.engine.entity.character.areaSound +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.chat.ChatType 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.has import world.gregs.voidps.engine.entity.character.sound import world.gregs.voidps.engine.entity.obj.GameObject +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.engine.timer.Timer import world.gregs.voidps.type.Direction +import world.gregs.voidps.type.Tile +import world.gregs.voidps.type.random class Pitfall : Script { init { @@ -31,8 +42,15 @@ class Pitfall : Script { message("I don't want to lose a finger by poking it. Maybe I should get a professional teasing implement instead.") return@npcOperate } - it.updateInteraction { - Combat.combat(it.character, target) + anim("tease") + sound("scythe_slash") + if (!target.dead && !target.underAttack) { + target.mode = CombatMovement(target, this) + target.target = this + val delay = target.attackSpeed / 2 + target.start("action_delay", delay) + target.start("under_attack", delay + 8) + attacker = target } } @@ -40,19 +58,42 @@ class Pitfall : Script { layTrap(it.target) } - objectOperate("Trap", "pitfall_*") { - // Temp to avoid #1059 - } - objectOperate("Jump", "pitfall_spiked") { (target) -> - val dir = if (target.rotation == 1 || target.rotation == 3) { - if (tile.x > target.tile.x) Direction.WEST else Direction.EAST - } else { - if (tile.y > target.tile.y) Direction.SOUTH else Direction.NORTH + val dir = jumpDirection(target, this.tile) + if (dir == Direction.EAST || dir == Direction.WEST) { + walkToDelay(tile.copy(y = tile.y.coerceIn(target.tile.y..target.tile.y + 1))) + } else if (dir == Direction.NORTH || dir == Direction.SOUTH) { + walkToDelay(tile.copy(x = tile.x.coerceIn(target.tile.x..target.tile.x + 1))) } anim("agility_pyramid_gap_jump") - exactMove(tile.add(dir).add(dir).add(dir), delay = 53, direction = dir) areaSound("hunting_jump", target.tile) + exactMoveDelay(tile.add(dir).add(dir).add(dir), delay = 53, direction = dir) + val attacker = attacker + if (attacker !is NPC || attacker.id != "horned_graahk") { + return@objectOperate + } + val offset = when (dir) { + Direction.WEST -> target.tile.addX(-2) + Direction.EAST -> target.tile.addX(2) + Direction.NORTH -> target.tile.addY(-2) + Direction.SOUTH -> target.tile.addY(2) + else -> return@objectOperate + } + if (attacker.tile.within(offset, 3)) { + attacker.walkToDelay(offset) + } else { + delay(2) + } + if (!inPosition(attacker, target, dir)) { + return@objectOperate + } + attacker.walkOverDelay(attacker.tile.add(dir).add(dir)) + attacker.levels.set(Skill.Constitution, 0) + attacker.gfx("pitfall_collapse_${random.nextInt(4)}") + areaSound("pitfall_collapse", target.tile) + set(target.id, "collapsed") + delay(2) + set(target.id, if (dir == Direction.SOUTH || dir == Direction.EAST) "inverse" else "caught") } objectOperate("Dismantle", "pitfall_*") { (target) -> @@ -60,18 +101,36 @@ class Pitfall : Script { } for (i in 0..16) { - timerStart("collapse_pitfall_$i") { 100 } + timerStart("collapse_pitfall_$i") { 100 } // TODO check collapse ticks timerTick("collapse_pitfall_$i") { clear("pitfall_$i") message("The pitfall trap that you constructed has collapsed.") Timer.CANCEL } } + + objectOperate("Trap", "pitfall_*") { + // Temp to avoid #1059 + } + } + + private fun jumpDirection(target: GameObject, tile: Tile): Direction = if (target.rotation == 1 || target.rotation == 3) { + if (tile.x > target.tile.x) Direction.WEST else Direction.EAST + } else { + if (tile.y > target.tile.y) Direction.SOUTH else Direction.NORTH + } + + private fun inPosition(attacker: NPC, target: GameObject, direction: Direction): Boolean = when (direction) { + Direction.WEST -> attacker.tile.x == target.tile.x + 2 && attacker.tile.y in target.tile.y..target.tile.y + 1 + Direction.EAST -> attacker.tile.x == target.tile.x - 2 && attacker.tile.y in target.tile.y..target.tile.y + 1 + Direction.NORTH -> attacker.tile.y == target.tile.y - 2 && attacker.tile.x in target.tile.x..target.tile.x + 1 + Direction.SOUTH -> attacker.tile.y == target.tile.y + 2 && attacker.tile.x in target.tile.x..target.tile.x + 1 + else -> false } private suspend fun Player.layTrap(obj: GameObject) { - val trap = Rows.getOrNull("traps.pitfall") ?: return - val level = trap.int("level") // TODO different pits have different levels + val trap = Rows.getOrNull("traps.${obj.id.substringBeforeLast("_")}") ?: return + val level = trap.int("level") if (!has(Skill.Hunter, level, message = false)) { message("You need a hunter level of at least $level to set a pitfall trap here.") return @@ -96,17 +155,33 @@ class Pitfall : Script { sound("place_branches") inc("trap_count") set(obj.id, "spiked") - softTimers.start("collapse_${obj.id}") + softTimers.start("collapse_pitfall_${obj.id.substringAfterLast("_")}") } private suspend fun Player.dismantleTrap(target: GameObject) { + val state = get(target.id, "empty") + val creature = Rows.get("creatures.${target.id.removePrefix("pitfall_").substringBeforeLast("_")}") + val items = creature.itemList("loot") + val loot = state == "caught" || state == "inverse" + if (loot && inventory.spaces < items.size) { + val slots = items.size - inventory.spaces + message("You don't have enough inventory space. You need $slots more free ${"slot".plural(slots)}.") + return + } message("You dismantle the trap.", ChatType.Filter) anim("lay_trap_small") delay(1) sound("take_branches") dec("trap_count") set(target.id, "empty") - softTimers.clear("collapse_${target.id}") + softTimers.clear("collapse_pitfall_${target.id.substringAfterLast("_")}") + if (loot) { + message("You've caught a horned graahk!", type = ChatType.Filter) + for (item in items) { + // TODO lerp chance of replacing tatty with full fur + inventory.add(item) + } + exp(Skill.Hunter, creature.int("xp") / 10.0) + } } - -} \ No newline at end of file +} diff --git a/game/src/main/kotlin/content/skill/melee/Weapon.kt b/game/src/main/kotlin/content/skill/melee/Weapon.kt index 872e0e08fc..c624dfcb27 100644 --- a/game/src/main/kotlin/content/skill/melee/Weapon.kt +++ b/game/src/main/kotlin/content/skill/melee/Weapon.kt @@ -16,6 +16,7 @@ class Weapon : Script { } variableSet("autocast,spell") { _, _, _ -> updateWeapon(this, weapon) } + variableSet("attack_style") { _, from, to -> if (to == "long_range") { updateWeapon(this, weapon, 2) diff --git a/game/src/test/kotlin/content/skill/hunter/PitfallTest.kt b/game/src/test/kotlin/content/skill/hunter/PitfallTest.kt new file mode 100644 index 0000000000..2c16b5869d --- /dev/null +++ b/game/src/test/kotlin/content/skill/hunter/PitfallTest.kt @@ -0,0 +1,78 @@ +package content.skill.hunter + +import WorldTest +import content.entity.combat.attacker +import content.entity.combat.inCombat +import npcOption +import objectOption +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertNull +import world.gregs.voidps.engine.entity.character.player.skill.Skill +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 +import kotlin.test.assertFalse + +class PitfallTest : WorldTest() { + + @Test + fun `Catch a horned graahk`() { + val player = createPlayer(Tile(2777, 3003)) + val pit = GameObjects.find(Tile(2777, 3001), "pitfall_horned_graahk_14") + player.inventory.add("knife") + player.inventory.add("logs") + player.inventory.add("teasing_stick") + player.levels.set(Skill.Hunter, 99) + + player.objectOption(pit, "Trap") + tick(2) + assertEquals("spiked", player["pitfall_horned_graahk_14", "empty"]) + + val graahk = createNPC("horned_graahk", Tile(2777, 3004)) + + player.npcOption(graahk, "Tease") + tick(2) + + player.objectOption(pit, "Jump") + tick(10) + assertEquals(Tile(2777, 3000), player.tile) + assertEquals("inverse", player["pitfall_horned_graahk_14", "empty"]) + + player.objectOption(pit, "Dismantle") + tick(2) + + assertEquals(1, player.inventory.count("big_bones")) + assertEquals(1, player.inventory.count("tatty_graahk_fur")) + assertNotEquals(0.0, player.experience.get(Skill.Hunter)) + } + + @Test + fun `Can't tease without hunter level`() { + val player = createPlayer(Tile(2777, 3003)) + val graahk = createNPC("horned_graahk", Tile(2777, 3004)) + player.inventory.add("teasing_stick") + player.levels.set(Skill.Hunter, 40) + player.npcOption(graahk, "Tease") + tick(2) + + assertFalse(graahk.inCombat) + assertNull(player.attacker) + } + + @Test + fun `Can't setup pitfall trap without hunter level`() { + val player = createPlayer(Tile(2777, 3003)) + val pit = GameObjects.find(Tile(2777, 3001), "pitfall_horned_graahk_14") + player.inventory.add("knife") + player.inventory.add("logs") + player.inventory.add("teasing_stick") + player.levels.set(Skill.Hunter, 40) + + player.objectOption(pit, "Trap") + tick(2) + assertEquals("empty", player["pitfall_horned_graahk_14", "empty"]) + } +} diff --git a/tools/src/main/kotlin/world/gregs/voidps/tools/map/obj/ObjectUsageFinder.kt b/tools/src/main/kotlin/world/gregs/voidps/tools/map/obj/ObjectUsageFinder.kt index 4e6b39ad29..36934cdb1b 100644 --- a/tools/src/main/kotlin/world/gregs/voidps/tools/map/obj/ObjectUsageFinder.kt +++ b/tools/src/main/kotlin/world/gregs/voidps/tools/map/obj/ObjectUsageFinder.kt @@ -31,10 +31,9 @@ object ObjectUsageFinder { } val set = setOf( - 29317, - 29319, - 29318, - 29320, + 19260, + 19261, + 19264 ) private fun matches(obj: MapObject, def: ObjectDefinitionFull) = set.contains(obj.id) From d07262d8a5b4f60f91a78779420f8e5145251b17 Mon Sep 17 00:00:00 2001 From: GregHib Date: Mon, 6 Jul 2026 20:29:43 +0100 Subject: [PATCH 07/11] Remove hunter class --- .../kotlin/content/skill/hunter/Hunter.kt | 206 ------------------ 1 file changed, 206 deletions(-) delete mode 100644 game/src/main/kotlin/content/skill/hunter/Hunter.kt diff --git a/game/src/main/kotlin/content/skill/hunter/Hunter.kt b/game/src/main/kotlin/content/skill/hunter/Hunter.kt deleted file mode 100644 index 2804a47929..0000000000 --- a/game/src/main/kotlin/content/skill/hunter/Hunter.kt +++ /dev/null @@ -1,206 +0,0 @@ -package content.skill.hunter - -import content.entity.effect.transform -import content.entity.player.inv.item.drop -import content.quest.questCompleted -import net.pearx.kasechange.toLowerSpaceCase -import world.gregs.voidps.engine.Script -import world.gregs.voidps.engine.client.message -import world.gregs.voidps.engine.client.ui.chat.an -import world.gregs.voidps.engine.client.ui.chat.plural -import world.gregs.voidps.engine.data.definition.Areas -import world.gregs.voidps.engine.data.definition.Rows -import world.gregs.voidps.engine.data.definition.Tables -import world.gregs.voidps.engine.entity.character.npc.NPC -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.chat.ChatType -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.has -import world.gregs.voidps.engine.entity.character.sound -import world.gregs.voidps.engine.entity.item.floor.FloorItem -import world.gregs.voidps.engine.entity.item.floor.FloorItems -import world.gregs.voidps.engine.entity.obj.* -import world.gregs.voidps.engine.inv.add -import world.gregs.voidps.engine.inv.carriesItem -import world.gregs.voidps.engine.inv.inventory -import world.gregs.voidps.engine.inv.remove -import world.gregs.voidps.type.Tile - -/** - * Hunter traps have an invisible npc under them which has a hunt mode which - * selects nearby targets to walk to it and attempt to be caught. - */ -class Hunter : Script { - - init { - playerDespawn { - val npcs = NPCs.filter { it["owner", ""] == accountName } - for (npc in npcs) { - NPCs.remove(npc) - } - } - - // TODO ferret, chinchompa, rabbits - - npcSpawn("hunting_*_trap_npc") { - transform("${id}_off") - revert(15) - } - - npcVariableSet("transform_id", "hunting_*_trap_npc") { _, _, to -> - if (to == null) { - despawn(100) - } - } - } - - private fun Player.collapse(npc: NPC, drop: Boolean) { - dec("trap_count") - val tile = npc.tile // TODO tile won't be correct for object traps - val obj = GameObjects.getLayer(tile, ObjectLayer.GROUND) ?: return - obj.remove() - if (drop) { - val id = when (obj.id) { - "snare_crimson_swift", "snare_golden_warbler", "bird_snare_fail" -> "bird_snare" - else -> obj.id - } - dropTrapItems(id, tile) // TODO does baited drop bait? - } - } - - private fun Player.dropTrapItems(id: String, tile: Tile) { - val items = Tables.itemList("traps.$id.items") - for (item in items) { - if (item == "logs") { - continue - } - drop(tile, item) - } - } - - private suspend fun Player.layTrap(trapId: String, obj: GameObject?, floorItem: FloorItem?) { - if (trapId == "box_trap" && !questCompleted("eagles_peak")) { - message("You need to learn how to set a box trap in the Eagle's Peak Quest.") - return - } - var obj = obj - val trap = Rows.getOrNull("traps.$trapId") ?: return - val level = levels.get(Skill.Hunter) - if (!has(Skill.Hunter, trap.int("level"), message = true)) { - return - } - if (Areas.get(tile.zone).any { it.tags.contains("bank") } || GameObjects.getLayer(tile, ObjectLayer.GROUND) != null) { - message("You can't lay a trap here.", ChatType.Filter) - return - } - val max = Traps.max(level, trap.int("max")) - val trapCount = get("trap_count", 0) - if (trapCount >= max) { - message("You may setup only $max ${"trap".plural(max)} at a time at your Hunter level.") - return - } - val message = trap.stringOrNull("item_message") - val requires = trap.itemList("requires") - for (item in requires) { - if (!carriesItem(item)) { - message(message ?: "You need${item.an()} ${item.toLowerSpaceCase()} to lay this trap.") - return - } - } - val items = trap.itemList("items") - for (item in items) { - if (!carriesItem(item) && floorItem != null && floorItem.id != item) { - message(message ?: "You need${item.an()} ${item.toLowerSpaceCase()} to lay this trap.") - return - } - } - arriveDelay() - message("You begin setting up ${if (max == 1) "the" else "a"} trap.", ChatType.Filter) - anim(trap.anim("setup_anim")) - sound(trap.string("setup_sound")) - delay(3) - for (item in items) { - if (floorItem != null && item == floorItem.id) { - FloorItems.remove(floorItem) - continue - } - inventory.remove(item) - } - inc("trap_count") - val trapId = trap.obj("trap") - if (trapId == "pitfall_0" && obj != null) { - set(obj.id, "spiked") - } else if (obj != null) { - obj.replace(trapId) - } else { - NPCs.add(trap.npc("npc"), tile, ticks = 100, owner = this) // TODO check times - obj = GameObjects.add(trapId, tile, ObjectShape.CENTRE_PIECE_STRAIGHT, 0, ticks = 50 * 60) - } - if (trap.bool("step_away")) { - stepAway(obj) - } - } - - private suspend fun Player.dismantleTrap(trapId: String, target: GameObject, tile: Tile) { - val id = Tables.npc("traps.$trapId.npc") - val npc = NPCs.findOrNull(tile, id) ?: return - if (npc["owner", ""] != accountName) { - message("This is not your trap.") - return - } - val trap = Rows.get("traps.$trapId") - val items = trap.itemList("items") - if (inventory.spaces < items.size) { - val slots = items.size - inventory.spaces - message("You don't have enough inventory space. You need $slots more free ${"slot".plural(slots)}.") - return - } - anim(trap.anim("take_down_anim")) - sound("trap_dismantle", delay = 25) - delay(2) - removeTrap(target, npc) - for (item in items) { - inventory.add(item) - } - message("You dismantle the trap.", ChatType.Filter) - } - - private suspend fun Player.collectCatch(creatureId: String, target: GameObject) { - val id = Tables.npc("traps.${target.id.removeSuffix("_caught")}.npc") - val npc = NPCs.findOrNull(target.tile, id) ?: return - if (npc["owner", ""] != accountName) { - message("This is not your trap.") - return - } - val creature = Rows.get("creatures.$creatureId") - val loot = creature.itemList("loot") - if (inventory.spaces < loot.size) { - val slots = loot.size - inventory.spaces - message("You don't have enough inventory space. You need $slots more free ${"slot".plural(slots)}.") - return - } - message("You dismantle the trap.", ChatType.Filter) - val trap = Rows.get("traps.${creature.string("trap")}") - anim(trap.anim("take_down_anim")) - sound("trap_dismantle", delay = 25) - delay(2) - removeTrap(target, npc) - for (item in loot) { - inventory.add(item) - } - exp(Skill.Hunter, creature.int("xp") / 10.0) - message("You've caught a ${creatureId.toLowerSpaceCase()}.", ChatType.Filter) - } - - private fun Player.removeTrap(target: GameObject, npc: NPC) { - dec("trap_count") - NPCs.remove(npc) - if (target.id.startsWith("pitfall")) { - set(target.id, "empty") - return - } - GameObjects.remove(target) - } -} From b4f08d9b213f09d07495211697d65067ef14b552 Mon Sep 17 00:00:00 2001 From: GregHib Date: Mon, 6 Jul 2026 21:15:54 +0100 Subject: [PATCH 08/11] Fix teasing, add tests for larupia and kyatts --- .../feldip_hills/feldip_hills.npcs.toml | 1 + data/skill/hunter/hunter.objs.toml | 16 ++++- data/skill/hunter/hunter.tables.toml | 2 +- .../kotlin/content/entity/combat/Target.kt | 2 +- .../kotlin/content/skill/hunter/Pitfall.kt | 34 +++++----- .../content/skill/hunter/PitfallTest.kt | 66 ++++++++++++++++++- 6 files changed, 99 insertions(+), 22 deletions(-) diff --git a/data/area/kandarin/feldip_hills/feldip_hills.npcs.toml b/data/area/kandarin/feldip_hills/feldip_hills.npcs.toml index d803404045..e4fbee651b 100644 --- a/data/area/kandarin/feldip_hills/feldip_hills.npcs.toml +++ b/data/area/kandarin/feldip_hills/feldip_hills.npcs.toml @@ -51,6 +51,7 @@ examine = "It uses its tail to hunt and skewer fish." id = 5104 combat_def = "panther" respawn_delay = 50 +max_hit_slash = 50 examine = "If you tried to ride that, you'd just impale yourself!" [hunting_expert_feldip_hills] diff --git a/data/skill/hunter/hunter.objs.toml b/data/skill/hunter/hunter.objs.toml index 7860e775e6..fe1f3e4d73 100644 --- a/data/skill/hunter/hunter.objs.toml +++ b/data/skill/hunter/hunter.objs.toml @@ -204,8 +204,20 @@ id = 19228 [pitfall_collapsed] id = 19229 -[pitfall_caught] +[pitfall_caught_spined_larupia] +id = 19232 + +[pitfall_inverse_spined_larupia] +id = 19235 + +[pitfall_caught_horned_graahk] id = 19231 -[pitfall_inverse] +[pitfall_inverse_horned_graahk] id = 19234 + +[pitfall_caught_sabre_toothed_kyatt] +id = 19233 + +[pitfall_inverse_sabre_toothed_kyatt] +id = 19236 diff --git a/data/skill/hunter/hunter.tables.toml b/data/skill/hunter/hunter.tables.toml index da3c1555be..f02c33a731 100644 --- a/data/skill/hunter/hunter.tables.toml +++ b/data/skill/hunter/hunter.tables.toml @@ -304,7 +304,7 @@ loot = ["rabbit_foot", "raw_rabbit", "bones"] [.spined_larupia] level = 31 xp = 1800 -loot = ["big_bones", "tatty_graahk_fur"] +loot = ["big_bones", "tatty_larupia_fur"] [.horned_graahk] level = 41 diff --git a/game/src/main/kotlin/content/entity/combat/Target.kt b/game/src/main/kotlin/content/entity/combat/Target.kt index bc25832042..6c2812b174 100644 --- a/game/src/main/kotlin/content/entity/combat/Target.kt +++ b/game/src/main/kotlin/content/entity/combat/Target.kt @@ -180,7 +180,7 @@ object Target { is NPC if target.def.contains("damage_cap") -> damage.coerceAtMost(target.def["damage_cap"]) is NPC if target.def.contains("immune_death") -> damage.coerceAtMost(target.levels.get(Skill.Constitution) - 10) is NPC if target.id.endsWith("_impling") -> 0 - is NPC if (target.id == "spined_laurpia" || target.id == "horned_graahk" || target.id == "sabre_toothed_kyatt") -> 0 + is NPC if (target.id == "spined_larupia" || target.id == "horned_graahk" || target.id == "sabre_toothed_kyatt") -> 0 else -> damage } } diff --git a/game/src/main/kotlin/content/skill/hunter/Pitfall.kt b/game/src/main/kotlin/content/skill/hunter/Pitfall.kt index 48fee15539..9ebd364fa1 100644 --- a/game/src/main/kotlin/content/skill/hunter/Pitfall.kt +++ b/game/src/main/kotlin/content/skill/hunter/Pitfall.kt @@ -5,6 +5,7 @@ import content.entity.combat.dead import content.entity.combat.target import content.entity.combat.underAttack import content.skill.melee.weapon.attackSpeed +import net.pearx.kasechange.toLowerSpaceCase import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.client.message import world.gregs.voidps.engine.client.ui.chat.plural @@ -12,6 +13,7 @@ import world.gregs.voidps.engine.client.variable.start import world.gregs.voidps.engine.data.definition.Rows import world.gregs.voidps.engine.data.definition.Tables import world.gregs.voidps.engine.entity.character.areaSound +import world.gregs.voidps.engine.entity.character.mode.combat.CombatApi 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 @@ -45,12 +47,14 @@ class Pitfall : Script { anim("tease") sound("scythe_slash") if (!target.dead && !target.underAttack) { + attacker = target + this.target = target target.mode = CombatMovement(target, this) target.target = this + target.attacker = this val delay = target.attackSpeed / 2 target.start("action_delay", delay) target.start("under_attack", delay + 8) - attacker = target } } @@ -69,12 +73,12 @@ class Pitfall : Script { areaSound("hunting_jump", target.tile) exactMoveDelay(tile.add(dir).add(dir).add(dir), delay = 53, direction = dir) val attacker = attacker - if (attacker !is NPC || attacker.id != "horned_graahk") { + if (attacker !is NPC || attacker.id != "spined_larupia" && attacker.id != "horned_graahk" && attacker.id != "sabre_toothed_kyatt") { return@objectOperate } val offset = when (dir) { - Direction.WEST -> target.tile.addX(-2) - Direction.EAST -> target.tile.addX(2) + Direction.WEST -> target.tile.addX(2) + Direction.EAST -> target.tile.addX(-2) Direction.NORTH -> target.tile.addY(-2) Direction.SOUTH -> target.tile.addY(2) else -> return@objectOperate @@ -84,7 +88,7 @@ class Pitfall : Script { } else { delay(2) } - if (!inPosition(attacker, target, dir)) { + if (attacker.tile != offset) { return@objectOperate } attacker.walkOverDelay(attacker.tile.add(dir).add(dir)) @@ -93,7 +97,13 @@ class Pitfall : Script { areaSound("pitfall_collapse", target.tile) set(target.id, "collapsed") delay(2) - set(target.id, if (dir == Direction.SOUTH || dir == Direction.EAST) "inverse" else "caught") + val flip = when { + attacker.id == "horned_graahk" -> dir == Direction.SOUTH || dir == Direction.EAST + attacker.id == "sabre_toothed_kyatt" -> dir == Direction.SOUTH || dir == Direction.WEST + else -> dir == Direction.NORTH || dir == Direction.EAST + } + set(target.id, if (flip) "inverse" else "caught") + CombatApi.stop(attacker, this) } objectOperate("Dismantle", "pitfall_*") { (target) -> @@ -120,14 +130,6 @@ class Pitfall : Script { if (tile.y > target.tile.y) Direction.SOUTH else Direction.NORTH } - private fun inPosition(attacker: NPC, target: GameObject, direction: Direction): Boolean = when (direction) { - Direction.WEST -> attacker.tile.x == target.tile.x + 2 && attacker.tile.y in target.tile.y..target.tile.y + 1 - Direction.EAST -> attacker.tile.x == target.tile.x - 2 && attacker.tile.y in target.tile.y..target.tile.y + 1 - Direction.NORTH -> attacker.tile.y == target.tile.y - 2 && attacker.tile.x in target.tile.x..target.tile.x + 1 - Direction.SOUTH -> attacker.tile.y == target.tile.y + 2 && attacker.tile.x in target.tile.x..target.tile.x + 1 - else -> false - } - private suspend fun Player.layTrap(obj: GameObject) { val trap = Rows.getOrNull("traps.${obj.id.substringBeforeLast("_")}") ?: return val level = trap.int("level") @@ -174,9 +176,9 @@ class Pitfall : Script { sound("take_branches") dec("trap_count") set(target.id, "empty") - softTimers.clear("collapse_pitfall_${target.id.substringAfterLast("_")}") + softTimers.clear("collapse_pitfall_${creature.rowId}") if (loot) { - message("You've caught a horned graahk!", type = ChatType.Filter) + message("You've caught a ${creature.rowId.toLowerSpaceCase()}!", type = ChatType.Filter) for (item in items) { // TODO lerp chance of replacing tatty with full fur inventory.add(item) diff --git a/game/src/test/kotlin/content/skill/hunter/PitfallTest.kt b/game/src/test/kotlin/content/skill/hunter/PitfallTest.kt index 2c16b5869d..a4c6ae5373 100644 --- a/game/src/test/kotlin/content/skill/hunter/PitfallTest.kt +++ b/game/src/test/kotlin/content/skill/hunter/PitfallTest.kt @@ -49,10 +49,72 @@ class PitfallTest : WorldTest() { assertNotEquals(0.0, player.experience.get(Skill.Hunter)) } + @Test + fun `Catch a spined larupia`() { + val player = createPlayer(Tile(2551, 2904)) + val pit = GameObjects.find(Tile(2552, 2904), "pitfall_spined_larupia_16") + player.inventory.add("knife") + player.inventory.add("logs") + player.inventory.add("teasing_stick") + player.levels.set(Skill.Hunter, 99) + + player.objectOption(pit, "Trap") + tick(2) + assertEquals("spiked", player["pitfall_spined_larupia_16", "empty"]) + + val larupia = createNPC("spined_larupia", Tile(2550, 2905)) + + player.npcOption(larupia, "Tease") + tick(2) + + player.objectOption(pit, "Jump") + tick(10) + assertEquals(Tile(2554, 2904), player.tile) + assertEquals("inverse", player["pitfall_spined_larupia_16", "empty"]) + + player.objectOption(pit, "Dismantle") + tick(2) + + assertEquals(1, player.inventory.count("big_bones")) + assertEquals(1, player.inventory.count("tatty_larupia_fur")) + assertNotEquals(0.0, player.experience.get(Skill.Hunter)) + } + + @Test + fun `Catch a sabre toothed kyatt`() { + val player = createPlayer(Tile(2700, 3787)) + val pit = GameObjects.find(Tile(2700, 3785), "pitfall_sabre_toothed_kyatt_1") + player.inventory.add("knife") + player.inventory.add("logs") + player.inventory.add("teasing_stick") + player.levels.set(Skill.Hunter, 99) + + player.objectOption(pit, "Trap") + tick(2) + assertEquals("spiked", player["pitfall_sabre_toothed_kyatt_1", "empty"]) + + val kyatt = createNPC("sabre_toothed_kyatt", Tile(2700, 3788)) + + player.npcOption(kyatt, "Tease") + tick(2) + + player.objectOption(pit, "Jump") + tick(10) + assertEquals(Tile(2700, 3784), player.tile) + assertEquals("inverse", player["pitfall_sabre_toothed_kyatt_1", "empty"]) + + player.objectOption(pit, "Dismantle") + tick(2) + + assertEquals(1, player.inventory.count("big_bones")) + assertEquals(1, player.inventory.count("tatty_kyatt_fur")) + assertNotEquals(0.0, player.experience.get(Skill.Hunter)) + } + @Test fun `Can't tease without hunter level`() { - val player = createPlayer(Tile(2777, 3003)) - val graahk = createNPC("horned_graahk", Tile(2777, 3004)) + val player = createPlayer() + val graahk = createNPC("horned_graahk", player.tile.addY(1)) player.inventory.add("teasing_stick") player.levels.set(Skill.Hunter, 40) player.npcOption(graahk, "Tease") From 4e43c12663bfd2682c49971f979857af7f6a52b7 Mon Sep 17 00:00:00 2001 From: GregHib Date: Mon, 6 Jul 2026 21:26:35 +0100 Subject: [PATCH 09/11] Remove duplicate mazchna --- data/area/morytania/canifis/canifis.npc-spawns.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/data/area/morytania/canifis/canifis.npc-spawns.toml b/data/area/morytania/canifis/canifis.npc-spawns.toml index 4b019b137a..953b03a0ff 100644 --- a/data/area/morytania/canifis/canifis.npc-spawns.toml +++ b/data/area/morytania/canifis/canifis.npc-spawns.toml @@ -31,7 +31,6 @@ spawns = [ { id = "zoja", x = 3505, y = 3491 }, { id = "liliya", x = 3479, y = 3498, level = 1 }, { id = "achtryn", x = 3511, y = 3509, members = true }, - { id = "mazchna", x = 3512, y = 3509, members = true }, # North of Mort Myre Swamp { id = "ghoul", x = 3423, y = 3461, members = true }, { id = "ghoul", x = 3426, y = 3465, members = true }, From b3eef97ff72230ecf3e2c8afa7b98d87d63efb93 Mon Sep 17 00:00:00 2001 From: GregHib Date: Mon, 6 Jul 2026 21:29:05 +0100 Subject: [PATCH 10/11] Fix barbarian course advanced laps reward collection --- .../kotlin/content/area/kandarin/barbarian_outpost/Gunnjorn.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/src/main/kotlin/content/area/kandarin/barbarian_outpost/Gunnjorn.kt b/game/src/main/kotlin/content/area/kandarin/barbarian_outpost/Gunnjorn.kt index 2d212a20b6..e4f6a31977 100644 --- a/game/src/main/kotlin/content/area/kandarin/barbarian_outpost/Gunnjorn.kt +++ b/game/src/main/kotlin/content/area/kandarin/barbarian_outpost/Gunnjorn.kt @@ -41,7 +41,7 @@ class Gunnjorn : Script { } else { inventoryFull() // TODO correct message } - } else if (get("gnome_course_advanced_laps", 0) >= 250) { + } else if (get("barbarian_course_advanced_laps", 0) >= 250) { npc("Sure, and congratulations, Player! That took dedication and great dexterity to complete that many laps.") npc("As promised, I'll give you an item you may find useful - an Agile top. You'll find yourself lighter than usual while wearing it.") npc("We barbarians are tough folks, as you know, so it'll even keep you safe if you get drawn into combat.") From db918bede0d405fe5d3ee83dcfea5e311abcce28 Mon Sep 17 00:00:00 2001 From: GregHib Date: Wed, 8 Jul 2026 18:01:41 +0100 Subject: [PATCH 11/11] Remove unused hunter data --- data/skill/hunter/hunter.tables.toml | 130 +++++---------------------- 1 file changed, 23 insertions(+), 107 deletions(-) diff --git a/data/skill/hunter/hunter.tables.toml b/data/skill/hunter/hunter.tables.toml index f02c33a731..3fa68fbe8b 100644 --- a/data/skill/hunter/hunter.tables.toml +++ b/data/skill/hunter/hunter.tables.toml @@ -1,140 +1,74 @@ -[trap_npcs] -row = "npc" -trap = "string" - -[.hunting_ojibway_trap_npc] -trap = "bird_snare" - -[.hunting_ojibway_trap_npc_off] -trap = "bird_snare" - -[.hunting_sapling_trap_npc] -trap = "swamp_net" - -[.hunting_sapling_trap_npc_off] -trap = "swamp_net" - [traps] -trap = "obj" # the active (set) object -fail = "obj" # the failed/collapsed object (optional) -setup_anim = "anim" -setup_sound = "string" -take_down_anim = "anim" level = "int" level_default = 1 requires = "list" items = "list" max = "int" max_default = 5 -step_away = "boolean" -step_away_default = true -item_message = "string" -collapse_ticks = "int" -collapse_message = "string" npc = "npc" bait = "item" [.bird_snare] -trap = "bird_snare" -fail = "bird_snare_fail" -setup_anim = "lay_trap" -setup_sound = "set_noose" -take_down_anim = "take_trap" items = ["bird_snare"] -collapse_ticks = 100 -collapse_message = "The bird snare that you laid has fallen over." npc = "hunting_ojibway_trap_npc" [.rabbit_snare] -trap = "rabbit_snare" -fail = "rabbit_snare_fail" -setup_anim = "lay_trap_small" -take_down_anim = "lay_trap_small" +#trap = "rabbit_snare" +#fail = "rabbit_snare_fail" +#setup_anim = "lay_trap_small" +#take_down_anim = "lay_trap_small" items = ["rabbit_snare"] -collapse_ticks = 104 -collapse_message = "The snare that you placed has fallen over." +#collapse_ticks = 104 +#collapse_message = "The snare that you placed has fallen over." npc = "hunting_snare_trap_npc" [.box_trap] level = 27 -trap = "box_trap" -fail = "box_trap_fail" -setup_anim = "lay_trap" -take_down_anim = "lay_trap" +#trap = "box_trap" +#fail = "box_trap_fail" +#setup_anim = "lay_trap" +#take_down_anim = "lay_trap" items = ["box_trap"] npc = "hunting_box_trap_npc" [.boulder_trap] level = 23 -trap = "boulder_trap_setup" -fail = "boulder_trap_fail" -setup_anim = "lay_trap" -take_down_anim = "lay_trap" -requires = ["knife"] +#trap = "boulder_trap_setup" +#fail = "boulder_trap_fail" +#setup_anim = "lay_trap" +#take_down_anim = "lay_trap" +#requires = ["knife"] items = ["logs"] max = 1 -step_away = false npc = "hunting_deadfall_trap_npc" [.swamp_net] level = 29 -trap = "swamp_net_setup" bait = "guam_tar" -fail = "swamp_net_failing" -setup_anim = "lay_net_trap" -setup_sound = "lay_net_trap" -take_down_anim = "take_trap" items = ["rope", "small_fishing_net"] -collapse_ticks = 102 -collapse_message = "The net trap that you set has collapsed." npc = "hunting_sapling_trap_npc" [.orange_net] level = 47 -trap = "orange_net_setup" bait = "marrentill_tar" -setup_anim = "lay_net_trap" -setup_sound = "lay_net_trap" -take_down_anim = "take_trap" items = ["rope", "small_fishing_net"] -collapse_ticks = 102 -collapse_message = "The net trap that you set has collapsed." npc = "hunting_sapling_trap_npc" [.red_net] level = 59 -trap = "red_net_setup" bait = "tarromin_tar" -setup_anim = "lay_net_trap" -setup_sound = "lay_net_trap" -take_down_anim = "take_trap" items = ["rope", "small_fishing_net"] -collapse_ticks = 102 -collapse_message = "The net trap that you set has collapsed." npc = "hunting_sapling_trap_npc" [.black_net] level = 67 -trap = "black_net_setup" bait = "harralander_tar" -setup_anim = "lay_net_trap" -setup_sound = "lay_net_trap" -take_down_anim = "take_trap" items = ["rope", "small_fishing_net"] -collapse_ticks = 102 -collapse_message = "The net trap that you set has collapsed." npc = "hunting_sapling_trap_npc" [.pitfall] level = 31 -trap = "pitfall_sabre_toothed_kyatt_0" -setup_anim = "lay_trap_small" -take_down_anim = "lay_trap_small" -requires = ["knife"] items = ["logs"] -item_message = "You need some logs and a knife to set a pitfall trap." -collapse_ticks = 300 -collapse_message = "The pitfall trap that you constructed has collapsed." [.pitfall_spined_larupia] level = 31 @@ -154,8 +88,6 @@ trap = "string" # which trap type is used caught_obj = "obj" # object id when creature is caught inside level = "int" # minimum hunter level to lure xp = "int" # xp * 0.1 (stored as tenths for int precision) -catch_anim = "anim" # animation played by creature as it enters trap -fail_anim = "anim" # animation played by creature on escape (optional) loot = "list" # items awarded on catch chance = "range" @@ -164,8 +96,6 @@ trap = "bird_snare" caught_obj = "snare_crimson_swift" level = 1 xp = 340 -catch_anim = "bird_catch" -fail_anim = "bird_fail" loot = ["red_feather", "raw_bird_meat", "bones"] chance = [100, 420] @@ -174,8 +104,6 @@ trap = "bird_snare" caught_obj = "snare_golden_warbler" level = 5 xp = 480 -catch_anim = "bird_catch" -fail_anim = "bird_fail" loot = ["yellow_feather", "raw_bird_meat", "bones"] chance = [92, 400] @@ -184,8 +112,6 @@ trap = "bird_snare" caught_obj = "snare_copper_longtail" level = 9 xp = 610 -catch_anim = "bird_catch" -fail_anim = "bird_fail" loot = ["orange_feather", "raw_bird_meat", "bones"] chance = [85, 390] @@ -194,8 +120,6 @@ trap = "bird_snare" caught_obj = "snare_cerulean_twitch" level = 11 xp = 647 -catch_anim = "bird_catch" -fail_anim = "bird_fail" loot = ["blue_feather", "raw_bird_meat", "bones"] chance = [82, 380] @@ -204,8 +128,6 @@ trap = "bird_snare" caught_obj = "snare_tropical_wagtail" level = 19 xp = 952 -catch_anim = "bird_catch" -fail_anim = "bird_fail" loot = ["stripy_feather", "raw_bird_meat", "bones"] chance = [75, 370] @@ -214,8 +136,6 @@ trap = "bird_snare" caught_obj = "snare_wimpy_bird" level = 39 xp = 1670 -catch_anim = "bird_catch" -fail_anim = "bird_fail" loot = ["wimpy_feather", "raw_bird_meat", "bones"] chance = [65, 350] @@ -224,7 +144,7 @@ trap = "box_trap" caught_obj = "box_trap_grey_chinchompa" level = 53 xp = 1984 -catch_anim = "chinchompa_catch" +#catch_anim = "chinchompa_catch" loot = ["box_trap", "chinchompa"] [.red_chinchompa] @@ -232,7 +152,7 @@ trap = "box_trap" caught_obj = "box_trap_red_chinchompa" level = 63 xp = 2650 -catch_anim = "chinchompa_catch" +#catch_anim = "chinchompa_catch" loot = ["box_trap", "red_chinchompa"] [.barb_tailed_kebbit] @@ -240,15 +160,14 @@ trap = "boulder_trap" caught_obj = "boulder_trap_barb_tailed_kebbit" level = 23 xp = 1680 -catch_anim = "kebbit_catch" -fail_anim = "kebbit_fail" +#catch_anim = "kebbit_catch" +#fail_anim = "kebbit_fail" loot = ["bones", "barb_tail_harpoon"] [.swamp_lizard] trap = "swamp_net" level = 29 xp = 1520 -catch_anim = "net_catch" loot = ["green_salamander"] chance = [52, 360] @@ -256,7 +175,6 @@ chance = [52, 360] trap = "orange_net" level = 47 xp = 2240 -catch_anim = "net_catch" loot = ["orange_salamander"] chance = [16, 288] @@ -264,7 +182,6 @@ chance = [16, 288] trap = "red_net" level = 59 xp = 2720 -catch_anim = "net_catch" loot = ["red_salamander"] chance = [0, 240] @@ -272,7 +189,6 @@ chance = [0, 240] trap = "black_net" level = 67 xp = 3040 -catch_anim = "net_catch" loot = ["black_salamander"] chance = [0, 212] @@ -281,7 +197,7 @@ trap = "box_trap" caught_obj = "box_trap_pawya" level = 66 xp = 4000 -catch_anim = "chinchompa_catch" +#catch_anim = "chinchompa_catch" loot = ["bones", "raw_pawya_meat"] [.grenwall] @@ -289,7 +205,7 @@ trap = "box_trap" caught_obj = "box_trap_grenwall" level = 77 xp = 11000 -catch_anim = "chinchompa_catch" +#catch_anim = "chinchompa_catch" loot = ["grenwall_spikes", "grenwall_spikes", "grenwall_spikes", "grenwall_spikes", "grenwall_spikes", "grenwall_spikes", "grenwall_spikes", "grenwall_spikes", "grenwall_spikes", "grenwall_spikes", "grenwall_spikes", "grenwall_spikes", "grenwall_spikes", "grenwall_spikes", "grenwall_spikes", "grenwall_spikes", "grenwall_spikes", "grenwall_spikes"] [.rabbit] @@ -297,8 +213,8 @@ trap = "rabbit_snare" caught_obj = "rabbit_snare_caught" level = 27 xp = 1440 -catch_anim = "lay_trap_small" -fail_anim = "lay_trap_small" +#catch_anim = "lay_trap_small" +#fail_anim = "lay_trap_small" loot = ["rabbit_foot", "raw_rabbit", "bones"] [.spined_larupia]