aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-18 18:50:54 -0400
committerhistoria <[not public]>2026-06-18 18:50:54 -0400
commit17e7725f252c7f5d3be2e9c37d62751c631f196f (patch)
tree40698c5c95cb21302adbb8cd021bd8d049b6fce1
parentfae979b75e37b6ad57e57062a1b637a37ec87174 (diff)
downloadthehouseoficarus-17e7725f252c7f5d3be2e9c37d62751c631f196f.tar.gz
feat: fletching added including zero-time bolt feathering, which probably will break lots of things
-rw-r--r--data/behaviors/chop_magic.yaml1
-rw-r--r--data/behaviors/chop_mahogany.yaml1
-rw-r--r--data/behaviors/chop_maple.yaml1
-rw-r--r--data/behaviors/chop_oak.yaml1
-rw-r--r--data/behaviors/chop_redwood.yaml1
-rw-r--r--data/behaviors/chop_teak.yaml1
-rw-r--r--data/behaviors/chop_tree.yaml1
-rw-r--r--data/behaviors/chop_willow.yaml1
-rw-r--r--data/behaviors/chop_yew.yaml1
-rw-r--r--data/behaviors/mine_adamantite.yaml1
-rw-r--r--data/behaviors/mine_clay.yaml1
-rw-r--r--data/behaviors/mine_coal.yaml1
-rw-r--r--data/behaviors/mine_copper.yaml1
-rw-r--r--data/behaviors/mine_gold.yaml1
-rw-r--r--data/behaviors/mine_iron.yaml1
-rw-r--r--data/behaviors/mine_mithril.yaml1
-rw-r--r--data/behaviors/mine_runite.yaml1
-rw-r--r--data/behaviors/mine_scrap.yaml1
-rw-r--r--data/behaviors/mine_silver.yaml1
-rw-r--r--data/behaviors/mine_tin.yaml1
-rw-r--r--data/help/fletch.yaml16
-rw-r--r--data/help/fletching.yaml27
-rw-r--r--data/items/adamant_arrow.yaml9
-rw-r--r--data/items/adamant_bolts.yaml9
-rw-r--r--data/items/arrow_shaft.yaml6
-rw-r--r--data/items/bowstring.yaml5
-rw-r--r--data/items/bronze_arrow.yaml9
-rw-r--r--data/items/bronze_bolts.yaml9
-rw-r--r--data/items/chisel.yaml6
-rw-r--r--data/items/diamond_bolt_tips.yaml6
-rw-r--r--data/items/diamond_bolts.yaml9
-rw-r--r--data/items/emerald_bolt_tips.yaml6
-rw-r--r--data/items/emerald_bolts.yaml9
-rw-r--r--data/items/headless_arrow.yaml6
-rw-r--r--data/items/iron_arrow.yaml9
-rw-r--r--data/items/iron_bolts.yaml9
-rw-r--r--data/items/knife.yaml6
-rw-r--r--data/items/longbow.yaml10
-rw-r--r--data/items/longbow_u.yaml5
-rw-r--r--data/items/magic_longbow.yaml10
-rw-r--r--data/items/magic_longbow_u.yaml5
-rw-r--r--data/items/magic_shortbow.yaml10
-rw-r--r--data/items/magic_shortbow_u.yaml5
-rw-r--r--data/items/maple_longbow.yaml10
-rw-r--r--data/items/maple_longbow_u.yaml5
-rw-r--r--data/items/maple_shortbow.yaml10
-rw-r--r--data/items/maple_shortbow_u.yaml5
-rw-r--r--data/items/mithril_arrow.yaml9
-rw-r--r--data/items/mithril_bolts.yaml9
-rw-r--r--data/items/oak_longbow.yaml10
-rw-r--r--data/items/oak_longbow_u.yaml5
-rw-r--r--data/items/oak_shortbow.yaml10
-rw-r--r--data/items/oak_shortbow_u.yaml5
-rw-r--r--data/items/ruby_bolt_tips.yaml6
-rw-r--r--data/items/ruby_bolts.yaml9
-rw-r--r--data/items/rune_arrow.yaml9
-rw-r--r--data/items/rune_bolts.yaml9
-rw-r--r--data/items/sapphire_bolt_tips.yaml6
-rw-r--r--data/items/sapphire_bolts.yaml9
-rw-r--r--data/items/shortbow.yaml10
-rw-r--r--data/items/shortbow_u.yaml5
-rw-r--r--data/items/steel_arrow.yaml9
-rw-r--r--data/items/steel_bolts.yaml9
-rw-r--r--data/items/uncut_diamond.yaml2
-rw-r--r--data/items/uncut_emerald.yaml2
-rw-r--r--data/items/uncut_ruby.yaml2
-rw-r--r--data/items/uncut_sapphire.yaml2
-rw-r--r--data/items/willow_longbow.yaml10
-rw-r--r--data/items/willow_longbow_u.yaml5
-rw-r--r--data/items/willow_shortbow.yaml10
-rw-r--r--data/items/willow_shortbow_u.yaml5
-rw-r--r--data/items/yew_longbow.yaml10
-rw-r--r--data/items/yew_longbow_u.yaml5
-rw-r--r--data/items/yew_shortbow.yaml10
-rw-r--r--data/items/yew_shortbow_u.yaml5
-rw-r--r--data/recipes/fletch_adamant_arrow.yaml14
-rw-r--r--data/recipes/fletch_adamant_bolts.yaml14
-rw-r--r--data/recipes/fletch_arrow_shaft.yaml12
-rw-r--r--data/recipes/fletch_arrow_shaft_magic.yaml12
-rw-r--r--data/recipes/fletch_arrow_shaft_maple.yaml12
-rw-r--r--data/recipes/fletch_arrow_shaft_oak.yaml12
-rw-r--r--data/recipes/fletch_arrow_shaft_willow.yaml12
-rw-r--r--data/recipes/fletch_arrow_shaft_yew.yaml12
-rw-r--r--data/recipes/fletch_bronze_arrow.yaml14
-rw-r--r--data/recipes/fletch_bronze_bolts.yaml14
-rw-r--r--data/recipes/fletch_diamond_bolt_tips.yaml12
-rw-r--r--data/recipes/fletch_diamond_bolts.yaml14
-rw-r--r--data/recipes/fletch_emerald_bolt_tips.yaml12
-rw-r--r--data/recipes/fletch_emerald_bolts.yaml14
-rw-r--r--data/recipes/fletch_headless_arrow.yaml14
-rw-r--r--data/recipes/fletch_iron_arrow.yaml14
-rw-r--r--data/recipes/fletch_iron_bolts.yaml14
-rw-r--r--data/recipes/fletch_longbow.yaml13
-rw-r--r--data/recipes/fletch_longbow_u.yaml11
-rw-r--r--data/recipes/fletch_magic_longbow.yaml13
-rw-r--r--data/recipes/fletch_magic_longbow_u.yaml11
-rw-r--r--data/recipes/fletch_magic_shortbow.yaml13
-rw-r--r--data/recipes/fletch_magic_shortbow_u.yaml11
-rw-r--r--data/recipes/fletch_maple_longbow.yaml13
-rw-r--r--data/recipes/fletch_maple_longbow_u.yaml11
-rw-r--r--data/recipes/fletch_maple_shortbow.yaml13
-rw-r--r--data/recipes/fletch_maple_shortbow_u.yaml11
-rw-r--r--data/recipes/fletch_mithril_arrow.yaml14
-rw-r--r--data/recipes/fletch_mithril_bolts.yaml14
-rw-r--r--data/recipes/fletch_oak_longbow.yaml13
-rw-r--r--data/recipes/fletch_oak_longbow_u.yaml11
-rw-r--r--data/recipes/fletch_oak_shortbow.yaml13
-rw-r--r--data/recipes/fletch_oak_shortbow_u.yaml11
-rw-r--r--data/recipes/fletch_ruby_bolt_tips.yaml12
-rw-r--r--data/recipes/fletch_ruby_bolts.yaml14
-rw-r--r--data/recipes/fletch_rune_arrow.yaml14
-rw-r--r--data/recipes/fletch_rune_bolts.yaml14
-rw-r--r--data/recipes/fletch_sapphire_bolt_tips.yaml12
-rw-r--r--data/recipes/fletch_sapphire_bolts.yaml14
-rw-r--r--data/recipes/fletch_shortbow.yaml13
-rw-r--r--data/recipes/fletch_shortbow_u.yaml11
-rw-r--r--data/recipes/fletch_steel_arrow.yaml14
-rw-r--r--data/recipes/fletch_steel_bolts.yaml14
-rw-r--r--data/recipes/fletch_willow_longbow.yaml13
-rw-r--r--data/recipes/fletch_willow_longbow_u.yaml11
-rw-r--r--data/recipes/fletch_willow_shortbow.yaml13
-rw-r--r--data/recipes/fletch_willow_shortbow_u.yaml11
-rw-r--r--data/recipes/fletch_yew_longbow.yaml13
-rw-r--r--data/recipes/fletch_yew_longbow_u.yaml11
-rw-r--r--data/recipes/fletch_yew_shortbow.yaml13
-rw-r--r--data/recipes/fletch_yew_shortbow_u.yaml11
-rw-r--r--data/rooms/14.yaml12
-rw-r--r--internal/action/behavior.go1
-rw-r--r--internal/game/action.go23
-rw-r--r--internal/game/action_burn.go2
-rw-r--r--internal/game/action_fletch.go215
-rw-r--r--internal/game/action_gather.go3
-rw-r--r--internal/game/action_production.go53
-rw-r--r--internal/game/action_state.go3
-rw-r--r--internal/game/cmd_attack.go1
-rw-r--r--internal/game/cmd_cook.go4
-rw-r--r--internal/game/cmd_equipment.go7
-rw-r--r--internal/game/cmd_fletch.go383
-rw-r--r--internal/game/cmd_remove.go102
-rw-r--r--internal/game/cmd_score.go6
-rw-r--r--internal/game/cmd_smelt.go4
-rw-r--r--internal/game/cmd_smith.go103
-rw-r--r--internal/game/cmd_use.go19
-rw-r--r--internal/game/cmd_wear.go106
-rw-r--r--internal/game/game.go17
-rw-r--r--internal/net/server.go1
-rw-r--r--internal/player/player.go7
147 files changed, 2059 insertions, 107 deletions
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 <item> -- Fletch a specific item
+ fletch 10 <item> -- 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 <log> -- Cut shafts or bows
+ use chisel on <gem> -- Cut bolt tips
+ use feather on <bolts> -- Finish bolts
+ use bowstring on <bow> -- 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 {
@@ -477,6 +482,16 @@ func (g *Game) ProcessQueuedCommands() {
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 {
+ continue
+ }
cmds := g.freeQueue[p.Name]
for _, qc := range cmds {
g.cancelRest(p.Name)
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:"-"`