From 17e7725f252c7f5d3be2e9c37d62751c631f196f Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Thu, 18 Jun 2026 18:50:54 -0400 Subject: feat: fletching added including zero-time bolt feathering, which probably will break lots of things --- data/behaviors/chop_magic.yaml | 1 - data/behaviors/chop_mahogany.yaml | 1 - data/behaviors/chop_maple.yaml | 1 - data/behaviors/chop_oak.yaml | 1 - data/behaviors/chop_redwood.yaml | 1 - data/behaviors/chop_teak.yaml | 1 - data/behaviors/chop_tree.yaml | 1 - data/behaviors/chop_willow.yaml | 1 - data/behaviors/chop_yew.yaml | 1 - data/behaviors/mine_adamantite.yaml | 1 - data/behaviors/mine_clay.yaml | 1 - data/behaviors/mine_coal.yaml | 1 - data/behaviors/mine_copper.yaml | 1 - data/behaviors/mine_gold.yaml | 1 - data/behaviors/mine_iron.yaml | 1 - data/behaviors/mine_mithril.yaml | 1 - data/behaviors/mine_runite.yaml | 1 - data/behaviors/mine_scrap.yaml | 1 - data/behaviors/mine_silver.yaml | 1 - data/behaviors/mine_tin.yaml | 1 - data/help/fletch.yaml | 16 ++ data/help/fletching.yaml | 27 ++ data/items/adamant_arrow.yaml | 9 + data/items/adamant_bolts.yaml | 9 + data/items/arrow_shaft.yaml | 6 + data/items/bowstring.yaml | 5 + data/items/bronze_arrow.yaml | 9 + data/items/bronze_bolts.yaml | 9 + data/items/chisel.yaml | 6 + data/items/diamond_bolt_tips.yaml | 6 + data/items/diamond_bolts.yaml | 9 + data/items/emerald_bolt_tips.yaml | 6 + data/items/emerald_bolts.yaml | 9 + data/items/headless_arrow.yaml | 6 + data/items/iron_arrow.yaml | 9 + data/items/iron_bolts.yaml | 9 + data/items/knife.yaml | 6 + data/items/longbow.yaml | 10 + data/items/longbow_u.yaml | 5 + data/items/magic_longbow.yaml | 10 + data/items/magic_longbow_u.yaml | 5 + data/items/magic_shortbow.yaml | 10 + data/items/magic_shortbow_u.yaml | 5 + data/items/maple_longbow.yaml | 10 + data/items/maple_longbow_u.yaml | 5 + data/items/maple_shortbow.yaml | 10 + data/items/maple_shortbow_u.yaml | 5 + data/items/mithril_arrow.yaml | 9 + data/items/mithril_bolts.yaml | 9 + data/items/oak_longbow.yaml | 10 + data/items/oak_longbow_u.yaml | 5 + data/items/oak_shortbow.yaml | 10 + data/items/oak_shortbow_u.yaml | 5 + data/items/ruby_bolt_tips.yaml | 6 + data/items/ruby_bolts.yaml | 9 + data/items/rune_arrow.yaml | 9 + data/items/rune_bolts.yaml | 9 + data/items/sapphire_bolt_tips.yaml | 6 + data/items/sapphire_bolts.yaml | 9 + data/items/shortbow.yaml | 10 + data/items/shortbow_u.yaml | 5 + data/items/steel_arrow.yaml | 9 + data/items/steel_bolts.yaml | 9 + data/items/uncut_diamond.yaml | 2 +- data/items/uncut_emerald.yaml | 2 +- data/items/uncut_ruby.yaml | 2 +- data/items/uncut_sapphire.yaml | 2 +- data/items/willow_longbow.yaml | 10 + data/items/willow_longbow_u.yaml | 5 + data/items/willow_shortbow.yaml | 10 + data/items/willow_shortbow_u.yaml | 5 + data/items/yew_longbow.yaml | 10 + data/items/yew_longbow_u.yaml | 5 + data/items/yew_shortbow.yaml | 10 + data/items/yew_shortbow_u.yaml | 5 + data/recipes/fletch_adamant_arrow.yaml | 14 + data/recipes/fletch_adamant_bolts.yaml | 14 + data/recipes/fletch_arrow_shaft.yaml | 12 + data/recipes/fletch_arrow_shaft_magic.yaml | 12 + data/recipes/fletch_arrow_shaft_maple.yaml | 12 + data/recipes/fletch_arrow_shaft_oak.yaml | 12 + data/recipes/fletch_arrow_shaft_willow.yaml | 12 + data/recipes/fletch_arrow_shaft_yew.yaml | 12 + data/recipes/fletch_bronze_arrow.yaml | 14 + data/recipes/fletch_bronze_bolts.yaml | 14 + data/recipes/fletch_diamond_bolt_tips.yaml | 12 + data/recipes/fletch_diamond_bolts.yaml | 14 + data/recipes/fletch_emerald_bolt_tips.yaml | 12 + data/recipes/fletch_emerald_bolts.yaml | 14 + data/recipes/fletch_headless_arrow.yaml | 14 + data/recipes/fletch_iron_arrow.yaml | 14 + data/recipes/fletch_iron_bolts.yaml | 14 + data/recipes/fletch_longbow.yaml | 13 + data/recipes/fletch_longbow_u.yaml | 11 + data/recipes/fletch_magic_longbow.yaml | 13 + data/recipes/fletch_magic_longbow_u.yaml | 11 + data/recipes/fletch_magic_shortbow.yaml | 13 + data/recipes/fletch_magic_shortbow_u.yaml | 11 + data/recipes/fletch_maple_longbow.yaml | 13 + data/recipes/fletch_maple_longbow_u.yaml | 11 + data/recipes/fletch_maple_shortbow.yaml | 13 + data/recipes/fletch_maple_shortbow_u.yaml | 11 + data/recipes/fletch_mithril_arrow.yaml | 14 + data/recipes/fletch_mithril_bolts.yaml | 14 + data/recipes/fletch_oak_longbow.yaml | 13 + data/recipes/fletch_oak_longbow_u.yaml | 11 + data/recipes/fletch_oak_shortbow.yaml | 13 + data/recipes/fletch_oak_shortbow_u.yaml | 11 + data/recipes/fletch_ruby_bolt_tips.yaml | 12 + data/recipes/fletch_ruby_bolts.yaml | 14 + data/recipes/fletch_rune_arrow.yaml | 14 + data/recipes/fletch_rune_bolts.yaml | 14 + data/recipes/fletch_sapphire_bolt_tips.yaml | 12 + data/recipes/fletch_sapphire_bolts.yaml | 14 + data/recipes/fletch_shortbow.yaml | 13 + data/recipes/fletch_shortbow_u.yaml | 11 + data/recipes/fletch_steel_arrow.yaml | 14 + data/recipes/fletch_steel_bolts.yaml | 14 + data/recipes/fletch_willow_longbow.yaml | 13 + data/recipes/fletch_willow_longbow_u.yaml | 11 + data/recipes/fletch_willow_shortbow.yaml | 13 + data/recipes/fletch_willow_shortbow_u.yaml | 11 + data/recipes/fletch_yew_longbow.yaml | 13 + data/recipes/fletch_yew_longbow_u.yaml | 11 + data/recipes/fletch_yew_shortbow.yaml | 13 + data/recipes/fletch_yew_shortbow_u.yaml | 11 + data/rooms/14.yaml | 12 +- internal/action/behavior.go | 1 - internal/game/action.go | 23 ++ internal/game/action_burn.go | 2 + internal/game/action_fletch.go | 215 ++++++++++++++++ internal/game/action_gather.go | 3 - internal/game/action_production.go | 53 ++++ internal/game/action_state.go | 3 + internal/game/cmd_attack.go | 1 + internal/game/cmd_cook.go | 4 + internal/game/cmd_equipment.go | 7 +- internal/game/cmd_fletch.go | 383 ++++++++++++++++++++++++++++ internal/game/cmd_remove.go | 102 +++++++- internal/game/cmd_score.go | 6 +- internal/game/cmd_smelt.go | 4 + internal/game/cmd_smith.go | 103 +++----- internal/game/cmd_use.go | 19 ++ internal/game/cmd_wear.go | 106 +++++++- internal/game/game.go | 17 +- internal/net/server.go | 1 + internal/player/player.go | 7 + 147 files changed, 2059 insertions(+), 107 deletions(-) create mode 100644 data/help/fletch.yaml create mode 100644 data/help/fletching.yaml create mode 100644 data/items/adamant_arrow.yaml create mode 100644 data/items/adamant_bolts.yaml create mode 100644 data/items/arrow_shaft.yaml create mode 100644 data/items/bowstring.yaml create mode 100644 data/items/bronze_arrow.yaml create mode 100644 data/items/bronze_bolts.yaml create mode 100644 data/items/chisel.yaml create mode 100644 data/items/diamond_bolt_tips.yaml create mode 100644 data/items/diamond_bolts.yaml create mode 100644 data/items/emerald_bolt_tips.yaml create mode 100644 data/items/emerald_bolts.yaml create mode 100644 data/items/headless_arrow.yaml create mode 100644 data/items/iron_arrow.yaml create mode 100644 data/items/iron_bolts.yaml create mode 100644 data/items/knife.yaml create mode 100644 data/items/longbow.yaml create mode 100644 data/items/longbow_u.yaml create mode 100644 data/items/magic_longbow.yaml create mode 100644 data/items/magic_longbow_u.yaml create mode 100644 data/items/magic_shortbow.yaml create mode 100644 data/items/magic_shortbow_u.yaml create mode 100644 data/items/maple_longbow.yaml create mode 100644 data/items/maple_longbow_u.yaml create mode 100644 data/items/maple_shortbow.yaml create mode 100644 data/items/maple_shortbow_u.yaml create mode 100644 data/items/mithril_arrow.yaml create mode 100644 data/items/mithril_bolts.yaml create mode 100644 data/items/oak_longbow.yaml create mode 100644 data/items/oak_longbow_u.yaml create mode 100644 data/items/oak_shortbow.yaml create mode 100644 data/items/oak_shortbow_u.yaml create mode 100644 data/items/ruby_bolt_tips.yaml create mode 100644 data/items/ruby_bolts.yaml create mode 100644 data/items/rune_arrow.yaml create mode 100644 data/items/rune_bolts.yaml create mode 100644 data/items/sapphire_bolt_tips.yaml create mode 100644 data/items/sapphire_bolts.yaml create mode 100644 data/items/shortbow.yaml create mode 100644 data/items/shortbow_u.yaml create mode 100644 data/items/steel_arrow.yaml create mode 100644 data/items/steel_bolts.yaml create mode 100644 data/items/willow_longbow.yaml create mode 100644 data/items/willow_longbow_u.yaml create mode 100644 data/items/willow_shortbow.yaml create mode 100644 data/items/willow_shortbow_u.yaml create mode 100644 data/items/yew_longbow.yaml create mode 100644 data/items/yew_longbow_u.yaml create mode 100644 data/items/yew_shortbow.yaml create mode 100644 data/items/yew_shortbow_u.yaml create mode 100644 data/recipes/fletch_adamant_arrow.yaml create mode 100644 data/recipes/fletch_adamant_bolts.yaml create mode 100644 data/recipes/fletch_arrow_shaft.yaml create mode 100644 data/recipes/fletch_arrow_shaft_magic.yaml create mode 100644 data/recipes/fletch_arrow_shaft_maple.yaml create mode 100644 data/recipes/fletch_arrow_shaft_oak.yaml create mode 100644 data/recipes/fletch_arrow_shaft_willow.yaml create mode 100644 data/recipes/fletch_arrow_shaft_yew.yaml create mode 100644 data/recipes/fletch_bronze_arrow.yaml create mode 100644 data/recipes/fletch_bronze_bolts.yaml create mode 100644 data/recipes/fletch_diamond_bolt_tips.yaml create mode 100644 data/recipes/fletch_diamond_bolts.yaml create mode 100644 data/recipes/fletch_emerald_bolt_tips.yaml create mode 100644 data/recipes/fletch_emerald_bolts.yaml create mode 100644 data/recipes/fletch_headless_arrow.yaml create mode 100644 data/recipes/fletch_iron_arrow.yaml create mode 100644 data/recipes/fletch_iron_bolts.yaml create mode 100644 data/recipes/fletch_longbow.yaml create mode 100644 data/recipes/fletch_longbow_u.yaml create mode 100644 data/recipes/fletch_magic_longbow.yaml create mode 100644 data/recipes/fletch_magic_longbow_u.yaml create mode 100644 data/recipes/fletch_magic_shortbow.yaml create mode 100644 data/recipes/fletch_magic_shortbow_u.yaml create mode 100644 data/recipes/fletch_maple_longbow.yaml create mode 100644 data/recipes/fletch_maple_longbow_u.yaml create mode 100644 data/recipes/fletch_maple_shortbow.yaml create mode 100644 data/recipes/fletch_maple_shortbow_u.yaml create mode 100644 data/recipes/fletch_mithril_arrow.yaml create mode 100644 data/recipes/fletch_mithril_bolts.yaml create mode 100644 data/recipes/fletch_oak_longbow.yaml create mode 100644 data/recipes/fletch_oak_longbow_u.yaml create mode 100644 data/recipes/fletch_oak_shortbow.yaml create mode 100644 data/recipes/fletch_oak_shortbow_u.yaml create mode 100644 data/recipes/fletch_ruby_bolt_tips.yaml create mode 100644 data/recipes/fletch_ruby_bolts.yaml create mode 100644 data/recipes/fletch_rune_arrow.yaml create mode 100644 data/recipes/fletch_rune_bolts.yaml create mode 100644 data/recipes/fletch_sapphire_bolt_tips.yaml create mode 100644 data/recipes/fletch_sapphire_bolts.yaml create mode 100644 data/recipes/fletch_shortbow.yaml create mode 100644 data/recipes/fletch_shortbow_u.yaml create mode 100644 data/recipes/fletch_steel_arrow.yaml create mode 100644 data/recipes/fletch_steel_bolts.yaml create mode 100644 data/recipes/fletch_willow_longbow.yaml create mode 100644 data/recipes/fletch_willow_longbow_u.yaml create mode 100644 data/recipes/fletch_willow_shortbow.yaml create mode 100644 data/recipes/fletch_willow_shortbow_u.yaml create mode 100644 data/recipes/fletch_yew_longbow.yaml create mode 100644 data/recipes/fletch_yew_longbow_u.yaml create mode 100644 data/recipes/fletch_yew_shortbow.yaml create mode 100644 data/recipes/fletch_yew_shortbow_u.yaml create mode 100644 internal/game/action_fletch.go create mode 100644 internal/game/cmd_fletch.go diff --git a/data/behaviors/chop_magic.yaml b/data/behaviors/chop_magic.yaml index 02dbab3..3d55658 100644 --- a/data/behaviors/chop_magic.yaml +++ b/data/behaviors/chop_magic.yaml @@ -19,5 +19,4 @@ drops: respawn_timer: 199 deplete_timer: 390 nest_chance: 256 -respawn_message: "A new magic sapling grows in its place." respawn_broadcast: "A {name} grows back." diff --git a/data/behaviors/chop_mahogany.yaml b/data/behaviors/chop_mahogany.yaml index 7bacfe3..dd6c4c5 100644 --- a/data/behaviors/chop_mahogany.yaml +++ b/data/behaviors/chop_mahogany.yaml @@ -19,5 +19,4 @@ drops: respawn_timer: 14 deplete_timer: 100 nest_chance: 256 -respawn_message: "A new mahogany sapling grows in its place." respawn_broadcast: "A {name} grows back." diff --git a/data/behaviors/chop_maple.yaml b/data/behaviors/chop_maple.yaml index 44c45cf..8505217 100644 --- a/data/behaviors/chop_maple.yaml +++ b/data/behaviors/chop_maple.yaml @@ -19,5 +19,4 @@ drops: respawn_timer: 59 deplete_timer: 100 nest_chance: 256 -respawn_message: "A new maple sapling grows in its place." respawn_broadcast: "A {name} grows back." diff --git a/data/behaviors/chop_oak.yaml b/data/behaviors/chop_oak.yaml index 85c6be0..c9cfb48 100644 --- a/data/behaviors/chop_oak.yaml +++ b/data/behaviors/chop_oak.yaml @@ -19,5 +19,4 @@ drops: respawn_timer: 14 deplete_timer: 45 nest_chance: 256 -respawn_message: "A new oak sapling grows in its place." respawn_broadcast: "An {name} grows back." diff --git a/data/behaviors/chop_redwood.yaml b/data/behaviors/chop_redwood.yaml index 2db92ed..9652f7a 100644 --- a/data/behaviors/chop_redwood.yaml +++ b/data/behaviors/chop_redwood.yaml @@ -19,5 +19,4 @@ drops: respawn_timer: 199 deplete_timer: 440 nest_chance: 256 -respawn_message: "A new redwood sapling grows in its place." respawn_broadcast: "A {name} grows back." diff --git a/data/behaviors/chop_teak.yaml b/data/behaviors/chop_teak.yaml index ba8834d..f1ed770 100644 --- a/data/behaviors/chop_teak.yaml +++ b/data/behaviors/chop_teak.yaml @@ -19,5 +19,4 @@ drops: respawn_timer: 15 deplete_timer: 50 nest_chance: 256 -respawn_message: "A new teak sapling grows in its place." respawn_broadcast: "A {name} grows back." diff --git a/data/behaviors/chop_tree.yaml b/data/behaviors/chop_tree.yaml index fb8c3d2..4a12509 100644 --- a/data/behaviors/chop_tree.yaml +++ b/data/behaviors/chop_tree.yaml @@ -19,5 +19,4 @@ drops: message: "You get some {107}logs{/}." respawn_timer: 80 exhausted_message: "The tree comes crashing down!" -respawn_message: "A new tree grows in its place." respawn_broadcast: "A {name} grows back." diff --git a/data/behaviors/chop_willow.yaml b/data/behaviors/chop_willow.yaml index c4b14b3..597072e 100644 --- a/data/behaviors/chop_willow.yaml +++ b/data/behaviors/chop_willow.yaml @@ -19,5 +19,4 @@ drops: respawn_timer: 14 deplete_timer: 50 nest_chance: 256 -respawn_message: "A new willow sapling grows in its place." respawn_broadcast: "A {name} grows back." diff --git a/data/behaviors/chop_yew.yaml b/data/behaviors/chop_yew.yaml index 058cb5d..1586b4b 100644 --- a/data/behaviors/chop_yew.yaml +++ b/data/behaviors/chop_yew.yaml @@ -19,5 +19,4 @@ drops: respawn_timer: 99 deplete_timer: 190 nest_chance: 256 -respawn_message: "A new yew sapling grows in its place." respawn_broadcast: "A {name} grows back." diff --git a/data/behaviors/mine_adamantite.yaml b/data/behaviors/mine_adamantite.yaml index b5e5d62..0ba62db 100644 --- a/data/behaviors/mine_adamantite.yaml +++ b/data/behaviors/mine_adamantite.yaml @@ -17,5 +17,4 @@ drops: depletes: true message: "You manage to mine some {120}adamantite ore{/}." respawn_timer: 140 -respawn_message: "You see more ore in the rock." respawn_broadcast: "A green glint of adamantite catches your eye from some {name}." diff --git a/data/behaviors/mine_clay.yaml b/data/behaviors/mine_clay.yaml index b4058b1..ff477ca 100644 --- a/data/behaviors/mine_clay.yaml +++ b/data/behaviors/mine_clay.yaml @@ -17,5 +17,4 @@ drops: depletes: true message: "You manage to get some {173}clay{/}." respawn_timer: 50 -respawn_message: "The clay deposit is ready to dig again." respawn_broadcast: "A deposit of clay softens." diff --git a/data/behaviors/mine_coal.yaml b/data/behaviors/mine_coal.yaml index 334e9ec..f2fb01d 100644 --- a/data/behaviors/mine_coal.yaml +++ b/data/behaviors/mine_coal.yaml @@ -17,5 +17,4 @@ drops: depletes: true message: "You manage to mine some {240}coal{/}." respawn_timer: 80 -respawn_message: "You see more coal in the rock." respawn_broadcast: "A dark vein of coal is visible in some {name}." diff --git a/data/behaviors/mine_copper.yaml b/data/behaviors/mine_copper.yaml index 06ee9d2..e805f29 100644 --- a/data/behaviors/mine_copper.yaml +++ b/data/behaviors/mine_copper.yaml @@ -22,5 +22,4 @@ drops: depletes: false message: "You spot a glint of something valuable!" respawn_timer: 50 -respawn_message: "You see more ore in the rock." respawn_broadcast: "A glint of copper catches your eye from some {name}." diff --git a/data/behaviors/mine_gold.yaml b/data/behaviors/mine_gold.yaml index b61f6bf..0257ee6 100644 --- a/data/behaviors/mine_gold.yaml +++ b/data/behaviors/mine_gold.yaml @@ -17,5 +17,4 @@ drops: depletes: true message: "You manage to mine some {220}gold ore{/}." respawn_timer: 90 -respawn_message: "You see more ore in the rock." respawn_broadcast: "A golden glint catches your eye from some {name}." diff --git a/data/behaviors/mine_iron.yaml b/data/behaviors/mine_iron.yaml index e1a8181..290d99a 100644 --- a/data/behaviors/mine_iron.yaml +++ b/data/behaviors/mine_iron.yaml @@ -17,5 +17,4 @@ drops: depletes: true message: "You manage to mine some {250}iron ore{/}." respawn_timer: 60 -respawn_message: "You see more ore in the rock." respawn_broadcast: "A glint of iron catches your eye from some {name}." diff --git a/data/behaviors/mine_mithril.yaml b/data/behaviors/mine_mithril.yaml index 407f1ff..f1d0b43 100644 --- a/data/behaviors/mine_mithril.yaml +++ b/data/behaviors/mine_mithril.yaml @@ -17,5 +17,4 @@ drops: depletes: true message: "You manage to mine some {75}mithril ore{/}." respawn_timer: 110 -respawn_message: "You see more ore in the rock." respawn_broadcast: "A blue glint of mithril catches your eye from some {name}." diff --git a/data/behaviors/mine_runite.yaml b/data/behaviors/mine_runite.yaml index dddd410..534a78a 100644 --- a/data/behaviors/mine_runite.yaml +++ b/data/behaviors/mine_runite.yaml @@ -17,5 +17,4 @@ drops: depletes: true message: "You manage to mine some {87}runite ore{/}." respawn_timer: 180 -respawn_message: "You see more ore in the rock." respawn_broadcast: "A pale blue glint of runite catches your eye from some {name}." diff --git a/data/behaviors/mine_scrap.yaml b/data/behaviors/mine_scrap.yaml index ae07b36..c381b1c 100644 --- a/data/behaviors/mine_scrap.yaml +++ b/data/behaviors/mine_scrap.yaml @@ -17,5 +17,4 @@ drops: depletes: true message: "You salvage some {243}scrap metal{/}." respawn_timer: 50 -respawn_message: "More scrap is revealed in the pile." respawn_broadcast: "The scrap pile looks worth mining again." diff --git a/data/behaviors/mine_silver.yaml b/data/behaviors/mine_silver.yaml index 1257f87..259ab72 100644 --- a/data/behaviors/mine_silver.yaml +++ b/data/behaviors/mine_silver.yaml @@ -17,5 +17,4 @@ drops: depletes: true message: "You manage to mine some {253}silver ore{/}." respawn_timer: 70 -respawn_message: "You see more ore in the rock." respawn_broadcast: "A silvery glint catches your eye from some {name}." diff --git a/data/behaviors/mine_tin.yaml b/data/behaviors/mine_tin.yaml index 7a1a12a..ebc130a 100644 --- a/data/behaviors/mine_tin.yaml +++ b/data/behaviors/mine_tin.yaml @@ -17,5 +17,4 @@ drops: depletes: true message: "You manage to mine some {253}tin ore{/}." respawn_timer: 50 -respawn_message: "You see more ore in the rock." respawn_broadcast: "A glint of tin catches your eye from some {name}." diff --git a/data/help/fletch.yaml b/data/help/fletch.yaml new file mode 100644 index 0000000..6da4117 --- /dev/null +++ b/data/help/fletch.yaml @@ -0,0 +1,16 @@ +name: "fletch" +category: "Commands" +description: | + Fletch items from your inventory. Does not require a station. + + Usage: + fletch -- Show available fletching actions + fletch -- Fletch a specific item + fletch 10 -- Fletch a specific quantity + + Fletching can be done while walking and does not interrupt movement. + Just type "fletch" while walking to start fletching on the go. + + Pressing enter at the fletching table repeats your last selection. + + See also: fletching, use diff --git a/data/help/fletching.yaml b/data/help/fletching.yaml new file mode 100644 index 0000000..b2fe254 --- /dev/null +++ b/data/help/fletching.yaml @@ -0,0 +1,27 @@ +name: "fletching" +category: "Skills" +description: | + Fletching is the skill of making ranged weapons and ammunition. + + To fletch, simply use the "fletch" command anywhere. Fletching does + not require a station and can be done while walking or moving. + + Materials: + Arrow shafts -- Cut logs with a knife + Headless arrows -- Add feathers to arrow shafts + Finished arrows -- Add arrowtips to headless arrows + Bolts -- Add feathers to unfinished bolts + Bolt tips -- Cut gems with a chisel + Unstrung bows -- Cut logs with a knife + Strung bows -- Add bowstring to unstrung bow + + Items can also be combined with the "use" command: + use knife on -- Cut shafts or bows + use chisel on -- Cut bolt tips + use feather on -- Finish bolts + use bowstring on -- String a bow + + Finished arrows and bolts can be equipped in the ammo slot: + wear arrows, wear 100 arrows, remove arrows, remove 100 arrows + + See also: fletch, use, wear, remove diff --git a/data/items/adamant_arrow.yaml b/data/items/adamant_arrow.yaml new file mode 100644 index 0000000..5b3a364 --- /dev/null +++ b/data/items/adamant_arrow.yaml @@ -0,0 +1,9 @@ +id: adamant_arrow +name: adamant arrow +color: "120" +description: "An adamant-tipped arrow." +value: 20 +stackable: true +equip_slot: ammo +stats: + attack: 31 diff --git a/data/items/adamant_bolts.yaml b/data/items/adamant_bolts.yaml new file mode 100644 index 0000000..5f0509e --- /dev/null +++ b/data/items/adamant_bolts.yaml @@ -0,0 +1,9 @@ +id: adamant_bolts +name: adamant bolts +color: "120" +description: "Adamant crossbow bolts." +value: 24 +stackable: true +equip_slot: ammo +stats: + attack: 43 diff --git a/data/items/arrow_shaft.yaml b/data/items/arrow_shaft.yaml new file mode 100644 index 0000000..715a3e6 --- /dev/null +++ b/data/items/arrow_shaft.yaml @@ -0,0 +1,6 @@ +id: arrow_shaft +name: arrow shaft +color: "137" +description: "A wooden arrow shaft. Needs feathers and a head." +value: 1 +stackable: true diff --git a/data/items/bowstring.yaml b/data/items/bowstring.yaml new file mode 100644 index 0000000..a180081 --- /dev/null +++ b/data/items/bowstring.yaml @@ -0,0 +1,5 @@ +id: bowstring +name: bowstring +color: "230" +description: "A string used for stringing bows." +value: 10 diff --git a/data/items/bronze_arrow.yaml b/data/items/bronze_arrow.yaml new file mode 100644 index 0000000..991bc11 --- /dev/null +++ b/data/items/bronze_arrow.yaml @@ -0,0 +1,9 @@ +id: bronze_arrow +name: bronze arrow +color: "178" +description: "A bronze-tipped arrow." +value: 1 +stackable: true +equip_slot: ammo +stats: + attack: 7 diff --git a/data/items/bronze_bolts.yaml b/data/items/bronze_bolts.yaml new file mode 100644 index 0000000..76ca27c --- /dev/null +++ b/data/items/bronze_bolts.yaml @@ -0,0 +1,9 @@ +id: bronze_bolts +name: bronze bolts +color: "178" +description: "Bronze crossbow bolts." +value: 1 +stackable: true +equip_slot: ammo +stats: + attack: 10 diff --git a/data/items/chisel.yaml b/data/items/chisel.yaml new file mode 100644 index 0000000..a99c828 --- /dev/null +++ b/data/items/chisel.yaml @@ -0,0 +1,6 @@ +id: chisel +name: chisel +color: "250" +description: "A chisel used for cutting gems." +value: 5 +tool_type: chisel diff --git a/data/items/diamond_bolt_tips.yaml b/data/items/diamond_bolt_tips.yaml new file mode 100644 index 0000000..1529b11 --- /dev/null +++ b/data/items/diamond_bolt_tips.yaml @@ -0,0 +1,6 @@ +id: diamond_bolt_tips +name: diamond bolt tips +color: "255" +description: "Bolt tips cut from a diamond." +value: 200 +stackable: true diff --git a/data/items/diamond_bolts.yaml b/data/items/diamond_bolts.yaml new file mode 100644 index 0000000..abdc46f --- /dev/null +++ b/data/items/diamond_bolts.yaml @@ -0,0 +1,9 @@ +id: diamond_bolts +name: diamond bolts +color: "255" +description: "Adamant bolts tipped with diamond." +value: 230 +stackable: true +equip_slot: ammo +stats: + attack: 52 diff --git a/data/items/emerald_bolt_tips.yaml b/data/items/emerald_bolt_tips.yaml new file mode 100644 index 0000000..afd285b --- /dev/null +++ b/data/items/emerald_bolt_tips.yaml @@ -0,0 +1,6 @@ +id: emerald_bolt_tips +name: emerald bolt tips +color: "83" +description: "Bolt tips cut from an emerald." +value: 50 +stackable: true diff --git a/data/items/emerald_bolts.yaml b/data/items/emerald_bolts.yaml new file mode 100644 index 0000000..6c9be22 --- /dev/null +++ b/data/items/emerald_bolts.yaml @@ -0,0 +1,9 @@ +id: emerald_bolts +name: emerald bolts +color: "83" +description: "Mithril bolts tipped with emerald." +value: 65 +stackable: true +equip_slot: ammo +stats: + attack: 39 diff --git a/data/items/headless_arrow.yaml b/data/items/headless_arrow.yaml new file mode 100644 index 0000000..acf1384 --- /dev/null +++ b/data/items/headless_arrow.yaml @@ -0,0 +1,6 @@ +id: headless_arrow +name: headless arrow +color: "137" +description: "An arrow shaft with feathers attached. Needs a head." +value: 1 +stackable: true diff --git a/data/items/iron_arrow.yaml b/data/items/iron_arrow.yaml new file mode 100644 index 0000000..c4f698b --- /dev/null +++ b/data/items/iron_arrow.yaml @@ -0,0 +1,9 @@ +id: iron_arrow +name: iron arrow +color: "250" +description: "An iron-tipped arrow." +value: 2 +stackable: true +equip_slot: ammo +stats: + attack: 10 diff --git a/data/items/iron_bolts.yaml b/data/items/iron_bolts.yaml new file mode 100644 index 0000000..7b75132 --- /dev/null +++ b/data/items/iron_bolts.yaml @@ -0,0 +1,9 @@ +id: iron_bolts +name: iron bolts +color: "250" +description: "Iron crossbow bolts." +value: 3 +stackable: true +equip_slot: ammo +stats: + attack: 17 diff --git a/data/items/knife.yaml b/data/items/knife.yaml new file mode 100644 index 0000000..fddf120 --- /dev/null +++ b/data/items/knife.yaml @@ -0,0 +1,6 @@ +id: knife +name: knife +color: "250" +description: "A small knife used for cutting wood and fletching." +value: 5 +tool_type: knife diff --git a/data/items/longbow.yaml b/data/items/longbow.yaml new file mode 100644 index 0000000..b9de1cb --- /dev/null +++ b/data/items/longbow.yaml @@ -0,0 +1,10 @@ +id: longbow +name: longbow +color: "137" +description: "A longbow." +value: 20 +equip_slot: main_hand +weapon_type: ranged +speed: 6 +stats: + attack: 8 diff --git a/data/items/longbow_u.yaml b/data/items/longbow_u.yaml new file mode 100644 index 0000000..93e5ff0 --- /dev/null +++ b/data/items/longbow_u.yaml @@ -0,0 +1,5 @@ +id: longbow_u +name: longbow (u) +color: "137" +description: "An unstrung longbow." +value: 10 diff --git a/data/items/magic_longbow.yaml b/data/items/magic_longbow.yaml new file mode 100644 index 0000000..fbe3b4e --- /dev/null +++ b/data/items/magic_longbow.yaml @@ -0,0 +1,10 @@ +id: magic_longbow +name: magic longbow +color: "63" +description: "A magic longbow." +value: 6000 +equip_slot: main_hand +weapon_type: ranged +speed: 6 +stats: + attack: 69 diff --git a/data/items/magic_longbow_u.yaml b/data/items/magic_longbow_u.yaml new file mode 100644 index 0000000..65682ed --- /dev/null +++ b/data/items/magic_longbow_u.yaml @@ -0,0 +1,5 @@ +id: magic_longbow_u +name: magic longbow (u) +color: "63" +description: "An unstrung magic longbow." +value: 3000 diff --git a/data/items/magic_shortbow.yaml b/data/items/magic_shortbow.yaml new file mode 100644 index 0000000..26b152a --- /dev/null +++ b/data/items/magic_shortbow.yaml @@ -0,0 +1,10 @@ +id: magic_shortbow +name: magic shortbow +color: "63" +description: "A magic shortbow." +value: 4000 +equip_slot: main_hand +weapon_type: ranged +speed: 4 +stats: + attack: 69 diff --git a/data/items/magic_shortbow_u.yaml b/data/items/magic_shortbow_u.yaml new file mode 100644 index 0000000..2075bd1 --- /dev/null +++ b/data/items/magic_shortbow_u.yaml @@ -0,0 +1,5 @@ +id: magic_shortbow_u +name: magic shortbow (u) +color: "63" +description: "An unstrung magic shortbow." +value: 2000 diff --git a/data/items/maple_longbow.yaml b/data/items/maple_longbow.yaml new file mode 100644 index 0000000..6a5e2f6 --- /dev/null +++ b/data/items/maple_longbow.yaml @@ -0,0 +1,10 @@ +id: maple_longbow +name: maple longbow +color: "172" +description: "A maple longbow." +value: 1280 +equip_slot: main_hand +weapon_type: ranged +speed: 6 +stats: + attack: 29 diff --git a/data/items/maple_longbow_u.yaml b/data/items/maple_longbow_u.yaml new file mode 100644 index 0000000..120bf1d --- /dev/null +++ b/data/items/maple_longbow_u.yaml @@ -0,0 +1,5 @@ +id: maple_longbow_u +name: maple longbow (u) +color: "172" +description: "An unstrung maple longbow." +value: 640 diff --git a/data/items/maple_shortbow.yaml b/data/items/maple_shortbow.yaml new file mode 100644 index 0000000..a61b7d9 --- /dev/null +++ b/data/items/maple_shortbow.yaml @@ -0,0 +1,10 @@ +id: maple_shortbow +name: maple shortbow +color: "172" +description: "A maple shortbow." +value: 640 +equip_slot: main_hand +weapon_type: ranged +speed: 4 +stats: + attack: 29 diff --git a/data/items/maple_shortbow_u.yaml b/data/items/maple_shortbow_u.yaml new file mode 100644 index 0000000..6fc6b84 --- /dev/null +++ b/data/items/maple_shortbow_u.yaml @@ -0,0 +1,5 @@ +id: maple_shortbow_u +name: maple shortbow (u) +color: "172" +description: "An unstrung maple shortbow." +value: 320 diff --git a/data/items/mithril_arrow.yaml b/data/items/mithril_arrow.yaml new file mode 100644 index 0000000..e92bfeb --- /dev/null +++ b/data/items/mithril_arrow.yaml @@ -0,0 +1,9 @@ +id: mithril_arrow +name: mithril arrow +color: "75" +description: "A mithril-tipped arrow." +value: 10 +stackable: true +equip_slot: ammo +stats: + attack: 22 diff --git a/data/items/mithril_bolts.yaml b/data/items/mithril_bolts.yaml new file mode 100644 index 0000000..f3b852e --- /dev/null +++ b/data/items/mithril_bolts.yaml @@ -0,0 +1,9 @@ +id: mithril_bolts +name: mithril bolts +color: "75" +description: "Mithril crossbow bolts." +value: 12 +stackable: true +equip_slot: ammo +stats: + attack: 32 diff --git a/data/items/oak_longbow.yaml b/data/items/oak_longbow.yaml new file mode 100644 index 0000000..b66535e --- /dev/null +++ b/data/items/oak_longbow.yaml @@ -0,0 +1,10 @@ +id: oak_longbow +name: oak longbow +color: "136" +description: "An oak longbow." +value: 80 +equip_slot: main_hand +weapon_type: ranged +speed: 6 +stats: + attack: 14 diff --git a/data/items/oak_longbow_u.yaml b/data/items/oak_longbow_u.yaml new file mode 100644 index 0000000..6df0dab --- /dev/null +++ b/data/items/oak_longbow_u.yaml @@ -0,0 +1,5 @@ +id: oak_longbow_u +name: oak longbow (u) +color: "136" +description: "An unstrung oak longbow." +value: 40 diff --git a/data/items/oak_shortbow.yaml b/data/items/oak_shortbow.yaml new file mode 100644 index 0000000..550b210 --- /dev/null +++ b/data/items/oak_shortbow.yaml @@ -0,0 +1,10 @@ +id: oak_shortbow +name: oak shortbow +color: "136" +description: "An oak shortbow." +value: 40 +equip_slot: main_hand +weapon_type: ranged +speed: 4 +stats: + attack: 14 diff --git a/data/items/oak_shortbow_u.yaml b/data/items/oak_shortbow_u.yaml new file mode 100644 index 0000000..5bebc38 --- /dev/null +++ b/data/items/oak_shortbow_u.yaml @@ -0,0 +1,5 @@ +id: oak_shortbow_u +name: oak shortbow (u) +color: "136" +description: "An unstrung oak shortbow." +value: 20 diff --git a/data/items/ruby_bolt_tips.yaml b/data/items/ruby_bolt_tips.yaml new file mode 100644 index 0000000..6ebe1a0 --- /dev/null +++ b/data/items/ruby_bolt_tips.yaml @@ -0,0 +1,6 @@ +id: ruby_bolt_tips +name: ruby bolt tips +color: "196" +description: "Bolt tips cut from a ruby." +value: 100 +stackable: true diff --git a/data/items/ruby_bolts.yaml b/data/items/ruby_bolts.yaml new file mode 100644 index 0000000..3788d2c --- /dev/null +++ b/data/items/ruby_bolts.yaml @@ -0,0 +1,9 @@ +id: ruby_bolts +name: ruby bolts +color: "196" +description: "Adamant bolts tipped with ruby." +value: 130 +stackable: true +equip_slot: ammo +stats: + attack: 49 diff --git a/data/items/rune_arrow.yaml b/data/items/rune_arrow.yaml new file mode 100644 index 0000000..a7a3e2f --- /dev/null +++ b/data/items/rune_arrow.yaml @@ -0,0 +1,9 @@ +id: rune_arrow +name: rune arrow +color: "87" +description: "A rune-tipped arrow." +value: 40 +stackable: true +equip_slot: ammo +stats: + attack: 49 diff --git a/data/items/rune_bolts.yaml b/data/items/rune_bolts.yaml new file mode 100644 index 0000000..7f0f5af --- /dev/null +++ b/data/items/rune_bolts.yaml @@ -0,0 +1,9 @@ +id: rune_bolts +name: rune bolts +color: "87" +description: "Rune crossbow bolts." +value: 48 +stackable: true +equip_slot: ammo +stats: + attack: 55 diff --git a/data/items/sapphire_bolt_tips.yaml b/data/items/sapphire_bolt_tips.yaml new file mode 100644 index 0000000..7513fd3 --- /dev/null +++ b/data/items/sapphire_bolt_tips.yaml @@ -0,0 +1,6 @@ +id: sapphire_bolt_tips +name: sapphire bolt tips +color: "69" +description: "Bolt tips cut from a sapphire." +value: 25 +stackable: true diff --git a/data/items/sapphire_bolts.yaml b/data/items/sapphire_bolts.yaml new file mode 100644 index 0000000..c9d1b4e --- /dev/null +++ b/data/items/sapphire_bolts.yaml @@ -0,0 +1,9 @@ +id: sapphire_bolts +name: sapphire bolts +color: "69" +description: "Mithril bolts tipped with sapphire." +value: 40 +stackable: true +equip_slot: ammo +stats: + attack: 36 diff --git a/data/items/shortbow.yaml b/data/items/shortbow.yaml new file mode 100644 index 0000000..e5b1975 --- /dev/null +++ b/data/items/shortbow.yaml @@ -0,0 +1,10 @@ +id: shortbow +name: shortbow +color: "137" +description: "A shortbow." +value: 10 +equip_slot: main_hand +weapon_type: ranged +speed: 4 +stats: + attack: 8 diff --git a/data/items/shortbow_u.yaml b/data/items/shortbow_u.yaml new file mode 100644 index 0000000..6ffcafc --- /dev/null +++ b/data/items/shortbow_u.yaml @@ -0,0 +1,5 @@ +id: shortbow_u +name: shortbow (u) +color: "137" +description: "An unstrung shortbow." +value: 5 diff --git a/data/items/steel_arrow.yaml b/data/items/steel_arrow.yaml new file mode 100644 index 0000000..9c93e0b --- /dev/null +++ b/data/items/steel_arrow.yaml @@ -0,0 +1,9 @@ +id: steel_arrow +name: steel arrow +color: "253" +description: "A steel-tipped arrow." +value: 5 +stackable: true +equip_slot: ammo +stats: + attack: 16 diff --git a/data/items/steel_bolts.yaml b/data/items/steel_bolts.yaml new file mode 100644 index 0000000..20ed5f6 --- /dev/null +++ b/data/items/steel_bolts.yaml @@ -0,0 +1,9 @@ +id: steel_bolts +name: steel bolts +color: "253" +description: "Steel crossbow bolts." +value: 6 +stackable: true +equip_slot: ammo +stats: + attack: 25 diff --git a/data/items/uncut_diamond.yaml b/data/items/uncut_diamond.yaml index ce7dd3e..48d8e6f 100644 --- a/data/items/uncut_diamond.yaml +++ b/data/items/uncut_diamond.yaml @@ -1,6 +1,6 @@ id: uncut_diamond name: uncut diamond color: "255" -description: "An uncut diamond. Can be cut with crafting." +description: "An uncut diamond. Can be cut into bolt tips with a chisel." value: 200 stackable: false diff --git a/data/items/uncut_emerald.yaml b/data/items/uncut_emerald.yaml index 71da287..5fd5015 100644 --- a/data/items/uncut_emerald.yaml +++ b/data/items/uncut_emerald.yaml @@ -1,6 +1,6 @@ id: uncut_emerald name: uncut emerald color: "83" -description: "An uncut emerald. Can be cut with crafting." +description: "An uncut emerald. Can be cut into bolt tips with a chisel." value: 50 stackable: false diff --git a/data/items/uncut_ruby.yaml b/data/items/uncut_ruby.yaml index 2bd2ec5..b4912b8 100644 --- a/data/items/uncut_ruby.yaml +++ b/data/items/uncut_ruby.yaml @@ -1,6 +1,6 @@ id: uncut_ruby name: uncut ruby color: "196" -description: "An uncut ruby. Can be cut with crafting." +description: "An uncut ruby. Can be cut into bolt tips with a chisel." value: 100 stackable: false diff --git a/data/items/uncut_sapphire.yaml b/data/items/uncut_sapphire.yaml index 4d2ba91..eb0e51d 100644 --- a/data/items/uncut_sapphire.yaml +++ b/data/items/uncut_sapphire.yaml @@ -1,6 +1,6 @@ id: uncut_sapphire name: uncut sapphire color: "69" -description: "An uncut sapphire. Can be cut with crafting." +description: "An uncut sapphire. Can be cut into bolt tips with a chisel." value: 25 stackable: false diff --git a/data/items/willow_longbow.yaml b/data/items/willow_longbow.yaml new file mode 100644 index 0000000..1d8cd2f --- /dev/null +++ b/data/items/willow_longbow.yaml @@ -0,0 +1,10 @@ +id: willow_longbow +name: willow longbow +color: "107" +description: "A willow longbow." +value: 320 +equip_slot: main_hand +weapon_type: ranged +speed: 6 +stats: + attack: 20 diff --git a/data/items/willow_longbow_u.yaml b/data/items/willow_longbow_u.yaml new file mode 100644 index 0000000..5e4e9da --- /dev/null +++ b/data/items/willow_longbow_u.yaml @@ -0,0 +1,5 @@ +id: willow_longbow_u +name: willow longbow (u) +color: "107" +description: "An unstrung willow longbow." +value: 160 diff --git a/data/items/willow_shortbow.yaml b/data/items/willow_shortbow.yaml new file mode 100644 index 0000000..e8e21b1 --- /dev/null +++ b/data/items/willow_shortbow.yaml @@ -0,0 +1,10 @@ +id: willow_shortbow +name: willow shortbow +color: "107" +description: "A willow shortbow." +value: 160 +equip_slot: main_hand +weapon_type: ranged +speed: 4 +stats: + attack: 20 diff --git a/data/items/willow_shortbow_u.yaml b/data/items/willow_shortbow_u.yaml new file mode 100644 index 0000000..b467455 --- /dev/null +++ b/data/items/willow_shortbow_u.yaml @@ -0,0 +1,5 @@ +id: willow_shortbow_u +name: willow shortbow (u) +color: "107" +description: "An unstrung willow shortbow." +value: 80 diff --git a/data/items/yew_longbow.yaml b/data/items/yew_longbow.yaml new file mode 100644 index 0000000..a2e0754 --- /dev/null +++ b/data/items/yew_longbow.yaml @@ -0,0 +1,10 @@ +id: yew_longbow +name: yew longbow +color: "94" +description: "A yew longbow." +value: 2000 +equip_slot: main_hand +weapon_type: ranged +speed: 6 +stats: + attack: 47 diff --git a/data/items/yew_longbow_u.yaml b/data/items/yew_longbow_u.yaml new file mode 100644 index 0000000..ac4fdb2 --- /dev/null +++ b/data/items/yew_longbow_u.yaml @@ -0,0 +1,5 @@ +id: yew_longbow_u +name: yew longbow (u) +color: "94" +description: "An unstrung yew longbow." +value: 1000 diff --git a/data/items/yew_shortbow.yaml b/data/items/yew_shortbow.yaml new file mode 100644 index 0000000..6c29555 --- /dev/null +++ b/data/items/yew_shortbow.yaml @@ -0,0 +1,10 @@ +id: yew_shortbow +name: yew shortbow +color: "94" +description: "A yew shortbow." +value: 1600 +equip_slot: main_hand +weapon_type: ranged +speed: 4 +stats: + attack: 47 diff --git a/data/items/yew_shortbow_u.yaml b/data/items/yew_shortbow_u.yaml new file mode 100644 index 0000000..4c2124b --- /dev/null +++ b/data/items/yew_shortbow_u.yaml @@ -0,0 +1,5 @@ +id: yew_shortbow_u +name: yew shortbow (u) +color: "94" +description: "An unstrung yew shortbow." +value: 800 diff --git a/data/recipes/fletch_adamant_arrow.yaml b/data/recipes/fletch_adamant_arrow.yaml new file mode 100644 index 0000000..a43a9b9 --- /dev/null +++ b/data/recipes/fletch_adamant_arrow.yaml @@ -0,0 +1,14 @@ +id: fletch_adamant_arrow +type: fletching +skill: fletching +level: 60 +xp: 10 +wait: 2 +consume: + - items: [headless_arrow] + qty: 15 + - items: [adamant_arrowtips] + qty: 15 +output: adamant_arrow +output_qty: 15 +message: "You attach the adamant arrowtips to the headless arrows." diff --git a/data/recipes/fletch_adamant_bolts.yaml b/data/recipes/fletch_adamant_bolts.yaml new file mode 100644 index 0000000..c924fe8 --- /dev/null +++ b/data/recipes/fletch_adamant_bolts.yaml @@ -0,0 +1,14 @@ +id: fletch_adamant_bolts +type: fletching +skill: fletching +level: 61 +xp: 70 +wait: 0 +consume: + - items: [adamant_bolts_unf] + qty: 10 + - items: [feather] + qty: 10 +output: adamant_bolts +output_qty: 10 +message: "You attach feathers to the adamant bolts." diff --git a/data/recipes/fletch_arrow_shaft.yaml b/data/recipes/fletch_arrow_shaft.yaml new file mode 100644 index 0000000..fb211b8 --- /dev/null +++ b/data/recipes/fletch_arrow_shaft.yaml @@ -0,0 +1,12 @@ +id: fletch_arrow_shaft +type: fletching +skill: fletching +level: 1 +xp: 5 +wait: 5 +consume: + - items: [logs] + qty: 1 +output: arrow_shaft +output_qty: 15 +message: "You carefully cut the logs into arrow shafts." diff --git a/data/recipes/fletch_arrow_shaft_magic.yaml b/data/recipes/fletch_arrow_shaft_magic.yaml new file mode 100644 index 0000000..a6a8214 --- /dev/null +++ b/data/recipes/fletch_arrow_shaft_magic.yaml @@ -0,0 +1,12 @@ +id: fletch_arrow_shaft_magic +type: fletching +skill: fletching +level: 75 +xp: 30 +wait: 5 +consume: + - items: [magic_logs] + qty: 1 +output: arrow_shaft +output_qty: 40 +message: "You carefully cut the magic logs into arrow shafts." diff --git a/data/recipes/fletch_arrow_shaft_maple.yaml b/data/recipes/fletch_arrow_shaft_maple.yaml new file mode 100644 index 0000000..5bdcdb2 --- /dev/null +++ b/data/recipes/fletch_arrow_shaft_maple.yaml @@ -0,0 +1,12 @@ +id: fletch_arrow_shaft_maple +type: fletching +skill: fletching +level: 45 +xp: 20 +wait: 5 +consume: + - items: [maple_logs] + qty: 1 +output: arrow_shaft +output_qty: 30 +message: "You carefully cut the maple logs into arrow shafts." diff --git a/data/recipes/fletch_arrow_shaft_oak.yaml b/data/recipes/fletch_arrow_shaft_oak.yaml new file mode 100644 index 0000000..b72ed13 --- /dev/null +++ b/data/recipes/fletch_arrow_shaft_oak.yaml @@ -0,0 +1,12 @@ +id: fletch_arrow_shaft_oak +type: fletching +skill: fletching +level: 15 +xp: 10 +wait: 5 +consume: + - items: [oak_logs] + qty: 1 +output: arrow_shaft +output_qty: 20 +message: "You carefully cut the oak logs into arrow shafts." diff --git a/data/recipes/fletch_arrow_shaft_willow.yaml b/data/recipes/fletch_arrow_shaft_willow.yaml new file mode 100644 index 0000000..fe1ded6 --- /dev/null +++ b/data/recipes/fletch_arrow_shaft_willow.yaml @@ -0,0 +1,12 @@ +id: fletch_arrow_shaft_willow +type: fletching +skill: fletching +level: 30 +xp: 15 +wait: 5 +consume: + - items: [willow_logs] + qty: 1 +output: arrow_shaft +output_qty: 25 +message: "You carefully cut the willow logs into arrow shafts." diff --git a/data/recipes/fletch_arrow_shaft_yew.yaml b/data/recipes/fletch_arrow_shaft_yew.yaml new file mode 100644 index 0000000..c946c76 --- /dev/null +++ b/data/recipes/fletch_arrow_shaft_yew.yaml @@ -0,0 +1,12 @@ +id: fletch_arrow_shaft_yew +type: fletching +skill: fletching +level: 60 +xp: 25 +wait: 5 +consume: + - items: [yew_logs] + qty: 1 +output: arrow_shaft +output_qty: 35 +message: "You carefully cut the yew logs into arrow shafts." diff --git a/data/recipes/fletch_bronze_arrow.yaml b/data/recipes/fletch_bronze_arrow.yaml new file mode 100644 index 0000000..51922a8 --- /dev/null +++ b/data/recipes/fletch_bronze_arrow.yaml @@ -0,0 +1,14 @@ +id: fletch_bronze_arrow +type: fletching +skill: fletching +level: 1 +xp: 1 +wait: 2 +consume: + - items: [headless_arrow] + qty: 15 + - items: [bronze_arrowtips] + qty: 15 +output: bronze_arrow +output_qty: 15 +message: "You attach the bronze arrowtips to the headless arrows." diff --git a/data/recipes/fletch_bronze_bolts.yaml b/data/recipes/fletch_bronze_bolts.yaml new file mode 100644 index 0000000..c48602f --- /dev/null +++ b/data/recipes/fletch_bronze_bolts.yaml @@ -0,0 +1,14 @@ +id: fletch_bronze_bolts +type: fletching +skill: fletching +level: 9 +xp: 5 +wait: 0 +consume: + - items: [bronze_bolts_unf] + qty: 10 + - items: [feather] + qty: 10 +output: bronze_bolts +output_qty: 10 +message: "You attach feathers to the bronze bolts." diff --git a/data/recipes/fletch_diamond_bolt_tips.yaml b/data/recipes/fletch_diamond_bolt_tips.yaml new file mode 100644 index 0000000..961aec0 --- /dev/null +++ b/data/recipes/fletch_diamond_bolt_tips.yaml @@ -0,0 +1,12 @@ +id: fletch_diamond_bolt_tips +type: fletching +skill: fletching +level: 65 +xp: 7 +wait: 5 +consume: + - items: [uncut_diamond] + qty: 1 +output: diamond_bolt_tips +output_qty: 12 +message: "You cut the diamond into bolt tips." diff --git a/data/recipes/fletch_diamond_bolts.yaml b/data/recipes/fletch_diamond_bolts.yaml new file mode 100644 index 0000000..2b47268 --- /dev/null +++ b/data/recipes/fletch_diamond_bolts.yaml @@ -0,0 +1,14 @@ +id: fletch_diamond_bolts +type: fletching +skill: fletching +level: 65 +xp: 7 +wait: 2 +consume: + - items: [adamant_bolts] + qty: 10 + - items: [diamond_bolt_tips] + qty: 10 +output: diamond_bolts +output_qty: 10 +message: "You tip the adamant bolts with diamond." diff --git a/data/recipes/fletch_emerald_bolt_tips.yaml b/data/recipes/fletch_emerald_bolt_tips.yaml new file mode 100644 index 0000000..d2f58a0 --- /dev/null +++ b/data/recipes/fletch_emerald_bolt_tips.yaml @@ -0,0 +1,12 @@ +id: fletch_emerald_bolt_tips +type: fletching +skill: fletching +level: 66 +xp: 5 +wait: 5 +consume: + - items: [uncut_emerald] + qty: 1 +output: emerald_bolt_tips +output_qty: 12 +message: "You cut the emerald into bolt tips." diff --git a/data/recipes/fletch_emerald_bolts.yaml b/data/recipes/fletch_emerald_bolts.yaml new file mode 100644 index 0000000..cf80e64 --- /dev/null +++ b/data/recipes/fletch_emerald_bolts.yaml @@ -0,0 +1,14 @@ +id: fletch_emerald_bolts +type: fletching +skill: fletching +level: 66 +xp: 5 +wait: 2 +consume: + - items: [mithril_bolts] + qty: 10 + - items: [emerald_bolt_tips] + qty: 10 +output: emerald_bolts +output_qty: 10 +message: "You tip the mithril bolts with emerald." diff --git a/data/recipes/fletch_headless_arrow.yaml b/data/recipes/fletch_headless_arrow.yaml new file mode 100644 index 0000000..8229369 --- /dev/null +++ b/data/recipes/fletch_headless_arrow.yaml @@ -0,0 +1,14 @@ +id: fletch_headless_arrow +type: fletching +skill: fletching +level: 1 +xp: 1 +wait: 2 +consume: + - items: [arrow_shaft] + qty: 15 + - items: [feather] + qty: 15 +output: headless_arrow +output_qty: 15 +message: "You attach feathers to the arrow shafts." diff --git a/data/recipes/fletch_iron_arrow.yaml b/data/recipes/fletch_iron_arrow.yaml new file mode 100644 index 0000000..0e2f81b --- /dev/null +++ b/data/recipes/fletch_iron_arrow.yaml @@ -0,0 +1,14 @@ +id: fletch_iron_arrow +type: fletching +skill: fletching +level: 15 +xp: 2 +wait: 2 +consume: + - items: [headless_arrow] + qty: 15 + - items: [iron_arrowtips] + qty: 15 +output: iron_arrow +output_qty: 15 +message: "You attach the iron arrowtips to the headless arrows." diff --git a/data/recipes/fletch_iron_bolts.yaml b/data/recipes/fletch_iron_bolts.yaml new file mode 100644 index 0000000..d06ecd5 --- /dev/null +++ b/data/recipes/fletch_iron_bolts.yaml @@ -0,0 +1,14 @@ +id: fletch_iron_bolts +type: fletching +skill: fletching +level: 39 +xp: 15 +wait: 0 +consume: + - items: [iron_bolts_unf] + qty: 10 + - items: [feather] + qty: 10 +output: iron_bolts +output_qty: 10 +message: "You attach feathers to the iron bolts." diff --git a/data/recipes/fletch_longbow.yaml b/data/recipes/fletch_longbow.yaml new file mode 100644 index 0000000..8507dc9 --- /dev/null +++ b/data/recipes/fletch_longbow.yaml @@ -0,0 +1,13 @@ +id: fletch_longbow +type: fletching +skill: fletching +level: 10 +xp: 10 +wait: 2 +consume: + - items: [longbow_u] + qty: 1 + - items: [bowstring] + qty: 1 +output: longbow +message: "You string the longbow." diff --git a/data/recipes/fletch_longbow_u.yaml b/data/recipes/fletch_longbow_u.yaml new file mode 100644 index 0000000..1aeeaa9 --- /dev/null +++ b/data/recipes/fletch_longbow_u.yaml @@ -0,0 +1,11 @@ +id: fletch_longbow_u +type: fletching +skill: fletching +level: 10 +xp: 10 +wait: 3 +consume: + - items: [logs] + qty: 1 +output: longbow_u +message: "You carefully carve the log into a longbow." diff --git a/data/recipes/fletch_magic_longbow.yaml b/data/recipes/fletch_magic_longbow.yaml new file mode 100644 index 0000000..b8a0cb6 --- /dev/null +++ b/data/recipes/fletch_magic_longbow.yaml @@ -0,0 +1,13 @@ +id: fletch_magic_longbow +type: fletching +skill: fletching +level: 85 +xp: 91 +wait: 2 +consume: + - items: [magic_longbow_u] + qty: 1 + - items: [bowstring] + qty: 1 +output: magic_longbow +message: "You string the magic longbow." diff --git a/data/recipes/fletch_magic_longbow_u.yaml b/data/recipes/fletch_magic_longbow_u.yaml new file mode 100644 index 0000000..9b4662b --- /dev/null +++ b/data/recipes/fletch_magic_longbow_u.yaml @@ -0,0 +1,11 @@ +id: fletch_magic_longbow_u +type: fletching +skill: fletching +level: 85 +xp: 91 +wait: 3 +consume: + - items: [magic_logs] + qty: 1 +output: magic_longbow_u +message: "You carefully carve the magic log into a longbow." diff --git a/data/recipes/fletch_magic_shortbow.yaml b/data/recipes/fletch_magic_shortbow.yaml new file mode 100644 index 0000000..9d6a94f --- /dev/null +++ b/data/recipes/fletch_magic_shortbow.yaml @@ -0,0 +1,13 @@ +id: fletch_magic_shortbow +type: fletching +skill: fletching +level: 80 +xp: 83 +wait: 2 +consume: + - items: [magic_shortbow_u] + qty: 1 + - items: [bowstring] + qty: 1 +output: magic_shortbow +message: "You string the magic shortbow." diff --git a/data/recipes/fletch_magic_shortbow_u.yaml b/data/recipes/fletch_magic_shortbow_u.yaml new file mode 100644 index 0000000..dabfaf7 --- /dev/null +++ b/data/recipes/fletch_magic_shortbow_u.yaml @@ -0,0 +1,11 @@ +id: fletch_magic_shortbow_u +type: fletching +skill: fletching +level: 80 +xp: 83 +wait: 3 +consume: + - items: [magic_logs] + qty: 1 +output: magic_shortbow_u +message: "You carefully carve the magic log into a shortbow." diff --git a/data/recipes/fletch_maple_longbow.yaml b/data/recipes/fletch_maple_longbow.yaml new file mode 100644 index 0000000..b90a4fd --- /dev/null +++ b/data/recipes/fletch_maple_longbow.yaml @@ -0,0 +1,13 @@ +id: fletch_maple_longbow +type: fletching +skill: fletching +level: 55 +xp: 58 +wait: 2 +consume: + - items: [maple_longbow_u] + qty: 1 + - items: [bowstring] + qty: 1 +output: maple_longbow +message: "You string the maple longbow." diff --git a/data/recipes/fletch_maple_longbow_u.yaml b/data/recipes/fletch_maple_longbow_u.yaml new file mode 100644 index 0000000..8e34a4d --- /dev/null +++ b/data/recipes/fletch_maple_longbow_u.yaml @@ -0,0 +1,11 @@ +id: fletch_maple_longbow_u +type: fletching +skill: fletching +level: 55 +xp: 58 +wait: 3 +consume: + - items: [maple_logs] + qty: 1 +output: maple_longbow_u +message: "You carefully carve the maple log into a longbow." diff --git a/data/recipes/fletch_maple_shortbow.yaml b/data/recipes/fletch_maple_shortbow.yaml new file mode 100644 index 0000000..d623cc6 --- /dev/null +++ b/data/recipes/fletch_maple_shortbow.yaml @@ -0,0 +1,13 @@ +id: fletch_maple_shortbow +type: fletching +skill: fletching +level: 50 +xp: 50 +wait: 2 +consume: + - items: [maple_shortbow_u] + qty: 1 + - items: [bowstring] + qty: 1 +output: maple_shortbow +message: "You string the maple shortbow." diff --git a/data/recipes/fletch_maple_shortbow_u.yaml b/data/recipes/fletch_maple_shortbow_u.yaml new file mode 100644 index 0000000..9a44d9a --- /dev/null +++ b/data/recipes/fletch_maple_shortbow_u.yaml @@ -0,0 +1,11 @@ +id: fletch_maple_shortbow_u +type: fletching +skill: fletching +level: 50 +xp: 50 +wait: 3 +consume: + - items: [maple_logs] + qty: 1 +output: maple_shortbow_u +message: "You carefully carve the maple log into a shortbow." diff --git a/data/recipes/fletch_mithril_arrow.yaml b/data/recipes/fletch_mithril_arrow.yaml new file mode 100644 index 0000000..5c948b8 --- /dev/null +++ b/data/recipes/fletch_mithril_arrow.yaml @@ -0,0 +1,14 @@ +id: fletch_mithril_arrow +type: fletching +skill: fletching +level: 45 +xp: 7 +wait: 2 +consume: + - items: [headless_arrow] + qty: 15 + - items: [mithril_arrowtips] + qty: 15 +output: mithril_arrow +output_qty: 15 +message: "You attach the mithril arrowtips to the headless arrows." diff --git a/data/recipes/fletch_mithril_bolts.yaml b/data/recipes/fletch_mithril_bolts.yaml new file mode 100644 index 0000000..f695a90 --- /dev/null +++ b/data/recipes/fletch_mithril_bolts.yaml @@ -0,0 +1,14 @@ +id: fletch_mithril_bolts +type: fletching +skill: fletching +level: 54 +xp: 50 +wait: 0 +consume: + - items: [mithril_bolts_unf] + qty: 10 + - items: [feather] + qty: 10 +output: mithril_bolts +output_qty: 10 +message: "You attach feathers to the mithril bolts." diff --git a/data/recipes/fletch_oak_longbow.yaml b/data/recipes/fletch_oak_longbow.yaml new file mode 100644 index 0000000..3e48fe5 --- /dev/null +++ b/data/recipes/fletch_oak_longbow.yaml @@ -0,0 +1,13 @@ +id: fletch_oak_longbow +type: fletching +skill: fletching +level: 25 +xp: 25 +wait: 2 +consume: + - items: [oak_longbow_u] + qty: 1 + - items: [bowstring] + qty: 1 +output: oak_longbow +message: "You string the oak longbow." diff --git a/data/recipes/fletch_oak_longbow_u.yaml b/data/recipes/fletch_oak_longbow_u.yaml new file mode 100644 index 0000000..9127fff --- /dev/null +++ b/data/recipes/fletch_oak_longbow_u.yaml @@ -0,0 +1,11 @@ +id: fletch_oak_longbow_u +type: fletching +skill: fletching +level: 25 +xp: 25 +wait: 3 +consume: + - items: [oak_logs] + qty: 1 +output: oak_longbow_u +message: "You carefully carve the oak log into a longbow." diff --git a/data/recipes/fletch_oak_shortbow.yaml b/data/recipes/fletch_oak_shortbow.yaml new file mode 100644 index 0000000..96f70b0 --- /dev/null +++ b/data/recipes/fletch_oak_shortbow.yaml @@ -0,0 +1,13 @@ +id: fletch_oak_shortbow +type: fletching +skill: fletching +level: 20 +xp: 16 +wait: 2 +consume: + - items: [oak_shortbow_u] + qty: 1 + - items: [bowstring] + qty: 1 +output: oak_shortbow +message: "You string the oak shortbow." diff --git a/data/recipes/fletch_oak_shortbow_u.yaml b/data/recipes/fletch_oak_shortbow_u.yaml new file mode 100644 index 0000000..03cffb3 --- /dev/null +++ b/data/recipes/fletch_oak_shortbow_u.yaml @@ -0,0 +1,11 @@ +id: fletch_oak_shortbow_u +type: fletching +skill: fletching +level: 20 +xp: 16 +wait: 3 +consume: + - items: [oak_logs] + qty: 1 +output: oak_shortbow_u +message: "You carefully carve the oak log into a shortbow." diff --git a/data/recipes/fletch_ruby_bolt_tips.yaml b/data/recipes/fletch_ruby_bolt_tips.yaml new file mode 100644 index 0000000..976bc3a --- /dev/null +++ b/data/recipes/fletch_ruby_bolt_tips.yaml @@ -0,0 +1,12 @@ +id: fletch_ruby_bolt_tips +type: fletching +skill: fletching +level: 73 +xp: 6 +wait: 5 +consume: + - items: [uncut_ruby] + qty: 1 +output: ruby_bolt_tips +output_qty: 12 +message: "You cut the ruby into bolt tips." diff --git a/data/recipes/fletch_ruby_bolts.yaml b/data/recipes/fletch_ruby_bolts.yaml new file mode 100644 index 0000000..fe1b987 --- /dev/null +++ b/data/recipes/fletch_ruby_bolts.yaml @@ -0,0 +1,14 @@ +id: fletch_ruby_bolts +type: fletching +skill: fletching +level: 73 +xp: 6 +wait: 2 +consume: + - items: [adamant_bolts] + qty: 10 + - items: [ruby_bolt_tips] + qty: 10 +output: ruby_bolts +output_qty: 10 +message: "You tip the adamant bolts with ruby." diff --git a/data/recipes/fletch_rune_arrow.yaml b/data/recipes/fletch_rune_arrow.yaml new file mode 100644 index 0000000..61a6bab --- /dev/null +++ b/data/recipes/fletch_rune_arrow.yaml @@ -0,0 +1,14 @@ +id: fletch_rune_arrow +type: fletching +skill: fletching +level: 75 +xp: 12 +wait: 2 +consume: + - items: [headless_arrow] + qty: 15 + - items: [rune_arrowtips] + qty: 15 +output: rune_arrow +output_qty: 15 +message: "You attach the rune arrowtips to the headless arrows." diff --git a/data/recipes/fletch_rune_bolts.yaml b/data/recipes/fletch_rune_bolts.yaml new file mode 100644 index 0000000..9d016a6 --- /dev/null +++ b/data/recipes/fletch_rune_bolts.yaml @@ -0,0 +1,14 @@ +id: fletch_rune_bolts +type: fletching +skill: fletching +level: 69 +xp: 100 +wait: 0 +consume: + - items: [rune_bolts_unf] + qty: 10 + - items: [feather] + qty: 10 +output: rune_bolts +output_qty: 10 +message: "You attach feathers to the rune bolts." diff --git a/data/recipes/fletch_sapphire_bolt_tips.yaml b/data/recipes/fletch_sapphire_bolt_tips.yaml new file mode 100644 index 0000000..f78db3e --- /dev/null +++ b/data/recipes/fletch_sapphire_bolt_tips.yaml @@ -0,0 +1,12 @@ +id: fletch_sapphire_bolt_tips +type: fletching +skill: fletching +level: 63 +xp: 4 +wait: 5 +consume: + - items: [uncut_sapphire] + qty: 1 +output: sapphire_bolt_tips +output_qty: 12 +message: "You cut the sapphire into bolt tips." diff --git a/data/recipes/fletch_sapphire_bolts.yaml b/data/recipes/fletch_sapphire_bolts.yaml new file mode 100644 index 0000000..b678d5a --- /dev/null +++ b/data/recipes/fletch_sapphire_bolts.yaml @@ -0,0 +1,14 @@ +id: fletch_sapphire_bolts +type: fletching +skill: fletching +level: 63 +xp: 4 +wait: 2 +consume: + - items: [mithril_bolts] + qty: 10 + - items: [sapphire_bolt_tips] + qty: 10 +output: sapphire_bolts +output_qty: 10 +message: "You tip the mithril bolts with sapphire." diff --git a/data/recipes/fletch_shortbow.yaml b/data/recipes/fletch_shortbow.yaml new file mode 100644 index 0000000..ae5e196 --- /dev/null +++ b/data/recipes/fletch_shortbow.yaml @@ -0,0 +1,13 @@ +id: fletch_shortbow +type: fletching +skill: fletching +level: 5 +xp: 5 +wait: 2 +consume: + - items: [shortbow_u] + qty: 1 + - items: [bowstring] + qty: 1 +output: shortbow +message: "You string the shortbow." diff --git a/data/recipes/fletch_shortbow_u.yaml b/data/recipes/fletch_shortbow_u.yaml new file mode 100644 index 0000000..b8f3654 --- /dev/null +++ b/data/recipes/fletch_shortbow_u.yaml @@ -0,0 +1,11 @@ +id: fletch_shortbow_u +type: fletching +skill: fletching +level: 5 +xp: 5 +wait: 3 +consume: + - items: [logs] + qty: 1 +output: shortbow_u +message: "You carefully carve the log into a shortbow." diff --git a/data/recipes/fletch_steel_arrow.yaml b/data/recipes/fletch_steel_arrow.yaml new file mode 100644 index 0000000..98a4d70 --- /dev/null +++ b/data/recipes/fletch_steel_arrow.yaml @@ -0,0 +1,14 @@ +id: fletch_steel_arrow +type: fletching +skill: fletching +level: 30 +xp: 5 +wait: 2 +consume: + - items: [headless_arrow] + qty: 15 + - items: [steel_arrowtips] + qty: 15 +output: steel_arrow +output_qty: 15 +message: "You attach the steel arrowtips to the headless arrows." diff --git a/data/recipes/fletch_steel_bolts.yaml b/data/recipes/fletch_steel_bolts.yaml new file mode 100644 index 0000000..ed4d4c2 --- /dev/null +++ b/data/recipes/fletch_steel_bolts.yaml @@ -0,0 +1,14 @@ +id: fletch_steel_bolts +type: fletching +skill: fletching +level: 46 +xp: 35 +wait: 0 +consume: + - items: [steel_bolts_unf] + qty: 10 + - items: [feather] + qty: 10 +output: steel_bolts +output_qty: 10 +message: "You attach feathers to the steel bolts." diff --git a/data/recipes/fletch_willow_longbow.yaml b/data/recipes/fletch_willow_longbow.yaml new file mode 100644 index 0000000..6015c00 --- /dev/null +++ b/data/recipes/fletch_willow_longbow.yaml @@ -0,0 +1,13 @@ +id: fletch_willow_longbow +type: fletching +skill: fletching +level: 40 +xp: 41 +wait: 2 +consume: + - items: [willow_longbow_u] + qty: 1 + - items: [bowstring] + qty: 1 +output: willow_longbow +message: "You string the willow longbow." diff --git a/data/recipes/fletch_willow_longbow_u.yaml b/data/recipes/fletch_willow_longbow_u.yaml new file mode 100644 index 0000000..336ae51 --- /dev/null +++ b/data/recipes/fletch_willow_longbow_u.yaml @@ -0,0 +1,11 @@ +id: fletch_willow_longbow_u +type: fletching +skill: fletching +level: 40 +xp: 41 +wait: 3 +consume: + - items: [willow_logs] + qty: 1 +output: willow_longbow_u +message: "You carefully carve the willow log into a longbow." diff --git a/data/recipes/fletch_willow_shortbow.yaml b/data/recipes/fletch_willow_shortbow.yaml new file mode 100644 index 0000000..028132f --- /dev/null +++ b/data/recipes/fletch_willow_shortbow.yaml @@ -0,0 +1,13 @@ +id: fletch_willow_shortbow +type: fletching +skill: fletching +level: 35 +xp: 33 +wait: 2 +consume: + - items: [willow_shortbow_u] + qty: 1 + - items: [bowstring] + qty: 1 +output: willow_shortbow +message: "You string the willow shortbow." diff --git a/data/recipes/fletch_willow_shortbow_u.yaml b/data/recipes/fletch_willow_shortbow_u.yaml new file mode 100644 index 0000000..2eb7499 --- /dev/null +++ b/data/recipes/fletch_willow_shortbow_u.yaml @@ -0,0 +1,11 @@ +id: fletch_willow_shortbow_u +type: fletching +skill: fletching +level: 35 +xp: 33 +wait: 3 +consume: + - items: [willow_logs] + qty: 1 +output: willow_shortbow_u +message: "You carefully carve the willow log into a shortbow." diff --git a/data/recipes/fletch_yew_longbow.yaml b/data/recipes/fletch_yew_longbow.yaml new file mode 100644 index 0000000..26df51b --- /dev/null +++ b/data/recipes/fletch_yew_longbow.yaml @@ -0,0 +1,13 @@ +id: fletch_yew_longbow +type: fletching +skill: fletching +level: 65 +xp: 75 +wait: 2 +consume: + - items: [yew_longbow_u] + qty: 1 + - items: [bowstring] + qty: 1 +output: yew_longbow +message: "You string the yew longbow." diff --git a/data/recipes/fletch_yew_longbow_u.yaml b/data/recipes/fletch_yew_longbow_u.yaml new file mode 100644 index 0000000..fa7b3b0 --- /dev/null +++ b/data/recipes/fletch_yew_longbow_u.yaml @@ -0,0 +1,11 @@ +id: fletch_yew_longbow_u +type: fletching +skill: fletching +level: 65 +xp: 75 +wait: 3 +consume: + - items: [yew_logs] + qty: 1 +output: yew_longbow_u +message: "You carefully carve the yew log into a longbow." diff --git a/data/recipes/fletch_yew_shortbow.yaml b/data/recipes/fletch_yew_shortbow.yaml new file mode 100644 index 0000000..c54bba0 --- /dev/null +++ b/data/recipes/fletch_yew_shortbow.yaml @@ -0,0 +1,13 @@ +id: fletch_yew_shortbow +type: fletching +skill: fletching +level: 60 +xp: 67 +wait: 2 +consume: + - items: [yew_shortbow_u] + qty: 1 + - items: [bowstring] + qty: 1 +output: yew_shortbow +message: "You string the yew shortbow." diff --git a/data/recipes/fletch_yew_shortbow_u.yaml b/data/recipes/fletch_yew_shortbow_u.yaml new file mode 100644 index 0000000..48877de --- /dev/null +++ b/data/recipes/fletch_yew_shortbow_u.yaml @@ -0,0 +1,11 @@ +id: fletch_yew_shortbow_u +type: fletching +skill: fletching +level: 60 +xp: 67 +wait: 3 +consume: + - items: [yew_logs] + qty: 1 +output: yew_shortbow_u +message: "You carefully carve the yew log into a shortbow." diff --git a/data/rooms/14.yaml b/data/rooms/14.yaml index 2e90539..c3d910d 100644 --- a/data/rooms/14.yaml +++ b/data/rooms/14.yaml @@ -1,6 +1,16 @@ id: 14 name: "Fletching Bench" -description: "A workbench covered in wood shavings, feathers, and arrowheads. This area is not yet accessible." +description: "A workbench covered in wood shavings, feathers, and arrowheads. Racks of unfinished bows line the walls, and a pile of bowstrings sits in the corner." exits: east: 15 west: 13 +spawns: + - item_id: bowstring + quantity: 5 + respawn_ticks: 50 + - item_id: knife + quantity: 1 + respawn_ticks: 50 + - item_id: chisel + quantity: 1 + respawn_ticks: 50 diff --git a/internal/action/behavior.go b/internal/action/behavior.go index c7ed0c4..67baab0 100644 --- a/internal/action/behavior.go +++ b/internal/action/behavior.go @@ -14,7 +14,6 @@ type GatherConfig struct { FailMsg string `yaml:"fail_message"` Drops []DropEntry `yaml:"drops"` RespawnTimer float64 `yaml:"respawn_timer"` - RespawnMsg string `yaml:"respawn_message"` RespawnBroadcast string `yaml:"respawn_broadcast"` DepleteTimer float64 `yaml:"deplete_timer"` NestChance int `yaml:"nest_chance"` diff --git a/internal/game/action.go b/internal/game/action.go index a62ccec..3d0223a 100644 --- a/internal/game/action.go +++ b/internal/game/action.go @@ -53,6 +53,7 @@ func (g *Game) StartAction(sess *net.Session, verb, target string) { if p.Action != nil { g.CancelAction(p) } + g.CancelBackgroundAction(p) lower := strings.ToLower(target) instanceIdx := -1 @@ -202,6 +203,11 @@ func (g *Game) CancelAction(p *player.Player) { p.ClearMoveState() } +func (g *Game) CancelBackgroundAction(p *player.Player) { + p.BackgroundAction = nil + p.BackgroundActionState = nil +} + func (g *Game) AdvanceActions() { if g.Hub == nil { return @@ -238,6 +244,23 @@ func (g *Game) AdvanceActions() { g.writePrompt(sess) } } + + for _, sess := range g.Hub.AllSessions() { + p, ok := sess.Player.(*player.Player) + if !ok || p.BackgroundAction == nil { + continue + } + if !p.BackgroundAction.Advance() { + continue + } + switch p.BackgroundAction.Type { + case "fletch": + g.advanceFletch(sess, p) + } + if p.BackgroundAction == nil { + g.writePrompt(sess) + } + } } func (g *Game) checkCondition(sess *net.Session, c *action.Condition) bool { diff --git a/internal/game/action_burn.go b/internal/game/action_burn.go index 01bf385..0b7db2c 100644 --- a/internal/game/action_burn.go +++ b/internal/game/action_burn.go @@ -15,6 +15,7 @@ func (g *Game) doBurn(sess *net.Session, p *player.Player, input string) { if p.Action != nil { g.CancelAction(p) } + g.CancelBackgroundAction(p) itemID, fromGround, ambiguous := g.resolveBurnTarget(p, input) if ambiguous { @@ -33,6 +34,7 @@ func (g *Game) doStoke(sess *net.Session, p *player.Player, input string) { if p.Action != nil { g.CancelAction(p) } + g.CancelBackgroundAction(p) if !g.hasFireObject(p.RoomID) { sess.WriteLine("There's no fire here to stoke.") diff --git a/internal/game/action_fletch.go b/internal/game/action_fletch.go new file mode 100644 index 0000000..734453b --- /dev/null +++ b/internal/game/action_fletch.go @@ -0,0 +1,215 @@ +package game + +import ( + "fmt" + + "thehouseoficarus/internal/action" + "thehouseoficarus/internal/engine" + "thehouseoficarus/internal/net" + "thehouseoficarus/internal/player" +) + +func (g *Game) startFletchAction(sess *net.Session, p *player.Player, recipe *action.RecipeDef, count int) { + if p.Flags == nil { + p.Flags = make(map[string]any) + } + p.Flags["last_fletch"] = recipe.ID + g.AccountStore.SaveCharacter(p) + + if recipe.Wait <= 0 { + g.doInstantFletch(sess, p, recipe, count) + return + } + + g.startTimedFletch(sess, p, recipe, count) +} + +func (g *Game) doInstantFletch(sess *net.Session, p *player.Player, recipe *action.RecipeDef, count int) { + outDef, _ := g.ItemStore.Load(recipe.Output) + outputQty := recipe.OutputQty + if outputQty <= 0 { + outputQty = 1 + } + + batches := 0 + totalOutput := 0 + for { + if !g.canDoFletchRecipe(p, recipe) { + break + } + + placed := false + if outDef != nil && outDef.Stackable { + for i := 0; i < 28; i++ { + slot := p.InvSlot(i) + if slot != nil && slot.ItemID == recipe.Output { + recipe.ConsumeAll(p.HasItem, p.RemoveItem) + slot.Quantity += outputQty + placed = true + break + } + } + } + if !placed { + recipe.ConsumeAll(p.HasItem, p.RemoveItem) + freeSlot := p.FirstFreeSlot() + if freeSlot == -1 { + break + } + p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: recipe.Output, Quantity: outputQty}) + } + + if recipe.XP > 0 { + if newLevel := p.AddSkillXP(player.Fletching, recipe.XP); newLevel > 0 { + sess.WriteLine(g.colorize(sess, "level_up", fmt.Sprintf("*** You are now level %d fletching! ***", newLevel))) + } + } + + batches++ + totalOutput += outputQty + + if count > 0 { + count-- + if count <= 0 { + break + } + } + } + + if batches == 0 { + sess.WriteLine("You don't have the materials for that.") + return + } + + g.AccountStore.SaveCharacter(p) + + outputName := recipe.Output + if outDef != nil { + outputName = outDef.Name + } + + msg := fmt.Sprintf("You fletch %d %s.", totalOutput, outputName) + if p.OptionBool("xp_drops") && recipe.XP > 0 { + totalXP := recipe.XP * batches + abbr := player.SkillAbbr[player.Fletching] + msg += g.colorize(sess, "xp", fmt.Sprintf(" (+%dxp %s)", totalXP, abbr)) + } + sess.WriteLine(msg) +} + +func (g *Game) startTimedFletch(sess *net.Session, p *player.Player, recipe *action.RecipeDef, count int) { + outDef, _ := g.ItemStore.Load(recipe.Output) + outputName := recipe.Output + if outDef != nil { + outputName = outDef.Name + } + + p.BackgroundAction = &action.Action{ + Type: "fletch", + TargetID: recipe.ID, + TargetName: outputName, + Data: map[string]any{ + "recipe_id": recipe.ID, + "phase": 0, + "wait": recipe.Wait, + "remaining": count, + }, + WaitLeft: engine.ToTicks(1), + } + p.BackgroundActionState = &ActionState{Type: ActionFletching, TargetName: outputName} + + sess.WriteLine(fmt.Sprintf("\nYou begin fletching %s.", outputName)) +} + +func (g *Game) advanceFletch(sess *net.Session, p *player.Player) { + recipeID, _ := p.BackgroundAction.Data["recipe_id"].(string) + phase, _ := p.BackgroundAction.Data["phase"].(int) + wait, _ := p.BackgroundAction.Data["wait"].(float64) + remaining, _ := p.BackgroundAction.Data["remaining"].(int) + + recipe := g.loadProductionRecipe(recipeID) + if recipe == nil { + g.CancelBackgroundAction(p) + return + } + + if phase == 0 { + p.BackgroundAction.Data["phase"] = 1 + p.BackgroundAction.WaitLeft = engine.ToTicks(wait) + return + } + + if !g.canDoFletchRecipe(p, recipe) { + sess.WriteLine("\nYou've run out of fletching materials.") + g.CancelBackgroundAction(p) + return + } + + outDef, _ := g.ItemStore.Load(recipe.Output) + outputQty := recipe.OutputQty + if outputQty <= 0 { + outputQty = 1 + } + + placed := false + if outDef != nil && outDef.Stackable { + for i := 0; i < 28; i++ { + slot := p.InvSlot(i) + if slot != nil && slot.ItemID == recipe.Output { + recipe.ConsumeAll(p.HasItem, p.RemoveItem) + slot.Quantity += outputQty + placed = true + break + } + } + } + if !placed { + recipe.ConsumeAll(p.HasItem, p.RemoveItem) + freeSlot := p.FirstFreeSlot() + if freeSlot == -1 { + sess.WriteLine("\nYour inventory is too full!") + g.CancelBackgroundAction(p) + return + } + p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: recipe.Output, Quantity: outputQty}) + } + + if recipe.XP > 0 { + if newLevel := p.AddSkillXP(player.Fletching, recipe.XP); newLevel > 0 { + sess.WriteLine(g.colorize(sess, "level_up", fmt.Sprintf("*** You are now level %d fletching! ***", newLevel))) + } + } + g.AccountStore.SaveCharacter(p) + + outputName := recipe.Output + if outDef != nil { + outputName = outDef.Name + } + msg := recipe.Message + if msg == "" { + msg = fmt.Sprintf("You fletch %s.", outputName) + } + if p.OptionBool("xp_drops") && recipe.XP > 0 { + abbr := player.SkillAbbr[player.Fletching] + msg += g.colorize(sess, "xp", fmt.Sprintf(" (+%dxp %s)", recipe.XP, abbr)) + } + sess.WriteLine(msg) + + if remaining > 0 { + remaining-- + p.BackgroundAction.Data["remaining"] = remaining + if remaining <= 0 { + sess.WriteLine("\nYou've finished fletching.") + g.CancelBackgroundAction(p) + return + } + } + + if !g.canContinueProduction(p, recipe) { + sess.WriteLine("\nYou've run out of fletching materials.") + g.CancelBackgroundAction(p) + return + } + + p.BackgroundAction.WaitLeft = engine.ToTicks(wait) +} diff --git a/internal/game/action_gather.go b/internal/game/action_gather.go index 045c51b..ffd30e1 100644 --- a/internal/game/action_gather.go +++ b/internal/game/action_gather.go @@ -203,9 +203,6 @@ func (g *Game) advanceGather(sess *net.Session, p *player.Player) { p.Action.WaitLeft = engine.ToTicks(3) return } - if cfg.RespawnMsg != "" { - sess.WriteLine(fmt.Sprintf("\n%s", cfg.RespawnMsg)) - } p.Action.Data["step"] = 0 p.Action.WaitLeft = engine.ToTicks(wait) return diff --git a/internal/game/action_production.go b/internal/game/action_production.go index 0b8ca45..d4e2274 100644 --- a/internal/game/action_production.go +++ b/internal/game/action_production.go @@ -41,6 +41,58 @@ func (g *Game) promptHowMany(sess *net.Session, recipeID string) { sess.Write("How many (return for all)?: ") } +type recipeSelection struct { + Recipe *action.RecipeDef + Count int +} + +func (g *Game) resolveRecipeByInput(input string, recipes []action.RecipeDef, lastRecipeID string) (sel recipeSelection, errMsg string) { + if input == "" { + if lastRecipeID == "" { + return sel, "never_mind" + } + for i := range recipes { + if recipes[i].ID == lastRecipeID { + sel.Recipe = &recipes[i] + return sel, "" + } + } + return sel, "never_mind" + } + + qty, productName := parseQty(input) + productName = strings.TrimSpace(productName) + + if idx, err := strconv.Atoi(productName); err == nil && idx > 0 && idx <= len(recipes) { + sel.Recipe = &recipes[idx-1] + sel.Count = qty + return sel, "" + } + + matchCount := 0 + for i := range recipes { + outDef, _ := g.ItemStore.Load(recipes[i].Output) + name := recipes[i].Output + if outDef != nil { + name = outDef.Name + } + if world.WordPrefixMatch(productName, name) { + if matchCount == 0 { + sel.Recipe = &recipes[i] + sel.Count = qty + } + matchCount++ + } + } + if matchCount > 1 { + return sel, "ambiguous" + } + if sel.Recipe == nil { + return sel, "never_mind" + } + return sel, "" +} + func (g *Game) showMenuTable(sess *net.Session, title string, names []string) { p := sess.Player.(*player.Player) tbl := &Table{Title: title} @@ -129,6 +181,7 @@ func (g *Game) buildCombineRecipe(def *object.ItemDef) *action.RecipeDef { func (g *Game) startProduction(sess *net.Session, p *player.Player, recipe *action.RecipeDef, actionType, displayVerb, startMsg, endMsg string, count int) { g.CancelAction(p) + g.CancelBackgroundAction(p) if recipe.Skill != "" { skillLevel := p.Level(player.SkillName(recipe.Skill)) diff --git a/internal/game/action_state.go b/internal/game/action_state.go index 26c22f3..4510afd 100644 --- a/internal/game/action_state.go +++ b/internal/game/action_state.go @@ -20,6 +20,7 @@ const ( ActionResting ActionType = "resting" ActionWalking ActionType = "walking" ActionProducing ActionType = "producing" + ActionFletching ActionType = "fletching" ActionEating ActionType = "eating" ) @@ -68,6 +69,8 @@ func (a *ActionState) Description() string { return "walking somewhere with a purpose!" case ActionProducing: return a.Verb + " some " + a.TargetName + case ActionFletching: + return "fletching " + a.TargetName case ActionEating: return "eating " + a.TargetName } diff --git a/internal/game/cmd_attack.go b/internal/game/cmd_attack.go index d7fb365..51ac54a 100644 --- a/internal/game/cmd_attack.go +++ b/internal/game/cmd_attack.go @@ -111,6 +111,7 @@ func (g *Game) findMob(sess *net.Session, input string, roomID int) *world.MobIn func (g *Game) startCombat(sess *net.Session, p *player.Player, mob *world.MobInstance) { g.cancelRest(p.Name) + g.CancelBackgroundAction(p) combat.EnterCombat(p.Name, mob.InstanceID) diff --git a/internal/game/cmd_cook.go b/internal/game/cmd_cook.go index b27b293..9b76a84 100644 --- a/internal/game/cmd_cook.go +++ b/internal/game/cmd_cook.go @@ -102,6 +102,10 @@ func (g *Game) showCookMenu(sess *net.Session, p *player.Player, allRecipes []ac } if len(entries) == 1 { + if p.OptionBool("cook_all") { + g.startProductionFromRecipe(sess, p, &entries[0].Recipe, 0) + return + } g.promptHowMany(sess, entries[0].Recipe.ID) return } diff --git a/internal/game/cmd_equipment.go b/internal/game/cmd_equipment.go index 7113880..b256bc3 100644 --- a/internal/game/cmd_equipment.go +++ b/internal/game/cmd_equipment.go @@ -4,6 +4,7 @@ import ( "fmt" "thehouseoficarus/internal/net" + "thehouseoficarus/internal/object" "thehouseoficarus/internal/player" ) @@ -23,6 +24,10 @@ func (g *Game) doEquipment(sess *net.Session) { if err == nil { name = def.Name } - sess.WriteLine(fmt.Sprintf(" %-12s %s", slot, g.itemColorize(sess, def, name))) + if slot == object.SlotAmmo && p.AmmoQty > 0 { + sess.WriteLine(fmt.Sprintf(" %-12s %s (x%d)", slot, g.itemColorize(sess, def, name), p.AmmoQty)) + } else { + sess.WriteLine(fmt.Sprintf(" %-12s %s", slot, g.itemColorize(sess, def, name))) + } } } diff --git a/internal/game/cmd_fletch.go b/internal/game/cmd_fletch.go new file mode 100644 index 0000000..fe8c0b3 --- /dev/null +++ b/internal/game/cmd_fletch.go @@ -0,0 +1,383 @@ +package game + +import ( + "fmt" + "sort" + "strings" + + "thehouseoficarus/internal/action" + "thehouseoficarus/internal/color" + "thehouseoficarus/internal/combat" + "thehouseoficarus/internal/net" + "thehouseoficarus/internal/player" + "thehouseoficarus/internal/world" +) + +var fletchLogItems = map[string]bool{ + "logs": true, "oak_logs": true, "willow_logs": true, + "maple_logs": true, "yew_logs": true, "magic_logs": true, +} + +var fletchGemItems = map[string]bool{ + "uncut_sapphire": true, "uncut_emerald": true, + "uncut_ruby": true, "uncut_diamond": true, +} + +func fletchNeedsKnife(r *action.RecipeDef) bool { + for _, e := range r.Consume { + for _, id := range e.Items { + if fletchLogItems[id] { + if strings.Contains(r.Output, "shaft") || strings.Contains(r.Output, "bow") { + return true + } + } + } + } + return false +} + +func fletchNeedsChisel(r *action.RecipeDef) bool { + for _, e := range r.Consume { + for _, id := range e.Items { + if fletchGemItems[id] { + return true + } + } + } + return false +} + +func (g *Game) doFletch(sess *net.Session, input string) { + p := sess.Player.(*player.Player) + + if combat.GetCombat(p.Name) != nil { + sess.WriteLine("You can't do that during combat!") + return + } + + if p.Action != nil { + p.Action = nil + p.ActionState = nil + } + + allRecipes, err := g.RecipeStore.LoadAll() + if err != nil { + sess.WriteLine("Error loading recipes.") + return + } + + var fletchRecipes []action.RecipeDef + for _, r := range allRecipes { + if r.Type != "fletching" { + continue + } + fletchRecipes = append(fletchRecipes, r) + } + + if input != "" { + g.doFletchWithInput(sess, p, fletchRecipes, input) + return + } + + lastRecipeID, _ := p.Flags["last_fletch"].(string) + if lastRecipeID != "" { + for i := range fletchRecipes { + if fletchRecipes[i].ID == lastRecipeID { + r := &fletchRecipes[i] + if g.canDoFletchRecipe(p, r) { + g.startFletchAction(sess, p, r, 0) + return + } + break + } + } + } + + available := g.availableFletchRecipes(p, fletchRecipes) + if len(available) == 0 { + sess.WriteLine("You don't have any materials to fletch.") + return + } + + if p.OptionBool("fletch_all") && len(available) == 1 { + g.startFletchAction(sess, p, &available[0], 0) + return + } + + g.showFletchTable(sess, p, available) +} + +func (g *Game) doFletchWithInput(sess *net.Session, p *player.Player, fletchRecipes []action.RecipeDef, input string) { + qty, productName := parseQty(input) + productName = strings.TrimSpace(productName) + + var matched []action.RecipeDef + for _, r := range fletchRecipes { + outDef, _ := g.ItemStore.Load(r.Output) + name := r.Output + if outDef != nil { + name = outDef.Name + } + if world.WordPrefixMatch(productName, name) { + matched = append(matched, r) + } + } + + if len(matched) == 0 { + sess.WriteLine("You can't fletch that.") + return + } + + var available []action.RecipeDef + for _, r := range matched { + if g.canDoFletchRecipe(p, &r) { + available = append(available, r) + } + } + + if len(available) == 0 { + sess.WriteLine("You don't have the materials for that.") + return + } + + if len(available) == 1 { + count := qty + g.startFletchAction(sess, p, &available[0], count) + return + } + + g.showFletchTable(sess, p, available) +} + +func (g *Game) hasFletchTools(p *player.Player, r *action.RecipeDef) bool { + if fletchNeedsKnife(r) && !g.hasToolType(p, "knife") { + return false + } + if fletchNeedsChisel(r) && !g.hasToolType(p, "chisel") { + return false + } + return true +} + +func (g *Game) canDoFletchRecipe(p *player.Player, r *action.RecipeDef) bool { + return p.Level(player.Fletching) >= r.Level && + r.HasAllItemsQty(p.CountItem) && + g.hasFletchTools(p, r) +} + +func (g *Game) availableFletchRecipes(p *player.Player, allFletch []action.RecipeDef) []action.RecipeDef { + var result []action.RecipeDef + for _, r := range allFletch { + if r.HasAllItemsQty(p.CountItem) && g.hasFletchTools(p, &r) { + result = append(result, r) + } + } + return result +} + +func (g *Game) showFletchTable(sess *net.Session, p *player.Player, available []action.RecipeDef) { + sort.Slice(available, func(i, j int) bool { + return available[i].Level < available[j].Level + }) + + mode := g.colorMode(sess) + skillLevel := p.Level(player.Fletching) + dimSpec := color.Parse("240") + + tbl := &Table{ + Title: "Fletching Actions", + Columns: []string{"#", "Product", "Materials", "Level"}, + } + + for i, r := range available { + outDef, _ := g.ItemStore.Load(r.Output) + productName := r.Output + if outDef != nil { + productName = outDef.Name + } + + outputQty := r.OutputQty + if outputQty <= 0 { + outputQty = 1 + } + if outDef != nil && outDef.Stackable && outputQty > 1 { + productName = fmt.Sprintf("%s x%d", productName, outputQty) + } + + var matParts []string + for _, e := range r.Consume { + itemName := e.Items[0] + if def, err := g.ItemStore.Load(e.Items[0]); err == nil { + itemName = def.Name + } + if e.Qty > 1 { + matParts = append(matParts, fmt.Sprintf("%s x%d", itemName, e.Qty)) + } else { + matParts = append(matParts, itemName) + } + } + matStr := strings.Join(matParts, ", ") + levelStr := fmt.Sprint(r.Level) + numStr := fmt.Sprintf("%d", i+1) + + canMake := skillLevel >= r.Level + if canMake { + productName = g.itemColorize(sess, outDef, productName) + } else { + productName = color.Render(mode, dimSpec, productName) + matStr = color.Render(mode, dimSpec, matStr) + levelStr = color.Render(mode, dimSpec, levelStr) + numStr = color.Render(mode, dimSpec, numStr) + } + + tbl.Rows = append(tbl.Rows, []string{numStr, productName, matStr, levelStr}) + } + + unicode := p.OptionBool("unicode") + sess.WriteLine("") + for _, line := range tbl.Render(unicode) { + sess.WriteLine(line) + } + + lastRecipeID, _ := p.Flags["last_fletch"].(string) + hint := "" + if lastRecipeID != "" { + for _, r := range available { + if r.ID == lastRecipeID { + if outDef, err := g.ItemStore.Load(r.Output); err == nil { + hint = outDef.Name + } + break + } + } + } + + if hint != "" { + sess.Write(fmt.Sprintf("Fletch what (enter for all %s): ", hint)) + } else { + sess.Write("Fletch what: ") + } + + sess.PendingMenu = recipeMenuData(available) + sess.State = net.StateFletchProduct +} + +func (g *Game) handleFletchProduct(sess *net.Session, input string) { + p := sess.Player.(*player.Player) + menuData := sess.PendingMenu + sess.PendingMenu = nil + sess.State = net.StateGame + + input = strings.TrimSpace(input) + + allRecipes, err := g.RecipeStore.LoadAll() + if err != nil { + g.reprompt(sess) + return + } + + var recipes []action.RecipeDef + for _, entry := range menuData { + rid := entry["recipe_id"] + for _, r := range allRecipes { + if r.ID == rid { + recipes = append(recipes, r) + break + } + } + } + + if len(recipes) == 0 { + g.reprompt(sess) + return + } + + sort.Slice(recipes, func(i, j int) bool { + return recipes[i].Level < recipes[j].Level + }) + + lastRecipeID, _ := p.Flags["last_fletch"].(string) + + sel, errMsg := g.resolveRecipeByInput(input, recipes, lastRecipeID) + switch errMsg { + case "ambiguous": + sess.WriteLine("That's ambiguous.") + g.reprompt(sess) + return + case "never_mind": + sess.WriteLine("Never mind.") + g.reprompt(sess) + return + } + + if !g.canDoFletchRecipe(p, sel.Recipe) { + if p.Level(player.Fletching) < sel.Recipe.Level { + sess.WriteLine(fmt.Sprintf("You need level %d fletching to make that.", sel.Recipe.Level)) + } else { + sess.WriteLine("You don't have the materials for that.") + } + g.reprompt(sess) + return + } + + g.startFletchAction(sess, p, sel.Recipe, sel.Count) +} + +func (g *Game) findFletchRecipesForItems(p *player.Player, itemAID, itemBID string) []action.RecipeDef { + allRecipes, err := g.RecipeStore.LoadAll() + if err != nil { + return nil + } + + toolTypeA := "" + toolTypeB := "" + if defA, _ := g.ItemStore.Load(itemAID); defA != nil { + toolTypeA = defA.ToolType + } + if defB, _ := g.ItemStore.Load(itemBID); defB != nil { + toolTypeB = defB.ToolType + } + isToolA := toolTypeA == "knife" || toolTypeA == "chisel" + isToolB := toolTypeB == "knife" || toolTypeB == "chisel" + + var matched []action.RecipeDef + for _, r := range allRecipes { + if r.Type != "fletching" { + continue + } + + aMatch := false + bMatch := false + for _, e := range r.Consume { + for _, id := range e.Items { + if id == itemAID { + aMatch = true + } + if id == itemBID { + bMatch = true + } + } + } + + if aMatch && bMatch { + matched = append(matched, r) + continue + } + + if isToolA && bMatch { + if (fletchNeedsKnife(&r) && toolTypeA == "knife") || + (fletchNeedsChisel(&r) && toolTypeA == "chisel") { + matched = append(matched, r) + continue + } + } + if isToolB && aMatch { + if (fletchNeedsKnife(&r) && toolTypeB == "knife") || + (fletchNeedsChisel(&r) && toolTypeB == "chisel") { + matched = append(matched, r) + continue + } + } + } + return matched +} diff --git a/internal/game/cmd_remove.go b/internal/game/cmd_remove.go index 8f1a413..d36a8fd 100644 --- a/internal/game/cmd_remove.go +++ b/internal/game/cmd_remove.go @@ -24,6 +24,8 @@ func (g *Game) doRemove(sess *net.Session, input string) { g.CancelAction(p) + qty, itemName := parseQty(input) + var foundSlot object.EquipSlot var foundItemID string @@ -32,7 +34,7 @@ func (g *Game) doRemove(sess *net.Session, input string) { if !ok { continue } - if strings.ToLower(string(slot)) == lower { + if strings.ToLower(string(slot)) == strings.ToLower(itemName) { foundSlot = slot foundItemID = itemID break @@ -41,7 +43,7 @@ func (g *Game) doRemove(sess *net.Session, input string) { if err != nil { continue } - if def.MatchesName(input) { + if def.MatchesName(itemName) { foundSlot = slot foundItemID = itemID break @@ -53,6 +55,11 @@ func (g *Game) doRemove(sess *net.Session, input string) { return } + if foundSlot == object.SlotAmmo && p.AmmoQty > 0 { + g.removeAmmo(sess, p, qty) + return + } + freeSlot := p.FirstFreeSlot() if freeSlot == -1 { sess.WriteLine("Nowhere to put that!") @@ -79,6 +86,55 @@ func (g *Game) doRemove(sess *net.Session, input string) { sess.WriteLine(fmt.Sprintf("You remove %s.", g.itemColorize(sess, def, name))) } +func (g *Game) removeAmmo(sess *net.Session, p *player.Player, qty int) { + ammoID := p.Equipment[object.SlotAmmo] + removeQty := p.AmmoQty + if qty > 0 && qty < removeQty { + removeQty = qty + } + + for i := 0; i < 28; i++ { + s := p.InvSlot(i) + if s != nil && s.ItemID == ammoID { + s.Quantity += removeQty + p.AmmoQty -= removeQty + if p.AmmoQty <= 0 { + delete(p.Equipment, object.SlotAmmo) + p.AmmoQty = 0 + } + g.AccountStore.SaveCharacter(p) + def, _ := g.ItemStore.Load(ammoID) + name := ammoID + if def != nil { + name = def.Name + } + sess.WriteLine(fmt.Sprintf("You unequip %s (x%d).", g.itemColorize(sess, def, name), removeQty)) + return + } + } + + freeSlot := p.FirstFreeSlot() + if freeSlot == -1 { + sess.WriteLine("Nowhere to put that!") + return + } + + p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: ammoID, Quantity: removeQty}) + p.AmmoQty -= removeQty + if p.AmmoQty <= 0 { + delete(p.Equipment, object.SlotAmmo) + p.AmmoQty = 0 + } + g.AccountStore.SaveCharacter(p) + + def, _ := g.ItemStore.Load(ammoID) + name := ammoID + if def != nil { + name = def.Name + } + sess.WriteLine(fmt.Sprintf("You unequip %s (x%d).", g.itemColorize(sess, def, name), removeQty)) +} + func (g *Game) doRemoveAll(sess *net.Session) { p := sess.Player.(*player.Player) @@ -87,7 +143,12 @@ func (g *Game) doRemoveAll(sess *net.Session) { return } - if len(p.Equipment) > p.FreeSlots() { + needed := len(p.Equipment) + if _, hasAmmo := p.Equipment[object.SlotAmmo]; hasAmmo { + needed-- + needed++ + } + if needed > p.FreeSlots() { sess.WriteLine("You don't have room to remove everything!") return } @@ -100,6 +161,11 @@ func (g *Game) doRemoveAll(sess *net.Session) { continue } + if slot == object.SlotAmmo && p.AmmoQty > 0 { + g.removeAllAmmo(sess, p, itemID) + continue + } + freeSlot := p.FirstFreeSlot() delete(p.Equipment, slot) invSlot := &player.InventorySlot{ItemID: itemID, Quantity: 1} @@ -122,3 +188,33 @@ func (g *Game) doRemoveAll(sess *net.Session) { g.AccountStore.SaveCharacter(p) } + +func (g *Game) removeAllAmmo(sess *net.Session, p *player.Player, itemID string) { + for i := 0; i < 28; i++ { + s := p.InvSlot(i) + if s != nil && s.ItemID == itemID { + s.Quantity += p.AmmoQty + delete(p.Equipment, object.SlotAmmo) + p.AmmoQty = 0 + def, _ := g.ItemStore.Load(itemID) + name := itemID + if def != nil { + name = def.Name + } + sess.WriteLine(fmt.Sprintf("You remove %s.", g.itemColorize(sess, def, name))) + return + } + } + freeSlot := p.FirstFreeSlot() + if freeSlot >= 0 { + p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: itemID, Quantity: p.AmmoQty}) + } + delete(p.Equipment, object.SlotAmmo) + p.AmmoQty = 0 + def, _ := g.ItemStore.Load(itemID) + name := itemID + if def != nil { + name = def.Name + } + sess.WriteLine(fmt.Sprintf("You remove %s.", g.itemColorize(sess, def, name))) +} diff --git a/internal/game/cmd_score.go b/internal/game/cmd_score.go index 40d3007..c31433b 100644 --- a/internal/game/cmd_score.go +++ b/internal/game/cmd_score.go @@ -22,8 +22,10 @@ func (g *Game) doScore(sess *net.Session) { t := &Table{Title: "Skills", Columns: []string{ color.Render(mode, color.Parse("75"), "Skill"), + color.Render(mode, color.Parse("245"), "Abbr"), color.Render(mode, color.Parse("230"), "Level"), color.Render(mode, color.Parse("222"), "XP"), + color.Render(mode, color.Parse("179"), "XP to Next"), }} for _, s := range player.AllSkills { level := p.Level(s) @@ -31,8 +33,10 @@ func (g *Game) doScore(sess *net.Session) { next := player.XPForNextLevel(xp) t.Rows = append(t.Rows, []string{ color.Render(mode, color.Parse("75"), string(s)), + color.Render(mode, color.Parse("245"), player.SkillAbbr[s]), color.Render(mode, color.Parse("230"), strconv.Itoa(level)), - color.Render(mode, color.Parse("222"), fmt.Sprintf("%d / %d XP", xp, xp+next)), + color.Render(mode, color.Parse("222"), strconv.Itoa(xp)), + color.Render(mode, color.Parse("179"), strconv.Itoa(next)), }) } for _, line := range t.Render(p.OptionBool("unicode")) { diff --git a/internal/game/cmd_smelt.go b/internal/game/cmd_smelt.go index 1cd5436..a5bbc06 100644 --- a/internal/game/cmd_smelt.go +++ b/internal/game/cmd_smelt.go @@ -94,6 +94,10 @@ func (g *Game) showSmeltMenu(sess *net.Session, p *player.Player, allRecipes []a } if len(entries) == 1 { + if p.OptionBool("smelt_all") { + g.startProductionFromRecipe(sess, p, &entries[0].Recipe, 0) + return + } g.promptHowMany(sess, entries[0].Recipe.ID) return } diff --git a/internal/game/cmd_smith.go b/internal/game/cmd_smith.go index 7fc14d2..f2787a5 100644 --- a/internal/game/cmd_smith.go +++ b/internal/game/cmd_smith.go @@ -3,7 +3,6 @@ package game import ( "fmt" "sort" - "strconv" "strings" "thehouseoficarus/internal/action" @@ -11,7 +10,6 @@ import ( "thehouseoficarus/internal/net" "thehouseoficarus/internal/object" "thehouseoficarus/internal/player" - "thehouseoficarus/internal/world" ) func (g *Game) doSmith(sess *net.Session, input string) { @@ -62,6 +60,12 @@ func (g *Game) doSmith(sess *net.Session, input string) { } if len(barTypes) == 1 { + if p.OptionBool("smith_all") { + if recipe := g.findSingleSmithRecipe(p, barTypes[0], allRecipes); recipe != nil { + g.startProductionFromRecipe(sess, p, recipe, 0) + return + } + } g.showSmithTable(sess, p, barTypes[0], allRecipes) return } @@ -203,72 +207,28 @@ func (g *Game) handleSmithProduct(sess *net.Session, input string) { return recipes[i].Level < recipes[j].Level }) - var targetRecipe *action.RecipeDef - var count int - - if input == "" { - lastFlag := fmt.Sprintf("last_smith_%s", barID) - lastRecipeID, _ := p.Flags[lastFlag].(string) - if lastRecipeID == "" { - sess.WriteLine("Never mind.") - g.reprompt(sess) - return - } - for i := range recipes { - if recipes[i].ID == lastRecipeID { - targetRecipe = &recipes[i] - break - } - } - if targetRecipe == nil { - sess.WriteLine("Never mind.") - g.reprompt(sess) - return - } - count = 0 - } else { - qty, productName := parseQty(input) - count = qty - productName = strings.TrimSpace(productName) - - if idx, err := strconv.Atoi(productName); err == nil && idx > 0 && idx <= len(recipes) { - targetRecipe = &recipes[idx-1] - } else { - matchCount := 0 - for i := range recipes { - outDef, _ := g.ItemStore.Load(recipes[i].Output) - name := recipes[i].Output - if outDef != nil { - name = outDef.Name - } - if world.WordPrefixMatch(productName, name) { - if matchCount == 0 { - targetRecipe = &recipes[i] - } - matchCount++ - } - } - if matchCount > 1 { - sess.WriteLine("That's ambiguous.") - g.reprompt(sess) - return - } - } + lastFlag := fmt.Sprintf("last_smith_%s", barID) + lastRecipeID, _ := p.Flags[lastFlag].(string) - if targetRecipe == nil { - sess.WriteLine("Never mind.") - g.reprompt(sess) - return - } + sel, errMsg := g.resolveRecipeByInput(input, recipes, lastRecipeID) + switch errMsg { + case "ambiguous": + sess.WriteLine("That's ambiguous.") + g.reprompt(sess) + return + case "never_mind": + sess.WriteLine("Never mind.") + g.reprompt(sess) + return } skillLevel := p.Level(player.Smithing) - if skillLevel < targetRecipe.Level { - sess.WriteLine(fmt.Sprintf("You need level %d smithing to make that.", targetRecipe.Level)) + if skillLevel < sel.Recipe.Level { + sess.WriteLine(fmt.Sprintf("You need level %d smithing to make that.", sel.Recipe.Level)) g.reprompt(sess) return } - if !targetRecipe.HasAllItemsQty(p.CountItem) { + if !sel.Recipe.HasAllItemsQty(p.CountItem) { sess.WriteLine("You don't have enough bars.") g.reprompt(sess) return @@ -277,10 +237,10 @@ func (g *Game) handleSmithProduct(sess *net.Session, input string) { if p.Flags == nil { p.Flags = make(map[string]any) } - p.Flags[fmt.Sprintf("last_smith_%s", barID)] = targetRecipe.ID + p.Flags[lastFlag] = sel.Recipe.ID g.AccountStore.SaveCharacter(p) - g.startProductionFromRecipe(sess, p, targetRecipe, count) + g.startProductionFromRecipe(sess, p, sel.Recipe, sel.Count) } func (g *Game) hasToolType(p *player.Player, toolType string) bool { @@ -355,6 +315,23 @@ func barMenuData(barIDs []string) []map[string]string { return out } +func (g *Game) findSingleSmithRecipe(p *player.Player, barID string, allRecipes []action.RecipeDef) *action.RecipeDef { + var makeable []*action.RecipeDef + skillLevel := p.Level(player.Smithing) + for i, r := range allRecipes { + if r.Type != "smithing" || !r.MatchesEntry(barID) { + continue + } + if skillLevel >= r.Level && r.HasAllItemsQty(p.CountItem) { + makeable = append(makeable, &allRecipes[i]) + } + } + if len(makeable) == 1 { + return makeable[0] + } + return nil +} + func titleCase(s string) string { words := strings.Fields(s) for i, w := range words { diff --git a/internal/game/cmd_use.go b/internal/game/cmd_use.go index 7c7bca0..8b8f43e 100644 --- a/internal/game/cmd_use.go +++ b/internal/game/cmd_use.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "thehouseoficarus/internal/action" "thehouseoficarus/internal/net" "thehouseoficarus/internal/object" "thehouseoficarus/internal/player" @@ -170,6 +171,24 @@ func (g *Game) doUseItemOnTarget(sess *net.Session, p *player.Player, itemAName, itemBID := matchesB[0].ID + fletchRecipes := g.findFletchRecipesForItems(p, itemAID, itemBID) + if len(fletchRecipes) > 0 { + var available []action.RecipeDef + for _, r := range fletchRecipes { + if g.canDoFletchRecipe(p, &r) { + available = append(available, r) + } + } + if len(available) == 1 { + g.startFletchAction(sess, p, &available[0], 0) + return + } + if len(available) > 1 { + g.showFletchTable(sess, p, available) + return + } + } + matched := g.findCombineResults(sess, p, itemAID, itemBID) if len(matched) == 1 { g.promptHowMany(sess, "combine_"+matched[0].ID) diff --git a/internal/game/cmd_wear.go b/internal/game/cmd_wear.go index 8910e75..ddc970b 100644 --- a/internal/game/cmd_wear.go +++ b/internal/game/cmd_wear.go @@ -25,9 +25,11 @@ func (g *Game) doWear(sess *net.Session, input string) { g.CancelAction(p) - matches := g.findInventoryMatches(input, p) + qty, itemName := parseQty(input) + + matches := g.findInventoryMatches(itemName, p) if len(matches) == 0 { - sess.WriteLine(fmt.Sprintf("You don't have any '%s'.", input)) + sess.WriteLine(fmt.Sprintf("You don't have any '%s'.", itemName)) return } @@ -52,6 +54,11 @@ func (g *Game) doWear(sess *net.Session, input string) { return } + if def.EquipSlot == object.SlotAmmo && def.Stackable { + g.equipAmmo(sess, p, match.Slot, slot, def, qty) + return + } + existingItemID, slotOccupied := p.Equipment[def.EquipSlot] if slotOccupied { @@ -88,13 +95,64 @@ func (g *Game) doWear(sess *net.Session, input string) { sess.WriteLine(fmt.Sprintf("You %s %s.", verb, g.itemColorize(sess, def, match.Name))) } +func (g *Game) equipAmmo(sess *net.Session, p *player.Player, invIdx int, slot *player.InventorySlot, def *object.ItemDef, qty int) { + transferQty := slot.Quantity + if qty > 0 && qty < transferQty { + transferQty = qty + } + + existingAmmoID, hasAmmo := p.Equipment[object.SlotAmmo] + if hasAmmo && existingAmmoID != slot.ItemID { + g.unequipAmmo(p) + } + + if slot.Quantity <= transferQty { + p.SetInvSlot(invIdx, nil) + } else { + slot.Quantity -= transferQty + } + + if hasAmmo && existingAmmoID == slot.ItemID { + p.AmmoQty += transferQty + } else { + p.Equipment[object.SlotAmmo] = slot.ItemID + p.AmmoQty = transferQty + } + + g.AccountStore.SaveCharacter(p) + displayName := g.itemColorize(sess, def, def.Name) + sess.WriteLine(fmt.Sprintf("You equip %s (x%d).", displayName, p.AmmoQty)) +} + +func (g *Game) unequipAmmo(p *player.Player) { + ammoID, ok := p.Equipment[object.SlotAmmo] + if !ok || p.AmmoQty <= 0 { + return + } + for i := 0; i < 28; i++ { + s := p.InvSlot(i) + if s != nil && s.ItemID == ammoID { + s.Quantity += p.AmmoQty + delete(p.Equipment, object.SlotAmmo) + p.AmmoQty = 0 + return + } + } + freeSlot := p.FirstFreeSlot() + if freeSlot >= 0 { + p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: ammoID, Quantity: p.AmmoQty}) + } + delete(p.Equipment, object.SlotAmmo) + p.AmmoQty = 0 +} + func (g *Game) doWearAll(sess *net.Session) { p := sess.Player.(*player.Player) g.CancelAction(p) bestPerSlot := make(map[object.EquipSlot]struct { - itemID string - value int + itemID string + value int invSlot int }) @@ -108,12 +166,16 @@ func (g *Game) doWearAll(sess *net.Session) { continue } current, exists := bestPerSlot[def.EquipSlot] - if !exists || def.Value > current.value { + val := def.Value + if def.EquipSlot == object.SlotAmmo && def.Stackable { + val = def.Value * slot.Quantity + } + if !exists || val > current.value { bestPerSlot[def.EquipSlot] = struct { - itemID string - value int + itemID string + value int invSlot int - }{slot.ItemID, def.Value, i} + }{slot.ItemID, val, i} } } @@ -125,7 +187,33 @@ func (g *Game) doWearAll(sess *net.Session) { var equipped []string for eqSlot, best := range bestPerSlot { if existing, ok := p.Equipment[eqSlot]; ok && existing == best.itemID { - continue + if eqSlot != object.SlotAmmo { + continue + } + } + + if eqSlot == object.SlotAmmo { + bestDef, _ := g.ItemStore.Load(best.itemID) + if bestDef != nil && bestDef.Stackable { + invSlot := p.InvSlot(best.invSlot) + if invSlot == nil { + continue + } + existingID, hasAmmo := p.Equipment[object.SlotAmmo] + if hasAmmo && existingID != best.itemID { + g.unequipAmmo(p) + } + transferQty := invSlot.Quantity + p.SetInvSlot(best.invSlot, nil) + if hasAmmo && existingID == best.itemID { + p.AmmoQty += transferQty + } else { + p.Equipment[object.SlotAmmo] = best.itemID + p.AmmoQty = transferQty + } + equipped = append(equipped, g.itemColorize(sess, bestDef, bestDef.Name)) + continue + } } if existing, ok := p.Equipment[eqSlot]; ok { diff --git a/internal/game/game.go b/internal/game/game.go index 87539b1..ef81fca 100644 --- a/internal/game/game.go +++ b/internal/game/game.go @@ -128,6 +128,8 @@ func (g *Game) HandleSession(sess *net.Session, input string) { g.handleHowMany(sess, input) case net.StateSmithProduct: g.handleSmithProduct(sess, input) + case net.StateFletchProduct: + g.handleFletchProduct(sess, input) case net.StateColorChoice: g.handleColorChoice(sess, input) } @@ -149,7 +151,7 @@ func classifyCommand(cmd string) CommandClass { "west", "w", "up", "u", "down", "d", "quit", "use", "burn", "stoke", "search", "walk", "cook", "smelt", "smith": return ClassActive - case "eat": + case "eat", "fletch": return ClassFree } if _, ok := verbAliases[cmd]; ok { @@ -379,6 +381,9 @@ func (g *Game) executeCommand(sess *net.Session, cmd string, args []string, rawI case "eat": g.doEat(sess, strings.Join(args, " ")) return + case "fletch": + g.doFletch(sess, strings.Join(args, " ")) + return case "talk", "speak", "ask": g.CancelAction(p) if len(args) == 0 { @@ -472,6 +477,16 @@ func (g *Game) ProcessQueuedCommands() { } } + for _, sess := range g.Hub.AllSessions() { + p, ok := sess.Player.(*player.Player) + if !ok || p == nil { + continue + } + if p.BackgroundAction == nil { + p.BackgroundActionState = nil + } + } + for _, sess := range g.Hub.AllSessions() { p, ok := sess.Player.(*player.Player) if !ok || p == nil { diff --git a/internal/net/server.go b/internal/net/server.go index bb14b04..e74f15f 100644 --- a/internal/net/server.go +++ b/internal/net/server.go @@ -34,6 +34,7 @@ const ( StateRecipeChoice StateHowMany StateSmithProduct + StateFletchProduct StateColorChoice ) diff --git a/internal/player/player.go b/internal/player/player.go index e7d98ac..edfe5dd 100644 --- a/internal/player/player.go +++ b/internal/player/player.go @@ -127,6 +127,10 @@ var OptionDefs = []OptionDef{ {"visual_ticks", OptBool, false, nil, "Display a tick marker every game tick"}, {"visual_tick_count", OptInt, 0, nil, "Cycle length for tick counter (0 = no counter)"}, {"visual_tick_text", OptString, "Tick", nil, "Text displayed for visual ticks"}, + {"fletch_all", OptBool, false, nil, "Auto-start fletching when only one product is possible"}, + {"smith_all", OptBool, false, nil, "Auto-start smithing when only one product is possible"}, + {"cook_all", OptBool, false, nil, "Auto-start cooking when only one product is possible"}, + {"smelt_all", OptBool, false, nil, "Auto-start smelting when only one product is possible"}, } var optionByName map[string]*OptionDef @@ -157,8 +161,11 @@ type Player struct { Options map[string]any `yaml:"-"` Flags map[string]any `yaml:"flags"` RegenerateTick int + AmmoQty int `yaml:"ammo_qty,omitempty"` Action *action.Action `yaml:"-"` ActionState any `yaml:"-"` + BackgroundAction *action.Action `yaml:"-"` + BackgroundActionState any `yaml:"-"` WalkSequence []string `yaml:"-"` ConsumeCooldown int `yaml:"-"` MoveTicks int `yaml:"-"` -- cgit v1.2.3