From 1b1b5d4628cea9d9bf65512ff899f0eac8d22226 Mon Sep 17 00:00:00 2001 From: reldo-dev <316375707+reldo-dev@users.noreply.github.com> Date: Thu, 20 Aug 2026 12:06:58 -0700 Subject: [PATCH 1/4] feat: add fletching recipe tables Five server-only production tables sourced from the wiki's Recipe bucket, split by the script that will consume them: cutting, stringing, attaching, gem tips and assembly. 411 raw wiki rows for uses_skill=Fletching reduce to the 204 here; the header comment carries the full accounting of what was dropped and why. Three things are worth knowing before reading the data: XP is stored multiplied by ten. 46 of these recipes have fractional xp - a magic shortbow is 83.3 - and the column is an int, matching how CombinationRune already stores tenths. Content divides once. Only obj.feather appears as an input. Any feather fletches bolts, darts and headless arrows, and the other seven types are substituted in the content layer rather than duplicated as 154 near-identical rows. The `tool` column is held, never consumed, so it is not an input. For cutting and gem tips it is also half the trigger - the tool is used on the first material and the rest come from the inventory, which is how the wiki describes the interaction and matches the vanilla cache's own dbtable.fletching_blowpipe_crafting, split into log_resource and secondary_resource. It is declared only on the three tables that use it, so stringing and attaching generate no tool accessor at all. dbrow ids are append-only within the reserved 56000-56299 block, so no id moves when a later batch lands; gamevals.toml maps them. Tables and the animation enum live in the plugin's own `pack` module, so the cache build never compiles the fletching scripts to reach them. --- content/skills/fletching/build.gradle.kts | 9 + .../skills/fletching/pack/build.gradle.kts | 3 + .../fletching/pack/FletchingPluginPack.kt | 15 + .../skills/fletching/pack/FletchingTables.kt | 2050 +++++++++++++++++ .../pack/configs/fletching_anim.toml | 87 + .../src/main/resources/gamevals.toml | 222 ++ 6 files changed, 2386 insertions(+) create mode 100644 content/skills/fletching/build.gradle.kts create mode 100644 content/skills/fletching/pack/build.gradle.kts create mode 100644 content/skills/fletching/pack/src/main/kotlin/org/rsmod/content/skills/fletching/pack/FletchingPluginPack.kt create mode 100644 content/skills/fletching/pack/src/main/kotlin/org/rsmod/content/skills/fletching/pack/FletchingTables.kt create mode 100644 content/skills/fletching/pack/src/main/resources/pack/configs/fletching_anim.toml create mode 100644 content/skills/fletching/src/main/resources/gamevals.toml diff --git a/content/skills/fletching/build.gradle.kts b/content/skills/fletching/build.gradle.kts new file mode 100644 index 000000000..a5db8fcad --- /dev/null +++ b/content/skills/fletching/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(projects.api.pluginCommons) + implementation(projects.api.registry) + implementation(projects.content.skills.utils) +} diff --git a/content/skills/fletching/pack/build.gradle.kts b/content/skills/fletching/pack/build.gradle.kts new file mode 100644 index 000000000..92f804a7e --- /dev/null +++ b/content/skills/fletching/pack/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + id("base-conventions") +} diff --git a/content/skills/fletching/pack/src/main/kotlin/org/rsmod/content/skills/fletching/pack/FletchingPluginPack.kt b/content/skills/fletching/pack/src/main/kotlin/org/rsmod/content/skills/fletching/pack/FletchingPluginPack.kt new file mode 100644 index 000000000..41a38e0e3 --- /dev/null +++ b/content/skills/fletching/pack/src/main/kotlin/org/rsmod/content/skills/fletching/pack/FletchingPluginPack.kt @@ -0,0 +1,15 @@ +package org.rsmod.content.skills.fletching.pack + +import dev.openrune.definition.dbtables.DBTable +import dev.openrune.pack.PluginPack + +class FletchingPluginPack : PluginPack() { + override fun dbTables(): List = + listOf( + FletchingTables.cutting(), + FletchingTables.stringing(), + FletchingTables.attaching(), + FletchingTables.gemTips(), + FletchingTables.assembly(), + ) +} diff --git a/content/skills/fletching/pack/src/main/kotlin/org/rsmod/content/skills/fletching/pack/FletchingTables.kt b/content/skills/fletching/pack/src/main/kotlin/org/rsmod/content/skills/fletching/pack/FletchingTables.kt new file mode 100644 index 000000000..780b31cd2 --- /dev/null +++ b/content/skills/fletching/pack/src/main/kotlin/org/rsmod/content/skills/fletching/pack/FletchingTables.kt @@ -0,0 +1,2050 @@ +package org.rsmod.content.skills.fletching.pack + +import dev.openrune.definition.util.VarType +import dev.openrune.tables.production.productionTable + +/** + * Fletching recipes, sourced from the wiki Recipe bucket. + * + * XP is stored multiplied by ten. 46 of these recipes have fractional XP — a magic shortbow is 83.3 + * — and the xp column is an int. This matches CombinationRune, which stores tenths the same way. + * Content divides by ten once, in FletchingDefinitions. + * + * Any feather fletches bolts, darts and headless arrows. Only obj.feather appears here; the other + * seven feather types are substituted in the content layer rather than duplicated as rows. + * + * The `tool` column is held, never consumed, so it is not an input. For cutting and gem tips it is + * also half the trigger: the tool is used on the first input and the remaining materials are taken + * from the inventory, which is how the wiki describes the interaction — "the wielder will still + * need a knife in their inventory to use on logs to initiate the cutting" + * (https://oldschool.runescape.wiki/w/Fletching_knife) — and matches the vanilla cache's own + * dbtable.fletching_blowpipe_crafting, which splits its inputs into log_resource and + * secondary_resource. Stringing and attaching are plain item pairs and declare no tool column at + * all; assembly pairs stock with limbs and holds a hammer. + * + * Recipe accounting: 411 raw wiki rows for uses_skill=Fletching reduce to the 204 below. + * - 33 need a facility Forestry doesn't implement: 32 Vale-totem offerings, 1 repair bench. + * - 1 has no output; 1 is an exact duplicate; 154 are coloured-feather duplicates that collapse + * into these obj.feather rows (any feather fletches bolts, darts and headless arrows). + * - 18 are deferred one-offs, each a multi-stage assembly or a boss/quest/Forestry-gated build: the + * 4-stage ballista chain, 6 Greenman mask variants, mith grapple + (unf), lyre, scorching bow, + * webweaver bow (u), bone shortbow. + * - 9 pair a finished arrow with a Seeking headless arrow (e.g. Adamant arrow x150 + Seeking + * headless arrow x150). These are included, not dropped: it's the documented bulk-conversion + * route that turns 150 finished arrows into their seeking variant in one action. + * https://oldschool.runescape.wiki/w/Seeking_headless_arrow + */ +object FletchingTables { + + const val COL_TICKS = 7 + const val COL_TOOL = 8 + + private data class Recipe( + val row: String, + val inputs: List>, + val output: String, + val outputAmount: Int, + val level: Int, + val xp: Double, + val ticks: Int, + val crafting: Int? = null, + /** + * Held, never consumed, so it is not one of [inputs]. For the knife and chisel families it + * is also half the trigger: the tool is used on `inputs[0]` and the rest of the materials + * come from the inventory. Null for the families that need no tool. + */ + val tool: String? = null, + ) + + private fun productionRows(tableId: String, category: String, recipes: List) = + productionTable( + tableId, + serverOnly = true, + defaultCategory = category, + extraColumns = { + column("ticks", COL_TICKS, VarType.INT) + // Declared only where it is used, so the two toolless families don't generate a + // column that no row of theirs could ever fill. + if (recipes.any { it.tool != null }) column("tool", COL_TOOL, VarType.OBJ) + }, + ) { + recipes.forEach { recipe -> + row(recipe.row) { + production { + recipe.inputs.forEach { (item, amount) -> input(item, amount) } + statReq("stat.fletching", recipe.level) + recipe.crafting?.let { statReq("stat.crafting", it) } + xp((recipe.xp * 10).toInt()) + output(recipe.output, recipe.outputAmount) + } + column(COL_TICKS, recipe.ticks) + recipe.tool?.let { columnRSCM(COL_TOOL, it) } + } + } + } + + /** + * Cutting and gem tips are single-tool families by construction — a recipe belongs to them + * *because* the wiki lists a knife or a chisel — so the tool is stated once here rather than + * repeated on all 76 rows. A row that already names a tool keeps it. + */ + private fun List.withTool(tool: String) = map { it.copy(tool = it.tool ?: tool) } + + fun cutting() = + productionRows("dbtable.fletching_cutting", "Cut", CUTTING.withTool("obj.knife")) + + fun stringing() = productionRows("dbtable.fletching_stringing", "String", STRINGING) + + fun attaching() = productionRows("dbtable.fletching_attaching", "Make", ATTACHING) + + fun gemTips() = + productionRows("dbtable.fletching_gem_tips", "Cut", GEM_TIPS.withTool("obj.chisel")) + + // Assembly is mixed: every crossbow and brutal-arrow row needs a hammer, but attaching + // wolfbone arrowtips to flighted ogre arrows needs none, so the hammer is stated per row. + fun assembly() = productionRows("dbtable.fletching_assembly", "Make", ASSEMBLY) + + private val CUTTING = + listOf( + Recipe( + "dbrow.fletching_arrow_shaft_from_logs", + listOf("obj.logs" to 1), + "obj.arrow_shaft", + 15, + level = 1, + xp = 5.0, + ticks = 2, + ), + // Wiki reports 0 ticks for this row - inherited data, not a defaulted blank - so this + // knife action plays no animation. + Recipe( + "dbrow.fletching_brew_scrapey_bark_from_brew_scrapey_logs", + listOf("obj.brew_scrapey_logs" to 1), + "obj.brew_scrapey_bark", + 1, + level = 1, + xp = 50.0, + ticks = 0, + ), + Recipe( + "dbrow.fletching_seeking_arrow_shaft_from_sap_treated_logs", + listOf("obj.sap_treated_logs" to 1), + "obj.seeking_arrow_shaft", + 15, + level = 1, + xp = 5.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_javelin_shaft_from_logs", + listOf("obj.logs" to 1), + "obj.javelin_shaft", + 15, + level = 3, + xp = 5.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_ogre_arrow_shaft_from_achey_tree_logs", + listOf("obj.achey_tree_logs" to 1), + "obj.ogre_arrow_shaft", + 4, + level = 5, + xp = 6.4, + ticks = 3, + ), + Recipe( + "dbrow.fletching_unstrung_shortbow_from_logs", + listOf("obj.logs" to 1), + "obj.unstrung_shortbow", + 1, + level = 5, + xp = 5.0, + ticks = 3, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_stock_wood_from_logs", + listOf("obj.logs" to 1), + "obj.xbows_crossbow_stock_wood", + 1, + level = 9, + xp = 6.0, + ticks = 3, + ), + Recipe( + "dbrow.fletching_unstrung_longbow_from_logs", + listOf("obj.logs" to 1), + "obj.unstrung_longbow", + 1, + level = 10, + xp = 10.0, + ticks = 3, + ), + Recipe( + "dbrow.fletching_arrow_shaft_from_oak_logs", + listOf("obj.oak_logs" to 1), + "obj.arrow_shaft", + 30, + level = 15, + xp = 10.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_seeking_arrow_shaft_from_sap_treated_oak_logs", + listOf("obj.sap_treated_oak_logs" to 1), + "obj.seeking_arrow_shaft", + 30, + level = 15, + xp = 10.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_unstrung_oak_shortbow_from_oak_logs", + listOf("obj.oak_logs" to 1), + "obj.unstrung_oak_shortbow", + 1, + level = 20, + xp = 16.5, + ticks = 3, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_stock_oak_from_oak_logs", + listOf("obj.oak_logs" to 1), + "obj.xbows_crossbow_stock_oak", + 1, + level = 24, + xp = 16.0, + ticks = 3, + ), + Recipe( + "dbrow.fletching_unstrung_oak_longbow_from_oak_logs", + listOf("obj.oak_logs" to 1), + "obj.unstrung_oak_longbow", + 1, + level = 25, + xp = 25.0, + ticks = 3, + ), + Recipe( + "dbrow.fletching_oak_shield_from_oak_logs", + listOf("obj.oak_logs" to 2), + "obj.oak_shield", + 1, + level = 27, + xp = 50.0, + ticks = 7, + ), + Recipe( + "dbrow.fletching_arrow_shaft_from_willow_logs", + listOf("obj.willow_logs" to 1), + "obj.arrow_shaft", + 45, + level = 30, + xp = 15.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_seeking_arrow_shaft_from_sap_treated_willow_logs", + listOf("obj.sap_treated_willow_logs" to 1), + "obj.seeking_arrow_shaft", + 45, + level = 30, + xp = 15.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_unstrung_willow_shortbow_from_willow_logs", + listOf("obj.willow_logs" to 1), + "obj.unstrung_willow_shortbow", + 1, + level = 35, + xp = 33.3, + ticks = 3, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_stock_willow_from_willow_logs", + listOf("obj.willow_logs" to 1), + "obj.xbows_crossbow_stock_willow", + 1, + level = 39, + xp = 22.0, + ticks = 3, + ), + Recipe( + "dbrow.fletching_unstrung_willow_longbow_from_willow_logs", + listOf("obj.willow_logs" to 1), + "obj.unstrung_willow_longbow", + 1, + level = 40, + xp = 41.5, + ticks = 3, + ), + Recipe( + "dbrow.fletching_willow_shield_from_willow_logs", + listOf("obj.willow_logs" to 2), + "obj.willow_shield", + 1, + level = 42, + xp = 83.0, + ticks = 7, + ), + Recipe( + "dbrow.fletching_arrow_shaft_from_maple_logs", + listOf("obj.maple_logs" to 1), + "obj.arrow_shaft", + 60, + level = 45, + xp = 20.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_seeking_arrow_shaft_from_sap_treated_maple_logs", + listOf("obj.sap_treated_maple_logs" to 1), + "obj.seeking_arrow_shaft", + 60, + level = 45, + xp = 20.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_stock_teak_from_teak_logs", + listOf("obj.teak_logs" to 1), + "obj.xbows_crossbow_stock_teak", + 1, + level = 46, + xp = 27.0, + ticks = 3, + ), + Recipe( + "dbrow.fletching_unstrung_maple_shortbow_from_maple_logs", + listOf("obj.maple_logs" to 1), + "obj.unstrung_maple_shortbow", + 1, + level = 50, + xp = 50.0, + ticks = 3, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_stock_maple_from_maple_logs", + listOf("obj.maple_logs" to 1), + "obj.xbows_crossbow_stock_maple", + 1, + level = 54, + xp = 32.0, + ticks = 3, + ), + Recipe( + "dbrow.fletching_unstrung_maple_longbow_from_maple_logs", + listOf("obj.maple_logs" to 1), + "obj.unstrung_maple_longbow", + 1, + level = 55, + xp = 58.3, + ticks = 3, + ), + Recipe( + "dbrow.fletching_maple_shield_from_maple_logs", + listOf("obj.maple_logs" to 2), + "obj.maple_shield", + 1, + level = 57, + xp = 116.5, + ticks = 7, + ), + Recipe( + "dbrow.fletching_arrow_shaft_from_yew_logs", + listOf("obj.yew_logs" to 1), + "obj.arrow_shaft", + 75, + level = 60, + xp = 25.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_seeking_arrow_shaft_from_sap_treated_yew_logs", + listOf("obj.sap_treated_yew_logs" to 1), + "obj.seeking_arrow_shaft", + 75, + level = 60, + xp = 25.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_stock_mahogany_from_mahogany_logs", + listOf("obj.mahogany_logs" to 1), + "obj.xbows_crossbow_stock_mahogany", + 1, + level = 61, + xp = 41.0, + ticks = 3, + ), + Recipe( + "dbrow.fletching_unstrung_yew_shortbow_from_yew_logs", + listOf("obj.yew_logs" to 1), + "obj.unstrung_yew_shortbow", + 1, + level = 65, + xp = 67.5, + ticks = 3, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_stock_yew_from_yew_logs", + listOf("obj.yew_logs" to 1), + "obj.xbows_crossbow_stock_yew", + 1, + level = 69, + xp = 50.0, + ticks = 3, + ), + Recipe( + "dbrow.fletching_blisterwood_stake_from_blisterwood_logs", + listOf("obj.blisterwood_logs" to 1), + "obj.blisterwood_stake", + 5, + level = 70, + xp = 0.0, + ticks = 3, + ), + Recipe( + "dbrow.fletching_unstrung_yew_longbow_from_yew_logs", + listOf("obj.yew_logs" to 1), + "obj.unstrung_yew_longbow", + 1, + level = 70, + xp = 75.0, + ticks = 3, + ), + Recipe( + "dbrow.fletching_yew_shield_from_yew_logs", + listOf("obj.yew_logs" to 2), + "obj.yew_shield", + 1, + level = 72, + xp = 150.0, + ticks = 7, + ), + Recipe( + "dbrow.fletching_atlatl_dart_shaft_from_ent_branch", + listOf("obj.ent_branch" to 1), + "obj.atlatl_dart_shaft", + 100, + level = 74, + xp = 30.0, + ticks = 3, + ), + Recipe( + "dbrow.fletching_arrow_shaft_from_magic_logs", + listOf("obj.magic_logs" to 1), + "obj.arrow_shaft", + 90, + level = 75, + xp = 30.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_seeking_arrow_shaft_from_sap_treated_magic_logs", + listOf("obj.sap_treated_magic_logs" to 1), + "obj.seeking_arrow_shaft", + 90, + level = 75, + xp = 30.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_stock_magic_from_magic_logs", + listOf("obj.magic_logs" to 1), + "obj.xbows_crossbow_stock_magic", + 1, + level = 78, + xp = 70.0, + ticks = 3, + ), + Recipe( + "dbrow.fletching_unstrung_magic_shortbow_from_magic_logs", + listOf("obj.magic_logs" to 1), + "obj.unstrung_magic_shortbow", + 1, + level = 80, + xp = 83.3, + ticks = 3, + ), + Recipe( + "dbrow.fletching_unstrung_magic_longbow_from_magic_logs", + listOf("obj.magic_logs" to 1), + "obj.unstrung_magic_longbow", + 1, + level = 85, + xp = 91.5, + ticks = 3, + ), + Recipe( + "dbrow.fletching_magic_shield_from_magic_logs", + listOf("obj.magic_logs" to 2), + "obj.magic_shield", + 1, + level = 87, + xp = 183.0, + ticks = 7, + ), + Recipe( + "dbrow.fletching_arrow_shaft_from_redwood_logs", + listOf("obj.redwood_logs" to 1), + "obj.arrow_shaft", + 105, + level = 90, + xp = 35.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_redwood_hiking_staff_from_redwood_logs", + listOf("obj.redwood_logs" to 1), + "obj.redwood_hiking_staff", + 1, + level = 90, + xp = 10.5, + ticks = 3, + ), + Recipe( + "dbrow.fletching_seeking_arrow_shaft_from_sap_treated_redwood_logs", + listOf("obj.sap_treated_redwood_logs" to 1), + "obj.seeking_arrow_shaft", + 105, + level = 90, + xp = 35.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_redwood_shield_from_redwood_logs", + listOf("obj.redwood_logs" to 2), + "obj.redwood_shield", + 1, + level = 92, + xp = 216.0, + ticks = 7, + ), + // Unlocked by the tool column: the knife is used on the log named first, and any + // further material is taken from the inventory. Battlestaff and Root kindling need no + // second material and were only ever held back alongside these. + Recipe( + "dbrow.fletching_unstrung_zogre_bow_from_achey_tree_logs", + listOf("obj.achey_tree_logs" to 1, "obj.wolf_bones" to 1), + "obj.unstrung_zogre_bow", + 1, + level = 30, + xp = 45.0, + ticks = 3, + ), + Recipe( + "dbrow.fletching_battlestaff_from_celastrus_wood", + listOf("obj.celastrus_wood" to 1), + "obj.battlestaff", + 1, + level = 40, + xp = 80.0, + ticks = 4, + ), + Recipe( + "dbrow.fletching_vmq4_root_kindling_from_vmq4_roots", + listOf("obj.vmq4_roots" to 1), + "obj.vmq4_root_kindling", + 4, + level = 52, + xp = 10.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_greenman_statue_from_maple_logs", + listOf("obj.maple_logs" to 1, "obj.ent_branch" to 5, "obj.greenman_mask" to 1), + "obj.greenman_statue", + 1, + level = 53, + xp = 55.0, + ticks = 4, + ), + Recipe( + "dbrow.fletching_camphor_blowpipe_empty_from_camphor_logs", + listOf("obj.camphor_logs" to 2, "obj.squid_beak" to 1), + "obj.camphor_blowpipe_empty", + 1, + level = 58, + xp = 140.0, + ticks = 3, + ), + Recipe( + "dbrow.fletching_blisterwood_sickle_from_blisterwood_logs", + listOf("obj.blisterwood_logs" to 1, "obj.silver_sickle_ruby_enchanted" to 1), + "obj.blisterwood_sickle", + 1, + level = 60, + xp = 82.0, + ticks = 1, + ), + Recipe( + "dbrow.fletching_hg_hunter_spear_from_teak_logs", + listOf( + "obj.teak_logs" to 1, + "obj.hg_speartip" to 5, + "obj.hunting_jerboa_tail" to 5, + ), + "obj.hg_hunter_spear", + 5, + level = 60, + xp = 9.5, + ticks = 3, + ), + Recipe( + "dbrow.fletching_blisterwood_sickle_enhanced_from_blisterwood_logs", + listOf("obj.blisterwood_logs" to 1, "obj.silver_sickle_diamond_enchanted" to 1), + "obj.blisterwood_sickle_enhanced", + 1, + level = 70, + xp = 94.0, + ticks = 1, + ), + Recipe( + "dbrow.fletching_ironwood_blowpipe_empty_from_ironwood_logs", + listOf("obj.ironwood_logs" to 2, "obj.squid_beak" to 1), + "obj.ironwood_blowpipe_empty", + 1, + level = 72, + xp = 170.0, + ticks = 3, + ), + Recipe( + "dbrow.fletching_greenman_wall_decoration_from_yew_logs", + listOf("obj.yew_logs" to 1, "obj.ent_branch" to 5, "obj.greenman_mask" to 1), + "obj.greenman_wall_decoration", + 1, + level = 79, + xp = 70.0, + ticks = 2, + ), // wiki ticks blank + Recipe( + "dbrow.fletching_rosewood_blowpipe_empty_from_rosewood_logs", + listOf("obj.rosewood_logs" to 2, "obj.squid_beak" to 1), + "obj.rosewood_blowpipe_empty", + 1, + level = 84, + xp = 200.0, + ticks = 3, + ), + ) + + private val STRINGING = + listOf( + Recipe( + "dbrow.fletching_shortbow_from_bow_string", + listOf("obj.bow_string" to 1, "obj.unstrung_shortbow" to 1), + "obj.shortbow", + 1, + level = 5, + xp = 5.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_bronze_from_xbows_crossbow_unstrung_bronze", + listOf("obj.xbows_crossbow_unstrung_bronze" to 1, "obj.xbows_crossbow_string" to 1), + "obj.xbows_crossbow_bronze", + 1, + level = 9, + xp = 6.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_longbow_from_bow_string", + listOf("obj.bow_string" to 1, "obj.unstrung_longbow" to 1), + "obj.longbow", + 1, + level = 10, + xp = 10.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_oak_shortbow_from_bow_string", + listOf("obj.bow_string" to 1, "obj.unstrung_oak_shortbow" to 1), + "obj.oak_shortbow", + 1, + level = 20, + xp = 16.5, + ticks = 2, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_blurite_from_xbows_crossbow_unstrung_blurite", + listOf( + "obj.xbows_crossbow_unstrung_blurite" to 1, + "obj.xbows_crossbow_string" to 1, + ), + "obj.xbows_crossbow_blurite", + 1, + level = 24, + xp = 16.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_oak_longbow_from_bow_string", + listOf("obj.bow_string" to 1, "obj.unstrung_oak_longbow" to 1), + "obj.oak_longbow", + 1, + level = 25, + xp = 25.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_zogre_bow_from_bow_string", + listOf("obj.bow_string" to 1, "obj.unstrung_zogre_bow" to 1), + "obj.zogre_bow", + 1, + level = 30, + xp = 45.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_willow_shortbow_from_bow_string", + listOf("obj.bow_string" to 1, "obj.unstrung_willow_shortbow" to 1), + "obj.willow_shortbow", + 1, + level = 35, + xp = 33.2, + ticks = 2, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_iron_from_xbows_crossbow_string", + listOf("obj.xbows_crossbow_string" to 1, "obj.xbows_crossbow_unstrung_iron" to 1), + "obj.xbows_crossbow_iron", + 1, + level = 39, + xp = 22.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_willow_longbow_from_bow_string", + listOf("obj.bow_string" to 1, "obj.unstrung_willow_longbow" to 1), + "obj.willow_longbow", + 1, + level = 40, + xp = 41.5, + ticks = 2, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_steel_from_xbows_crossbow_string", + listOf("obj.xbows_crossbow_string" to 1, "obj.xbows_crossbow_unstrung_steel" to 1), + "obj.xbows_crossbow_steel", + 1, + level = 46, + xp = 27.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_devious_bowsword_from_bow_string", + listOf("obj.bow_string" to 1, "obj.devious_slenderblade" to 1), + "obj.devious_bowsword", + 1, + level = 50, + xp = 50.0, + ticks = 2, + ), // wiki ticks blank + Recipe( + "dbrow.fletching_maple_shortbow_from_bow_string", + listOf("obj.bow_string" to 1, "obj.unstrung_maple_shortbow" to 1), + "obj.maple_shortbow", + 1, + level = 50, + xp = 50.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_mithril_from_xbows_crossbow_string", + listOf( + "obj.xbows_crossbow_string" to 1, + "obj.xbows_crossbow_unstrung_mithril" to 1, + ), + "obj.xbows_crossbow_mithril", + 1, + level = 54, + xp = 32.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_maple_longbow_from_bow_string", + listOf("obj.bow_string" to 1, "obj.unstrung_maple_longbow" to 1), + "obj.maple_longbow", + 1, + level = 55, + xp = 58.2, + ticks = 2, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_adamantite_from_xbows_crossbow_unstrung_adamantite", + listOf( + "obj.xbows_crossbow_unstrung_adamantite" to 1, + "obj.xbows_crossbow_string" to 1, + ), + "obj.xbows_crossbow_adamantite", + 1, + level = 61, + xp = 41.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_yew_shortbow_from_bow_string", + listOf("obj.bow_string" to 1, "obj.unstrung_yew_shortbow" to 1), + "obj.yew_shortbow", + 1, + level = 65, + xp = 67.5, + ticks = 2, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_runite_from_xbows_crossbow_string", + listOf("obj.xbows_crossbow_string" to 1, "obj.xbows_crossbow_unstrung_runite" to 1), + "obj.xbows_crossbow_runite", + 1, + level = 69, + xp = 50.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_yew_longbow_from_bow_string", + listOf("obj.bow_string" to 1, "obj.unstrung_yew_longbow" to 1), + "obj.yew_longbow", + 1, + level = 70, + xp = 75.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_dragon_from_xbows_crossbow_string", + listOf("obj.xbows_crossbow_string" to 1, "obj.xbows_crossbow_unstrung_dragon" to 1), + "obj.xbows_crossbow_dragon", + 1, + level = 78, + xp = 70.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_magic_shortbow_from_bow_string", + listOf("obj.bow_string" to 1, "obj.unstrung_magic_shortbow" to 1), + "obj.magic_shortbow", + 1, + level = 80, + xp = 83.3, + ticks = 2, + ), + Recipe( + "dbrow.fletching_magic_longbow_from_bow_string", + listOf("obj.bow_string" to 1, "obj.unstrung_magic_longbow" to 1), + "obj.magic_longbow", + 1, + level = 85, + xp = 91.5, + ticks = 2, + ), + ) + + private val ATTACHING = + listOf( + Recipe( + "dbrow.fletching_bronze_arrow_from_bronze_arrowheads", + listOf("obj.bronze_arrowheads" to 15, "obj.headless_arrow" to 15), + "obj.bronze_arrow", + 15, + level = 1, + xp = 19.5, + ticks = 2, + ), + Recipe( + "dbrow.fletching_headless_arrow_from_arrow_shaft", + listOf("obj.arrow_shaft" to 15, "obj.feather" to 15), + "obj.headless_arrow", + 15, + level = 1, + xp = 15.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_seeking_adamant_arrow_from_adamant_arrow", + listOf("obj.adamant_arrow" to 15, "obj.bucket_of_bloodwood_sap" to 1), + "obj.seeking_adamant_arrow", + 15, + level = 1, + xp = 0.0, + ticks = 1, + ), + Recipe( + "dbrow.fletching_seeking_amethyst_arrow_from_amethyst_arrow", + listOf("obj.amethyst_arrow" to 15, "obj.bucket_of_bloodwood_sap" to 1), + "obj.seeking_amethyst_arrow", + 15, + level = 1, + xp = 0.0, + ticks = 1, + ), + // Broad arrows/bolts below need an unlocked Slayer reward; that gate does not exist + // in the codebase yet, so only the stat.fletching level check is enforced. + Recipe( + "dbrow.fletching_seeking_slayer_broad_arrows_from_slayer_broad_arrows", + listOf("obj.slayer_broad_arrows" to 15, "obj.bucket_of_bloodwood_sap" to 1), + "obj.seeking_slayer_broad_arrows", + 15, + level = 1, + xp = 0.0, + ticks = 1, + ), + Recipe( + "dbrow.fletching_seeking_bronze_arrow_from_bronze_arrowheads", + listOf("obj.bronze_arrowheads" to 15, "obj.seeking_headless_arrow" to 15), + "obj.seeking_bronze_arrow", + 15, + level = 1, + xp = 19.5, + ticks = 2, + ), + Recipe( + "dbrow.fletching_seeking_bronze_arrow_from_bronze_arrow", + listOf("obj.bronze_arrow" to 15, "obj.bucket_of_bloodwood_sap" to 1), + "obj.seeking_bronze_arrow", + 15, + level = 1, + xp = 0.0, + ticks = 1, + ), + Recipe( + "dbrow.fletching_seeking_dragon_arrow_from_bucket_of_bloodwood_sap", + listOf("obj.bucket_of_bloodwood_sap" to 1, "obj.dragon_arrow" to 15), + "obj.seeking_dragon_arrow", + 15, + level = 1, + xp = 0.0, + ticks = 1, + ), + Recipe( + "dbrow.fletching_seeking_headless_arrow_from_feather", + listOf("obj.feather" to 15, "obj.seeking_arrow_shaft" to 15), + "obj.seeking_headless_arrow", + 15, + level = 1, + xp = 15.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_seeking_iron_arrow_from_bucket_of_bloodwood_sap", + listOf("obj.bucket_of_bloodwood_sap" to 1, "obj.iron_arrow" to 15), + "obj.seeking_iron_arrow", + 15, + level = 1, + xp = 0.0, + ticks = 1, + ), + Recipe( + "dbrow.fletching_seeking_mithril_arrow_from_bucket_of_bloodwood_sap", + listOf("obj.bucket_of_bloodwood_sap" to 1, "obj.mithril_arrow" to 15), + "obj.seeking_mithril_arrow", + 15, + level = 1, + xp = 0.0, + ticks = 1, + ), + Recipe( + "dbrow.fletching_seeking_rune_arrow_from_bucket_of_bloodwood_sap", + listOf("obj.bucket_of_bloodwood_sap" to 1, "obj.rune_arrow" to 15), + "obj.seeking_rune_arrow", + 15, + level = 1, + xp = 0.0, + ticks = 1, + ), + Recipe( + "dbrow.fletching_seeking_steel_arrow_from_bucket_of_bloodwood_sap", + listOf("obj.bucket_of_bloodwood_sap" to 1, "obj.steel_arrow" to 15), + "obj.seeking_steel_arrow", + 15, + level = 1, + xp = 0.0, + ticks = 1, + ), + Recipe( + "dbrow.fletching_bronze_javelin_from_bronze_javelin_head", + listOf("obj.bronze_javelin_head" to 15, "obj.javelin_shaft" to 15), + "obj.bronze_javelin", + 15, + level = 3, + xp = 15.0, + ticks = 1, + ), + Recipe( + "dbrow.fletching_ogre_headless_arrow_from_feather", + listOf("obj.feather" to 24, "obj.ogre_arrow_shaft" to 6), + "obj.ogre_headless_arrow", + 6, + level = 5, + xp = 5.4, + ticks = 2, + ), + // The wiki Recipe bucket lists ticks=0 for the ten feather + bolts(unf) -> bolts + // rows in this family, but its own mechanics page disagrees: "Fletching arrows (all + // steps) or feathering bolts: 2 ticks per set of 15 arrows/10 bolts" - + // https://oldschool.runescape.wiki/w/Game_tick/Action_lengths. Corrected to 2 below; + // the dart rows here are unaffected and correctly stay at 0 (up to 10 dart sets can + // queue per tick, which is why darts are the fastest fletching xp in the game). + Recipe( + "dbrow.fletching_bolt_from_xbows_crossbow_bolts_bronze_unfeathered", + listOf("obj.xbows_crossbow_bolts_bronze_unfeathered" to 10, "obj.feather" to 10), + "obj.bolt", + 10, + level = 9, + xp = 5.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_bronze_dart_from_bronze_dart_tip", + listOf("obj.bronze_dart_tip" to 10, "obj.feather" to 10), + "obj.bronze_dart", + 10, + level = 10, + xp = 18.0, + ticks = 0, + ), + Recipe( + "dbrow.fletching_thprotodart_from_feather", + listOf("obj.feather" to 10, "obj.thprotodarttip" to 1), + "obj.thprotodart", + 1, + level = 10, + xp = 10.0, + ticks = 2, + ), // wiki ticks blank + Recipe( + "dbrow.fletching_opal_bolt_from_bolt", + listOf("obj.bolt" to 10, "obj.opal_bolttips" to 10), + "obj.opal_bolt", + 10, + level = 11, + xp = 16.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_iron_arrow_from_headless_arrow", + listOf("obj.headless_arrow" to 15, "obj.iron_arrowheads" to 15), + "obj.iron_arrow", + 15, + level = 15, + xp = 37.5, + ticks = 2, + ), + Recipe( + "dbrow.fletching_seeking_iron_arrow_from_iron_arrowheads", + listOf("obj.iron_arrowheads" to 15, "obj.seeking_headless_arrow" to 15), + "obj.seeking_iron_arrow", + 15, + level = 15, + xp = 37.5, + ticks = 2, + ), + Recipe( + "dbrow.fletching_seeking_steel_arrow_from_seeking_headless_arrow", + listOf("obj.seeking_headless_arrow" to 15, "obj.steel_arrowheads" to 15), + "obj.seeking_steel_arrow", + 15, + level = 15, + xp = 37.5, + ticks = 2, + ), + Recipe( + "dbrow.fletching_iron_javelin_from_iron_javelin_head", + listOf("obj.iron_javelin_head" to 15, "obj.javelin_shaft" to 15), + "obj.iron_javelin", + 15, + level = 17, + xp = 30.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_iron_dart_from_feather", + listOf("obj.feather" to 10, "obj.iron_dart_tip" to 10), + "obj.iron_dart", + 10, + level = 22, + xp = 38.0, + ticks = 0, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_bolts_blurite_from_xbows_crossbow_bolts_blurite_unfeathered", + listOf("obj.xbows_crossbow_bolts_blurite_unfeathered" to 10, "obj.feather" to 10), + "obj.xbows_crossbow_bolts_blurite", + 10, + level = 24, + xp = 10.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_bolts_blurite_tipped_jade_from_xbows_crossbow_bolts_blurite", + listOf("obj.xbows_crossbow_bolts_blurite" to 10, "obj.xbows_bolt_tips_jade" to 10), + "obj.xbows_crossbow_bolts_blurite_tipped_jade", + 10, + level = 26, + xp = 24.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_steel_arrow_from_headless_arrow", + listOf("obj.headless_arrow" to 15, "obj.steel_arrowheads" to 15), + "obj.steel_arrow", + 15, + level = 30, + xp = 75.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_steel_javelin_from_javelin_shaft", + listOf("obj.javelin_shaft" to 15, "obj.steel_javelin_head" to 15), + "obj.steel_javelin", + 15, + level = 32, + xp = 75.0, + ticks = 1, + ), + Recipe( + "dbrow.fletching_steel_dart_from_feather", + listOf("obj.feather" to 10, "obj.steel_dart_tip" to 10), + "obj.steel_dart", + 10, + level = 37, + xp = 75.0, + ticks = 0, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_bolts_iron_from_feather", + listOf("obj.feather" to 10, "obj.xbows_crossbow_bolts_iron_unfeathered" to 10), + "obj.xbows_crossbow_bolts_iron", + 10, + level = 39, + xp = 15.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_pearl_bolt_from_xbows_crossbow_bolts_iron", + listOf("obj.xbows_crossbow_bolts_iron" to 10, "obj.pearl_bolttips" to 10), + "obj.pearl_bolt", + 10, + level = 41, + xp = 32.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_bolts_silver_from_feather", + listOf("obj.feather" to 10, "obj.xbows_crossbow_bolts_silver_unfeathered" to 10), + "obj.xbows_crossbow_bolts_silver", + 10, + level = 43, + xp = 25.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_mithril_arrow_from_headless_arrow", + listOf("obj.headless_arrow" to 15, "obj.mithril_arrowheads" to 15), + "obj.mithril_arrow", + 15, + level = 45, + xp = 112.5, + ticks = 2, + ), + Recipe( + "dbrow.fletching_seeking_mithril_arrow_from_mithril_arrowheads", + listOf("obj.mithril_arrowheads" to 15, "obj.seeking_headless_arrow" to 15), + "obj.seeking_mithril_arrow", + 15, + level = 45, + xp = 112.5, + ticks = 2, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_bolts_steel_from_feather", + listOf("obj.feather" to 10, "obj.xbows_crossbow_bolts_steel_unfeathered" to 10), + "obj.xbows_crossbow_bolts_steel", + 10, + level = 46, + xp = 35.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_mithril_javelin_from_javelin_shaft", + listOf("obj.javelin_shaft" to 15, "obj.mithril_javelin_head" to 15), + "obj.mithril_javelin", + 15, + level = 47, + xp = 120.0, + ticks = 1, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_bolts_steel_tipped_redtopaz_from_xbows_crossbow_bolts_steel", + listOf( + "obj.xbows_crossbow_bolts_steel" to 10, + "obj.xbows_bolt_tips_redtopaz" to 10, + ), + "obj.xbows_crossbow_bolts_steel_tipped_redtopaz", + 10, + level = 48, + xp = 39.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_barbed_bolt_from_barbed_bolttips", + listOf("obj.barbed_bolttips" to 10, "obj.bolt" to 10), + "obj.barbed_bolt", + 10, + level = 51, + xp = 95.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_slayer_broad_arrows_from_slayer_broad_arrowhead", + listOf("obj.slayer_broad_arrowhead" to 15, "obj.headless_arrow" to 15), + "obj.slayer_broad_arrows", + 15, + level = 52, + xp = 150.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_mithril_dart_from_feather", + listOf("obj.feather" to 10, "obj.mithril_dart_tip" to 10), + "obj.mithril_dart", + 10, + level = 52, + xp = 112.0, + ticks = 0, + ), + Recipe( + "dbrow.fletching_seeking_slayer_broad_arrows_from_slayer_broad_arrowhead", + listOf("obj.slayer_broad_arrowhead" to 15, "obj.seeking_headless_arrow" to 15), + "obj.seeking_slayer_broad_arrows", + 15, + level = 52, + xp = 150.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_bolts_mithril_from_feather", + listOf("obj.feather" to 10, "obj.xbows_crossbow_bolts_mithril_unfeathered" to 10), + "obj.xbows_crossbow_bolts_mithril", + 10, + level = 54, + xp = 50.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_slayer_broad_bolt_from_feather", + listOf("obj.feather" to 10, "obj.slayer_broad_bolt_unfinished" to 10), + "obj.slayer_broad_bolt", + 10, + level = 55, + xp = 30.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_bolts_mithril_tipped_sapphire_from_xbows_crossbow_bolts_mithril", + listOf( + "obj.xbows_crossbow_bolts_mithril" to 10, + "obj.xbows_bolt_tips_sapphire" to 10, + ), + "obj.xbows_crossbow_bolts_mithril_tipped_sapphire", + 10, + level = 56, + xp = 47.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_bolts_mithril_tipped_emerald_from_xbows_bolt_tips_emerald", + listOf( + "obj.xbows_bolt_tips_emerald" to 10, + "obj.xbows_crossbow_bolts_mithril" to 10, + ), + "obj.xbows_crossbow_bolts_mithril_tipped_emerald", + 10, + level = 58, + xp = 55.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_adamant_arrow_from_adamant_arrowheads", + listOf("obj.adamant_arrowheads" to 15, "obj.headless_arrow" to 15), + "obj.adamant_arrow", + 15, + level = 60, + xp = 150.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_seeking_adamant_arrow_from_adamant_arrowheads", + listOf("obj.adamant_arrowheads" to 15, "obj.seeking_headless_arrow" to 15), + "obj.seeking_adamant_arrow", + 15, + level = 60, + xp = 150.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_bolts_adamantite_from_xbows_crossbow_bolts_adamantite_unfeathered", + listOf( + "obj.xbows_crossbow_bolts_adamantite_unfeathered" to 10, + "obj.feather" to 10, + ), + "obj.xbows_crossbow_bolts_adamantite", + 10, + level = 61, + xp = 70.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_adamant_javelin_from_adamant_javelin_head", + listOf("obj.adamant_javelin_head" to 15, "obj.javelin_shaft" to 15), + "obj.adamant_javelin", + 15, + level = 62, + xp = 150.0, + ticks = 1, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_bolts_adamantite_tipped_ruby_from_xbows_crossbow_bolts_adamantite", + listOf( + "obj.xbows_crossbow_bolts_adamantite" to 10, + "obj.xbows_bolt_tips_ruby" to 10, + ), + "obj.xbows_crossbow_bolts_adamantite_tipped_ruby", + 10, + level = 63, + xp = 63.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_bolts_adamantite_tipped_diamond_from_xbows_crossbow_bolts_adamantite", + listOf( + "obj.xbows_crossbow_bolts_adamantite" to 10, + "obj.xbows_bolt_tips_diamond" to 10, + ), + "obj.xbows_crossbow_bolts_adamantite_tipped_diamond", + 10, + level = 65, + xp = 70.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_adamant_dart_from_adamant_dart_tip", + listOf("obj.adamant_dart_tip" to 10, "obj.feather" to 10), + "obj.adamant_dart", + 10, + level = 67, + xp = 150.0, + ticks = 0, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_bolts_runite_from_feather", + listOf("obj.feather" to 10, "obj.xbows_crossbow_bolts_runite_unfeathered" to 10), + "obj.xbows_crossbow_bolts_runite", + 10, + level = 69, + xp = 100.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_bolts_runite_tipped_dragonstone_from_xbows_bolt_tips_dragonstone", + listOf( + "obj.xbows_bolt_tips_dragonstone" to 10, + "obj.xbows_crossbow_bolts_runite" to 10, + ), + "obj.xbows_crossbow_bolts_runite_tipped_dragonstone", + 10, + level = 71, + xp = 82.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_xbows_crossbow_bolts_runite_tipped_onyx_from_xbows_bolt_tips_onyx", + listOf("obj.xbows_bolt_tips_onyx" to 10, "obj.xbows_crossbow_bolts_runite" to 10), + "obj.xbows_crossbow_bolts_runite_tipped_onyx", + 10, + level = 73, + xp = 94.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_atlatl_dart_from_atlatl_dart_tips", + listOf("obj.atlatl_dart_tips" to 20, "obj.headless_atlatl_dart" to 20), + "obj.atlatl_dart", + 20, + level = 74, + xp = 30.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_headless_atlatl_dart_from_atlatl_dart_shaft", + listOf("obj.atlatl_dart_shaft" to 20, "obj.feather" to 20), + "obj.headless_atlatl_dart", + 20, + level = 74, + xp = 20.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_rune_arrow_from_headless_arrow", + listOf("obj.headless_arrow" to 15, "obj.rune_arrowheads" to 15), + "obj.rune_arrow", + 15, + level = 75, + xp = 187.5, + ticks = 2, + ), + Recipe( + "dbrow.fletching_seeking_rune_arrow_from_rune_arrowheads", + listOf("obj.rune_arrowheads" to 15, "obj.seeking_headless_arrow" to 15), + "obj.seeking_rune_arrow", + 15, + level = 75, + xp = 187.5, + ticks = 2, + ), + Recipe( + "dbrow.fletching_slayer_broad_bolt_amethyst_from_xbows_bolt_tips_amethyst", + listOf("obj.xbows_bolt_tips_amethyst" to 10, "obj.slayer_broad_bolt" to 10), + "obj.slayer_broad_bolt_amethyst", + 10, + level = 76, + xp = 106.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_rune_javelin_from_javelin_shaft", + listOf("obj.javelin_shaft" to 15, "obj.rune_javelin_head" to 15), + "obj.rune_javelin", + 15, + level = 77, + xp = 187.5, + ticks = 1, + ), + Recipe( + "dbrow.fletching_rune_dart_from_feather", + listOf("obj.feather" to 10, "obj.rune_dart_tip" to 10), + "obj.rune_dart", + 10, + level = 81, + xp = 188.0, + ticks = 0, + ), + Recipe( + "dbrow.fletching_amethyst_arrow_from_amethyst_arrowheads", + listOf("obj.amethyst_arrowheads" to 15, "obj.headless_arrow" to 15), + "obj.amethyst_arrow", + 15, + level = 82, + xp = 202.5, + ticks = 2, + ), + Recipe( + "dbrow.fletching_seeking_amethyst_arrow_from_amethyst_arrowheads", + listOf("obj.amethyst_arrowheads" to 15, "obj.seeking_headless_arrow" to 15), + "obj.seeking_amethyst_arrow", + 15, + level = 82, + xp = 202.5, + ticks = 2, + ), + Recipe( + "dbrow.fletching_amethyst_javelin_from_amethyst_javelin_head", + listOf("obj.amethyst_javelin_head" to 15, "obj.javelin_shaft" to 15), + "obj.amethyst_javelin", + 15, + level = 84, + xp = 202.5, + ticks = 1, + ), + Recipe( + "dbrow.fletching_dragon_bolts_unenchanted_diamond_from_xbows_bolt_tips_diamond", + listOf("obj.xbows_bolt_tips_diamond" to 10, "obj.dragon_bolts" to 10), + "obj.dragon_bolts_unenchanted_diamond", + 10, + level = 84, + xp = 70.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_dragon_bolts_from_dragon_bolts_unfeathered", + listOf("obj.dragon_bolts_unfeathered" to 10, "obj.feather" to 10), + "obj.dragon_bolts", + 10, + level = 84, + xp = 120.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_dragon_bolts_unenchanted_dragonstone_from_dragon_bolts", + listOf("obj.dragon_bolts" to 10, "obj.xbows_bolt_tips_dragonstone" to 10), + "obj.dragon_bolts_unenchanted_dragonstone", + 10, + level = 84, + xp = 82.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_dragon_bolts_unenchanted_emerald_from_dragon_bolts", + listOf("obj.dragon_bolts" to 10, "obj.xbows_bolt_tips_emerald" to 10), + "obj.dragon_bolts_unenchanted_emerald", + 10, + level = 84, + xp = 55.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_dragon_bolts_unenchanted_jade_from_dragon_bolts", + listOf("obj.dragon_bolts" to 10, "obj.xbows_bolt_tips_jade" to 10), + "obj.dragon_bolts_unenchanted_jade", + 10, + level = 84, + xp = 24.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_dragon_bolts_unenchanted_onyx_from_dragon_bolts", + listOf("obj.dragon_bolts" to 10, "obj.xbows_bolt_tips_onyx" to 10), + "obj.dragon_bolts_unenchanted_onyx", + 10, + level = 84, + xp = 94.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_dragon_bolts_unenchanted_opal_from_dragon_bolts", + listOf("obj.dragon_bolts" to 10, "obj.opal_bolttips" to 10), + "obj.dragon_bolts_unenchanted_opal", + 10, + level = 84, + xp = 16.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_dragon_bolts_unenchanted_pearl_from_dragon_bolts", + listOf("obj.dragon_bolts" to 10, "obj.pearl_bolttips" to 10), + "obj.dragon_bolts_unenchanted_pearl", + 10, + level = 84, + xp = 32.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_dragon_bolts_unenchanted_ruby_from_dragon_bolts", + listOf("obj.dragon_bolts" to 10, "obj.xbows_bolt_tips_ruby" to 10), + "obj.dragon_bolts_unenchanted_ruby", + 10, + level = 84, + xp = 63.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_dragon_bolts_unenchanted_sapphire_from_dragon_bolts", + listOf("obj.dragon_bolts" to 10, "obj.xbows_bolt_tips_sapphire" to 10), + "obj.dragon_bolts_unenchanted_sapphire", + 10, + level = 84, + xp = 47.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_dragon_bolts_unenchanted_topaz_from_dragon_bolts", + listOf("obj.dragon_bolts" to 10, "obj.xbows_bolt_tips_redtopaz" to 10), + "obj.dragon_bolts_unenchanted_topaz", + 10, + level = 84, + xp = 39.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_amethyst_dart_from_amethyst_dart_tip", + listOf("obj.amethyst_dart_tip" to 10, "obj.feather" to 10), + "obj.amethyst_dart", + 10, + level = 90, + xp = 210.0, + ticks = 0, + ), + Recipe( + "dbrow.fletching_dragon_arrow_from_dragon_arrowheads", + listOf("obj.dragon_arrowheads" to 15, "obj.headless_arrow" to 15), + "obj.dragon_arrow", + 15, + level = 90, + xp = 225.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_seeking_dragon_arrow_from_dragon_arrowheads", + listOf("obj.dragon_arrowheads" to 15, "obj.seeking_headless_arrow" to 15), + "obj.seeking_dragon_arrow", + 15, + level = 90, + xp = 225.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_dragon_javelin_from_dragon_javelin_head", + listOf("obj.dragon_javelin_head" to 15, "obj.javelin_shaft" to 15), + "obj.dragon_javelin", + 15, + level = 92, + xp = 225.0, + ticks = 1, + ), + Recipe( + "dbrow.fletching_dragon_dart_from_dragon_dart_tip", + listOf("obj.dragon_dart_tip" to 10, "obj.feather" to 10), + "obj.dragon_dart", + 10, + level = 95, + xp = 250.0, + ticks = 0, + ), + // Seeking headless arrows also bulk-convert already-finished arrows to their seeking + // variant, in sets of 150 - a documented mechanic, not a data error: + // https://oldschool.runescape.wiki/w/Seeking_headless_arrow + Recipe( + "dbrow.fletching_seeking_adamant_arrow_from_adamant_arrow_x150", + listOf("obj.adamant_arrow" to 150, "obj.seeking_headless_arrow" to 150), + "obj.seeking_adamant_arrow", + 150, + level = 1, + xp = 0.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_seeking_amethyst_arrow_from_amethyst_arrow_x150", + listOf("obj.amethyst_arrow" to 150, "obj.seeking_headless_arrow" to 150), + "obj.seeking_amethyst_arrow", + 150, + level = 1, + xp = 0.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_seeking_slayer_broad_arrows_from_slayer_broad_arrows_x150", + listOf("obj.slayer_broad_arrows" to 150, "obj.seeking_headless_arrow" to 150), + "obj.seeking_slayer_broad_arrows", + 150, + level = 1, + xp = 0.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_seeking_bronze_arrow_from_bronze_arrow_x150", + listOf("obj.bronze_arrow" to 150, "obj.seeking_headless_arrow" to 150), + "obj.seeking_bronze_arrow", + 150, + level = 1, + xp = 0.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_seeking_dragon_arrow_from_dragon_arrow_x150", + listOf("obj.dragon_arrow" to 150, "obj.seeking_headless_arrow" to 150), + "obj.seeking_dragon_arrow", + 150, + level = 1, + xp = 0.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_seeking_iron_arrow_from_iron_arrow_x150", + listOf("obj.iron_arrow" to 150, "obj.seeking_headless_arrow" to 150), + "obj.seeking_iron_arrow", + 150, + level = 1, + xp = 0.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_seeking_mithril_arrow_from_mithril_arrow_x150", + listOf("obj.mithril_arrow" to 150, "obj.seeking_headless_arrow" to 150), + "obj.seeking_mithril_arrow", + 150, + level = 1, + xp = 0.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_seeking_rune_arrow_from_rune_arrow_x150", + listOf("obj.rune_arrow" to 150, "obj.seeking_headless_arrow" to 150), + "obj.seeking_rune_arrow", + 150, + level = 1, + xp = 0.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_seeking_steel_arrow_from_steel_arrow_x150", + listOf("obj.steel_arrow" to 150, "obj.seeking_headless_arrow" to 150), + "obj.seeking_steel_arrow", + 150, + level = 1, + xp = 0.0, + ticks = 2, + ), + ) + + private val GEM_TIPS = + listOf( + Recipe( + "dbrow.fletching_wolfbone_arrowheads_from_wolf_bones", + listOf("obj.wolf_bones" to 1), + "obj.wolfbone_arrowheads", + 4, + level = 5, + xp = 10.0, + ticks = 2, + crafting = 1, + ), + Recipe( + "dbrow.fletching_opal_bolttips_from_opal", + listOf("obj.opal" to 1), + "obj.opal_bolttips", + 12, + level = 11, + xp = 1.5, + ticks = 5, + ), + Recipe( + "dbrow.fletching_xbows_bolt_tips_jade_from_jade", + listOf("obj.jade" to 1), + "obj.xbows_bolt_tips_jade", + 12, + level = 26, + xp = 2.0, + ticks = 5, + ), + Recipe( + "dbrow.fletching_huntingbow_bolts_from_huntingbeast_spike", + listOf("obj.huntingbeast_spike" to 1), + "obj.huntingbow_bolts", + 12, + level = 32, + xp = 5.8, + ticks = 3, + ), + Recipe( + "dbrow.fletching_pearl_bolttips_from_smalloysterpearls", + listOf("obj.smalloysterpearls" to 1), + "obj.pearl_bolttips", + 6, + level = 41, + xp = 3.2, + ticks = 5, + ), + Recipe( + "dbrow.fletching_pearl_bolttips_from_bigoysterpearls", + listOf("obj.bigoysterpearls" to 1), + "obj.pearl_bolttips", + 24, + level = 41, + xp = 3.2, + ticks = 5, + ), + Recipe( + "dbrow.fletching_huntingbow_bigbolts_from_huntingbeast_bigspike", + listOf("obj.huntingbeast_bigspike" to 1), + "obj.huntingbow_bigbolts", + 12, + level = 42, + xp = 7.9, + ticks = 2, + ), + Recipe( + "dbrow.fletching_xbows_bolt_tips_redtopaz_from_red_topaz", + listOf("obj.red_topaz" to 1), + "obj.xbows_bolt_tips_redtopaz", + 12, + level = 48, + xp = 3.9, + ticks = 5, + ), + Recipe( + "dbrow.fletching_xbows_bolt_tips_sapphire_from_sapphire", + listOf("obj.sapphire" to 1), + "obj.xbows_bolt_tips_sapphire", + 12, + level = 56, + xp = 4.0, + ticks = 5, + ), + Recipe( + "dbrow.fletching_xbows_bolt_tips_emerald_from_emerald", + listOf("obj.emerald" to 1), + "obj.xbows_bolt_tips_emerald", + 12, + level = 58, + xp = 5.5, + ticks = 5, + ), + Recipe( + "dbrow.fletching_sunlight_antelope_bolt_from_hunting_antelopesun_horn", + listOf("obj.hunting_antelopesun_horn" to 1), + "obj.sunlight_antelope_bolt", + 12, + level = 62, + xp = 10.0, + ticks = 3, + ), + Recipe( + "dbrow.fletching_xbows_bolt_tips_ruby_from_ruby", + listOf("obj.ruby" to 1), + "obj.xbows_bolt_tips_ruby", + 12, + level = 63, + xp = 6.3, + ticks = 5, + ), + Recipe( + "dbrow.fletching_xbows_bolt_tips_diamond_from_diamond", + listOf("obj.diamond" to 1), + "obj.xbows_bolt_tips_diamond", + 12, + level = 65, + xp = 7.0, + ticks = 5, + ), + Recipe( + "dbrow.fletching_xbows_bolt_tips_dragonstone_from_dragonstone", + listOf("obj.dragonstone" to 1), + "obj.xbows_bolt_tips_dragonstone", + 12, + level = 71, + xp = 8.2, + ticks = 5, + ), + Recipe( + "dbrow.fletching_moonlight_antelope_bolt_from_hunting_antelopemoon_horn", + listOf("obj.hunting_antelopemoon_horn" to 1), + "obj.moonlight_antelope_bolt", + 12, + level = 72, + xp = 12.1, + ticks = 3, + ), + Recipe( + "dbrow.fletching_xbows_bolt_tips_onyx_from_onyx", + listOf("obj.onyx" to 1), + "obj.xbows_bolt_tips_onyx", + 24, + level = 73, + xp = 9.4, + ticks = 5, + ), + Recipe( + "dbrow.fletching_atlatl_dart_tips_from_custodian_broken_antler", + listOf("obj.custodian_broken_antler" to 1), + "obj.atlatl_dart_tips", + 100, + level = 74, + xp = 10.0, + ticks = 4, + ), + // Wiki reports 0 ticks for this row - inherited data, not a defaulted blank - so this + // chisel action plays no animation. + Recipe( + "dbrow.fletching_toxic_blowpipe_from_blowpipe_fang", + listOf("obj.blowpipe_fang" to 1), + "obj.toxic_blowpipe", + 1, + level = 78, + xp = 120.0, + ticks = 0, + ), + // Chiselling the antler with a Hunters' crossbow held upgrades the crossbow, so this + // shares its trigger - and its make-X menu - with the antler's bolt-tip row above. + Recipe( + "dbrow.fletching_hunting_crossbow_sunlight_from_hunting_antelopesun_horn", + listOf("obj.hunting_antelopesun_horn" to 1, "obj.hunting_crossbow" to 1), + "obj.hunting_crossbow_sunlight", + 1, + level = 74, + xp = 0.0, + ticks = 0, + ), + ) + + private val ASSEMBLY = + listOf( + Recipe( + "dbrow.fletching_ogre_arrow_from_ogre_headless_arrow", + listOf("obj.ogre_headless_arrow" to 6, "obj.wolfbone_arrowheads" to 6), + "obj.ogre_arrow", + 6, + level = 5, + xp = 6.0, + ticks = 2, + ), + Recipe( + "dbrow.fletching_zogre_brutal_bronze_from_nails_bronze", + listOf("obj.nails_bronze" to 6, "obj.ogre_headless_arrow" to 6), + "obj.zogre_brutal_bronze", + 6, + level = 7, + xp = 8.4, + ticks = 2, + tool = "obj.hammer", + ), + Recipe( + "dbrow.fletching_xbows_crossbow_unstrung_bronze_from_xbows_crossbow_limbs_bronze", + listOf( + "obj.xbows_crossbow_limbs_bronze" to 1, + "obj.xbows_crossbow_stock_wood" to 1, + ), + "obj.xbows_crossbow_unstrung_bronze", + 1, + level = 9, + xp = 12.0, + ticks = 2, + tool = "obj.hammer", + ), + Recipe( + "dbrow.fletching_zogre_brutal_iron_from_ogre_headless_arrow", + listOf("obj.ogre_headless_arrow" to 6, "obj.nails_iron" to 6), + "obj.zogre_brutal_iron", + 6, + level = 18, + xp = 15.6, + ticks = 2, + tool = "obj.hammer", + ), + Recipe( + "dbrow.fletching_xbows_crossbow_unstrung_blurite_from_xbows_crossbow_limbs_blurite", + listOf( + "obj.xbows_crossbow_limbs_blurite" to 1, + "obj.xbows_crossbow_stock_oak" to 1, + ), + "obj.xbows_crossbow_unstrung_blurite", + 1, + level = 24, + xp = 32.0, + ticks = 2, + tool = "obj.hammer", + ), + Recipe( + "dbrow.fletching_zogre_brutal_steel_from_ogre_headless_arrow", + listOf("obj.ogre_headless_arrow" to 6, "obj.nails" to 6), + "obj.zogre_brutal_steel", + 6, + level = 33, + xp = 30.6, + ticks = 2, + tool = "obj.hammer", + ), + Recipe( + "dbrow.fletching_zogre_brutal_black_from_nails_black", + listOf("obj.nails_black" to 6, "obj.ogre_headless_arrow" to 6), + "obj.zogre_brutal_black", + 6, + level = 38, + xp = 39.0, + ticks = 2, + tool = "obj.hammer", + ), + Recipe( + "dbrow.fletching_xbows_crossbow_unstrung_iron_from_xbows_crossbow_limbs_iron", + listOf( + "obj.xbows_crossbow_limbs_iron" to 1, + "obj.xbows_crossbow_stock_willow" to 1, + ), + "obj.xbows_crossbow_unstrung_iron", + 1, + level = 39, + xp = 44.0, + ticks = 2, + tool = "obj.hammer", + ), + Recipe( + "dbrow.fletching_xbows_crossbow_unstrung_steel_from_xbows_crossbow_limbs_steel", + listOf("obj.xbows_crossbow_limbs_steel" to 1, "obj.xbows_crossbow_stock_teak" to 1), + "obj.xbows_crossbow_unstrung_steel", + 1, + level = 46, + xp = 54.0, + ticks = 2, + tool = "obj.hammer", + ), + Recipe( + "dbrow.fletching_zogre_brutal_mithril_from_ogre_headless_arrow", + listOf("obj.ogre_headless_arrow" to 6, "obj.nails_mithril" to 6), + "obj.zogre_brutal_mithril", + 6, + level = 49, + xp = 45.0, + ticks = 2, + tool = "obj.hammer", + ), + Recipe( + "dbrow.fletching_xbows_crossbow_unstrung_mithril_from_xbows_crossbow_stock_maple", + listOf( + "obj.xbows_crossbow_stock_maple" to 1, + "obj.xbows_crossbow_limbs_mithril" to 1, + ), + "obj.xbows_crossbow_unstrung_mithril", + 1, + level = 54, + xp = 64.0, + ticks = 2, + tool = "obj.hammer", + ), + Recipe( + "dbrow.fletching_xbows_crossbow_unstrung_adamantite_from_xbows_crossbow_limbs_adamantite", + listOf( + "obj.xbows_crossbow_limbs_adamantite" to 1, + "obj.xbows_crossbow_stock_mahogany" to 1, + ), + "obj.xbows_crossbow_unstrung_adamantite", + 1, + level = 61, + xp = 82.0, + ticks = 2, + tool = "obj.hammer", + ), + Recipe( + "dbrow.fletching_zogre_brutal_adamant_from_nails_adamant", + listOf("obj.nails_adamant" to 6, "obj.ogre_headless_arrow" to 6), + "obj.zogre_brutal_adamant", + 6, + level = 62, + xp = 61.2, + ticks = 2, + tool = "obj.hammer", + ), + Recipe( + "dbrow.fletching_xbows_crossbow_unstrung_runite_from_xbows_crossbow_limbs_runite", + listOf("obj.xbows_crossbow_limbs_runite" to 1, "obj.xbows_crossbow_stock_yew" to 1), + "obj.xbows_crossbow_unstrung_runite", + 1, + level = 69, + xp = 100.0, + ticks = 2, + tool = "obj.hammer", + ), + Recipe( + "dbrow.fletching_zogre_brutal_rune_from_ogre_headless_arrow", + listOf("obj.ogre_headless_arrow" to 6, "obj.nails_rune" to 6), + "obj.zogre_brutal_rune", + 6, + level = 77, + xp = 75.0, + ticks = 2, + tool = "obj.hammer", + ), + Recipe( + "dbrow.fletching_xbows_crossbow_unstrung_dragon_from_xbows_crossbow_limbs_dragon", + listOf( + "obj.xbows_crossbow_limbs_dragon" to 1, + "obj.xbows_crossbow_stock_magic" to 1, + ), + "obj.xbows_crossbow_unstrung_dragon", + 1, + level = 78, + xp = 135.0, + ticks = 2, + tool = "obj.hammer", + ), + ) +} diff --git a/content/skills/fletching/pack/src/main/resources/pack/configs/fletching_anim.toml b/content/skills/fletching/pack/src/main/resources/pack/configs/fletching_anim.toml new file mode 100644 index 000000000..9b6f9117c --- /dev/null +++ b/content/skills/fletching/pack/src/main/resources/pack/configs/fletching_anim.toml @@ -0,0 +1,87 @@ +[[enum]] +isServerOnly = true +id = "enum.fletching_anims" +keyType = "OBJ" +valueType = "SEQ" + +[enum.values] +"obj.adamant_arrow" = "seq.human_fletching_add_arrow_tips" +"obj.adamant_dart" = "seq.human_fletching_add_dart_feathers_adamant" +"obj.amethyst_arrow" = "seq.human_fletching_add_arrow_tips" +"obj.amethyst_dart" = "seq.human_fletching_add_dart_feathers_amethyst" +"obj.barbed_bolt" = "seq.human_fletching_add_bolt_tips_bronze" +"obj.bolt" = "seq.human_fletching_add_bolt_feathers_bronze" +"obj.bronze_arrow" = "seq.human_fletching_add_arrow_tips" +"obj.bronze_dart" = "seq.human_fletching_add_dart_feathers_bronze" +"obj.dragon_arrow" = "seq.human_fletching_add_arrow_tips" +"obj.dragon_bolts" = "seq.human_fletching_add_bolt_feathers_dragon" +"obj.dragon_bolts_unenchanted_diamond" = "seq.human_fletching_add_bolt_tips_dragon" +"obj.dragon_bolts_unenchanted_dragonstone" = "seq.human_fletching_add_bolt_tips_dragon" +"obj.dragon_bolts_unenchanted_emerald" = "seq.human_fletching_add_bolt_tips_dragon" +"obj.dragon_bolts_unenchanted_jade" = "seq.human_fletching_add_bolt_tips_dragon" +"obj.dragon_bolts_unenchanted_onyx" = "seq.human_fletching_add_bolt_tips_dragon" +"obj.dragon_bolts_unenchanted_opal" = "seq.human_fletching_add_bolt_tips_dragon" +"obj.dragon_bolts_unenchanted_pearl" = "seq.human_fletching_add_bolt_tips_dragon" +"obj.dragon_bolts_unenchanted_ruby" = "seq.human_fletching_add_bolt_tips_dragon" +"obj.dragon_bolts_unenchanted_sapphire" = "seq.human_fletching_add_bolt_tips_dragon" +"obj.dragon_bolts_unenchanted_topaz" = "seq.human_fletching_add_bolt_tips_dragon" +"obj.dragon_dart" = "seq.human_fletching_add_dart_feathers_dragon" +"obj.headless_arrow" = "seq.human_fletching_add_feather" +"obj.headless_atlatl_dart" = "seq.human_fletching_add_feather" +"obj.iron_arrow" = "seq.human_fletching_add_arrow_tips" +"obj.iron_dart" = "seq.human_fletching_add_dart_feathers_iron" +"obj.longbow" = "seq.stringing_longbow" +"obj.magic_longbow" = "seq.stringing_magic_longbow" +"obj.magic_shortbow" = "seq.stringing_magic_shortbow" +"obj.maple_longbow" = "seq.stringing_maple_longbow" +"obj.maple_shortbow" = "seq.stringing_maple_shortbow" +"obj.mithril_arrow" = "seq.human_fletching_add_arrow_tips" +"obj.mithril_dart" = "seq.human_fletching_add_dart_feathers_mithril" +"obj.oak_longbow" = "seq.stringing_oak_longbow" +"obj.oak_shortbow" = "seq.stringing_oak_shortbow" +"obj.ogre_headless_arrow" = "seq.human_fletching_add_feather" +"obj.opal_bolt" = "seq.human_fletching_add_bolt_tips_bronze" +"obj.pearl_bolt" = "seq.human_fletching_add_bolt_tips_iron" +"obj.rune_arrow" = "seq.human_fletching_add_arrow_tips" +"obj.rune_dart" = "seq.human_fletching_add_dart_feathers_rune" +"obj.seeking_adamant_arrow" = "seq.human_fletching_add_arrow_tips" +"obj.seeking_amethyst_arrow" = "seq.human_fletching_add_arrow_tips" +"obj.seeking_bronze_arrow" = "seq.human_fletching_add_arrow_tips" +"obj.seeking_dragon_arrow" = "seq.human_fletching_add_arrow_tips" +"obj.seeking_headless_arrow" = "seq.human_fletching_add_feather" +"obj.seeking_iron_arrow" = "seq.human_fletching_add_arrow_tips" +"obj.seeking_mithril_arrow" = "seq.human_fletching_add_arrow_tips" +"obj.seeking_rune_arrow" = "seq.human_fletching_add_arrow_tips" +"obj.seeking_slayer_broad_arrows" = "seq.human_fletching_add_arrow_tips" +"obj.seeking_steel_arrow" = "seq.human_fletching_add_arrow_tips" +"obj.shortbow" = "seq.stringing_shortbow" +"obj.slayer_broad_arrows" = "seq.human_fletching_add_arrow_tips" +"obj.steel_arrow" = "seq.human_fletching_add_arrow_tips" +"obj.steel_dart" = "seq.human_fletching_add_dart_feathers_steel" +"obj.willow_longbow" = "seq.stringing_willow_longbow" +"obj.willow_shortbow" = "seq.stringing_willow_shortbow" +"obj.xbows_crossbow_adamantite" = "seq.xbows_stringing_crossbow_adamantite" +"obj.xbows_crossbow_blurite" = "seq.xbows_stringing_crossbow_blurite" +"obj.xbows_crossbow_bolts_adamantite" = "seq.human_fletching_add_bolt_feathers_adamant" +"obj.xbows_crossbow_bolts_adamantite_tipped_diamond" = "seq.human_fletching_add_bolt_tips_adamant" +"obj.xbows_crossbow_bolts_adamantite_tipped_ruby" = "seq.human_fletching_add_bolt_tips_adamant" +"obj.xbows_crossbow_bolts_blurite" = "seq.human_fletching_add_bolt_feathers_blurite" +"obj.xbows_crossbow_bolts_blurite_tipped_jade" = "seq.human_fletching_add_bolt_tips_blurite" +"obj.xbows_crossbow_bolts_iron" = "seq.human_fletching_add_bolt_feathers_iron" +"obj.xbows_crossbow_bolts_mithril" = "seq.human_fletching_add_bolt_feathers_mithril" +"obj.xbows_crossbow_bolts_mithril_tipped_emerald" = "seq.human_fletching_add_bolt_tips_mithril" +"obj.xbows_crossbow_bolts_mithril_tipped_sapphire" = "seq.human_fletching_add_bolt_tips_mithril" +"obj.xbows_crossbow_bolts_runite" = "seq.human_fletching_add_bolt_feathers_rune" +"obj.xbows_crossbow_bolts_runite_tipped_dragonstone" = "seq.human_fletching_add_bolt_tips_rune" +"obj.xbows_crossbow_bolts_runite_tipped_onyx" = "seq.human_fletching_add_bolt_tips_rune" +"obj.xbows_crossbow_bolts_silver" = "seq.human_fletching_add_bolt_feathers_silver" +"obj.xbows_crossbow_bolts_steel" = "seq.human_fletching_add_bolt_feathers_steel" +"obj.xbows_crossbow_bolts_steel_tipped_redtopaz" = "seq.human_fletching_add_bolt_tips_steel" +"obj.xbows_crossbow_bronze" = "seq.xbows_stringing_crossbow_bronze" +"obj.xbows_crossbow_dragon" = "seq.xbows_stringing_crossbow_dragon" +"obj.xbows_crossbow_iron" = "seq.xbows_stringing_crossbow_iron" +"obj.xbows_crossbow_mithril" = "seq.xbows_stringing_crossbow_mithril" +"obj.xbows_crossbow_runite" = "seq.xbows_stringing_crossbow_runite" +"obj.xbows_crossbow_steel" = "seq.xbows_stringing_crossbow_steel" +"obj.yew_longbow" = "seq.stringing_yew_longbow" +"obj.yew_shortbow" = "seq.stringing_yew_shortbow" diff --git a/content/skills/fletching/src/main/resources/gamevals.toml b/content/skills/fletching/src/main/resources/gamevals.toml new file mode 100644 index 000000000..5e2d2173f --- /dev/null +++ b/content/skills/fletching/src/main/resources/gamevals.toml @@ -0,0 +1,222 @@ +[gamevals.enum] +fletching_anims = 65473 + +[gamevals.dbtable] +fletching_cutting = 55538 +fletching_stringing = 55539 +fletching_attaching = 55540 +fletching_gem_tips = 55541 +fletching_assembly = 55542 + +[gamevals.queue] +fletching_produce = 60 + +[gamevals.dbrow] +# cutting 56000-56045, 56192-56202 | stringing 56046-56067 +# attaching 56068-56148, 56183-56191 | gem_tips 56149-56166, 56203 +# assembly 56167-56182 +# Ids are append-only: later batches take the next free id rather than renumbering. +fletching_arrow_shaft_from_logs = 56000 +fletching_brew_scrapey_bark_from_brew_scrapey_logs = 56001 +fletching_seeking_arrow_shaft_from_sap_treated_logs = 56002 +fletching_javelin_shaft_from_logs = 56003 +fletching_ogre_arrow_shaft_from_achey_tree_logs = 56004 +fletching_unstrung_shortbow_from_logs = 56005 +fletching_xbows_crossbow_stock_wood_from_logs = 56006 +fletching_unstrung_longbow_from_logs = 56007 +fletching_arrow_shaft_from_oak_logs = 56008 +fletching_seeking_arrow_shaft_from_sap_treated_oak_logs = 56009 +fletching_unstrung_oak_shortbow_from_oak_logs = 56010 +fletching_xbows_crossbow_stock_oak_from_oak_logs = 56011 +fletching_unstrung_oak_longbow_from_oak_logs = 56012 +fletching_oak_shield_from_oak_logs = 56013 +fletching_arrow_shaft_from_willow_logs = 56014 +fletching_seeking_arrow_shaft_from_sap_treated_willow_logs = 56015 +fletching_unstrung_willow_shortbow_from_willow_logs = 56016 +fletching_xbows_crossbow_stock_willow_from_willow_logs = 56017 +fletching_unstrung_willow_longbow_from_willow_logs = 56018 +fletching_willow_shield_from_willow_logs = 56019 +fletching_arrow_shaft_from_maple_logs = 56020 +fletching_seeking_arrow_shaft_from_sap_treated_maple_logs = 56021 +fletching_xbows_crossbow_stock_teak_from_teak_logs = 56022 +fletching_unstrung_maple_shortbow_from_maple_logs = 56023 +fletching_xbows_crossbow_stock_maple_from_maple_logs = 56024 +fletching_unstrung_maple_longbow_from_maple_logs = 56025 +fletching_maple_shield_from_maple_logs = 56026 +fletching_arrow_shaft_from_yew_logs = 56027 +fletching_seeking_arrow_shaft_from_sap_treated_yew_logs = 56028 +fletching_xbows_crossbow_stock_mahogany_from_mahogany_logs = 56029 +fletching_unstrung_yew_shortbow_from_yew_logs = 56030 +fletching_xbows_crossbow_stock_yew_from_yew_logs = 56031 +fletching_blisterwood_stake_from_blisterwood_logs = 56032 +fletching_unstrung_yew_longbow_from_yew_logs = 56033 +fletching_yew_shield_from_yew_logs = 56034 +fletching_atlatl_dart_shaft_from_ent_branch = 56035 +fletching_arrow_shaft_from_magic_logs = 56036 +fletching_seeking_arrow_shaft_from_sap_treated_magic_logs = 56037 +fletching_xbows_crossbow_stock_magic_from_magic_logs = 56038 +fletching_unstrung_magic_shortbow_from_magic_logs = 56039 +fletching_unstrung_magic_longbow_from_magic_logs = 56040 +fletching_magic_shield_from_magic_logs = 56041 +fletching_arrow_shaft_from_redwood_logs = 56042 +fletching_redwood_hiking_staff_from_redwood_logs = 56043 +fletching_seeking_arrow_shaft_from_sap_treated_redwood_logs = 56044 +fletching_redwood_shield_from_redwood_logs = 56045 +fletching_shortbow_from_bow_string = 56046 +fletching_xbows_crossbow_bronze_from_xbows_crossbow_unstrung_bronze = 56047 +fletching_longbow_from_bow_string = 56048 +fletching_oak_shortbow_from_bow_string = 56049 +fletching_xbows_crossbow_blurite_from_xbows_crossbow_unstrung_blurite = 56050 +fletching_oak_longbow_from_bow_string = 56051 +fletching_zogre_bow_from_bow_string = 56052 +fletching_willow_shortbow_from_bow_string = 56053 +fletching_xbows_crossbow_iron_from_xbows_crossbow_string = 56054 +fletching_willow_longbow_from_bow_string = 56055 +fletching_xbows_crossbow_steel_from_xbows_crossbow_string = 56056 +fletching_devious_bowsword_from_bow_string = 56057 +fletching_maple_shortbow_from_bow_string = 56058 +fletching_xbows_crossbow_mithril_from_xbows_crossbow_string = 56059 +fletching_maple_longbow_from_bow_string = 56060 +fletching_xbows_crossbow_adamantite_from_xbows_crossbow_unstrung_adamantite = 56061 +fletching_yew_shortbow_from_bow_string = 56062 +fletching_xbows_crossbow_runite_from_xbows_crossbow_string = 56063 +fletching_yew_longbow_from_bow_string = 56064 +fletching_xbows_crossbow_dragon_from_xbows_crossbow_string = 56065 +fletching_magic_shortbow_from_bow_string = 56066 +fletching_magic_longbow_from_bow_string = 56067 +fletching_bronze_arrow_from_bronze_arrowheads = 56068 +fletching_headless_arrow_from_arrow_shaft = 56069 +fletching_seeking_adamant_arrow_from_adamant_arrow = 56070 +fletching_seeking_amethyst_arrow_from_amethyst_arrow = 56071 +fletching_seeking_slayer_broad_arrows_from_slayer_broad_arrows = 56072 +fletching_seeking_bronze_arrow_from_bronze_arrowheads = 56073 +fletching_seeking_bronze_arrow_from_bronze_arrow = 56074 +fletching_seeking_dragon_arrow_from_bucket_of_bloodwood_sap = 56075 +fletching_seeking_headless_arrow_from_feather = 56076 +fletching_seeking_iron_arrow_from_bucket_of_bloodwood_sap = 56077 +fletching_seeking_mithril_arrow_from_bucket_of_bloodwood_sap = 56078 +fletching_seeking_rune_arrow_from_bucket_of_bloodwood_sap = 56079 +fletching_seeking_steel_arrow_from_bucket_of_bloodwood_sap = 56080 +fletching_bronze_javelin_from_bronze_javelin_head = 56081 +fletching_ogre_headless_arrow_from_feather = 56082 +fletching_bolt_from_xbows_crossbow_bolts_bronze_unfeathered = 56083 +fletching_bronze_dart_from_bronze_dart_tip = 56084 +fletching_thprotodart_from_feather = 56085 +fletching_opal_bolt_from_bolt = 56086 +fletching_iron_arrow_from_headless_arrow = 56087 +fletching_seeking_iron_arrow_from_iron_arrowheads = 56088 +fletching_seeking_steel_arrow_from_seeking_headless_arrow = 56089 +fletching_iron_javelin_from_iron_javelin_head = 56090 +fletching_iron_dart_from_feather = 56091 +fletching_xbows_crossbow_bolts_blurite_from_xbows_crossbow_bolts_blurite_unfeathered = 56092 +fletching_xbows_crossbow_bolts_blurite_tipped_jade_from_xbows_crossbow_bolts_blurite = 56093 +fletching_steel_arrow_from_headless_arrow = 56094 +fletching_steel_javelin_from_javelin_shaft = 56095 +fletching_steel_dart_from_feather = 56096 +fletching_xbows_crossbow_bolts_iron_from_feather = 56097 +fletching_pearl_bolt_from_xbows_crossbow_bolts_iron = 56098 +fletching_xbows_crossbow_bolts_silver_from_feather = 56099 +fletching_mithril_arrow_from_headless_arrow = 56100 +fletching_seeking_mithril_arrow_from_mithril_arrowheads = 56101 +fletching_xbows_crossbow_bolts_steel_from_feather = 56102 +fletching_mithril_javelin_from_javelin_shaft = 56103 +fletching_xbows_crossbow_bolts_steel_tipped_redtopaz_from_xbows_crossbow_bolts_steel = 56104 +fletching_barbed_bolt_from_barbed_bolttips = 56105 +fletching_slayer_broad_arrows_from_slayer_broad_arrowhead = 56106 +fletching_mithril_dart_from_feather = 56107 +fletching_seeking_slayer_broad_arrows_from_slayer_broad_arrowhead = 56108 +fletching_xbows_crossbow_bolts_mithril_from_feather = 56109 +fletching_slayer_broad_bolt_from_feather = 56110 +fletching_xbows_crossbow_bolts_mithril_tipped_sapphire_from_xbows_crossbow_bolts_mithril = 56111 +fletching_xbows_crossbow_bolts_mithril_tipped_emerald_from_xbows_bolt_tips_emerald = 56112 +fletching_adamant_arrow_from_adamant_arrowheads = 56113 +fletching_seeking_adamant_arrow_from_adamant_arrowheads = 56114 +fletching_xbows_crossbow_bolts_adamantite_from_xbows_crossbow_bolts_adamantite_unfeathered = 56115 +fletching_adamant_javelin_from_adamant_javelin_head = 56116 +fletching_xbows_crossbow_bolts_adamantite_tipped_ruby_from_xbows_crossbow_bolts_adamantite = 56117 +fletching_xbows_crossbow_bolts_adamantite_tipped_diamond_from_xbows_crossbow_bolts_adamantite = 56118 +fletching_adamant_dart_from_adamant_dart_tip = 56119 +fletching_xbows_crossbow_bolts_runite_from_feather = 56120 +fletching_xbows_crossbow_bolts_runite_tipped_dragonstone_from_xbows_bolt_tips_dragonstone = 56121 +fletching_xbows_crossbow_bolts_runite_tipped_onyx_from_xbows_bolt_tips_onyx = 56122 +fletching_atlatl_dart_from_atlatl_dart_tips = 56123 +fletching_headless_atlatl_dart_from_atlatl_dart_shaft = 56124 +fletching_rune_arrow_from_headless_arrow = 56125 +fletching_seeking_rune_arrow_from_rune_arrowheads = 56126 +fletching_slayer_broad_bolt_amethyst_from_xbows_bolt_tips_amethyst = 56127 +fletching_rune_javelin_from_javelin_shaft = 56128 +fletching_rune_dart_from_feather = 56129 +fletching_amethyst_arrow_from_amethyst_arrowheads = 56130 +fletching_seeking_amethyst_arrow_from_amethyst_arrowheads = 56131 +fletching_amethyst_javelin_from_amethyst_javelin_head = 56132 +fletching_dragon_bolts_unenchanted_diamond_from_xbows_bolt_tips_diamond = 56133 +fletching_dragon_bolts_from_dragon_bolts_unfeathered = 56134 +fletching_dragon_bolts_unenchanted_dragonstone_from_dragon_bolts = 56135 +fletching_dragon_bolts_unenchanted_emerald_from_dragon_bolts = 56136 +fletching_dragon_bolts_unenchanted_jade_from_dragon_bolts = 56137 +fletching_dragon_bolts_unenchanted_onyx_from_dragon_bolts = 56138 +fletching_dragon_bolts_unenchanted_opal_from_dragon_bolts = 56139 +fletching_dragon_bolts_unenchanted_pearl_from_dragon_bolts = 56140 +fletching_dragon_bolts_unenchanted_ruby_from_dragon_bolts = 56141 +fletching_dragon_bolts_unenchanted_sapphire_from_dragon_bolts = 56142 +fletching_dragon_bolts_unenchanted_topaz_from_dragon_bolts = 56143 +fletching_amethyst_dart_from_amethyst_dart_tip = 56144 +fletching_dragon_arrow_from_dragon_arrowheads = 56145 +fletching_seeking_dragon_arrow_from_dragon_arrowheads = 56146 +fletching_dragon_javelin_from_dragon_javelin_head = 56147 +fletching_dragon_dart_from_dragon_dart_tip = 56148 +fletching_wolfbone_arrowheads_from_wolf_bones = 56149 +fletching_opal_bolttips_from_opal = 56150 +fletching_xbows_bolt_tips_jade_from_jade = 56151 +fletching_huntingbow_bolts_from_huntingbeast_spike = 56152 +fletching_pearl_bolttips_from_smalloysterpearls = 56153 +fletching_pearl_bolttips_from_bigoysterpearls = 56154 +fletching_huntingbow_bigbolts_from_huntingbeast_bigspike = 56155 +fletching_xbows_bolt_tips_redtopaz_from_red_topaz = 56156 +fletching_xbows_bolt_tips_sapphire_from_sapphire = 56157 +fletching_xbows_bolt_tips_emerald_from_emerald = 56158 +fletching_sunlight_antelope_bolt_from_hunting_antelopesun_horn = 56159 +fletching_xbows_bolt_tips_ruby_from_ruby = 56160 +fletching_xbows_bolt_tips_diamond_from_diamond = 56161 +fletching_xbows_bolt_tips_dragonstone_from_dragonstone = 56162 +fletching_moonlight_antelope_bolt_from_hunting_antelopemoon_horn = 56163 +fletching_xbows_bolt_tips_onyx_from_onyx = 56164 +fletching_atlatl_dart_tips_from_custodian_broken_antler = 56165 +fletching_toxic_blowpipe_from_blowpipe_fang = 56166 +fletching_ogre_arrow_from_ogre_headless_arrow = 56167 +fletching_zogre_brutal_bronze_from_nails_bronze = 56168 +fletching_xbows_crossbow_unstrung_bronze_from_xbows_crossbow_limbs_bronze = 56169 +fletching_zogre_brutal_iron_from_ogre_headless_arrow = 56170 +fletching_xbows_crossbow_unstrung_blurite_from_xbows_crossbow_limbs_blurite = 56171 +fletching_zogre_brutal_steel_from_ogre_headless_arrow = 56172 +fletching_zogre_brutal_black_from_nails_black = 56173 +fletching_xbows_crossbow_unstrung_iron_from_xbows_crossbow_limbs_iron = 56174 +fletching_xbows_crossbow_unstrung_steel_from_xbows_crossbow_limbs_steel = 56175 +fletching_zogre_brutal_mithril_from_ogre_headless_arrow = 56176 +fletching_xbows_crossbow_unstrung_mithril_from_xbows_crossbow_stock_maple = 56177 +fletching_xbows_crossbow_unstrung_adamantite_from_xbows_crossbow_limbs_adamantite = 56178 +fletching_zogre_brutal_adamant_from_nails_adamant = 56179 +fletching_xbows_crossbow_unstrung_runite_from_xbows_crossbow_limbs_runite = 56180 +fletching_zogre_brutal_rune_from_ogre_headless_arrow = 56181 +fletching_xbows_crossbow_unstrung_dragon_from_xbows_crossbow_limbs_dragon = 56182 +fletching_seeking_adamant_arrow_from_adamant_arrow_x150 = 56183 +fletching_seeking_amethyst_arrow_from_amethyst_arrow_x150 = 56184 +fletching_seeking_slayer_broad_arrows_from_slayer_broad_arrows_x150 = 56185 +fletching_seeking_bronze_arrow_from_bronze_arrow_x150 = 56186 +fletching_seeking_dragon_arrow_from_dragon_arrow_x150 = 56187 +fletching_seeking_iron_arrow_from_iron_arrow_x150 = 56188 +fletching_seeking_mithril_arrow_from_mithril_arrow_x150 = 56189 +fletching_seeking_rune_arrow_from_rune_arrow_x150 = 56190 +fletching_seeking_steel_arrow_from_steel_arrow_x150 = 56191 +fletching_unstrung_zogre_bow_from_achey_tree_logs = 56192 +fletching_battlestaff_from_celastrus_wood = 56193 +fletching_vmq4_root_kindling_from_vmq4_roots = 56194 +fletching_greenman_statue_from_maple_logs = 56195 +fletching_camphor_blowpipe_empty_from_camphor_logs = 56196 +fletching_blisterwood_sickle_from_blisterwood_logs = 56197 +fletching_hg_hunter_spear_from_teak_logs = 56198 +fletching_blisterwood_sickle_enhanced_from_blisterwood_logs = 56199 +fletching_ironwood_blowpipe_empty_from_ironwood_logs = 56200 +fletching_greenman_wall_decoration_from_yew_logs = 56201 +fletching_rosewood_blowpipe_empty_from_rosewood_logs = 56202 +fletching_hunting_crossbow_sunlight_from_hunting_antelopesun_horn = 56203 From 3f97b1f0d8a2ce1c1b986d0c8b984f1d8d40ed5b Mon Sep 17 00:00:00 2001 From: reldo-dev <316375707+reldo-dev@users.noreply.github.com> Date: Thu, 20 Aug 2026 12:07:21 -0700 Subject: [PATCH 2/4] feat: add the fletching production engine One normalised FletchingRecipe that every table row maps onto, and one queued loop that drives all five flows. Giving each family the same recipe shape means there is only one loop to write, so the engine is four files and under 300 lines. FletchingDefinitions holds the two groupings the trigger shapes need - a material pair for stringing, attaching and assembly, and (first material, tool) for cutting and gem tips - plus the feather and tool substitution sets. It is also the only place that divides xp by ten. FletchingAction consumes inputs, adds the output, pays the xp and re-queues, restoring anything already consumed if a later step fails. The tool is re-checked every tick, so dropping a hammer mid-run stops the loop rather than forging without one; an Imcando hammer counts, matching the check smithing already uses. One set is produced per cycle. Darts and unfinished bolts are faster than that in OSRS, but not by a rate this loop can express: they are an instant per-interaction action the player repeats by hand, optionally swapped for a bounded Make-X queue by an account setting. Neither is modelled here, so their table rows carry the wiki's zero-tick value and run at one set per tick. The queue lives in its own PluginScript so no single family's removal can take the other four down with it. --- .../skills/fletching/FletchingAction.kt | 158 +++++++++++++++ .../skills/fletching/FletchingDefinitions.kt | 189 ++++++++++++++++++ .../skills/fletching/FletchingQueues.kt | 15 ++ .../skills/fletching/FletchingRecipe.kt | 40 ++++ 4 files changed, 402 insertions(+) create mode 100644 content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/FletchingAction.kt create mode 100644 content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/FletchingDefinitions.kt create mode 100644 content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/FletchingQueues.kt create mode 100644 content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/FletchingRecipe.kt diff --git a/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/FletchingAction.kt b/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/FletchingAction.kt new file mode 100644 index 000000000..c0268e6bf --- /dev/null +++ b/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/FletchingAction.kt @@ -0,0 +1,158 @@ +package org.rsmod.content.skills.fletching + +import dev.openrune.types.StatType +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.script.onPlayerQueueWithArgs +import org.rsmod.api.table.Tuple2 +import org.rsmod.content.skills.Material +import org.rsmod.content.skills.SkillMultiConfig +import org.rsmod.content.skills.SkillMultiEntry +import org.rsmod.content.skills.SkillingActionType +import org.rsmod.content.skills.openSkillMulti +import org.rsmod.plugin.scripts.ScriptContext + +private const val FLETCH_QUEUE = "queue.fletching_produce" +private const val FLETCH_ANIM = "seq.human_fletching" + +data class FletchingTask(val recipe: FletchingRecipe, val amount: Int, val created: Int) + +fun ScriptContext.registerFletchingQueue() { + onPlayerQueueWithArgs(FLETCH_QUEUE) { processFletchTick(it.args) } +} + +/** + * Silent stat-requirement check used to build the candidate list for the make-X menu. + * [meetsStatReqs] below shows a message box on failure, which is only appropriate for the single + * recipe the player actually chose - running it over every candidate here would fire a blocking + * dialog for each recipe the player doesn't yet qualify for. + */ +private fun ProtectedAccess.hasStatReqs(reqs: List>): Boolean = + reqs.all { statBase(it.t0.internalName) >= it.t1 } + +private suspend fun ProtectedAccess.meetsStatReqs(reqs: List>): Boolean { + for (req in reqs) { + val stat = req.t0 + val level = req.t1 + if (statBase(stat.internalName) < level) { + val name = stat.displayName.ifEmpty { stat.internalName.removePrefix("stat.") } + mesbox("You need a $name level of $level.") + return false + } + } + return true +} + +/** "You need a hammer to make that." The tool is named by its cache display name. */ +private fun missingToolMessage(tool: String): String = + "You need a ${Material(tool).obj.name.lowercase()} to make that." + +/** Some gem tip rows also carry a crafting requirement, so the fletching one is picked by name. */ +private fun FletchingRecipe.fletchingLevel(): Int = + statReq.firstOrNull { it.t0.internalName == "stat.fletching" }?.t1 ?: 0 + +suspend fun ProtectedAccess.startFletching(recipe: FletchingRecipe, amount: Int) { + if (!meetsStatReqs(recipe.statReq)) return + val tool = recipe.tool + if (tool != null && !recipe.hasTool(inv)) { + mes(missingToolMessage(tool)) + return + } + if (!recipe.hasMaterials(inv)) { + mes("You don't have the materials needed to make that.") + return + } + weakQueue(FLETCH_QUEUE, 1, FletchingTask(recipe, amount, 0)) +} + +/** + * Opens the standard make-X menu for whichever recipes the player can currently make, and starts + * the chosen one. Every fletching flow funnels through here; the only thing that varies between + * them is the action type the client uses to label the menu. + */ +suspend fun ProtectedAccess.openFletchMenu( + recipes: List, + actionType: SkillingActionType, + preferredFeather: String? = null, +) { + // Sorted by level because a trigger can now gather recipes from more than one place in the + // table - a knife on teak logs reaches both the teak stock and the hunter's spear - and the + // make-X menu should still read low level to high. + val affordable = + recipes + .map { it.withHeldFeather(inv, preferredFeather) } + .filter { hasStatReqs(it.statReq) && it.maxProducible(inv) > 0 } + .sortedBy { it.fletchingLevel() } + val candidates = affordable.filter { it.hasTool(inv) } + if (candidates.isEmpty()) { + // Only complain when the tool is the sole thing standing in the way; an empty menu for any + // other reason stays silent, as it did before. + affordable.firstNotNullOfOrNull { it.tool }?.let { mes(missingToolMessage(it)) } + return + } + + // Two recipes can share an output name (e.g. multiple routes to obj.arrow_shaft), so the + // entry itself - internal name plus materials - is the disambiguating key back to the recipe + // that produced it, not the output name alone. + val entries = candidates.map { SkillMultiEntry(it.output.internalName, it.inputs) } + val recipeByEntry = entries.zip(candidates).toMap() + + openSkillMulti( + SkillMultiConfig( + actionType = actionType, + verb = "make", + entries = entries, + maxCountProvider = { inventory, entry -> + recipeByEntry[entry]?.maxProducible(inventory) ?: 0 + }, + ) + ) { selection -> + val recipe = recipeByEntry[selection.entry] ?: return@openSkillMulti + startFletching(recipe, selection.amount) + } +} + +private suspend fun ProtectedAccess.processFletchTick(task: FletchingTask) { + val recipe = task.recipe + + if (!meetsStatReqs(recipe.statReq) || !recipe.hasTool(inv) || !recipe.hasMaterials(inv)) { + resetAnim() + return + } + + // Tip and feather attaching is animationless in OSRS; ticks == 0 marks those recipes. + if (recipe.ticks > 0) { + anim(FLETCH_ANIM) + } + + val consumed = mutableListOf>() + for (material in recipe.inputs) { + val name = material.obj.internalName + if (invDel(inv, name, material.count).success) { + consumed += name to material.count + } else { + consumed.forEach { (restore, count) -> invAdd(inv, restore, count) } + resetAnim() + return + } + } + + if (invAdd(inv, recipe.output.internalName, recipe.outputAmount).failure) { + consumed.forEach { (restore, count) -> invAdd(inv, restore, count) } + mes("You don't have enough inventory space to make that.") + resetAnim() + return + } + + statAdvance("stat.fletching", recipe.xp) + + val created = task.created + 1 + if (created < task.amount) { + weakQueue( + FLETCH_QUEUE, + if (recipe.ticks > 0) recipe.ticks else 1, + FletchingTask(recipe, task.amount, created), + ) + } else { + resetAnim() + } +} diff --git a/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/FletchingDefinitions.kt b/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/FletchingDefinitions.kt new file mode 100644 index 000000000..b3e1d1d4d --- /dev/null +++ b/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/FletchingDefinitions.kt @@ -0,0 +1,189 @@ +package org.rsmod.content.skills.fletching + +import dev.openrune.types.ItemServerType +import dev.openrune.types.StatType +import org.rsmod.api.table.Tuple2 +import org.rsmod.api.table.fletching.FletchingAssemblyRow +import org.rsmod.api.table.fletching.FletchingAttachingRow +import org.rsmod.api.table.fletching.FletchingCuttingRow +import org.rsmod.api.table.fletching.FletchingGemTipsRow +import org.rsmod.api.table.fletching.FletchingStringingRow +import org.rsmod.content.skills.Material +import org.rsmod.game.inv.Inventory + +/** + * The five generated row types have no common supertype, so each gets a one-line adapter into this + * shared builder rather than repeating the body five times. + * + * The tables store XP in tenths — see the header comment on Fletching.kt. This is the only place + * that divides; no script should ever read a row's xp property directly. + */ +private fun recipe( + input: List, + inputAmount: List, + output: ItemServerType, + outputAmount: Int, + statReq: List>, + xpTenths: Int, + ticks: Int, + category: String, + tool: ItemServerType?, +) = + FletchingRecipe( + inputs = + input.mapIndexed { i, item -> + Material(item.internalName, inputAmount.getOrElse(i) { 1 }) + }, + output = output, + outputAmount = outputAmount, + statReq = statReq, + xp = xpTenths / 10.0, + ticks = ticks, + category = category, + tool = tool?.internalName, + ) + +private fun FletchingCuttingRow.toRecipe() = + recipe(input, inputAmount, output, outputAmount, statReq, xp, ticks, category, tool) + +private fun FletchingGemTipsRow.toRecipe() = + recipe(input, inputAmount, output, outputAmount, statReq, xp, ticks, category, tool) + +private fun FletchingStringingRow.toRecipe() = + recipe(input, inputAmount, output, outputAmount, statReq, xp, ticks, category, tool = null) + +private fun FletchingAttachingRow.toRecipe() = + recipe(input, inputAmount, output, outputAmount, statReq, xp, ticks, category, tool = null) + +private fun FletchingAssemblyRow.toRecipe() = + recipe(input, inputAmount, output, outputAmount, statReq, xp, ticks, category, tool) + +object FletchingDefinitions { + val cutting: List = FletchingCuttingRow.all().map { it.toRecipe() } + val stringing: List = FletchingStringingRow.all().map { it.toRecipe() } + val attaching: List = FletchingAttachingRow.all().map { it.toRecipe() } + val gemTips: List = FletchingGemTipsRow.all().map { it.toRecipe() } + val assembly: List = FletchingAssemblyRow.all().map { it.toRecipe() } + + /** + * Any feather fletches bolts, darts and headless arrows; the table only carries obj.feather. + * The other seven were resolved by display-name lookup against the packed cache: the + * `hunting_*_feather` keys are named by biome, not by colour, so the mapping below is not + * derivable from spelling. + * - Feather -> id=314 -> obj.feather + * - Yellow feather -> id=10090 -> obj.hunting_desert_feather + * - Orange feather -> id=10091 -> obj.hunting_woodland_feather + * - Red feather -> id=10088 -> obj.hunting_jungle_feather + * - Blue feather -> id=10089 -> obj.hunting_polar_feather + * - Stripy feather -> id=10087 -> obj.hunting_stripy_bird_feather + * - Gryphon feather -> id=31235 -> obj.gryphon_feather + * - Stymphike feather -> id=33651 -> obj.stymphike_feather + */ + val feathers: Set = + setOf( + "obj.feather", + "obj.hunting_desert_feather", + "obj.hunting_woodland_feather", + "obj.hunting_jungle_feather", + "obj.hunting_polar_feather", + "obj.hunting_stripy_bird_feather", + "obj.gryphon_feather", + "obj.stymphike_feather", + ) + + /** + * Items that stand in for the tool a row names. Only the hammer has any, matching + * SmithingUtils.hasHammer - without them, requiring a hammer for crossbow assembly would lock + * out players carrying an Imcando one. + * + * The Forestry fletching knife is deliberately absent. It speeds fletching up but does not + * replace the knife: "the wielder will still need a knife in their inventory to use on logs to + * initiate the cutting". https://oldschool.runescape.wiki/w/Fletching_knife + */ + private val TOOL_VARIANTS: Map> = + mapOf( + "obj.hammer" to setOf("obj.hammer", "obj.imcando_hammer", "obj.imcando_hammer_offhand") + ) + + fun toolVariants(tool: String): Set = TOOL_VARIANTS[tool] ?: setOf(tool) +} + +/** + * Groups two-input recipes by the ordered item pair that triggers them. + * + * onOpHeldU is keyed per ordered pair and throws at boot on a duplicate or reversed registration, + * so callers must register one handler per key of this map and never per recipe. + * + * A feather input expands to all eight feather types, since any feather fletches bolts, darts and + * headless arrows and the table only carries obj.feather. + */ +fun pairsFor(recipes: List): Map, List> { + val byPair = mutableMapOf, MutableList>() + for (recipe in recipes) { + val names = recipe.inputs.map { it.obj.internalName } + if (names.size != 2) continue + for (first in variantsOf(names[0])) { + for (second in variantsOf(names[1])) { + byPair.getOrPut(first to second) { mutableListOf() }.add(recipe) + } + } + } + return byPair +} + +private fun variantsOf(item: String): Set = + if (item in FletchingDefinitions.feathers) FletchingDefinitions.feathers else setOf(item) + +/** + * Groups tool-driven recipes by the ordered pair that triggers them: the tool used on the recipe's + * first material. Any further materials are ordinary inventory requirements, which is what lets a + * blowpipe (two logs plus a squid beak) or a hunter's spear (three materials) share the same + * trigger shape as a plain log. + * + * Note this deliberately keys on the tool rather than on a material pair. A knife on teak logs + * therefore offers the teak stock and the hunter's spear from one menu, and a chisel on a sunlight + * antelope antler offers both its bolt tips and the crossbow upgrade, which is how those recipes + * are reached in game. + */ +fun byToolAndPrimary( + recipes: List +): Map, List> = + recipes + .mapNotNull { recipe -> + val tool = recipe.tool ?: return@mapNotNull null + (recipe.inputs.first().obj.internalName to tool) to recipe + } + .groupBy({ it.first }, { it.second }) + +/** + * Rewrites a canonical obj.feather input to whichever feather type the player actually holds. + * Recipes without a feather input are returned unchanged, so this is safe to apply to every recipe + * regardless of family. + * + * [preferred], when given, is the feather the player actually clicked to trigger this recipe (see + * `pairsFor`); if the player holds enough of it, it is used regardless of the fallback order below. + * This keeps the menu's candidate/max-count pass and the consume step substituting the same + * feather. + */ +fun FletchingRecipe.withHeldFeather(inv: Inventory, preferred: String? = null): FletchingRecipe { + val feather = inputs.firstOrNull { it.obj.internalName == "obj.feather" } ?: return this + if (preferred != null && inv.count(preferred) >= feather.count) { + return copy( + inputs = + inputs.map { + if (it.obj.internalName == "obj.feather") Material(preferred, it.count) else it + } + ) + } + // Plain feathers take priority whenever the player holds enough of them; this is checked + // explicitly rather than relying on "obj.feather" being iterated first in the set below. + if (inv.count("obj.feather") >= feather.count) return this + val held = + FletchingDefinitions.feathers.firstOrNull { inv.count(it) >= feather.count } ?: return this + return copy( + inputs = + inputs.map { + if (it.obj.internalName == "obj.feather") Material(held, it.count) else it + } + ) +} diff --git a/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/FletchingQueues.kt b/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/FletchingQueues.kt new file mode 100644 index 000000000..ceb9321cc --- /dev/null +++ b/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/FletchingQueues.kt @@ -0,0 +1,15 @@ +package org.rsmod.content.skills.fletching + +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Owns the shared fletching production queue. Cutting, stringing, attaching, gem tips and assembly + * all queue onto [registerFletchingQueue] - it lives in its own script so no single family's + * removal or refactor can silently take the other four down with it. + */ +class FletchingQueues : PluginScript() { + override fun ScriptContext.startup() { + registerFletchingQueue() + } +} diff --git a/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/FletchingRecipe.kt b/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/FletchingRecipe.kt new file mode 100644 index 000000000..34e2c5f8d --- /dev/null +++ b/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/FletchingRecipe.kt @@ -0,0 +1,40 @@ +package org.rsmod.content.skills.fletching + +import dev.openrune.types.ItemServerType +import dev.openrune.types.StatType +import org.rsmod.api.table.Tuple2 +import org.rsmod.content.skills.Material +import org.rsmod.game.inv.Inventory + +/** + * One fletching recipe, normalised from whichever generated row type produced it. + * + * All five tables share this shape, which is what lets a single queued action drive every flow + * instead of one loop per family. + */ +data class FletchingRecipe( + val inputs: List, + val output: ItemServerType, + val outputAmount: Int, + val statReq: List>, + val xp: Double, + val ticks: Int, + val category: String, + /** Held, never consumed, so deliberately not one of [inputs]. Null when none is needed. */ + val tool: String? = null, +) + +fun FletchingRecipe.hasMaterials(inv: Inventory): Boolean = + inputs.all { inv.count(it.obj.internalName) >= it.count } + +fun FletchingRecipe.maxProducible(inv: Inventory): Int = + inputs.minOfOrNull { inv.count(it.obj.internalName) / it.count } ?: 0 + +/** + * The tool is held rather than consumed, so it is a presence check and never limits how many can be + * made - which is why it is not folded into [maxProducible]. + */ +fun FletchingRecipe.hasTool(inv: Inventory): Boolean { + val tool = tool ?: return true + return FletchingDefinitions.toolVariants(tool).any { inv.count(it) > 0 } +} From 196c196007ed16495064ee743e4d1b57b53876ea Mon Sep 17 00:00:00 2001 From: reldo-dev <316375707+reldo-dev@users.noreply.github.com> Date: Thu, 20 Aug 2026 21:30:07 -0700 Subject: [PATCH 3/4] feat: add the five fletching flows One PluginScript per family, each a handful of lines: iterate the distinct trigger pairs and register an onOpHeldU per pair. 332 registrations, no collisions - onOpHeldU throws at boot on a duplicate or a reversed pair, so a clean boot is the proof. Cutting and gem tips key on (material, tool) taken from the row rather than a hardcoded knife or chisel, which is what lets a knife on teak logs offer both the teak stock and the hunter's spear, and a chisel on a sunlight antelope antler offer both its bolt tips and the crossbow upgrade. Attaching passes the feather the player actually clicked through to the menu, so the candidate list and the consume step agree on which of the eight feather types to use. The pairs are registered material-first. Registering knife-first instead would put the reversed click order behind firemaking's default log handler, which runs before ours and would swallow it. Stringing and attaching resolve an animation from the recipe output, the cache carrying one per bow tier and per bolt or dart metal; the shared animation is left to the rest. It loops, so a lone action takes the one-shot variant and only a looping one is reset at the end, which otherwise cut the last animation short. The mapping is enum.fletching_anims rather than something derived from the item name, because a bolt item says adamantite where its animation says adamant, while crossbow stringing keeps adamantite. A repeat re-queues at one cycle more than the recipe's tick cost. A queue submitted while the queue list is being processed is decremented on that same cycle, so without it every repeat arrives a tick early and a 2-tick recipe runs at 0.6s. --- .../skills/fletching/AssemblyEvents.kt | 17 ++++++++++ .../skills/fletching/AttachingEvents.kt | 19 +++++++++++ .../content/skills/fletching/CuttingEvents.kt | 15 ++++++++ .../skills/fletching/FletchingAction.kt | 20 +++++++---- .../skills/fletching/FletchingAnims.kt | 34 +++++++++++++++++++ .../content/skills/fletching/GemTipEvents.kt | 15 ++++++++ .../skills/fletching/StringingEvents.kt | 17 ++++++++++ 7 files changed, 131 insertions(+), 6 deletions(-) create mode 100644 content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/AssemblyEvents.kt create mode 100644 content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/AttachingEvents.kt create mode 100644 content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/CuttingEvents.kt create mode 100644 content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/FletchingAnims.kt create mode 100644 content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/GemTipEvents.kt create mode 100644 content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/StringingEvents.kt diff --git a/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/AssemblyEvents.kt b/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/AssemblyEvents.kt new file mode 100644 index 000000000..f316622a1 --- /dev/null +++ b/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/AssemblyEvents.kt @@ -0,0 +1,17 @@ +package org.rsmod.content.skills.fletching + +import org.rsmod.api.script.onOpHeldU +import org.rsmod.content.skills.SkillingActionType +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +class AssemblyEvents : PluginScript() { + + override fun ScriptContext.startup() { + for ((pair, recipes) in pairsFor(FletchingDefinitions.assembly)) { + onOpHeldU(pair.first, pair.second) { + openFletchMenu(recipes, SkillingActionType.MAKE_SETS) + } + } + } +} diff --git a/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/AttachingEvents.kt b/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/AttachingEvents.kt new file mode 100644 index 000000000..3372d198a --- /dev/null +++ b/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/AttachingEvents.kt @@ -0,0 +1,19 @@ +package org.rsmod.content.skills.fletching + +import org.rsmod.api.script.onOpHeldU +import org.rsmod.content.skills.SkillingActionType +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +class AttachingEvents : PluginScript() { + + override fun ScriptContext.startup() { + for ((pair, recipes) in pairsFor(FletchingDefinitions.attaching)) { + val usedFeather = + listOf(pair.first, pair.second).firstOrNull { it in FletchingDefinitions.feathers } + onOpHeldU(pair.first, pair.second) { + openFletchMenu(recipes, SkillingActionType.MAKE_SETS, usedFeather) + } + } + } +} diff --git a/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/CuttingEvents.kt b/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/CuttingEvents.kt new file mode 100644 index 000000000..2b105d225 --- /dev/null +++ b/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/CuttingEvents.kt @@ -0,0 +1,15 @@ +package org.rsmod.content.skills.fletching + +import org.rsmod.api.script.onOpHeldU +import org.rsmod.content.skills.SkillingActionType +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +class CuttingEvents : PluginScript() { + + override fun ScriptContext.startup() { + for ((pair, recipes) in byToolAndPrimary(FletchingDefinitions.cutting)) { + onOpHeldU(pair.first, pair.second) { openFletchMenu(recipes, SkillingActionType.CUT) } + } + } +} diff --git a/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/FletchingAction.kt b/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/FletchingAction.kt index c0268e6bf..c83b21d9c 100644 --- a/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/FletchingAction.kt +++ b/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/FletchingAction.kt @@ -12,7 +12,6 @@ import org.rsmod.content.skills.openSkillMulti import org.rsmod.plugin.scripts.ScriptContext private const val FLETCH_QUEUE = "queue.fletching_produce" -private const val FLETCH_ANIM = "seq.human_fletching" data class FletchingTask(val recipe: FletchingRecipe, val amount: Int, val created: Int) @@ -119,9 +118,15 @@ private suspend fun ProtectedAccess.processFletchTick(task: FletchingTask) { return } - // Tip and feather attaching is animationless in OSRS; ticks == 0 marks those recipes. - if (recipe.ticks > 0) { - anim(FLETCH_ANIM) + // The shared animation loops, so it suits a run of actions and a lone one takes the one-shot + // variant. Its tick gate covers the rows that animate nothing; a row with an animation of its + // own is exempt, the dart rows being 0-tick yet animated. + val specific = FletchingAnims.forOutput(recipe.output) + val looping = specific == null && recipe.ticks > 0 && task.amount > 1 + if (specific != null) { + anim(specific) + } else if (recipe.ticks > 0) { + anim(if (looping) FletchingAnims.GENERIC else FletchingAnims.GENERIC_SINGLE) } val consumed = mutableListOf>() @@ -147,12 +152,15 @@ private suspend fun ProtectedAccess.processFletchTick(task: FletchingTask) { val created = task.created + 1 if (created < task.amount) { + // A queue submitted while the queue list is being processed is decremented on that same + // cycle, so a re-queue arrives a cycle sooner than the opening one in startFletching. weakQueue( FLETCH_QUEUE, - if (recipe.ticks > 0) recipe.ticks else 1, + if (recipe.ticks > 0) recipe.ticks + 1 else 1, FletchingTask(recipe, task.amount, created), ) - } else { + } else if (looping) { + // Only the looping animation needs stopping; a one-shot would be cut short instead. resetAnim() } } diff --git a/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/FletchingAnims.kt b/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/FletchingAnims.kt new file mode 100644 index 000000000..772b1d0b3 --- /dev/null +++ b/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/FletchingAnims.kt @@ -0,0 +1,34 @@ +package org.rsmod.content.skills.fletching + +import dev.openrune.rscm.RSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.ItemServerType +import org.rsmod.api.enums.NamedEnums.fletching_anims + +/** + * Per-recipe fletching animations, read from `enum.fletching_anims`. + * + * Cutting shares one animation; stringing and attaching each have one per bow tier and per + * bolt/dart metal. An output with no entry resolves to null and leaves the caller on [GENERIC], so + * a row can only ever be unanimated, never wrongly animated. + */ +internal object FletchingAnims { + /** + * Loops (`loops=15`, `maxloops=2`), so it covers a run of actions and has to be cancelled when + * the run ends. [GENERIC_SINGLE] is the same frames without the looping; every animation in the + * enum is likewise a one-shot. + */ + const val GENERIC: String = "seq.human_fletching" + + const val GENERIC_SINGLE: String = "seq.human_fletching_single" + + private val byOutputId: Map by lazy { + fletching_anims + .mapNotNull { (obj, seq) -> + seq?.let { obj.id to RSCM.getReverseMapping(RSCMType.SEQ, it.id) } + } + .toMap() + } + + fun forOutput(output: ItemServerType): String? = byOutputId[output.id] +} diff --git a/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/GemTipEvents.kt b/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/GemTipEvents.kt new file mode 100644 index 000000000..a93424002 --- /dev/null +++ b/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/GemTipEvents.kt @@ -0,0 +1,15 @@ +package org.rsmod.content.skills.fletching + +import org.rsmod.api.script.onOpHeldU +import org.rsmod.content.skills.SkillingActionType +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +class GemTipEvents : PluginScript() { + + override fun ScriptContext.startup() { + for ((pair, recipes) in byToolAndPrimary(FletchingDefinitions.gemTips)) { + onOpHeldU(pair.first, pair.second) { openFletchMenu(recipes, SkillingActionType.CUT) } + } + } +} diff --git a/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/StringingEvents.kt b/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/StringingEvents.kt new file mode 100644 index 000000000..3b8e015db --- /dev/null +++ b/content/skills/fletching/src/main/kotlin/org/rsmod/content/skills/fletching/StringingEvents.kt @@ -0,0 +1,17 @@ +package org.rsmod.content.skills.fletching + +import org.rsmod.api.script.onOpHeldU +import org.rsmod.content.skills.SkillingActionType +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +class StringingEvents : PluginScript() { + + override fun ScriptContext.startup() { + for ((pair, recipes) in pairsFor(FletchingDefinitions.stringing)) { + onOpHeldU(pair.first, pair.second) { + openFletchMenu(recipes, SkillingActionType.STRING) + } + } + } +} From 39ed3873b29ae74c52fd4f87c9ec9273d393c3b4 Mon Sep 17 00:00:00 2001 From: Mark7625 <72366279+Mark7625@users.noreply.github.com> Date: Fri, 28 Aug 2026 16:55:58 +0100 Subject: [PATCH 4/4] Update gamevals.toml --- .../src/main/resources/gamevals.toml | 426 +++++++++--------- 1 file changed, 211 insertions(+), 215 deletions(-) diff --git a/content/skills/fletching/src/main/resources/gamevals.toml b/content/skills/fletching/src/main/resources/gamevals.toml index 5e2d2173f..a3367c69b 100644 --- a/content/skills/fletching/src/main/resources/gamevals.toml +++ b/content/skills/fletching/src/main/resources/gamevals.toml @@ -1,222 +1,218 @@ [gamevals.enum] -fletching_anims = 65473 +fletching_anims=65470 [gamevals.dbtable] -fletching_cutting = 55538 -fletching_stringing = 55539 -fletching_attaching = 55540 -fletching_gem_tips = 55541 -fletching_assembly = 55542 +fletching_cutting=65501 +fletching_stringing=65500 +fletching_attaching=65499 +fletching_gem_tips=65498 +fletching_assembly=65497 [gamevals.queue] -fletching_produce = 60 +fletching_produce = 61 [gamevals.dbrow] -# cutting 56000-56045, 56192-56202 | stringing 56046-56067 -# attaching 56068-56148, 56183-56191 | gem_tips 56149-56166, 56203 -# assembly 56167-56182 -# Ids are append-only: later batches take the next free id rather than renumbering. -fletching_arrow_shaft_from_logs = 56000 -fletching_brew_scrapey_bark_from_brew_scrapey_logs = 56001 -fletching_seeking_arrow_shaft_from_sap_treated_logs = 56002 -fletching_javelin_shaft_from_logs = 56003 -fletching_ogre_arrow_shaft_from_achey_tree_logs = 56004 -fletching_unstrung_shortbow_from_logs = 56005 -fletching_xbows_crossbow_stock_wood_from_logs = 56006 -fletching_unstrung_longbow_from_logs = 56007 -fletching_arrow_shaft_from_oak_logs = 56008 -fletching_seeking_arrow_shaft_from_sap_treated_oak_logs = 56009 -fletching_unstrung_oak_shortbow_from_oak_logs = 56010 -fletching_xbows_crossbow_stock_oak_from_oak_logs = 56011 -fletching_unstrung_oak_longbow_from_oak_logs = 56012 -fletching_oak_shield_from_oak_logs = 56013 -fletching_arrow_shaft_from_willow_logs = 56014 -fletching_seeking_arrow_shaft_from_sap_treated_willow_logs = 56015 -fletching_unstrung_willow_shortbow_from_willow_logs = 56016 -fletching_xbows_crossbow_stock_willow_from_willow_logs = 56017 -fletching_unstrung_willow_longbow_from_willow_logs = 56018 -fletching_willow_shield_from_willow_logs = 56019 -fletching_arrow_shaft_from_maple_logs = 56020 -fletching_seeking_arrow_shaft_from_sap_treated_maple_logs = 56021 -fletching_xbows_crossbow_stock_teak_from_teak_logs = 56022 -fletching_unstrung_maple_shortbow_from_maple_logs = 56023 -fletching_xbows_crossbow_stock_maple_from_maple_logs = 56024 -fletching_unstrung_maple_longbow_from_maple_logs = 56025 -fletching_maple_shield_from_maple_logs = 56026 -fletching_arrow_shaft_from_yew_logs = 56027 -fletching_seeking_arrow_shaft_from_sap_treated_yew_logs = 56028 -fletching_xbows_crossbow_stock_mahogany_from_mahogany_logs = 56029 -fletching_unstrung_yew_shortbow_from_yew_logs = 56030 -fletching_xbows_crossbow_stock_yew_from_yew_logs = 56031 -fletching_blisterwood_stake_from_blisterwood_logs = 56032 -fletching_unstrung_yew_longbow_from_yew_logs = 56033 -fletching_yew_shield_from_yew_logs = 56034 -fletching_atlatl_dart_shaft_from_ent_branch = 56035 -fletching_arrow_shaft_from_magic_logs = 56036 -fletching_seeking_arrow_shaft_from_sap_treated_magic_logs = 56037 -fletching_xbows_crossbow_stock_magic_from_magic_logs = 56038 -fletching_unstrung_magic_shortbow_from_magic_logs = 56039 -fletching_unstrung_magic_longbow_from_magic_logs = 56040 -fletching_magic_shield_from_magic_logs = 56041 -fletching_arrow_shaft_from_redwood_logs = 56042 -fletching_redwood_hiking_staff_from_redwood_logs = 56043 -fletching_seeking_arrow_shaft_from_sap_treated_redwood_logs = 56044 -fletching_redwood_shield_from_redwood_logs = 56045 -fletching_shortbow_from_bow_string = 56046 -fletching_xbows_crossbow_bronze_from_xbows_crossbow_unstrung_bronze = 56047 -fletching_longbow_from_bow_string = 56048 -fletching_oak_shortbow_from_bow_string = 56049 -fletching_xbows_crossbow_blurite_from_xbows_crossbow_unstrung_blurite = 56050 -fletching_oak_longbow_from_bow_string = 56051 -fletching_zogre_bow_from_bow_string = 56052 -fletching_willow_shortbow_from_bow_string = 56053 -fletching_xbows_crossbow_iron_from_xbows_crossbow_string = 56054 -fletching_willow_longbow_from_bow_string = 56055 -fletching_xbows_crossbow_steel_from_xbows_crossbow_string = 56056 -fletching_devious_bowsword_from_bow_string = 56057 -fletching_maple_shortbow_from_bow_string = 56058 -fletching_xbows_crossbow_mithril_from_xbows_crossbow_string = 56059 -fletching_maple_longbow_from_bow_string = 56060 -fletching_xbows_crossbow_adamantite_from_xbows_crossbow_unstrung_adamantite = 56061 -fletching_yew_shortbow_from_bow_string = 56062 -fletching_xbows_crossbow_runite_from_xbows_crossbow_string = 56063 -fletching_yew_longbow_from_bow_string = 56064 -fletching_xbows_crossbow_dragon_from_xbows_crossbow_string = 56065 -fletching_magic_shortbow_from_bow_string = 56066 -fletching_magic_longbow_from_bow_string = 56067 -fletching_bronze_arrow_from_bronze_arrowheads = 56068 -fletching_headless_arrow_from_arrow_shaft = 56069 -fletching_seeking_adamant_arrow_from_adamant_arrow = 56070 -fletching_seeking_amethyst_arrow_from_amethyst_arrow = 56071 -fletching_seeking_slayer_broad_arrows_from_slayer_broad_arrows = 56072 -fletching_seeking_bronze_arrow_from_bronze_arrowheads = 56073 -fletching_seeking_bronze_arrow_from_bronze_arrow = 56074 -fletching_seeking_dragon_arrow_from_bucket_of_bloodwood_sap = 56075 -fletching_seeking_headless_arrow_from_feather = 56076 -fletching_seeking_iron_arrow_from_bucket_of_bloodwood_sap = 56077 -fletching_seeking_mithril_arrow_from_bucket_of_bloodwood_sap = 56078 -fletching_seeking_rune_arrow_from_bucket_of_bloodwood_sap = 56079 -fletching_seeking_steel_arrow_from_bucket_of_bloodwood_sap = 56080 -fletching_bronze_javelin_from_bronze_javelin_head = 56081 -fletching_ogre_headless_arrow_from_feather = 56082 -fletching_bolt_from_xbows_crossbow_bolts_bronze_unfeathered = 56083 -fletching_bronze_dart_from_bronze_dart_tip = 56084 -fletching_thprotodart_from_feather = 56085 -fletching_opal_bolt_from_bolt = 56086 -fletching_iron_arrow_from_headless_arrow = 56087 -fletching_seeking_iron_arrow_from_iron_arrowheads = 56088 -fletching_seeking_steel_arrow_from_seeking_headless_arrow = 56089 -fletching_iron_javelin_from_iron_javelin_head = 56090 -fletching_iron_dart_from_feather = 56091 -fletching_xbows_crossbow_bolts_blurite_from_xbows_crossbow_bolts_blurite_unfeathered = 56092 -fletching_xbows_crossbow_bolts_blurite_tipped_jade_from_xbows_crossbow_bolts_blurite = 56093 -fletching_steel_arrow_from_headless_arrow = 56094 -fletching_steel_javelin_from_javelin_shaft = 56095 -fletching_steel_dart_from_feather = 56096 -fletching_xbows_crossbow_bolts_iron_from_feather = 56097 -fletching_pearl_bolt_from_xbows_crossbow_bolts_iron = 56098 -fletching_xbows_crossbow_bolts_silver_from_feather = 56099 -fletching_mithril_arrow_from_headless_arrow = 56100 -fletching_seeking_mithril_arrow_from_mithril_arrowheads = 56101 -fletching_xbows_crossbow_bolts_steel_from_feather = 56102 -fletching_mithril_javelin_from_javelin_shaft = 56103 -fletching_xbows_crossbow_bolts_steel_tipped_redtopaz_from_xbows_crossbow_bolts_steel = 56104 -fletching_barbed_bolt_from_barbed_bolttips = 56105 -fletching_slayer_broad_arrows_from_slayer_broad_arrowhead = 56106 -fletching_mithril_dart_from_feather = 56107 -fletching_seeking_slayer_broad_arrows_from_slayer_broad_arrowhead = 56108 -fletching_xbows_crossbow_bolts_mithril_from_feather = 56109 -fletching_slayer_broad_bolt_from_feather = 56110 -fletching_xbows_crossbow_bolts_mithril_tipped_sapphire_from_xbows_crossbow_bolts_mithril = 56111 -fletching_xbows_crossbow_bolts_mithril_tipped_emerald_from_xbows_bolt_tips_emerald = 56112 -fletching_adamant_arrow_from_adamant_arrowheads = 56113 -fletching_seeking_adamant_arrow_from_adamant_arrowheads = 56114 -fletching_xbows_crossbow_bolts_adamantite_from_xbows_crossbow_bolts_adamantite_unfeathered = 56115 -fletching_adamant_javelin_from_adamant_javelin_head = 56116 -fletching_xbows_crossbow_bolts_adamantite_tipped_ruby_from_xbows_crossbow_bolts_adamantite = 56117 -fletching_xbows_crossbow_bolts_adamantite_tipped_diamond_from_xbows_crossbow_bolts_adamantite = 56118 -fletching_adamant_dart_from_adamant_dart_tip = 56119 -fletching_xbows_crossbow_bolts_runite_from_feather = 56120 -fletching_xbows_crossbow_bolts_runite_tipped_dragonstone_from_xbows_bolt_tips_dragonstone = 56121 -fletching_xbows_crossbow_bolts_runite_tipped_onyx_from_xbows_bolt_tips_onyx = 56122 -fletching_atlatl_dart_from_atlatl_dart_tips = 56123 -fletching_headless_atlatl_dart_from_atlatl_dart_shaft = 56124 -fletching_rune_arrow_from_headless_arrow = 56125 -fletching_seeking_rune_arrow_from_rune_arrowheads = 56126 -fletching_slayer_broad_bolt_amethyst_from_xbows_bolt_tips_amethyst = 56127 -fletching_rune_javelin_from_javelin_shaft = 56128 -fletching_rune_dart_from_feather = 56129 -fletching_amethyst_arrow_from_amethyst_arrowheads = 56130 -fletching_seeking_amethyst_arrow_from_amethyst_arrowheads = 56131 -fletching_amethyst_javelin_from_amethyst_javelin_head = 56132 -fletching_dragon_bolts_unenchanted_diamond_from_xbows_bolt_tips_diamond = 56133 -fletching_dragon_bolts_from_dragon_bolts_unfeathered = 56134 -fletching_dragon_bolts_unenchanted_dragonstone_from_dragon_bolts = 56135 -fletching_dragon_bolts_unenchanted_emerald_from_dragon_bolts = 56136 -fletching_dragon_bolts_unenchanted_jade_from_dragon_bolts = 56137 -fletching_dragon_bolts_unenchanted_onyx_from_dragon_bolts = 56138 -fletching_dragon_bolts_unenchanted_opal_from_dragon_bolts = 56139 -fletching_dragon_bolts_unenchanted_pearl_from_dragon_bolts = 56140 -fletching_dragon_bolts_unenchanted_ruby_from_dragon_bolts = 56141 -fletching_dragon_bolts_unenchanted_sapphire_from_dragon_bolts = 56142 -fletching_dragon_bolts_unenchanted_topaz_from_dragon_bolts = 56143 -fletching_amethyst_dart_from_amethyst_dart_tip = 56144 -fletching_dragon_arrow_from_dragon_arrowheads = 56145 -fletching_seeking_dragon_arrow_from_dragon_arrowheads = 56146 -fletching_dragon_javelin_from_dragon_javelin_head = 56147 -fletching_dragon_dart_from_dragon_dart_tip = 56148 -fletching_wolfbone_arrowheads_from_wolf_bones = 56149 -fletching_opal_bolttips_from_opal = 56150 -fletching_xbows_bolt_tips_jade_from_jade = 56151 -fletching_huntingbow_bolts_from_huntingbeast_spike = 56152 -fletching_pearl_bolttips_from_smalloysterpearls = 56153 -fletching_pearl_bolttips_from_bigoysterpearls = 56154 -fletching_huntingbow_bigbolts_from_huntingbeast_bigspike = 56155 -fletching_xbows_bolt_tips_redtopaz_from_red_topaz = 56156 -fletching_xbows_bolt_tips_sapphire_from_sapphire = 56157 -fletching_xbows_bolt_tips_emerald_from_emerald = 56158 -fletching_sunlight_antelope_bolt_from_hunting_antelopesun_horn = 56159 -fletching_xbows_bolt_tips_ruby_from_ruby = 56160 -fletching_xbows_bolt_tips_diamond_from_diamond = 56161 -fletching_xbows_bolt_tips_dragonstone_from_dragonstone = 56162 -fletching_moonlight_antelope_bolt_from_hunting_antelopemoon_horn = 56163 -fletching_xbows_bolt_tips_onyx_from_onyx = 56164 -fletching_atlatl_dart_tips_from_custodian_broken_antler = 56165 -fletching_toxic_blowpipe_from_blowpipe_fang = 56166 -fletching_ogre_arrow_from_ogre_headless_arrow = 56167 -fletching_zogre_brutal_bronze_from_nails_bronze = 56168 -fletching_xbows_crossbow_unstrung_bronze_from_xbows_crossbow_limbs_bronze = 56169 -fletching_zogre_brutal_iron_from_ogre_headless_arrow = 56170 -fletching_xbows_crossbow_unstrung_blurite_from_xbows_crossbow_limbs_blurite = 56171 -fletching_zogre_brutal_steel_from_ogre_headless_arrow = 56172 -fletching_zogre_brutal_black_from_nails_black = 56173 -fletching_xbows_crossbow_unstrung_iron_from_xbows_crossbow_limbs_iron = 56174 -fletching_xbows_crossbow_unstrung_steel_from_xbows_crossbow_limbs_steel = 56175 -fletching_zogre_brutal_mithril_from_ogre_headless_arrow = 56176 -fletching_xbows_crossbow_unstrung_mithril_from_xbows_crossbow_stock_maple = 56177 -fletching_xbows_crossbow_unstrung_adamantite_from_xbows_crossbow_limbs_adamantite = 56178 -fletching_zogre_brutal_adamant_from_nails_adamant = 56179 -fletching_xbows_crossbow_unstrung_runite_from_xbows_crossbow_limbs_runite = 56180 -fletching_zogre_brutal_rune_from_ogre_headless_arrow = 56181 -fletching_xbows_crossbow_unstrung_dragon_from_xbows_crossbow_limbs_dragon = 56182 -fletching_seeking_adamant_arrow_from_adamant_arrow_x150 = 56183 -fletching_seeking_amethyst_arrow_from_amethyst_arrow_x150 = 56184 -fletching_seeking_slayer_broad_arrows_from_slayer_broad_arrows_x150 = 56185 -fletching_seeking_bronze_arrow_from_bronze_arrow_x150 = 56186 -fletching_seeking_dragon_arrow_from_dragon_arrow_x150 = 56187 -fletching_seeking_iron_arrow_from_iron_arrow_x150 = 56188 -fletching_seeking_mithril_arrow_from_mithril_arrow_x150 = 56189 -fletching_seeking_rune_arrow_from_rune_arrow_x150 = 56190 -fletching_seeking_steel_arrow_from_steel_arrow_x150 = 56191 -fletching_unstrung_zogre_bow_from_achey_tree_logs = 56192 -fletching_battlestaff_from_celastrus_wood = 56193 -fletching_vmq4_root_kindling_from_vmq4_roots = 56194 -fletching_greenman_statue_from_maple_logs = 56195 -fletching_camphor_blowpipe_empty_from_camphor_logs = 56196 -fletching_blisterwood_sickle_from_blisterwood_logs = 56197 -fletching_hg_hunter_spear_from_teak_logs = 56198 -fletching_blisterwood_sickle_enhanced_from_blisterwood_logs = 56199 -fletching_ironwood_blowpipe_empty_from_ironwood_logs = 56200 -fletching_greenman_wall_decoration_from_yew_logs = 56201 -fletching_rosewood_blowpipe_empty_from_rosewood_logs = 56202 -fletching_hunting_crossbow_sunlight_from_hunting_antelopesun_horn = 56203 +fletching_arrow_shaft_from_logs=63893 +fletching_brew_scrapey_bark_from_brew_scrapey_logs=63892 +fletching_seeking_arrow_shaft_from_sap_treated_logs=63891 +fletching_javelin_shaft_from_logs=63890 +fletching_ogre_arrow_shaft_from_achey_tree_logs=63889 +fletching_unstrung_shortbow_from_logs=63888 +fletching_xbows_crossbow_stock_wood_from_logs=63887 +fletching_unstrung_longbow_from_logs=63886 +fletching_arrow_shaft_from_oak_logs=63885 +fletching_seeking_arrow_shaft_from_sap_treated_oak_logs=63884 +fletching_unstrung_oak_shortbow_from_oak_logs=63883 +fletching_xbows_crossbow_stock_oak_from_oak_logs=63882 +fletching_unstrung_oak_longbow_from_oak_logs=63881 +fletching_oak_shield_from_oak_logs=63880 +fletching_arrow_shaft_from_willow_logs=63879 +fletching_seeking_arrow_shaft_from_sap_treated_willow_logs=63878 +fletching_unstrung_willow_shortbow_from_willow_logs=63877 +fletching_xbows_crossbow_stock_willow_from_willow_logs=63876 +fletching_unstrung_willow_longbow_from_willow_logs=63875 +fletching_willow_shield_from_willow_logs=63874 +fletching_arrow_shaft_from_maple_logs=63873 +fletching_seeking_arrow_shaft_from_sap_treated_maple_logs=63872 +fletching_xbows_crossbow_stock_teak_from_teak_logs=63871 +fletching_unstrung_maple_shortbow_from_maple_logs=63870 +fletching_xbows_crossbow_stock_maple_from_maple_logs=63869 +fletching_unstrung_maple_longbow_from_maple_logs=63868 +fletching_maple_shield_from_maple_logs=63867 +fletching_arrow_shaft_from_yew_logs=63866 +fletching_seeking_arrow_shaft_from_sap_treated_yew_logs=63865 +fletching_xbows_crossbow_stock_mahogany_from_mahogany_logs=63864 +fletching_unstrung_yew_shortbow_from_yew_logs=63863 +fletching_xbows_crossbow_stock_yew_from_yew_logs=63862 +fletching_blisterwood_stake_from_blisterwood_logs=63861 +fletching_unstrung_yew_longbow_from_yew_logs=63860 +fletching_yew_shield_from_yew_logs=63859 +fletching_atlatl_dart_shaft_from_ent_branch=63858 +fletching_arrow_shaft_from_magic_logs=63857 +fletching_seeking_arrow_shaft_from_sap_treated_magic_logs=63856 +fletching_xbows_crossbow_stock_magic_from_magic_logs=63855 +fletching_unstrung_magic_shortbow_from_magic_logs=63854 +fletching_unstrung_magic_longbow_from_magic_logs=63853 +fletching_magic_shield_from_magic_logs=63852 +fletching_arrow_shaft_from_redwood_logs=63851 +fletching_redwood_hiking_staff_from_redwood_logs=63850 +fletching_seeking_arrow_shaft_from_sap_treated_redwood_logs=63849 +fletching_redwood_shield_from_redwood_logs=63848 +fletching_shortbow_from_bow_string=63847 +fletching_xbows_crossbow_bronze_from_xbows_crossbow_unstrung_bronze=63846 +fletching_longbow_from_bow_string=63845 +fletching_oak_shortbow_from_bow_string=63844 +fletching_xbows_crossbow_blurite_from_xbows_crossbow_unstrung_blurite=63843 +fletching_oak_longbow_from_bow_string=63842 +fletching_zogre_bow_from_bow_string=63841 +fletching_willow_shortbow_from_bow_string=63840 +fletching_xbows_crossbow_iron_from_xbows_crossbow_string=63839 +fletching_willow_longbow_from_bow_string=63838 +fletching_xbows_crossbow_steel_from_xbows_crossbow_string=63837 +fletching_devious_bowsword_from_bow_string=63836 +fletching_maple_shortbow_from_bow_string=63835 +fletching_xbows_crossbow_mithril_from_xbows_crossbow_string=63834 +fletching_maple_longbow_from_bow_string=63833 +fletching_xbows_crossbow_adamantite_from_xbows_crossbow_unstrung_adamantite=63832 +fletching_yew_shortbow_from_bow_string=63831 +fletching_xbows_crossbow_runite_from_xbows_crossbow_string=63830 +fletching_yew_longbow_from_bow_string=63829 +fletching_xbows_crossbow_dragon_from_xbows_crossbow_string=63828 +fletching_magic_shortbow_from_bow_string=63827 +fletching_magic_longbow_from_bow_string=63826 +fletching_bronze_arrow_from_bronze_arrowheads=63825 +fletching_headless_arrow_from_arrow_shaft=63824 +fletching_seeking_adamant_arrow_from_adamant_arrow=63823 +fletching_seeking_amethyst_arrow_from_amethyst_arrow=63822 +fletching_seeking_slayer_broad_arrows_from_slayer_broad_arrows=63821 +fletching_seeking_bronze_arrow_from_bronze_arrowheads=63820 +fletching_seeking_bronze_arrow_from_bronze_arrow=63819 +fletching_seeking_dragon_arrow_from_bucket_of_bloodwood_sap=63818 +fletching_seeking_headless_arrow_from_feather=63817 +fletching_seeking_iron_arrow_from_bucket_of_bloodwood_sap=63816 +fletching_seeking_mithril_arrow_from_bucket_of_bloodwood_sap=63815 +fletching_seeking_rune_arrow_from_bucket_of_bloodwood_sap=63814 +fletching_seeking_steel_arrow_from_bucket_of_bloodwood_sap=63813 +fletching_bronze_javelin_from_bronze_javelin_head=63812 +fletching_ogre_headless_arrow_from_feather=63811 +fletching_bolt_from_xbows_crossbow_bolts_bronze_unfeathered=63810 +fletching_bronze_dart_from_bronze_dart_tip=63809 +fletching_thprotodart_from_feather=63808 +fletching_opal_bolt_from_bolt=63807 +fletching_iron_arrow_from_headless_arrow=63806 +fletching_seeking_iron_arrow_from_iron_arrowheads=63805 +fletching_seeking_steel_arrow_from_seeking_headless_arrow=63804 +fletching_iron_javelin_from_iron_javelin_head=63803 +fletching_iron_dart_from_feather=63802 +fletching_xbows_crossbow_bolts_blurite_from_xbows_crossbow_bolts_blurite_unfeathered=63801 +fletching_xbows_crossbow_bolts_blurite_tipped_jade_from_xbows_crossbow_bolts_blurite=63800 +fletching_steel_arrow_from_headless_arrow=63799 +fletching_steel_javelin_from_javelin_shaft=63798 +fletching_steel_dart_from_feather=63797 +fletching_xbows_crossbow_bolts_iron_from_feather=63796 +fletching_pearl_bolt_from_xbows_crossbow_bolts_iron=63795 +fletching_xbows_crossbow_bolts_silver_from_feather=63794 +fletching_mithril_arrow_from_headless_arrow=63793 +fletching_seeking_mithril_arrow_from_mithril_arrowheads=63792 +fletching_xbows_crossbow_bolts_steel_from_feather=63791 +fletching_mithril_javelin_from_javelin_shaft=63790 +fletching_xbows_crossbow_bolts_steel_tipped_redtopaz_from_xbows_crossbow_bolts_steel=63789 +fletching_barbed_bolt_from_barbed_bolttips=63788 +fletching_slayer_broad_arrows_from_slayer_broad_arrowhead=63787 +fletching_mithril_dart_from_feather=63786 +fletching_seeking_slayer_broad_arrows_from_slayer_broad_arrowhead=63785 +fletching_xbows_crossbow_bolts_mithril_from_feather=63784 +fletching_slayer_broad_bolt_from_feather=63783 +fletching_xbows_crossbow_bolts_mithril_tipped_sapphire_from_xbows_crossbow_bolts_mithril=63782 +fletching_xbows_crossbow_bolts_mithril_tipped_emerald_from_xbows_bolt_tips_emerald=63781 +fletching_adamant_arrow_from_adamant_arrowheads=63780 +fletching_seeking_adamant_arrow_from_adamant_arrowheads=63779 +fletching_xbows_crossbow_bolts_adamantite_from_xbows_crossbow_bolts_adamantite_unfeathered=63778 +fletching_adamant_javelin_from_adamant_javelin_head=63777 +fletching_xbows_crossbow_bolts_adamantite_tipped_ruby_from_xbows_crossbow_bolts_adamantite=63776 +fletching_xbows_crossbow_bolts_adamantite_tipped_diamond_from_xbows_crossbow_bolts_adamantite=63775 +fletching_adamant_dart_from_adamant_dart_tip=63774 +fletching_xbows_crossbow_bolts_runite_from_feather=63773 +fletching_xbows_crossbow_bolts_runite_tipped_dragonstone_from_xbows_bolt_tips_dragonstone=63772 +fletching_xbows_crossbow_bolts_runite_tipped_onyx_from_xbows_bolt_tips_onyx=63771 +fletching_atlatl_dart_from_atlatl_dart_tips=63770 +fletching_headless_atlatl_dart_from_atlatl_dart_shaft=63769 +fletching_rune_arrow_from_headless_arrow=63768 +fletching_seeking_rune_arrow_from_rune_arrowheads=63767 +fletching_slayer_broad_bolt_amethyst_from_xbows_bolt_tips_amethyst=63766 +fletching_rune_javelin_from_javelin_shaft=63765 +fletching_rune_dart_from_feather=63764 +fletching_amethyst_arrow_from_amethyst_arrowheads=63763 +fletching_seeking_amethyst_arrow_from_amethyst_arrowheads=63762 +fletching_amethyst_javelin_from_amethyst_javelin_head=63761 +fletching_dragon_bolts_unenchanted_diamond_from_xbows_bolt_tips_diamond=63760 +fletching_dragon_bolts_from_dragon_bolts_unfeathered=63759 +fletching_dragon_bolts_unenchanted_dragonstone_from_dragon_bolts=63758 +fletching_dragon_bolts_unenchanted_emerald_from_dragon_bolts=63757 +fletching_dragon_bolts_unenchanted_jade_from_dragon_bolts=63756 +fletching_dragon_bolts_unenchanted_onyx_from_dragon_bolts=63755 +fletching_dragon_bolts_unenchanted_opal_from_dragon_bolts=63754 +fletching_dragon_bolts_unenchanted_pearl_from_dragon_bolts=63753 +fletching_dragon_bolts_unenchanted_ruby_from_dragon_bolts=63752 +fletching_dragon_bolts_unenchanted_sapphire_from_dragon_bolts=63751 +fletching_dragon_bolts_unenchanted_topaz_from_dragon_bolts=63750 +fletching_amethyst_dart_from_amethyst_dart_tip=63749 +fletching_dragon_arrow_from_dragon_arrowheads=63748 +fletching_seeking_dragon_arrow_from_dragon_arrowheads=63747 +fletching_dragon_javelin_from_dragon_javelin_head=63746 +fletching_dragon_dart_from_dragon_dart_tip=63745 +fletching_wolfbone_arrowheads_from_wolf_bones=63744 +fletching_opal_bolttips_from_opal=63743 +fletching_xbows_bolt_tips_jade_from_jade=63742 +fletching_huntingbow_bolts_from_huntingbeast_spike=63741 +fletching_pearl_bolttips_from_smalloysterpearls=63740 +fletching_pearl_bolttips_from_bigoysterpearls=63739 +fletching_huntingbow_bigbolts_from_huntingbeast_bigspike=63738 +fletching_xbows_bolt_tips_redtopaz_from_red_topaz=63737 +fletching_xbows_bolt_tips_sapphire_from_sapphire=63736 +fletching_xbows_bolt_tips_emerald_from_emerald=63735 +fletching_sunlight_antelope_bolt_from_hunting_antelopesun_horn=63734 +fletching_xbows_bolt_tips_ruby_from_ruby=63733 +fletching_xbows_bolt_tips_diamond_from_diamond=63732 +fletching_xbows_bolt_tips_dragonstone_from_dragonstone=63731 +fletching_moonlight_antelope_bolt_from_hunting_antelopemoon_horn=63730 +fletching_xbows_bolt_tips_onyx_from_onyx=63729 +fletching_atlatl_dart_tips_from_custodian_broken_antler=63728 +fletching_toxic_blowpipe_from_blowpipe_fang=63727 +fletching_ogre_arrow_from_ogre_headless_arrow=63726 +fletching_zogre_brutal_bronze_from_nails_bronze=63725 +fletching_xbows_crossbow_unstrung_bronze_from_xbows_crossbow_limbs_bronze=63724 +fletching_zogre_brutal_iron_from_ogre_headless_arrow=63723 +fletching_xbows_crossbow_unstrung_blurite_from_xbows_crossbow_limbs_blurite=63722 +fletching_zogre_brutal_steel_from_ogre_headless_arrow=63721 +fletching_zogre_brutal_black_from_nails_black=63720 +fletching_xbows_crossbow_unstrung_iron_from_xbows_crossbow_limbs_iron=63719 +fletching_xbows_crossbow_unstrung_steel_from_xbows_crossbow_limbs_steel=63718 +fletching_zogre_brutal_mithril_from_ogre_headless_arrow=63717 +fletching_xbows_crossbow_unstrung_mithril_from_xbows_crossbow_stock_maple=63716 +fletching_xbows_crossbow_unstrung_adamantite_from_xbows_crossbow_limbs_adamantite=63715 +fletching_zogre_brutal_adamant_from_nails_adamant=63714 +fletching_xbows_crossbow_unstrung_runite_from_xbows_crossbow_limbs_runite=63713 +fletching_zogre_brutal_rune_from_ogre_headless_arrow=63712 +fletching_xbows_crossbow_unstrung_dragon_from_xbows_crossbow_limbs_dragon=63711 +fletching_seeking_adamant_arrow_from_adamant_arrow_x150=63710 +fletching_seeking_amethyst_arrow_from_amethyst_arrow_x150=63709 +fletching_seeking_slayer_broad_arrows_from_slayer_broad_arrows_x150=63708 +fletching_seeking_bronze_arrow_from_bronze_arrow_x150=63707 +fletching_seeking_dragon_arrow_from_dragon_arrow_x150=63706 +fletching_seeking_iron_arrow_from_iron_arrow_x150=63705 +fletching_seeking_mithril_arrow_from_mithril_arrow_x150=63704 +fletching_seeking_rune_arrow_from_rune_arrow_x150=63703 +fletching_seeking_steel_arrow_from_steel_arrow_x150=63702 +fletching_unstrung_zogre_bow_from_achey_tree_logs=63701 +fletching_battlestaff_from_celastrus_wood=63700 +fletching_vmq4_root_kindling_from_vmq4_roots=63699 +fletching_greenman_statue_from_maple_logs=63698 +fletching_camphor_blowpipe_empty_from_camphor_logs=63697 +fletching_blisterwood_sickle_from_blisterwood_logs=63696 +fletching_hg_hunter_spear_from_teak_logs=63695 +fletching_blisterwood_sickle_enhanced_from_blisterwood_logs=63694 +fletching_ironwood_blowpipe_empty_from_ironwood_logs=63693 +fletching_greenman_wall_decoration_from_yew_logs=63692 +fletching_rosewood_blowpipe_empty_from_rosewood_logs=63691 +fletching_hunting_crossbow_sunlight_from_hunting_antelopesun_horn=63690