aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AGENTS.md40
-rw-r--r--data/recipes/clean/avantoe.yaml (renamed from data/recipes/clean/clean_avantoe.yaml)2
-rw-r--r--data/recipes/clean/cadantine.yaml (renamed from data/recipes/clean/clean_cadantine.yaml)2
-rw-r--r--data/recipes/clean/dwarf_weed.yaml (renamed from data/recipes/clean/clean_dwarf_weed.yaml)2
-rw-r--r--data/recipes/clean/guam.yaml (renamed from data/recipes/clean/clean_guam.yaml)2
-rw-r--r--data/recipes/clean/harralander.yaml (renamed from data/recipes/clean/clean_harralander.yaml)2
-rw-r--r--data/recipes/clean/irit.yaml (renamed from data/recipes/clean/clean_irit.yaml)2
-rw-r--r--data/recipes/clean/kwuarm.yaml (renamed from data/recipes/clean/clean_kwuarm.yaml)2
-rw-r--r--data/recipes/clean/lantadyme.yaml (renamed from data/recipes/clean/clean_lantadyme.yaml)2
-rw-r--r--data/recipes/clean/marrentill.yaml (renamed from data/recipes/clean/clean_marrentill.yaml)2
-rw-r--r--data/recipes/clean/ranarr.yaml (renamed from data/recipes/clean/clean_ranarr.yaml)2
-rw-r--r--data/recipes/clean/snapdragon.yaml (renamed from data/recipes/clean/clean_snapdragon.yaml)2
-rw-r--r--data/recipes/clean/tarromin.yaml (renamed from data/recipes/clean/clean_tarromin.yaml)2
-rw-r--r--data/recipes/clean/toadflax.yaml (renamed from data/recipes/clean/clean_toadflax.yaml)2
-rw-r--r--data/recipes/clean/torstol.yaml (renamed from data/recipes/clean/clean_torstol.yaml)2
-rw-r--r--data/recipes/construction/mahogany_planks.yaml (renamed from data/recipes/construction/construct_mahogany_planks.yaml)2
-rw-r--r--data/recipes/construction/mahogany_table.yaml (renamed from data/recipes/construction/construct_mahogany_table.yaml)2
-rw-r--r--data/recipes/construction/oak_planks.yaml (renamed from data/recipes/construction/construct_oak_planks.yaml)2
-rw-r--r--data/recipes/construction/oak_shelf.yaml (renamed from data/recipes/construction/construct_oak_shelf.yaml)2
-rw-r--r--data/recipes/construction/oak_table.yaml (renamed from data/recipes/construction/construct_oak_table.yaml)2
-rw-r--r--data/recipes/construction/planks.yaml (renamed from data/recipes/construction/construct_planks.yaml)2
-rw-r--r--data/recipes/construction/teak_planks.yaml (renamed from data/recipes/construction/construct_teak_planks.yaml)2
-rw-r--r--data/recipes/construction/teak_table.yaml (renamed from data/recipes/construction/construct_teak_table.yaml)2
-rw-r--r--data/recipes/construction/wooden_chair.yaml (renamed from data/recipes/construction/construct_wooden_chair.yaml)2
-rw-r--r--data/recipes/construction/wooden_shelf.yaml (renamed from data/recipes/construction/construct_wooden_shelf.yaml)2
-rw-r--r--data/recipes/construction/wooden_table.yaml (renamed from data/recipes/construction/construct_wooden_table.yaml)2
-rw-r--r--data/recipes/cooking/anchovies.yaml (renamed from data/recipes/cooking/cook_anchovies.yaml)2
-rw-r--r--data/recipes/cooking/bread.yaml (renamed from data/recipes/cooking/cook_bread.yaml)2
-rw-r--r--data/recipes/cooking/herring.yaml (renamed from data/recipes/cooking/cook_herring.yaml)2
-rw-r--r--data/recipes/cooking/salmon.yaml (renamed from data/recipes/cooking/cook_salmon.yaml)2
-rw-r--r--data/recipes/cooking/sardine.yaml (renamed from data/recipes/cooking/cook_sardine.yaml)2
-rw-r--r--data/recipes/cooking/shrimps.yaml (renamed from data/recipes/cooking/cook_shrimps.yaml)2
-rw-r--r--data/recipes/cooking/trout.yaml (renamed from data/recipes/cooking/cook_trout.yaml)2
-rw-r--r--data/recipes/crafting/ball_of_wool.yaml (renamed from data/recipes/crafting/craft_ball_of_wool.yaml)2
-rw-r--r--data/recipes/crafting/bowstring.yaml (renamed from data/recipes/crafting/craft_bowstring.yaml)2
-rw-r--r--data/recipes/crafting/coif.yaml (renamed from data/recipes/crafting/craft_coif.yaml)2
-rw-r--r--data/recipes/crafting/cut_diamond.yaml (renamed from data/recipes/crafting/craft_cut_diamond.yaml)2
-rw-r--r--data/recipes/crafting/cut_emerald.yaml (renamed from data/recipes/crafting/craft_cut_emerald.yaml)2
-rw-r--r--data/recipes/crafting/cut_ruby.yaml (renamed from data/recipes/crafting/craft_cut_ruby.yaml)2
-rw-r--r--data/recipes/crafting/cut_sapphire.yaml (renamed from data/recipes/crafting/craft_cut_sapphire.yaml)2
-rw-r--r--data/recipes/crafting/diamond_amulet_u.yaml (renamed from data/recipes/crafting/craft_diamond_amulet_u.yaml)2
-rw-r--r--data/recipes/crafting/diamond_bracelet.yaml (renamed from data/recipes/crafting/craft_diamond_bracelet.yaml)2
-rw-r--r--data/recipes/crafting/diamond_necklace.yaml (renamed from data/recipes/crafting/craft_diamond_necklace.yaml)2
-rw-r--r--data/recipes/crafting/diamond_ring.yaml (renamed from data/recipes/crafting/craft_diamond_ring.yaml)2
-rw-r--r--data/recipes/crafting/emerald_amulet_u.yaml (renamed from data/recipes/crafting/craft_emerald_amulet_u.yaml)2
-rw-r--r--data/recipes/crafting/emerald_bracelet.yaml (renamed from data/recipes/crafting/craft_emerald_bracelet.yaml)2
-rw-r--r--data/recipes/crafting/emerald_necklace.yaml (renamed from data/recipes/crafting/craft_emerald_necklace.yaml)2
-rw-r--r--data/recipes/crafting/emerald_ring.yaml (renamed from data/recipes/crafting/craft_emerald_ring.yaml)2
-rw-r--r--data/recipes/crafting/gold_amulet_u.yaml (renamed from data/recipes/crafting/craft_gold_amulet_u.yaml)2
-rw-r--r--data/recipes/crafting/gold_bracelet.yaml (renamed from data/recipes/crafting/craft_gold_bracelet.yaml)2
-rw-r--r--data/recipes/crafting/gold_necklace.yaml (renamed from data/recipes/crafting/craft_gold_necklace.yaml)2
-rw-r--r--data/recipes/crafting/gold_ring.yaml (renamed from data/recipes/crafting/craft_gold_ring.yaml)2
-rw-r--r--data/recipes/crafting/hard_leather_body.yaml (renamed from data/recipes/crafting/craft_hard_leather_body.yaml)2
-rw-r--r--data/recipes/crafting/hard_leather_shield.yaml (renamed from data/recipes/crafting/craft_hard_leather_shield.yaml)2
-rw-r--r--data/recipes/crafting/leather_body.yaml (renamed from data/recipes/crafting/craft_leather_body.yaml)2
-rw-r--r--data/recipes/crafting/leather_boots.yaml (renamed from data/recipes/crafting/craft_leather_boots.yaml)2
-rw-r--r--data/recipes/crafting/leather_chaps.yaml (renamed from data/recipes/crafting/craft_leather_chaps.yaml)2
-rw-r--r--data/recipes/crafting/leather_cowl.yaml (renamed from data/recipes/crafting/craft_leather_cowl.yaml)2
-rw-r--r--data/recipes/crafting/leather_gloves.yaml (renamed from data/recipes/crafting/craft_leather_gloves.yaml)2
-rw-r--r--data/recipes/crafting/leather_vambraces.yaml (renamed from data/recipes/crafting/craft_leather_vambraces.yaml)2
-rw-r--r--data/recipes/crafting/pie_dish.yaml (renamed from data/recipes/crafting/craft_pie_dish.yaml)2
-rw-r--r--data/recipes/crafting/plant_pot.yaml (renamed from data/recipes/crafting/craft_plant_pot.yaml)2
-rw-r--r--data/recipes/crafting/pot.yaml (renamed from data/recipes/crafting/craft_pot.yaml)2
-rw-r--r--data/recipes/crafting/ruby_amulet_u.yaml (renamed from data/recipes/crafting/craft_ruby_amulet_u.yaml)2
-rw-r--r--data/recipes/crafting/ruby_bracelet.yaml (renamed from data/recipes/crafting/craft_ruby_bracelet.yaml)2
-rw-r--r--data/recipes/crafting/ruby_necklace.yaml (renamed from data/recipes/crafting/craft_ruby_necklace.yaml)2
-rw-r--r--data/recipes/crafting/ruby_ring.yaml (renamed from data/recipes/crafting/craft_ruby_ring.yaml)2
-rw-r--r--data/recipes/crafting/sapphire_amulet_u.yaml (renamed from data/recipes/crafting/craft_sapphire_amulet_u.yaml)2
-rw-r--r--data/recipes/crafting/sapphire_bracelet.yaml (renamed from data/recipes/crafting/craft_sapphire_bracelet.yaml)2
-rw-r--r--data/recipes/crafting/sapphire_necklace.yaml (renamed from data/recipes/crafting/craft_sapphire_necklace.yaml)2
-rw-r--r--data/recipes/crafting/sapphire_ring.yaml (renamed from data/recipes/crafting/craft_sapphire_ring.yaml)2
-rw-r--r--data/recipes/crafting/unfired_pie_dish.yaml (renamed from data/recipes/crafting/craft_unfired_pie_dish.yaml)2
-rw-r--r--data/recipes/crafting/unfired_plant_pot.yaml (renamed from data/recipes/crafting/craft_unfired_plant_pot.yaml)2
-rw-r--r--data/recipes/crafting/unfired_pot.yaml (renamed from data/recipes/crafting/craft_unfired_pot.yaml)2
-rw-r--r--data/recipes/fletching/arrow_shafts.yaml52
-rw-r--r--data/recipes/fletching/bolt_feathering.yaml63
-rw-r--r--data/recipes/fletching/fletch_adamant_arrow.yaml13
-rw-r--r--data/recipes/fletching/fletch_adamant_bolts.yaml13
-rw-r--r--data/recipes/fletching/fletch_arrow_shaft.yaml12
-rw-r--r--data/recipes/fletching/fletch_arrow_shaft_magic.yaml12
-rw-r--r--data/recipes/fletching/fletch_arrow_shaft_maple.yaml12
-rw-r--r--data/recipes/fletching/fletch_arrow_shaft_oak.yaml12
-rw-r--r--data/recipes/fletching/fletch_arrow_shaft_willow.yaml12
-rw-r--r--data/recipes/fletching/fletch_arrow_shaft_yew.yaml12
-rw-r--r--data/recipes/fletching/fletch_bronze_arrow.yaml13
-rw-r--r--data/recipes/fletching/fletch_bronze_bolts.yaml13
-rw-r--r--data/recipes/fletching/fletch_diamond_bolt_tips.yaml12
-rw-r--r--data/recipes/fletching/fletch_diamond_bolts.yaml13
-rw-r--r--data/recipes/fletching/fletch_emerald_bolt_tips.yaml12
-rw-r--r--data/recipes/fletching/fletch_emerald_bolts.yaml13
-rw-r--r--data/recipes/fletching/fletch_iron_arrow.yaml13
-rw-r--r--data/recipes/fletching/fletch_iron_bolts.yaml13
-rw-r--r--data/recipes/fletching/fletch_longbow.yaml12
-rw-r--r--data/recipes/fletching/fletch_longbow_u.yaml11
-rw-r--r--data/recipes/fletching/fletch_magic_longbow.yaml12
-rw-r--r--data/recipes/fletching/fletch_magic_longbow_u.yaml11
-rw-r--r--data/recipes/fletching/fletch_magic_shortbow.yaml12
-rw-r--r--data/recipes/fletching/fletch_magic_shortbow_u.yaml11
-rw-r--r--data/recipes/fletching/fletch_maple_longbow.yaml12
-rw-r--r--data/recipes/fletching/fletch_maple_longbow_u.yaml11
-rw-r--r--data/recipes/fletching/fletch_maple_shortbow.yaml12
-rw-r--r--data/recipes/fletching/fletch_maple_shortbow_u.yaml11
-rw-r--r--data/recipes/fletching/fletch_mithril_arrow.yaml13
-rw-r--r--data/recipes/fletching/fletch_mithril_bolts.yaml13
-rw-r--r--data/recipes/fletching/fletch_oak_longbow.yaml12
-rw-r--r--data/recipes/fletching/fletch_oak_longbow_u.yaml11
-rw-r--r--data/recipes/fletching/fletch_oak_shortbow.yaml12
-rw-r--r--data/recipes/fletching/fletch_oak_shortbow_u.yaml11
-rw-r--r--data/recipes/fletching/fletch_ruby_bolt_tips.yaml12
-rw-r--r--data/recipes/fletching/fletch_ruby_bolts.yaml13
-rw-r--r--data/recipes/fletching/fletch_rune_arrow.yaml13
-rw-r--r--data/recipes/fletching/fletch_rune_bolts.yaml13
-rw-r--r--data/recipes/fletching/fletch_sapphire_bolt_tips.yaml12
-rw-r--r--data/recipes/fletching/fletch_sapphire_bolts.yaml13
-rw-r--r--data/recipes/fletching/fletch_shortbow.yaml12
-rw-r--r--data/recipes/fletching/fletch_shortbow_u.yaml11
-rw-r--r--data/recipes/fletching/fletch_steel_arrow.yaml13
-rw-r--r--data/recipes/fletching/fletch_steel_bolts.yaml13
-rw-r--r--data/recipes/fletching/fletch_willow_longbow.yaml12
-rw-r--r--data/recipes/fletching/fletch_willow_longbow_u.yaml11
-rw-r--r--data/recipes/fletching/fletch_willow_shortbow.yaml12
-rw-r--r--data/recipes/fletching/fletch_willow_shortbow_u.yaml11
-rw-r--r--data/recipes/fletching/fletch_yew_longbow.yaml12
-rw-r--r--data/recipes/fletching/fletch_yew_longbow_u.yaml11
-rw-r--r--data/recipes/fletching/fletch_yew_shortbow.yaml12
-rw-r--r--data/recipes/fletching/fletch_yew_shortbow_u.yaml11
-rw-r--r--data/recipes/fletching/gem_bolt_tips.yaml36
-rw-r--r--data/recipes/fletching/gem_tipped_bolts.yaml43
-rw-r--r--data/recipes/fletching/headless_arrow.yaml (renamed from data/recipes/fletching/fletch_headless_arrow.yaml)2
-rw-r--r--data/recipes/fletching/strung_bows.yaml111
-rw-r--r--data/recipes/fletching/tipped_arrows.yaml63
-rw-r--r--data/recipes/fletching/unstrung_bows.yaml88
-rw-r--r--data/recipes/mixing/amp_potion.yaml (renamed from data/recipes/mixing/mix_amp_potion.yaml)2
-rw-r--r--data/recipes/mixing/bio_serum.yaml (renamed from data/recipes/mixing/mix_bio_serum.yaml)2
-rw-r--r--data/recipes/mixing/full_restore.yaml (renamed from data/recipes/mixing/mix_full_restore.yaml)2
-rw-r--r--data/recipes/mixing/nano_restore.yaml (renamed from data/recipes/mixing/mix_nano_restore.yaml)2
-rw-r--r--data/recipes/mixing/restoration_compound.yaml (renamed from data/recipes/mixing/mix_restoration_compound.yaml)2
-rw-r--r--data/recipes/mixing/science_serum.yaml (renamed from data/recipes/mixing/mix_science_serum.yaml)2
-rw-r--r--data/recipes/mixing/shield_potion.yaml (renamed from data/recipes/mixing/mix_shield_potion.yaml)2
-rw-r--r--data/recipes/mixing/stim_cell.yaml (renamed from data/recipes/mixing/mix_stim_cell.yaml)2
-rw-r--r--data/recipes/mixing/stim_potion.yaml (renamed from data/recipes/mixing/mix_stim_potion.yaml)2
-rw-r--r--data/recipes/mixing/super_amp.yaml (renamed from data/recipes/mixing/mix_super_amp.yaml)2
-rw-r--r--data/recipes/mixing/super_bio_serum.yaml (renamed from data/recipes/mixing/mix_super_bio_serum.yaml)2
-rw-r--r--data/recipes/mixing/super_shield.yaml (renamed from data/recipes/mixing/mix_super_shield.yaml)2
-rw-r--r--data/recipes/mixing/super_stim.yaml (renamed from data/recipes/mixing/mix_super_stim.yaml)2
-rw-r--r--data/recipes/mixing/super_stim_cell.yaml (renamed from data/recipes/mixing/mix_super_stim_cell.yaml)2
-rw-r--r--data/recipes/mixing/targeting_serum.yaml (renamed from data/recipes/mixing/mix_targeting_serum.yaml)2
-rw-r--r--data/recipes/mixing/tech_serum.yaml (renamed from data/recipes/mixing/mix_tech_serum.yaml)2
-rw-r--r--data/recipes/smelting/adamantite.yaml (renamed from data/recipes/smelting/smelt_adamantite.yaml)2
-rw-r--r--data/recipes/smelting/bronze.yaml (renamed from data/recipes/smelting/smelt_bronze.yaml)2
-rw-r--r--data/recipes/smelting/gold.yaml (renamed from data/recipes/smelting/smelt_gold.yaml)2
-rw-r--r--data/recipes/smelting/iron.yaml (renamed from data/recipes/smelting/smelt_iron.yaml)2
-rw-r--r--data/recipes/smelting/mithril.yaml (renamed from data/recipes/smelting/smelt_mithril.yaml)2
-rw-r--r--data/recipes/smelting/runite.yaml (renamed from data/recipes/smelting/smelt_runite.yaml)2
-rw-r--r--data/recipes/smelting/silver.yaml (renamed from data/recipes/smelting/smelt_silver.yaml)2
-rw-r--r--data/recipes/smelting/steel.yaml (renamed from data/recipes/smelting/smelt_steel.yaml)2
-rw-r--r--data/recipes/smithing/adamant.yaml48
-rw-r--r--data/recipes/smithing/bronze.yaml48
-rw-r--r--data/recipes/smithing/iron.yaml48
-rw-r--r--data/recipes/smithing/mithril.yaml48
-rw-r--r--data/recipes/smithing/rune.yaml48
-rw-r--r--data/recipes/smithing/smith_adamant_arrowtips.yaml12
-rw-r--r--data/recipes/smithing/smith_adamant_bolts_unf.yaml12
-rw-r--r--data/recipes/smithing/smith_adamant_dagger.yaml11
-rw-r--r--data/recipes/smithing/smith_adamant_full_helm.yaml11
-rw-r--r--data/recipes/smithing/smith_adamant_med_helm.yaml11
-rw-r--r--data/recipes/smithing/smith_adamant_nails.yaml12
-rw-r--r--data/recipes/smithing/smith_adamant_platebody.yaml11
-rw-r--r--data/recipes/smithing/smith_adamant_sword.yaml11
-rw-r--r--data/recipes/smithing/smith_bronze_arrowtips.yaml12
-rw-r--r--data/recipes/smithing/smith_bronze_bolts_unf.yaml12
-rw-r--r--data/recipes/smithing/smith_bronze_dagger.yaml11
-rw-r--r--data/recipes/smithing/smith_bronze_full_helm.yaml11
-rw-r--r--data/recipes/smithing/smith_bronze_med_helm.yaml11
-rw-r--r--data/recipes/smithing/smith_bronze_nails.yaml12
-rw-r--r--data/recipes/smithing/smith_bronze_platebody.yaml11
-rw-r--r--data/recipes/smithing/smith_bronze_sword.yaml11
-rw-r--r--data/recipes/smithing/smith_iron_arrowtips.yaml12
-rw-r--r--data/recipes/smithing/smith_iron_bolts_unf.yaml12
-rw-r--r--data/recipes/smithing/smith_iron_dagger.yaml11
-rw-r--r--data/recipes/smithing/smith_iron_full_helm.yaml11
-rw-r--r--data/recipes/smithing/smith_iron_med_helm.yaml11
-rw-r--r--data/recipes/smithing/smith_iron_nails.yaml12
-rw-r--r--data/recipes/smithing/smith_iron_platebody.yaml11
-rw-r--r--data/recipes/smithing/smith_iron_sword.yaml11
-rw-r--r--data/recipes/smithing/smith_mithril_arrowtips.yaml12
-rw-r--r--data/recipes/smithing/smith_mithril_bolts_unf.yaml12
-rw-r--r--data/recipes/smithing/smith_mithril_dagger.yaml11
-rw-r--r--data/recipes/smithing/smith_mithril_full_helm.yaml11
-rw-r--r--data/recipes/smithing/smith_mithril_med_helm.yaml11
-rw-r--r--data/recipes/smithing/smith_mithril_nails.yaml12
-rw-r--r--data/recipes/smithing/smith_mithril_platebody.yaml11
-rw-r--r--data/recipes/smithing/smith_mithril_sword.yaml11
-rw-r--r--data/recipes/smithing/smith_rune_arrowtips.yaml12
-rw-r--r--data/recipes/smithing/smith_rune_bolts_unf.yaml12
-rw-r--r--data/recipes/smithing/smith_rune_dagger.yaml11
-rw-r--r--data/recipes/smithing/smith_rune_full_helm.yaml11
-rw-r--r--data/recipes/smithing/smith_rune_med_helm.yaml11
-rw-r--r--data/recipes/smithing/smith_rune_nails.yaml12
-rw-r--r--data/recipes/smithing/smith_rune_platebody.yaml11
-rw-r--r--data/recipes/smithing/smith_rune_sword.yaml11
-rw-r--r--data/recipes/smithing/smith_steel_arrowtips.yaml12
-rw-r--r--data/recipes/smithing/smith_steel_bolts_unf.yaml12
-rw-r--r--data/recipes/smithing/smith_steel_dagger.yaml11
-rw-r--r--data/recipes/smithing/smith_steel_full_helm.yaml11
-rw-r--r--data/recipes/smithing/smith_steel_med_helm.yaml11
-rw-r--r--data/recipes/smithing/smith_steel_nails.yaml12
-rw-r--r--data/recipes/smithing/smith_steel_platebody.yaml11
-rw-r--r--data/recipes/smithing/smith_steel_sword.yaml11
-rw-r--r--data/recipes/smithing/steel.yaml48
-rw-r--r--data/rooms/town/1.yaml3
-rw-r--r--data/rooms/town/9.yaml1
-rw-r--r--internal/action/recipe.go107
-rw-r--r--internal/color/color.go12
-rw-r--r--internal/config/config.go6
-rw-r--r--internal/game/action.go4
-rw-r--r--internal/game/action_agility.go8
-rw-r--r--internal/game/action_burn.go48
-rw-r--r--internal/game/action_clean.go6
-rw-r--r--internal/game/action_default_target.go2
-rw-r--r--internal/game/action_farm.go10
-rw-r--r--internal/game/action_fletch.go20
-rw-r--r--internal/game/action_gather.go78
-rw-r--r--internal/game/action_search.go8
-rw-r--r--internal/game/action_steal.go8
-rw-r--r--internal/game/action_use.go8
-rw-r--r--internal/game/cmd_agility.go2
-rw-r--r--internal/game/cmd_attack.go14
-rw-r--r--internal/game/cmd_clean.go11
-rw-r--r--internal/game/cmd_consume.go8
-rw-r--r--internal/game/cmd_cook.go59
-rw-r--r--internal/game/cmd_craft.go34
-rw-r--r--internal/game/cmd_description.go4
-rw-r--r--internal/game/cmd_drop.go2
-rw-r--r--internal/game/cmd_fletch.go2
-rw-r--r--internal/game/cmd_get.go22
-rw-r--r--internal/game/cmd_jack.go10
-rw-r--r--internal/game/cmd_look.go4
-rw-r--r--internal/game/cmd_mix.go108
-rw-r--r--internal/game/cmd_quit.go10
-rw-r--r--internal/game/cmd_registry.go4
-rw-r--r--internal/game/cmd_smelt.go52
-rw-r--r--internal/game/cmd_smith.go4
-rw-r--r--internal/game/cmd_trigger_combat.go2
-rw-r--r--internal/game/cmd_trigger_enchant.go18
-rw-r--r--internal/game/cmd_trigger_transport.go9
-rw-r--r--internal/game/cmd_trigger_utility.go71
-rw-r--r--internal/game/cmd_use.go24
-rw-r--r--internal/game/core_login_account.go6
-rw-r--r--internal/game/core_messages.go13
-rw-r--r--internal/game/core_production.go142
-rw-r--r--internal/game/core_startup.go65
-rw-r--r--internal/game/core_types.go2
-rw-r--r--internal/game/core_utils.go16
-rw-r--r--internal/game/game.go16
-rw-r--r--internal/game/hacking/liars_dice.go (renamed from internal/game/hacking/hacking_liars_dice.go)0
-rw-r--r--internal/game/hacking/mastermind.go (renamed from internal/game/hacking/hacking_mastermind.go)0
-rw-r--r--internal/game/hacking/wumpus.go (renamed from internal/game/hacking/hacking_wumpus.go)0
-rw-r--r--internal/game/sys_combat.go6
-rw-r--r--internal/game/sys_science.go32
-rw-r--r--internal/net/server.go4
-rw-r--r--internal/player/validate.go4
-rw-r--r--internal/world/ground.go10
-rw-r--r--internal/world/objects.go4
-rw-r--r--internal/world/room.go4
-rw-r--r--internal/world/world.go2
-rw-r--r--worldbuilding_guide/recipes.md70
267 files changed, 1372 insertions, 1957 deletions
diff --git a/AGENTS.md b/AGENTS.md
index f97c8ee..f2f528e 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -52,7 +52,7 @@ internal/
## Key Conventions
-**YAML-driven.** Rooms, items, objects, mobs, drops, recipes, modules, courses are YAML files under `data/`. Read from disk on every access — edit and it takes effect immediately, no restart. Items, rooms, and recipes are organized into subdirectories by category (items: `equipment/`, `tools/`, etc.; rooms: `town/`, `wilderness/`, etc.; recipes: `smithing/`, `crafting/`, etc.). All IDs remain globally unique across subdirectories via path-indexed loading.
+**YAML-driven.** Rooms, items, objects, mobs, drops, recipes, modules, courses are YAML files under `data/`. Read from disk on every access — edit and it takes effect immediately, no restart. Recipes are walked from disk on first access then cached in-memory with an `O(1)` ID index (see GetByID below). Items, rooms, and recipes are organized into subdirectories by category (items: `equipment/`, `tools/`, etc.; rooms: `town/`, `wilderness/`, etc.; recipes: `smithing/`, `crafting/`, etc.). All IDs remain globally unique across subdirectories via path-indexed loading.
**Live state.** Player data (characters, accounts) is written to YAML on every state change. Ground items, mob instances, and object states live in memory only and are lost on restart.
@@ -91,7 +91,7 @@ data/mobs/<id>.yaml Mob definitions (combat stats, drops, behavior, steal
data/objects/<id>.yaml Object definitions (name, behavior, hidden, inroom_description, removal_item)
data/drops/<id>.yaml Shared drop tables (weighted item lists, sub-table references)
data/help/<id>.yaml Help topics
-data/recipes/<id>.yaml Recipe definitions (cooking, smithing, crafting, fletching, etc.)
+data/recipes/<id>.yaml Recipe definitions — individual or consolidated format (see worldbuilding_guide/recipes.md)
data/modules/<id>.yaml Science module definitions (id, level, max_hit, base_xp, junk_cost, category)
data/courses/<id>.yaml Agility course definitions (name, required_level, start_room, obstacles)
data/players/accounts/ Account YAML (gitignored)
@@ -516,7 +516,7 @@ Prompt variables: `%h` (HP), `%H` (max HP), `%b` (battery), `%B` (max battery),
Production skills share a unified system in `core_production.go`:
-1. Create recipe YAMLs in `data/recipes/` with the new `type`
+1. Create recipe YAMLs in `data/recipes/<type>/` — either individual (one per product) or consolidated (one per material group, with shared fields at top level and a `products:` list). See `worldbuilding_guide/recipes.md`.
2. Create a station object in `data/objects/` if needed
3. Add a `cmd_<skill>.go` command handler following existing patterns
4. Add the command to `commandRegistry` in `cmd_registry.go` with the appropriate class
@@ -524,6 +524,40 @@ Production skills share a unified system in `core_production.go`:
The unified production cycle handles: HowMany prompt, start message, timed loops, success/fail rolls, XP, output quantity, stackable stacking, byproducts, and end message. Tool requirements are checked in the command handler before calling `promptHowMany`.
+## Recipe Loading and Lookup
+
+Recipes have two YAML formats — **individual** (one file per recipe) and **consolidated** (shared `type`/`station`/`wait`/`consume` at top level, products listed under `products:`). The loader expands consolidated files into individual `RecipeDef` structs transparently. The production engine (`core_production.go`) sees only `RecipeDef` — no code changes needed for format.
+
+**RecipeStore** (`internal/action/recipe.go`) manages recipe access:
+
+### GetByID — Step by Step
+
+`GetByID(recipeID)` is an `O(1)` direct recipe lookup. Here's the full chain:
+
+1. **First call anywhere in the game** — a command like `cook`, `smith`, or the tick advancing an active production action calls `GetByID("smith_bronze_dagger")`.
+
+2. **Index check.** `GetByID` checks the `byID` map (`map[string]*RecipeDef`). On first call, it's empty, so the method calls `ensureLoaded()`.
+
+3. **`ensureLoaded()`** checks if the full recipe slice is already cached under key `"_all"` in `s.cache`. If not (cold cache), it calls `loadAllRaw()`.
+
+4. **`loadAllRaw()`** walks `data/recipes/` recursively. For each YAML file:
+ - First attempts to unmarshal as a plain `RecipeDef` (individual format). If `type` is non-empty, the recipe is appended.
+ - If that fails, attempts to unmarshal as `consolidatedRecipeDef`. If `type` is set and `products` is non-empty, it expands each product entry via `expandProduct()` into a full `RecipeDef` (inheriting top-level `type`, `station`, `wait`, `consume`; overriding with product-level fields where specified).
+ - **YAML parse errors are logged to stderr** — no more silent failures.
+
+5. **Index building.** `ensureLoaded()` caches the full recipe slice as `s.cache["_all"]` (permanent backing store), then populates `s.byID` with pointers into that slice: `byID["smith_bronze_dagger"] = &all[i]`. The cached slice keeps the backing array alive, so byID pointers never dangle.
+
+6. **GetByID returns.** The `*RecipeDef` pointer is returned and used by the caller (e.g., `loadProductionRecipe()` or `menuEntryName()`).
+
+7. **All subsequent calls** to `GetByID`, `LoadAll()`, or `LoadByType()` hit the in-memory cache — no filesystem walk. `LoadByType("smithing")` filters the cached `"_all"` slice and caches the filtered result.
+
+**Key properties:**
+- `GetByID` — `O(1)` map lookup, zero allocations
+- `LoadByType` — `O(n)` filter pass on first call per type, `O(1)` copy on cache hits
+- `LoadAll` — same as `LoadByType("")`, returns copy of `"_all"` cache
+- All public methods return copies — callers can't mutate cached data
+- No cache invalidation — recipes are static after startup. Server restart picks up changes.
+
## Adding a New Command
1. Add a `cmd_*.go` file in `internal/game/` with the handler function `func (g *Game) executeXxx(sess *net.Session, args []string, rawInput string)`
diff --git a/data/recipes/clean/clean_avantoe.yaml b/data/recipes/clean/avantoe.yaml
index 61f8f87..fe00db5 100644
--- a/data/recipes/clean/clean_avantoe.yaml
+++ b/data/recipes/clean/avantoe.yaml
@@ -1,4 +1,4 @@
-id: clean_avantoe
+id: avantoe
type: clean
skill: pharmacy
level: 48
diff --git a/data/recipes/clean/clean_cadantine.yaml b/data/recipes/clean/cadantine.yaml
index a013b5d..8acb2c5 100644
--- a/data/recipes/clean/clean_cadantine.yaml
+++ b/data/recipes/clean/cadantine.yaml
@@ -1,4 +1,4 @@
-id: clean_cadantine
+id: cadantine
type: clean
skill: pharmacy
level: 65
diff --git a/data/recipes/clean/clean_dwarf_weed.yaml b/data/recipes/clean/dwarf_weed.yaml
index 4ea9bb7..70c6c42 100644
--- a/data/recipes/clean/clean_dwarf_weed.yaml
+++ b/data/recipes/clean/dwarf_weed.yaml
@@ -1,4 +1,4 @@
-id: clean_dwarf_weed
+id: dwarf_weed
type: clean
skill: pharmacy
level: 70
diff --git a/data/recipes/clean/clean_guam.yaml b/data/recipes/clean/guam.yaml
index d8bb492..410e309 100644
--- a/data/recipes/clean/clean_guam.yaml
+++ b/data/recipes/clean/guam.yaml
@@ -1,4 +1,4 @@
-id: clean_guam
+id: guam
type: clean
skill: pharmacy
level: 3
diff --git a/data/recipes/clean/clean_harralander.yaml b/data/recipes/clean/harralander.yaml
index 15d9d22..f60a737 100644
--- a/data/recipes/clean/clean_harralander.yaml
+++ b/data/recipes/clean/harralander.yaml
@@ -1,4 +1,4 @@
-id: clean_harralander
+id: harralander
type: clean
skill: pharmacy
level: 20
diff --git a/data/recipes/clean/clean_irit.yaml b/data/recipes/clean/irit.yaml
index f75603b..5853b89 100644
--- a/data/recipes/clean/clean_irit.yaml
+++ b/data/recipes/clean/irit.yaml
@@ -1,4 +1,4 @@
-id: clean_irit
+id: irit
type: clean
skill: pharmacy
level: 40
diff --git a/data/recipes/clean/clean_kwuarm.yaml b/data/recipes/clean/kwuarm.yaml
index 1a11a84..04e50ad 100644
--- a/data/recipes/clean/clean_kwuarm.yaml
+++ b/data/recipes/clean/kwuarm.yaml
@@ -1,4 +1,4 @@
-id: clean_kwuarm
+id: kwuarm
type: clean
skill: pharmacy
level: 54
diff --git a/data/recipes/clean/clean_lantadyme.yaml b/data/recipes/clean/lantadyme.yaml
index 076f20d..499dee9 100644
--- a/data/recipes/clean/clean_lantadyme.yaml
+++ b/data/recipes/clean/lantadyme.yaml
@@ -1,4 +1,4 @@
-id: clean_lantadyme
+id: lantadyme
type: clean
skill: pharmacy
level: 67
diff --git a/data/recipes/clean/clean_marrentill.yaml b/data/recipes/clean/marrentill.yaml
index f1565dd..291ea21 100644
--- a/data/recipes/clean/clean_marrentill.yaml
+++ b/data/recipes/clean/marrentill.yaml
@@ -1,4 +1,4 @@
-id: clean_marrentill
+id: marrentill
type: clean
skill: pharmacy
level: 5
diff --git a/data/recipes/clean/clean_ranarr.yaml b/data/recipes/clean/ranarr.yaml
index 2e639d8..989de1e 100644
--- a/data/recipes/clean/clean_ranarr.yaml
+++ b/data/recipes/clean/ranarr.yaml
@@ -1,4 +1,4 @@
-id: clean_ranarr
+id: ranarr
type: clean
skill: pharmacy
level: 25
diff --git a/data/recipes/clean/clean_snapdragon.yaml b/data/recipes/clean/snapdragon.yaml
index 995ac8f..8819630 100644
--- a/data/recipes/clean/clean_snapdragon.yaml
+++ b/data/recipes/clean/snapdragon.yaml
@@ -1,4 +1,4 @@
-id: clean_snapdragon
+id: snapdragon
type: clean
skill: pharmacy
level: 59
diff --git a/data/recipes/clean/clean_tarromin.yaml b/data/recipes/clean/tarromin.yaml
index 1ad55e1..89bdce3 100644
--- a/data/recipes/clean/clean_tarromin.yaml
+++ b/data/recipes/clean/tarromin.yaml
@@ -1,4 +1,4 @@
-id: clean_tarromin
+id: tarromin
type: clean
skill: pharmacy
level: 11
diff --git a/data/recipes/clean/clean_toadflax.yaml b/data/recipes/clean/toadflax.yaml
index 11aafca..77fb955 100644
--- a/data/recipes/clean/clean_toadflax.yaml
+++ b/data/recipes/clean/toadflax.yaml
@@ -1,4 +1,4 @@
-id: clean_toadflax
+id: toadflax
type: clean
skill: pharmacy
level: 30
diff --git a/data/recipes/clean/clean_torstol.yaml b/data/recipes/clean/torstol.yaml
index c9bac28..8833eb4 100644
--- a/data/recipes/clean/clean_torstol.yaml
+++ b/data/recipes/clean/torstol.yaml
@@ -1,4 +1,4 @@
-id: clean_torstol
+id: torstol
type: clean
skill: pharmacy
level: 75
diff --git a/data/recipes/construction/construct_mahogany_planks.yaml b/data/recipes/construction/mahogany_planks.yaml
index 6634f78..3008a07 100644
--- a/data/recipes/construction/construct_mahogany_planks.yaml
+++ b/data/recipes/construction/mahogany_planks.yaml
@@ -1,4 +1,4 @@
-id: construct_mahogany_planks
+id: mahogany_planks
type: construction
level: 50
xp: 30
diff --git a/data/recipes/construction/construct_mahogany_table.yaml b/data/recipes/construction/mahogany_table.yaml
index 89d08e4..4b08c06 100644
--- a/data/recipes/construction/construct_mahogany_table.yaml
+++ b/data/recipes/construction/mahogany_table.yaml
@@ -1,4 +1,4 @@
-id: construct_mahogany_table
+id: mahogany_table
type: construction
level: 52
xp: 120
diff --git a/data/recipes/construction/construct_oak_planks.yaml b/data/recipes/construction/oak_planks.yaml
index 77c2fcb..da50f88 100644
--- a/data/recipes/construction/construct_oak_planks.yaml
+++ b/data/recipes/construction/oak_planks.yaml
@@ -1,4 +1,4 @@
-id: construct_oak_planks
+id: oak_planks
type: construction
level: 15
xp: 10
diff --git a/data/recipes/construction/construct_oak_shelf.yaml b/data/recipes/construction/oak_shelf.yaml
index 042e64f..c9f6242 100644
--- a/data/recipes/construction/construct_oak_shelf.yaml
+++ b/data/recipes/construction/oak_shelf.yaml
@@ -1,4 +1,4 @@
-id: construct_oak_shelf
+id: oak_shelf
type: construction
level: 18
xp: 40
diff --git a/data/recipes/construction/construct_oak_table.yaml b/data/recipes/construction/oak_table.yaml
index 8eafe15..6428200 100644
--- a/data/recipes/construction/construct_oak_table.yaml
+++ b/data/recipes/construction/oak_table.yaml
@@ -1,4 +1,4 @@
-id: construct_oak_table
+id: oak_table
type: construction
level: 20
xp: 50
diff --git a/data/recipes/construction/construct_planks.yaml b/data/recipes/construction/planks.yaml
index 3ed8058..9d202ff 100644
--- a/data/recipes/construction/construct_planks.yaml
+++ b/data/recipes/construction/planks.yaml
@@ -1,4 +1,4 @@
-id: construct_planks
+id: planks
type: construction
level: 1
xp: 5
diff --git a/data/recipes/construction/construct_teak_planks.yaml b/data/recipes/construction/teak_planks.yaml
index aec0888..1780d8b 100644
--- a/data/recipes/construction/construct_teak_planks.yaml
+++ b/data/recipes/construction/teak_planks.yaml
@@ -1,4 +1,4 @@
-id: construct_teak_planks
+id: teak_planks
type: construction
level: 35
xp: 20
diff --git a/data/recipes/construction/construct_teak_table.yaml b/data/recipes/construction/teak_table.yaml
index 88d4a2a..ce579e1 100644
--- a/data/recipes/construction/construct_teak_table.yaml
+++ b/data/recipes/construction/teak_table.yaml
@@ -1,4 +1,4 @@
-id: construct_teak_table
+id: teak_table
type: construction
level: 38
xp: 80
diff --git a/data/recipes/construction/construct_wooden_chair.yaml b/data/recipes/construction/wooden_chair.yaml
index 3a9120d..a1db252 100644
--- a/data/recipes/construction/construct_wooden_chair.yaml
+++ b/data/recipes/construction/wooden_chair.yaml
@@ -1,4 +1,4 @@
-id: construct_wooden_chair
+id: wooden_chair
type: construction
level: 8
xp: 30
diff --git a/data/recipes/construction/construct_wooden_shelf.yaml b/data/recipes/construction/wooden_shelf.yaml
index c8ea836..3ea97cc 100644
--- a/data/recipes/construction/construct_wooden_shelf.yaml
+++ b/data/recipes/construction/wooden_shelf.yaml
@@ -1,4 +1,4 @@
-id: construct_wooden_shelf
+id: wooden_shelf
type: construction
level: 4
xp: 20
diff --git a/data/recipes/construction/construct_wooden_table.yaml b/data/recipes/construction/wooden_table.yaml
index 21d6c53..73050a7 100644
--- a/data/recipes/construction/construct_wooden_table.yaml
+++ b/data/recipes/construction/wooden_table.yaml
@@ -1,4 +1,4 @@
-id: construct_wooden_table
+id: wooden_table
type: construction
level: 6
xp: 25
diff --git a/data/recipes/cooking/cook_anchovies.yaml b/data/recipes/cooking/anchovies.yaml
index bd4ea41..26bcc39 100644
--- a/data/recipes/cooking/cook_anchovies.yaml
+++ b/data/recipes/cooking/anchovies.yaml
@@ -1,4 +1,4 @@
-id: cook_anchovies
+id: anchovies
type: cooking
level: 1
xp: 30
diff --git a/data/recipes/cooking/cook_bread.yaml b/data/recipes/cooking/bread.yaml
index d1c68bf..0a27439 100644
--- a/data/recipes/cooking/cook_bread.yaml
+++ b/data/recipes/cooking/bread.yaml
@@ -1,4 +1,4 @@
-id: cook_bread
+id: bread
type: cooking
level: 1
xp: 40
diff --git a/data/recipes/cooking/cook_herring.yaml b/data/recipes/cooking/herring.yaml
index 3113092..486476c 100644
--- a/data/recipes/cooking/cook_herring.yaml
+++ b/data/recipes/cooking/herring.yaml
@@ -1,4 +1,4 @@
-id: cook_herring
+id: herring
type: cooking
level: 5
xp: 50
diff --git a/data/recipes/cooking/cook_salmon.yaml b/data/recipes/cooking/salmon.yaml
index 32c0fb0..c3362c0 100644
--- a/data/recipes/cooking/cook_salmon.yaml
+++ b/data/recipes/cooking/salmon.yaml
@@ -1,4 +1,4 @@
-id: cook_salmon
+id: salmon
type: cooking
level: 25
xp: 90
diff --git a/data/recipes/cooking/cook_sardine.yaml b/data/recipes/cooking/sardine.yaml
index fcda9b5..c03b7f0 100644
--- a/data/recipes/cooking/cook_sardine.yaml
+++ b/data/recipes/cooking/sardine.yaml
@@ -1,4 +1,4 @@
-id: cook_sardine
+id: sardine
type: cooking
level: 1
xp: 40
diff --git a/data/recipes/cooking/cook_shrimps.yaml b/data/recipes/cooking/shrimps.yaml
index feb4506..19ae396 100644
--- a/data/recipes/cooking/cook_shrimps.yaml
+++ b/data/recipes/cooking/shrimps.yaml
@@ -1,4 +1,4 @@
-id: cook_shrimps
+id: shrimps
type: cooking
level: 1
xp: 30
diff --git a/data/recipes/cooking/cook_trout.yaml b/data/recipes/cooking/trout.yaml
index cec6cd4..72587d5 100644
--- a/data/recipes/cooking/cook_trout.yaml
+++ b/data/recipes/cooking/trout.yaml
@@ -1,4 +1,4 @@
-id: cook_trout
+id: trout
type: cooking
level: 15
xp: 70
diff --git a/data/recipes/crafting/craft_ball_of_wool.yaml b/data/recipes/crafting/ball_of_wool.yaml
index ef25972..09fca8b 100644
--- a/data/recipes/crafting/craft_ball_of_wool.yaml
+++ b/data/recipes/crafting/ball_of_wool.yaml
@@ -1,4 +1,4 @@
-id: craft_ball_of_wool
+id: ball_of_wool
type: crafting
level: 1
xp: 3
diff --git a/data/recipes/crafting/craft_bowstring.yaml b/data/recipes/crafting/bowstring.yaml
index caca1a4..dfb4ea7 100644
--- a/data/recipes/crafting/craft_bowstring.yaml
+++ b/data/recipes/crafting/bowstring.yaml
@@ -1,4 +1,4 @@
-id: craft_bowstring
+id: bowstring
type: crafting
level: 1
xp: 15
diff --git a/data/recipes/crafting/craft_coif.yaml b/data/recipes/crafting/coif.yaml
index 4aa914a..8e6cac9 100644
--- a/data/recipes/crafting/craft_coif.yaml
+++ b/data/recipes/crafting/coif.yaml
@@ -1,4 +1,4 @@
-id: craft_coif
+id: coif
type: crafting
level: 38
xp: 37
diff --git a/data/recipes/crafting/craft_cut_diamond.yaml b/data/recipes/crafting/cut_diamond.yaml
index 175a518..e1e7343 100644
--- a/data/recipes/crafting/craft_cut_diamond.yaml
+++ b/data/recipes/crafting/cut_diamond.yaml
@@ -1,4 +1,4 @@
-id: craft_cut_diamond
+id: cut_diamond
type: crafting
level: 43
xp: 108
diff --git a/data/recipes/crafting/craft_cut_emerald.yaml b/data/recipes/crafting/cut_emerald.yaml
index f725103..405fb53 100644
--- a/data/recipes/crafting/craft_cut_emerald.yaml
+++ b/data/recipes/crafting/cut_emerald.yaml
@@ -1,4 +1,4 @@
-id: craft_cut_emerald
+id: cut_emerald
type: crafting
level: 27
xp: 68
diff --git a/data/recipes/crafting/craft_cut_ruby.yaml b/data/recipes/crafting/cut_ruby.yaml
index bfc5e88..02ab139 100644
--- a/data/recipes/crafting/craft_cut_ruby.yaml
+++ b/data/recipes/crafting/cut_ruby.yaml
@@ -1,4 +1,4 @@
-id: craft_cut_ruby
+id: cut_ruby
type: crafting
level: 63
xp: 85
diff --git a/data/recipes/crafting/craft_cut_sapphire.yaml b/data/recipes/crafting/cut_sapphire.yaml
index bfd9a00..330834f 100644
--- a/data/recipes/crafting/craft_cut_sapphire.yaml
+++ b/data/recipes/crafting/cut_sapphire.yaml
@@ -1,4 +1,4 @@
-id: craft_cut_sapphire
+id: cut_sapphire
type: crafting
level: 20
xp: 50
diff --git a/data/recipes/crafting/craft_diamond_amulet_u.yaml b/data/recipes/crafting/diamond_amulet_u.yaml
index 533ce2e..9c90b8c 100644
--- a/data/recipes/crafting/craft_diamond_amulet_u.yaml
+++ b/data/recipes/crafting/diamond_amulet_u.yaml
@@ -1,4 +1,4 @@
-id: craft_diamond_amulet_u
+id: diamond_amulet_u
type: crafting
level: 70
xp: 100
diff --git a/data/recipes/crafting/craft_diamond_bracelet.yaml b/data/recipes/crafting/diamond_bracelet.yaml
index f9486d3..0a15546 100644
--- a/data/recipes/crafting/craft_diamond_bracelet.yaml
+++ b/data/recipes/crafting/diamond_bracelet.yaml
@@ -1,4 +1,4 @@
-id: craft_diamond_bracelet
+id: diamond_bracelet
type: crafting
level: 58
xp: 95
diff --git a/data/recipes/crafting/craft_diamond_necklace.yaml b/data/recipes/crafting/diamond_necklace.yaml
index bdd4425..9c15245 100644
--- a/data/recipes/crafting/craft_diamond_necklace.yaml
+++ b/data/recipes/crafting/diamond_necklace.yaml
@@ -1,4 +1,4 @@
-id: craft_diamond_necklace
+id: diamond_necklace
type: crafting
level: 56
xp: 90
diff --git a/data/recipes/crafting/craft_diamond_ring.yaml b/data/recipes/crafting/diamond_ring.yaml
index eaf3a59..3045b74 100644
--- a/data/recipes/crafting/craft_diamond_ring.yaml
+++ b/data/recipes/crafting/diamond_ring.yaml
@@ -1,4 +1,4 @@
-id: craft_diamond_ring
+id: diamond_ring
type: crafting
level: 43
xp: 85
diff --git a/data/recipes/crafting/craft_emerald_amulet_u.yaml b/data/recipes/crafting/emerald_amulet_u.yaml
index 82773fb..4530fa2 100644
--- a/data/recipes/crafting/craft_emerald_amulet_u.yaml
+++ b/data/recipes/crafting/emerald_amulet_u.yaml
@@ -1,4 +1,4 @@
-id: craft_emerald_amulet_u
+id: emerald_amulet_u
type: crafting
level: 31
xp: 70
diff --git a/data/recipes/crafting/craft_emerald_bracelet.yaml b/data/recipes/crafting/emerald_bracelet.yaml
index 3c2f7b4..498b1ce 100644
--- a/data/recipes/crafting/craft_emerald_bracelet.yaml
+++ b/data/recipes/crafting/emerald_bracelet.yaml
@@ -1,4 +1,4 @@
-id: craft_emerald_bracelet
+id: emerald_bracelet
type: crafting
level: 30
xp: 65
diff --git a/data/recipes/crafting/craft_emerald_necklace.yaml b/data/recipes/crafting/emerald_necklace.yaml
index 7815c16..2d0596e 100644
--- a/data/recipes/crafting/craft_emerald_necklace.yaml
+++ b/data/recipes/crafting/emerald_necklace.yaml
@@ -1,4 +1,4 @@
-id: craft_emerald_necklace
+id: emerald_necklace
type: crafting
level: 29
xp: 60
diff --git a/data/recipes/crafting/craft_emerald_ring.yaml b/data/recipes/crafting/emerald_ring.yaml
index 2590490..c5cee62 100644
--- a/data/recipes/crafting/craft_emerald_ring.yaml
+++ b/data/recipes/crafting/emerald_ring.yaml
@@ -1,4 +1,4 @@
-id: craft_emerald_ring
+id: emerald_ring
type: crafting
level: 27
xp: 55
diff --git a/data/recipes/crafting/craft_gold_amulet_u.yaml b/data/recipes/crafting/gold_amulet_u.yaml
index 69daa89..d960be6 100644
--- a/data/recipes/crafting/craft_gold_amulet_u.yaml
+++ b/data/recipes/crafting/gold_amulet_u.yaml
@@ -1,4 +1,4 @@
-id: craft_gold_amulet_u
+id: gold_amulet_u
type: crafting
level: 8
xp: 30
diff --git a/data/recipes/crafting/craft_gold_bracelet.yaml b/data/recipes/crafting/gold_bracelet.yaml
index e3de6d0..9669943 100644
--- a/data/recipes/crafting/craft_gold_bracelet.yaml
+++ b/data/recipes/crafting/gold_bracelet.yaml
@@ -1,4 +1,4 @@
-id: craft_gold_bracelet
+id: gold_bracelet
type: crafting
level: 7
xp: 25
diff --git a/data/recipes/crafting/craft_gold_necklace.yaml b/data/recipes/crafting/gold_necklace.yaml
index ab52bef..e2a96c4 100644
--- a/data/recipes/crafting/craft_gold_necklace.yaml
+++ b/data/recipes/crafting/gold_necklace.yaml
@@ -1,4 +1,4 @@
-id: craft_gold_necklace
+id: gold_necklace
type: crafting
level: 6
xp: 20
diff --git a/data/recipes/crafting/craft_gold_ring.yaml b/data/recipes/crafting/gold_ring.yaml
index 943c31d..dcb3c09 100644
--- a/data/recipes/crafting/craft_gold_ring.yaml
+++ b/data/recipes/crafting/gold_ring.yaml
@@ -1,4 +1,4 @@
-id: craft_gold_ring
+id: gold_ring
type: crafting
level: 5
xp: 15
diff --git a/data/recipes/crafting/craft_hard_leather_body.yaml b/data/recipes/crafting/hard_leather_body.yaml
index d1287df..869867e 100644
--- a/data/recipes/crafting/craft_hard_leather_body.yaml
+++ b/data/recipes/crafting/hard_leather_body.yaml
@@ -1,4 +1,4 @@
-id: craft_hard_leather_body
+id: hard_leather_body
type: crafting
level: 28
xp: 35
diff --git a/data/recipes/crafting/craft_hard_leather_shield.yaml b/data/recipes/crafting/hard_leather_shield.yaml
index 7dbd66a..b46222f 100644
--- a/data/recipes/crafting/craft_hard_leather_shield.yaml
+++ b/data/recipes/crafting/hard_leather_shield.yaml
@@ -1,4 +1,4 @@
-id: craft_hard_leather_shield
+id: hard_leather_shield
type: crafting
level: 41
xp: 40
diff --git a/data/recipes/crafting/craft_leather_body.yaml b/data/recipes/crafting/leather_body.yaml
index 0f9774e..4f87c8f 100644
--- a/data/recipes/crafting/craft_leather_body.yaml
+++ b/data/recipes/crafting/leather_body.yaml
@@ -1,4 +1,4 @@
-id: craft_leather_body
+id: leather_body
type: crafting
level: 14
xp: 25
diff --git a/data/recipes/crafting/craft_leather_boots.yaml b/data/recipes/crafting/leather_boots.yaml
index b935a08..569fb81 100644
--- a/data/recipes/crafting/craft_leather_boots.yaml
+++ b/data/recipes/crafting/leather_boots.yaml
@@ -1,4 +1,4 @@
-id: craft_leather_boots
+id: leather_boots
type: crafting
level: 7
xp: 16
diff --git a/data/recipes/crafting/craft_leather_chaps.yaml b/data/recipes/crafting/leather_chaps.yaml
index fac2a9d..82fdac9 100644
--- a/data/recipes/crafting/craft_leather_chaps.yaml
+++ b/data/recipes/crafting/leather_chaps.yaml
@@ -1,4 +1,4 @@
-id: craft_leather_chaps
+id: leather_chaps
type: crafting
level: 18
xp: 27
diff --git a/data/recipes/crafting/craft_leather_cowl.yaml b/data/recipes/crafting/leather_cowl.yaml
index 9e5de25..70fc500 100644
--- a/data/recipes/crafting/craft_leather_cowl.yaml
+++ b/data/recipes/crafting/leather_cowl.yaml
@@ -1,4 +1,4 @@
-id: craft_leather_cowl
+id: leather_cowl
type: crafting
level: 9
xp: 19
diff --git a/data/recipes/crafting/craft_leather_gloves.yaml b/data/recipes/crafting/leather_gloves.yaml
index 4b4fdfc..fa4cd37 100644
--- a/data/recipes/crafting/craft_leather_gloves.yaml
+++ b/data/recipes/crafting/leather_gloves.yaml
@@ -1,4 +1,4 @@
-id: craft_leather_gloves
+id: leather_gloves
type: crafting
level: 1
xp: 14
diff --git a/data/recipes/crafting/craft_leather_vambraces.yaml b/data/recipes/crafting/leather_vambraces.yaml
index 1227271..6167294 100644
--- a/data/recipes/crafting/craft_leather_vambraces.yaml
+++ b/data/recipes/crafting/leather_vambraces.yaml
@@ -1,4 +1,4 @@
-id: craft_leather_vambraces
+id: leather_vambraces
type: crafting
level: 11
xp: 22
diff --git a/data/recipes/crafting/craft_pie_dish.yaml b/data/recipes/crafting/pie_dish.yaml
index 2b31d17..5f49d62 100644
--- a/data/recipes/crafting/craft_pie_dish.yaml
+++ b/data/recipes/crafting/pie_dish.yaml
@@ -1,4 +1,4 @@
-id: craft_pie_dish
+id: pie_dish
type: crafting
level: 7
xp: 10
diff --git a/data/recipes/crafting/craft_plant_pot.yaml b/data/recipes/crafting/plant_pot.yaml
index ddfcefa..2c4b228 100644
--- a/data/recipes/crafting/craft_plant_pot.yaml
+++ b/data/recipes/crafting/plant_pot.yaml
@@ -1,4 +1,4 @@
-id: craft_plant_pot
+id: plant_pot
type: crafting
level: 19
xp: 18
diff --git a/data/recipes/crafting/craft_pot.yaml b/data/recipes/crafting/pot.yaml
index 1b80eef..3215036 100644
--- a/data/recipes/crafting/craft_pot.yaml
+++ b/data/recipes/crafting/pot.yaml
@@ -1,4 +1,4 @@
-id: craft_pot
+id: pot
type: crafting
level: 1
xp: 6
diff --git a/data/recipes/crafting/craft_ruby_amulet_u.yaml b/data/recipes/crafting/ruby_amulet_u.yaml
index 38a864e..2958d77 100644
--- a/data/recipes/crafting/craft_ruby_amulet_u.yaml
+++ b/data/recipes/crafting/ruby_amulet_u.yaml
@@ -1,4 +1,4 @@
-id: craft_ruby_amulet_u
+id: ruby_amulet_u
type: crafting
level: 50
xp: 85
diff --git a/data/recipes/crafting/craft_ruby_bracelet.yaml b/data/recipes/crafting/ruby_bracelet.yaml
index bcc963e..7f36bce 100644
--- a/data/recipes/crafting/craft_ruby_bracelet.yaml
+++ b/data/recipes/crafting/ruby_bracelet.yaml
@@ -1,4 +1,4 @@
-id: craft_ruby_bracelet
+id: ruby_bracelet
type: crafting
level: 42
xp: 80
diff --git a/data/recipes/crafting/craft_ruby_necklace.yaml b/data/recipes/crafting/ruby_necklace.yaml
index 60e47a3..7be562e 100644
--- a/data/recipes/crafting/craft_ruby_necklace.yaml
+++ b/data/recipes/crafting/ruby_necklace.yaml
@@ -1,4 +1,4 @@
-id: craft_ruby_necklace
+id: ruby_necklace
type: crafting
level: 40
xp: 75
diff --git a/data/recipes/crafting/craft_ruby_ring.yaml b/data/recipes/crafting/ruby_ring.yaml
index 61d2b46..78a6339 100644
--- a/data/recipes/crafting/craft_ruby_ring.yaml
+++ b/data/recipes/crafting/ruby_ring.yaml
@@ -1,4 +1,4 @@
-id: craft_ruby_ring
+id: ruby_ring
type: crafting
level: 63
xp: 70
diff --git a/data/recipes/crafting/craft_sapphire_amulet_u.yaml b/data/recipes/crafting/sapphire_amulet_u.yaml
index 07ef6bf..36302d8 100644
--- a/data/recipes/crafting/craft_sapphire_amulet_u.yaml
+++ b/data/recipes/crafting/sapphire_amulet_u.yaml
@@ -1,4 +1,4 @@
-id: craft_sapphire_amulet_u
+id: sapphire_amulet_u
type: crafting
level: 24
xp: 65
diff --git a/data/recipes/crafting/craft_sapphire_bracelet.yaml b/data/recipes/crafting/sapphire_bracelet.yaml
index 3bc1067..2e1031e 100644
--- a/data/recipes/crafting/craft_sapphire_bracelet.yaml
+++ b/data/recipes/crafting/sapphire_bracelet.yaml
@@ -1,4 +1,4 @@
-id: craft_sapphire_bracelet
+id: sapphire_bracelet
type: crafting
level: 23
xp: 60
diff --git a/data/recipes/crafting/craft_sapphire_necklace.yaml b/data/recipes/crafting/sapphire_necklace.yaml
index 8006bf7..2671e6b 100644
--- a/data/recipes/crafting/craft_sapphire_necklace.yaml
+++ b/data/recipes/crafting/sapphire_necklace.yaml
@@ -1,4 +1,4 @@
-id: craft_sapphire_necklace
+id: sapphire_necklace
type: crafting
level: 22
xp: 55
diff --git a/data/recipes/crafting/craft_sapphire_ring.yaml b/data/recipes/crafting/sapphire_ring.yaml
index cc67b73..f755b6f 100644
--- a/data/recipes/crafting/craft_sapphire_ring.yaml
+++ b/data/recipes/crafting/sapphire_ring.yaml
@@ -1,4 +1,4 @@
-id: craft_sapphire_ring
+id: sapphire_ring
type: crafting
level: 20
xp: 40
diff --git a/data/recipes/crafting/craft_unfired_pie_dish.yaml b/data/recipes/crafting/unfired_pie_dish.yaml
index 7ba5032..d698cf4 100644
--- a/data/recipes/crafting/craft_unfired_pie_dish.yaml
+++ b/data/recipes/crafting/unfired_pie_dish.yaml
@@ -1,4 +1,4 @@
-id: craft_unfired_pie_dish
+id: unfired_pie_dish
type: crafting
level: 7
xp: 15
diff --git a/data/recipes/crafting/craft_unfired_plant_pot.yaml b/data/recipes/crafting/unfired_plant_pot.yaml
index db7a38e..e0675db 100644
--- a/data/recipes/crafting/craft_unfired_plant_pot.yaml
+++ b/data/recipes/crafting/unfired_plant_pot.yaml
@@ -1,4 +1,4 @@
-id: craft_unfired_plant_pot
+id: unfired_plant_pot
type: crafting
level: 19
xp: 18
diff --git a/data/recipes/crafting/craft_unfired_pot.yaml b/data/recipes/crafting/unfired_pot.yaml
index 5b2762d..04604e3 100644
--- a/data/recipes/crafting/craft_unfired_pot.yaml
+++ b/data/recipes/crafting/unfired_pot.yaml
@@ -1,4 +1,4 @@
-id: craft_unfired_pot
+id: unfired_pot
type: crafting
level: 1
xp: 6
diff --git a/data/recipes/fletching/arrow_shafts.yaml b/data/recipes/fletching/arrow_shafts.yaml
new file mode 100644
index 0000000..c382340
--- /dev/null
+++ b/data/recipes/fletching/arrow_shafts.yaml
@@ -0,0 +1,52 @@
+type: fletching
+tool: knife
+wait: 5
+products:
+ - id: arrow_shaft
+ output: arrow_shaft
+ level: 1
+ xp: 5
+ output_qty: 15
+ consume:
+ - items: [logs]
+ quantity: 1
+ - id: arrow_shaft_oak
+ output: arrow_shaft
+ level: 15
+ xp: 10
+ output_qty: 20
+ consume:
+ - items: [oak_logs]
+ quantity: 1
+ - id: arrow_shaft_willow
+ output: arrow_shaft
+ level: 30
+ xp: 15
+ output_qty: 25
+ consume:
+ - items: [willow_logs]
+ quantity: 1
+ - id: arrow_shaft_maple
+ output: arrow_shaft
+ level: 45
+ xp: 20
+ output_qty: 30
+ consume:
+ - items: [maple_logs]
+ quantity: 1
+ - id: arrow_shaft_yew
+ output: arrow_shaft
+ level: 60
+ xp: 25
+ output_qty: 35
+ consume:
+ - items: [yew_logs]
+ quantity: 1
+ - id: arrow_shaft_magic
+ output: arrow_shaft
+ level: 75
+ xp: 30
+ output_qty: 40
+ consume:
+ - items: [magic_logs]
+ quantity: 1
diff --git a/data/recipes/fletching/bolt_feathering.yaml b/data/recipes/fletching/bolt_feathering.yaml
new file mode 100644
index 0000000..2197de1
--- /dev/null
+++ b/data/recipes/fletching/bolt_feathering.yaml
@@ -0,0 +1,63 @@
+type: fletching
+wait: 0
+products:
+ - id: bronze_bolts
+ output: bronze_bolts
+ level: 9
+ xp: 5
+ output_qty: 10
+ consume:
+ - items: [bronze_bolts_unf]
+ quantity: 10
+ - items: [feather]
+ quantity: 10
+ - id: iron_bolts
+ output: iron_bolts
+ level: 39
+ xp: 15
+ output_qty: 10
+ consume:
+ - items: [iron_bolts_unf]
+ quantity: 10
+ - items: [feather]
+ quantity: 10
+ - id: steel_bolts
+ output: steel_bolts
+ level: 46
+ xp: 35
+ output_qty: 10
+ consume:
+ - items: [steel_bolts_unf]
+ quantity: 10
+ - items: [feather]
+ quantity: 10
+ - id: mithril_bolts
+ output: mithril_bolts
+ level: 54
+ xp: 50
+ output_qty: 10
+ consume:
+ - items: [mithril_bolts_unf]
+ quantity: 10
+ - items: [feather]
+ quantity: 10
+ - id: adamant_bolts
+ output: adamant_bolts
+ level: 61
+ xp: 70
+ output_qty: 10
+ consume:
+ - items: [adamant_bolts_unf]
+ quantity: 10
+ - items: [feather]
+ quantity: 10
+ - id: rune_bolts
+ output: rune_bolts
+ level: 69
+ xp: 100
+ output_qty: 10
+ consume:
+ - items: [rune_bolts_unf]
+ quantity: 10
+ - items: [feather]
+ quantity: 10
diff --git a/data/recipes/fletching/fletch_adamant_arrow.yaml b/data/recipes/fletching/fletch_adamant_arrow.yaml
deleted file mode 100644
index 57f899b..0000000
--- a/data/recipes/fletching/fletch_adamant_arrow.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: fletch_adamant_arrow
-type: fletching
-level: 60
-xp: 10
-wait: 2
-consume:
- - items: [headless_arrow]
- quantity: 15
- - items: [adamant_arrowtips]
- quantity: 15
-output: adamant_arrow
-output_qty: 15
-message: "You attach the adamant arrowtips to the headless arrows."
diff --git a/data/recipes/fletching/fletch_adamant_bolts.yaml b/data/recipes/fletching/fletch_adamant_bolts.yaml
deleted file mode 100644
index d96c811..0000000
--- a/data/recipes/fletching/fletch_adamant_bolts.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: fletch_adamant_bolts
-type: fletching
-level: 61
-xp: 70
-wait: 0
-consume:
- - items: [adamant_bolts_unf]
- quantity: 10
- - items: [feather]
- quantity: 10
-output: adamant_bolts
-output_qty: 10
-message: "You attach feathers to the adamant bolts."
diff --git a/data/recipes/fletching/fletch_arrow_shaft.yaml b/data/recipes/fletching/fletch_arrow_shaft.yaml
deleted file mode 100644
index ebf3861..0000000
--- a/data/recipes/fletching/fletch_arrow_shaft.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: fletch_arrow_shaft
-type: fletching
-tool: knife
-level: 1
-xp: 5
-wait: 5
-consume:
- - items: [logs]
- quantity: 1
-output: arrow_shaft
-output_qty: 15
-message: "You carefully cut the logs into arrow shafts."
diff --git a/data/recipes/fletching/fletch_arrow_shaft_magic.yaml b/data/recipes/fletching/fletch_arrow_shaft_magic.yaml
deleted file mode 100644
index 0621859..0000000
--- a/data/recipes/fletching/fletch_arrow_shaft_magic.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: fletch_arrow_shaft_magic
-type: fletching
-tool: knife
-level: 75
-xp: 30
-wait: 5
-consume:
- - items: [magic_logs]
- quantity: 1
-output: arrow_shaft
-output_qty: 40
-message: "You carefully cut the magic logs into arrow shafts."
diff --git a/data/recipes/fletching/fletch_arrow_shaft_maple.yaml b/data/recipes/fletching/fletch_arrow_shaft_maple.yaml
deleted file mode 100644
index b6671da..0000000
--- a/data/recipes/fletching/fletch_arrow_shaft_maple.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: fletch_arrow_shaft_maple
-type: fletching
-tool: knife
-level: 45
-xp: 20
-wait: 5
-consume:
- - items: [maple_logs]
- quantity: 1
-output: arrow_shaft
-output_qty: 30
-message: "You carefully cut the maple logs into arrow shafts."
diff --git a/data/recipes/fletching/fletch_arrow_shaft_oak.yaml b/data/recipes/fletching/fletch_arrow_shaft_oak.yaml
deleted file mode 100644
index cf38335..0000000
--- a/data/recipes/fletching/fletch_arrow_shaft_oak.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: fletch_arrow_shaft_oak
-type: fletching
-tool: knife
-level: 15
-xp: 10
-wait: 5
-consume:
- - items: [oak_logs]
- quantity: 1
-output: arrow_shaft
-output_qty: 20
-message: "You carefully cut the oak logs into arrow shafts."
diff --git a/data/recipes/fletching/fletch_arrow_shaft_willow.yaml b/data/recipes/fletching/fletch_arrow_shaft_willow.yaml
deleted file mode 100644
index 0c39a13..0000000
--- a/data/recipes/fletching/fletch_arrow_shaft_willow.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: fletch_arrow_shaft_willow
-type: fletching
-tool: knife
-level: 30
-xp: 15
-wait: 5
-consume:
- - items: [willow_logs]
- quantity: 1
-output: arrow_shaft
-output_qty: 25
-message: "You carefully cut the willow logs into arrow shafts."
diff --git a/data/recipes/fletching/fletch_arrow_shaft_yew.yaml b/data/recipes/fletching/fletch_arrow_shaft_yew.yaml
deleted file mode 100644
index 785325e..0000000
--- a/data/recipes/fletching/fletch_arrow_shaft_yew.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: fletch_arrow_shaft_yew
-type: fletching
-tool: knife
-level: 60
-xp: 25
-wait: 5
-consume:
- - items: [yew_logs]
- quantity: 1
-output: arrow_shaft
-output_qty: 35
-message: "You carefully cut the yew logs into arrow shafts."
diff --git a/data/recipes/fletching/fletch_bronze_arrow.yaml b/data/recipes/fletching/fletch_bronze_arrow.yaml
deleted file mode 100644
index 4b9e924..0000000
--- a/data/recipes/fletching/fletch_bronze_arrow.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: fletch_bronze_arrow
-type: fletching
-level: 1
-xp: 1
-wait: 2
-consume:
- - items: [headless_arrow]
- quantity: 15
- - items: [bronze_arrowtips]
- quantity: 15
-output: bronze_arrow
-output_qty: 15
-message: "You attach the bronze arrowtips to the headless arrows."
diff --git a/data/recipes/fletching/fletch_bronze_bolts.yaml b/data/recipes/fletching/fletch_bronze_bolts.yaml
deleted file mode 100644
index cdcf6db..0000000
--- a/data/recipes/fletching/fletch_bronze_bolts.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: fletch_bronze_bolts
-type: fletching
-level: 9
-xp: 5
-wait: 0
-consume:
- - items: [bronze_bolts_unf]
- quantity: 10
- - items: [feather]
- quantity: 10
-output: bronze_bolts
-output_qty: 10
-message: "You attach feathers to the bronze bolts."
diff --git a/data/recipes/fletching/fletch_diamond_bolt_tips.yaml b/data/recipes/fletching/fletch_diamond_bolt_tips.yaml
deleted file mode 100644
index 0ccabe2..0000000
--- a/data/recipes/fletching/fletch_diamond_bolt_tips.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: fletch_diamond_bolt_tips
-type: fletching
-tool: chisel
-level: 65
-xp: 7
-wait: 5
-consume:
- - items: [uncut_diamond]
- quantity: 1
-output: diamond_bolt_tips
-output_qty: 12
-message: "You cut the diamond into bolt tips."
diff --git a/data/recipes/fletching/fletch_diamond_bolts.yaml b/data/recipes/fletching/fletch_diamond_bolts.yaml
deleted file mode 100644
index 8c98fc2..0000000
--- a/data/recipes/fletching/fletch_diamond_bolts.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: fletch_diamond_bolts
-type: fletching
-level: 65
-xp: 7
-wait: 2
-consume:
- - items: [adamant_bolts]
- quantity: 10
- - items: [diamond_bolt_tips]
- quantity: 10
-output: diamond_bolts
-output_qty: 10
-message: "You tip the adamant bolts with diamond."
diff --git a/data/recipes/fletching/fletch_emerald_bolt_tips.yaml b/data/recipes/fletching/fletch_emerald_bolt_tips.yaml
deleted file mode 100644
index d48cf73..0000000
--- a/data/recipes/fletching/fletch_emerald_bolt_tips.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: fletch_emerald_bolt_tips
-type: fletching
-tool: chisel
-level: 66
-xp: 5
-wait: 5
-consume:
- - items: [uncut_emerald]
- quantity: 1
-output: emerald_bolt_tips
-output_qty: 12
-message: "You cut the emerald into bolt tips."
diff --git a/data/recipes/fletching/fletch_emerald_bolts.yaml b/data/recipes/fletching/fletch_emerald_bolts.yaml
deleted file mode 100644
index 8e7e446..0000000
--- a/data/recipes/fletching/fletch_emerald_bolts.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: fletch_emerald_bolts
-type: fletching
-level: 66
-xp: 5
-wait: 2
-consume:
- - items: [mithril_bolts]
- quantity: 10
- - items: [emerald_bolt_tips]
- quantity: 10
-output: emerald_bolts
-output_qty: 10
-message: "You tip the mithril bolts with emerald."
diff --git a/data/recipes/fletching/fletch_iron_arrow.yaml b/data/recipes/fletching/fletch_iron_arrow.yaml
deleted file mode 100644
index fbb1c2c..0000000
--- a/data/recipes/fletching/fletch_iron_arrow.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: fletch_iron_arrow
-type: fletching
-level: 15
-xp: 2
-wait: 2
-consume:
- - items: [headless_arrow]
- quantity: 15
- - items: [iron_arrowtips]
- quantity: 15
-output: iron_arrow
-output_qty: 15
-message: "You attach the iron arrowtips to the headless arrows."
diff --git a/data/recipes/fletching/fletch_iron_bolts.yaml b/data/recipes/fletching/fletch_iron_bolts.yaml
deleted file mode 100644
index bb90403..0000000
--- a/data/recipes/fletching/fletch_iron_bolts.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: fletch_iron_bolts
-type: fletching
-level: 39
-xp: 15
-wait: 0
-consume:
- - items: [iron_bolts_unf]
- quantity: 10
- - items: [feather]
- quantity: 10
-output: iron_bolts
-output_qty: 10
-message: "You attach feathers to the iron bolts."
diff --git a/data/recipes/fletching/fletch_longbow.yaml b/data/recipes/fletching/fletch_longbow.yaml
deleted file mode 100644
index fa46921..0000000
--- a/data/recipes/fletching/fletch_longbow.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: fletch_longbow
-type: fletching
-level: 10
-xp: 10
-wait: 2
-consume:
- - items: [longbow_u]
- quantity: 1
- - items: [bowstring]
- quantity: 1
-output: longbow
-message: "You string the longbow."
diff --git a/data/recipes/fletching/fletch_longbow_u.yaml b/data/recipes/fletching/fletch_longbow_u.yaml
deleted file mode 100644
index a893b49..0000000
--- a/data/recipes/fletching/fletch_longbow_u.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: fletch_longbow_u
-type: fletching
-tool: knife
-level: 10
-xp: 10
-wait: 3
-consume:
- - items: [logs]
- quantity: 1
-output: longbow_u
-message: "You carefully carve the log into a longbow."
diff --git a/data/recipes/fletching/fletch_magic_longbow.yaml b/data/recipes/fletching/fletch_magic_longbow.yaml
deleted file mode 100644
index 8e16b9c..0000000
--- a/data/recipes/fletching/fletch_magic_longbow.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: fletch_magic_longbow
-type: fletching
-level: 85
-xp: 91
-wait: 2
-consume:
- - items: [magic_longbow_u]
- quantity: 1
- - items: [bowstring]
- quantity: 1
-output: magic_longbow
-message: "You string the magic longbow."
diff --git a/data/recipes/fletching/fletch_magic_longbow_u.yaml b/data/recipes/fletching/fletch_magic_longbow_u.yaml
deleted file mode 100644
index ce2e247..0000000
--- a/data/recipes/fletching/fletch_magic_longbow_u.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: fletch_magic_longbow_u
-type: fletching
-tool: knife
-level: 85
-xp: 91
-wait: 3
-consume:
- - items: [magic_logs]
- quantity: 1
-output: magic_longbow_u
-message: "You carefully carve the magic log into a longbow."
diff --git a/data/recipes/fletching/fletch_magic_shortbow.yaml b/data/recipes/fletching/fletch_magic_shortbow.yaml
deleted file mode 100644
index 58abce9..0000000
--- a/data/recipes/fletching/fletch_magic_shortbow.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: fletch_magic_shortbow
-type: fletching
-level: 80
-xp: 83
-wait: 2
-consume:
- - items: [magic_shortbow_u]
- quantity: 1
- - items: [bowstring]
- quantity: 1
-output: magic_shortbow
-message: "You string the magic shortbow."
diff --git a/data/recipes/fletching/fletch_magic_shortbow_u.yaml b/data/recipes/fletching/fletch_magic_shortbow_u.yaml
deleted file mode 100644
index 3fbf6cd..0000000
--- a/data/recipes/fletching/fletch_magic_shortbow_u.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: fletch_magic_shortbow_u
-type: fletching
-tool: knife
-level: 80
-xp: 83
-wait: 3
-consume:
- - items: [magic_logs]
- quantity: 1
-output: magic_shortbow_u
-message: "You carefully carve the magic log into a shortbow."
diff --git a/data/recipes/fletching/fletch_maple_longbow.yaml b/data/recipes/fletching/fletch_maple_longbow.yaml
deleted file mode 100644
index 123d531..0000000
--- a/data/recipes/fletching/fletch_maple_longbow.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: fletch_maple_longbow
-type: fletching
-level: 55
-xp: 58
-wait: 2
-consume:
- - items: [maple_longbow_u]
- quantity: 1
- - items: [bowstring]
- quantity: 1
-output: maple_longbow
-message: "You string the maple longbow."
diff --git a/data/recipes/fletching/fletch_maple_longbow_u.yaml b/data/recipes/fletching/fletch_maple_longbow_u.yaml
deleted file mode 100644
index 4c61993..0000000
--- a/data/recipes/fletching/fletch_maple_longbow_u.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: fletch_maple_longbow_u
-type: fletching
-tool: knife
-level: 55
-xp: 58
-wait: 3
-consume:
- - items: [maple_logs]
- quantity: 1
-output: maple_longbow_u
-message: "You carefully carve the maple log into a longbow."
diff --git a/data/recipes/fletching/fletch_maple_shortbow.yaml b/data/recipes/fletching/fletch_maple_shortbow.yaml
deleted file mode 100644
index aebe50a..0000000
--- a/data/recipes/fletching/fletch_maple_shortbow.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: fletch_maple_shortbow
-type: fletching
-level: 50
-xp: 50
-wait: 2
-consume:
- - items: [maple_shortbow_u]
- quantity: 1
- - items: [bowstring]
- quantity: 1
-output: maple_shortbow
-message: "You string the maple shortbow."
diff --git a/data/recipes/fletching/fletch_maple_shortbow_u.yaml b/data/recipes/fletching/fletch_maple_shortbow_u.yaml
deleted file mode 100644
index 36270dd..0000000
--- a/data/recipes/fletching/fletch_maple_shortbow_u.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: fletch_maple_shortbow_u
-type: fletching
-tool: knife
-level: 50
-xp: 50
-wait: 3
-consume:
- - items: [maple_logs]
- quantity: 1
-output: maple_shortbow_u
-message: "You carefully carve the maple log into a shortbow."
diff --git a/data/recipes/fletching/fletch_mithril_arrow.yaml b/data/recipes/fletching/fletch_mithril_arrow.yaml
deleted file mode 100644
index 87a46e2..0000000
--- a/data/recipes/fletching/fletch_mithril_arrow.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: fletch_mithril_arrow
-type: fletching
-level: 45
-xp: 7
-wait: 2
-consume:
- - items: [headless_arrow]
- quantity: 15
- - items: [mithril_arrowtips]
- quantity: 15
-output: mithril_arrow
-output_qty: 15
-message: "You attach the mithril arrowtips to the headless arrows."
diff --git a/data/recipes/fletching/fletch_mithril_bolts.yaml b/data/recipes/fletching/fletch_mithril_bolts.yaml
deleted file mode 100644
index a2afcb8..0000000
--- a/data/recipes/fletching/fletch_mithril_bolts.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: fletch_mithril_bolts
-type: fletching
-level: 54
-xp: 50
-wait: 0
-consume:
- - items: [mithril_bolts_unf]
- quantity: 10
- - items: [feather]
- quantity: 10
-output: mithril_bolts
-output_qty: 10
-message: "You attach feathers to the mithril bolts."
diff --git a/data/recipes/fletching/fletch_oak_longbow.yaml b/data/recipes/fletching/fletch_oak_longbow.yaml
deleted file mode 100644
index f1c96ba..0000000
--- a/data/recipes/fletching/fletch_oak_longbow.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: fletch_oak_longbow
-type: fletching
-level: 25
-xp: 25
-wait: 2
-consume:
- - items: [oak_longbow_u]
- quantity: 1
- - items: [bowstring]
- quantity: 1
-output: oak_longbow
-message: "You string the oak longbow."
diff --git a/data/recipes/fletching/fletch_oak_longbow_u.yaml b/data/recipes/fletching/fletch_oak_longbow_u.yaml
deleted file mode 100644
index 8f0b234..0000000
--- a/data/recipes/fletching/fletch_oak_longbow_u.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: fletch_oak_longbow_u
-type: fletching
-tool: knife
-level: 25
-xp: 25
-wait: 3
-consume:
- - items: [oak_logs]
- quantity: 1
-output: oak_longbow_u
-message: "You carefully carve the oak log into a longbow."
diff --git a/data/recipes/fletching/fletch_oak_shortbow.yaml b/data/recipes/fletching/fletch_oak_shortbow.yaml
deleted file mode 100644
index 198657f..0000000
--- a/data/recipes/fletching/fletch_oak_shortbow.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: fletch_oak_shortbow
-type: fletching
-level: 20
-xp: 16
-wait: 2
-consume:
- - items: [oak_shortbow_u]
- quantity: 1
- - items: [bowstring]
- quantity: 1
-output: oak_shortbow
-message: "You string the oak shortbow."
diff --git a/data/recipes/fletching/fletch_oak_shortbow_u.yaml b/data/recipes/fletching/fletch_oak_shortbow_u.yaml
deleted file mode 100644
index 4f96a09..0000000
--- a/data/recipes/fletching/fletch_oak_shortbow_u.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: fletch_oak_shortbow_u
-type: fletching
-tool: knife
-level: 20
-xp: 16
-wait: 3
-consume:
- - items: [oak_logs]
- quantity: 1
-output: oak_shortbow_u
-message: "You carefully carve the oak log into a shortbow."
diff --git a/data/recipes/fletching/fletch_ruby_bolt_tips.yaml b/data/recipes/fletching/fletch_ruby_bolt_tips.yaml
deleted file mode 100644
index bc9f695..0000000
--- a/data/recipes/fletching/fletch_ruby_bolt_tips.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: fletch_ruby_bolt_tips
-type: fletching
-tool: chisel
-level: 73
-xp: 6
-wait: 5
-consume:
- - items: [uncut_ruby]
- quantity: 1
-output: ruby_bolt_tips
-output_qty: 12
-message: "You cut the ruby into bolt tips."
diff --git a/data/recipes/fletching/fletch_ruby_bolts.yaml b/data/recipes/fletching/fletch_ruby_bolts.yaml
deleted file mode 100644
index ac1d150..0000000
--- a/data/recipes/fletching/fletch_ruby_bolts.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: fletch_ruby_bolts
-type: fletching
-level: 73
-xp: 6
-wait: 2
-consume:
- - items: [adamant_bolts]
- quantity: 10
- - items: [ruby_bolt_tips]
- quantity: 10
-output: ruby_bolts
-output_qty: 10
-message: "You tip the adamant bolts with ruby."
diff --git a/data/recipes/fletching/fletch_rune_arrow.yaml b/data/recipes/fletching/fletch_rune_arrow.yaml
deleted file mode 100644
index aa2795e..0000000
--- a/data/recipes/fletching/fletch_rune_arrow.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: fletch_rune_arrow
-type: fletching
-level: 75
-xp: 12
-wait: 2
-consume:
- - items: [headless_arrow]
- quantity: 15
- - items: [rune_arrowtips]
- quantity: 15
-output: rune_arrow
-output_qty: 15
-message: "You attach the rune arrowtips to the headless arrows."
diff --git a/data/recipes/fletching/fletch_rune_bolts.yaml b/data/recipes/fletching/fletch_rune_bolts.yaml
deleted file mode 100644
index 20bb7b9..0000000
--- a/data/recipes/fletching/fletch_rune_bolts.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: fletch_rune_bolts
-type: fletching
-level: 69
-xp: 100
-wait: 0
-consume:
- - items: [rune_bolts_unf]
- quantity: 10
- - items: [feather]
- quantity: 10
-output: rune_bolts
-output_qty: 10
-message: "You attach feathers to the rune bolts."
diff --git a/data/recipes/fletching/fletch_sapphire_bolt_tips.yaml b/data/recipes/fletching/fletch_sapphire_bolt_tips.yaml
deleted file mode 100644
index dca6fef..0000000
--- a/data/recipes/fletching/fletch_sapphire_bolt_tips.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: fletch_sapphire_bolt_tips
-type: fletching
-tool: chisel
-level: 63
-xp: 4
-wait: 5
-consume:
- - items: [uncut_sapphire]
- quantity: 1
-output: sapphire_bolt_tips
-output_qty: 12
-message: "You cut the sapphire into bolt tips."
diff --git a/data/recipes/fletching/fletch_sapphire_bolts.yaml b/data/recipes/fletching/fletch_sapphire_bolts.yaml
deleted file mode 100644
index 712578f..0000000
--- a/data/recipes/fletching/fletch_sapphire_bolts.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: fletch_sapphire_bolts
-type: fletching
-level: 63
-xp: 4
-wait: 2
-consume:
- - items: [mithril_bolts]
- quantity: 10
- - items: [sapphire_bolt_tips]
- quantity: 10
-output: sapphire_bolts
-output_qty: 10
-message: "You tip the mithril bolts with sapphire."
diff --git a/data/recipes/fletching/fletch_shortbow.yaml b/data/recipes/fletching/fletch_shortbow.yaml
deleted file mode 100644
index e96317d..0000000
--- a/data/recipes/fletching/fletch_shortbow.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: fletch_shortbow
-type: fletching
-level: 5
-xp: 5
-wait: 2
-consume:
- - items: [shortbow_u]
- quantity: 1
- - items: [bowstring]
- quantity: 1
-output: shortbow
-message: "You string the shortbow."
diff --git a/data/recipes/fletching/fletch_shortbow_u.yaml b/data/recipes/fletching/fletch_shortbow_u.yaml
deleted file mode 100644
index 3ab4931..0000000
--- a/data/recipes/fletching/fletch_shortbow_u.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: fletch_shortbow_u
-type: fletching
-tool: knife
-level: 5
-xp: 5
-wait: 3
-consume:
- - items: [logs]
- quantity: 1
-output: shortbow_u
-message: "You carefully carve the log into a shortbow."
diff --git a/data/recipes/fletching/fletch_steel_arrow.yaml b/data/recipes/fletching/fletch_steel_arrow.yaml
deleted file mode 100644
index 9bd229a..0000000
--- a/data/recipes/fletching/fletch_steel_arrow.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: fletch_steel_arrow
-type: fletching
-level: 30
-xp: 5
-wait: 2
-consume:
- - items: [headless_arrow]
- quantity: 15
- - items: [steel_arrowtips]
- quantity: 15
-output: steel_arrow
-output_qty: 15
-message: "You attach the steel arrowtips to the headless arrows."
diff --git a/data/recipes/fletching/fletch_steel_bolts.yaml b/data/recipes/fletching/fletch_steel_bolts.yaml
deleted file mode 100644
index 9bfdafe..0000000
--- a/data/recipes/fletching/fletch_steel_bolts.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: fletch_steel_bolts
-type: fletching
-level: 46
-xp: 35
-wait: 0
-consume:
- - items: [steel_bolts_unf]
- quantity: 10
- - items: [feather]
- quantity: 10
-output: steel_bolts
-output_qty: 10
-message: "You attach feathers to the steel bolts."
diff --git a/data/recipes/fletching/fletch_willow_longbow.yaml b/data/recipes/fletching/fletch_willow_longbow.yaml
deleted file mode 100644
index 28071ac..0000000
--- a/data/recipes/fletching/fletch_willow_longbow.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: fletch_willow_longbow
-type: fletching
-level: 40
-xp: 41
-wait: 2
-consume:
- - items: [willow_longbow_u]
- quantity: 1
- - items: [bowstring]
- quantity: 1
-output: willow_longbow
-message: "You string the willow longbow."
diff --git a/data/recipes/fletching/fletch_willow_longbow_u.yaml b/data/recipes/fletching/fletch_willow_longbow_u.yaml
deleted file mode 100644
index ef808c0..0000000
--- a/data/recipes/fletching/fletch_willow_longbow_u.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: fletch_willow_longbow_u
-type: fletching
-tool: knife
-level: 40
-xp: 41
-wait: 3
-consume:
- - items: [willow_logs]
- quantity: 1
-output: willow_longbow_u
-message: "You carefully carve the willow log into a longbow."
diff --git a/data/recipes/fletching/fletch_willow_shortbow.yaml b/data/recipes/fletching/fletch_willow_shortbow.yaml
deleted file mode 100644
index a2b2c2b..0000000
--- a/data/recipes/fletching/fletch_willow_shortbow.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: fletch_willow_shortbow
-type: fletching
-level: 35
-xp: 33
-wait: 2
-consume:
- - items: [willow_shortbow_u]
- quantity: 1
- - items: [bowstring]
- quantity: 1
-output: willow_shortbow
-message: "You string the willow shortbow."
diff --git a/data/recipes/fletching/fletch_willow_shortbow_u.yaml b/data/recipes/fletching/fletch_willow_shortbow_u.yaml
deleted file mode 100644
index 95609b3..0000000
--- a/data/recipes/fletching/fletch_willow_shortbow_u.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: fletch_willow_shortbow_u
-type: fletching
-tool: knife
-level: 35
-xp: 33
-wait: 3
-consume:
- - items: [willow_logs]
- quantity: 1
-output: willow_shortbow_u
-message: "You carefully carve the willow log into a shortbow."
diff --git a/data/recipes/fletching/fletch_yew_longbow.yaml b/data/recipes/fletching/fletch_yew_longbow.yaml
deleted file mode 100644
index 208c623..0000000
--- a/data/recipes/fletching/fletch_yew_longbow.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: fletch_yew_longbow
-type: fletching
-level: 65
-xp: 75
-wait: 2
-consume:
- - items: [yew_longbow_u]
- quantity: 1
- - items: [bowstring]
- quantity: 1
-output: yew_longbow
-message: "You string the yew longbow."
diff --git a/data/recipes/fletching/fletch_yew_longbow_u.yaml b/data/recipes/fletching/fletch_yew_longbow_u.yaml
deleted file mode 100644
index bdce4f8..0000000
--- a/data/recipes/fletching/fletch_yew_longbow_u.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: fletch_yew_longbow_u
-type: fletching
-tool: knife
-level: 65
-xp: 75
-wait: 3
-consume:
- - items: [yew_logs]
- quantity: 1
-output: yew_longbow_u
-message: "You carefully carve the yew log into a longbow."
diff --git a/data/recipes/fletching/fletch_yew_shortbow.yaml b/data/recipes/fletching/fletch_yew_shortbow.yaml
deleted file mode 100644
index fbe5f62..0000000
--- a/data/recipes/fletching/fletch_yew_shortbow.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: fletch_yew_shortbow
-type: fletching
-level: 60
-xp: 67
-wait: 2
-consume:
- - items: [yew_shortbow_u]
- quantity: 1
- - items: [bowstring]
- quantity: 1
-output: yew_shortbow
-message: "You string the yew shortbow."
diff --git a/data/recipes/fletching/fletch_yew_shortbow_u.yaml b/data/recipes/fletching/fletch_yew_shortbow_u.yaml
deleted file mode 100644
index 774e3a5..0000000
--- a/data/recipes/fletching/fletch_yew_shortbow_u.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: fletch_yew_shortbow_u
-type: fletching
-tool: knife
-level: 60
-xp: 67
-wait: 3
-consume:
- - items: [yew_logs]
- quantity: 1
-output: yew_shortbow_u
-message: "You carefully carve the yew log into a shortbow."
diff --git a/data/recipes/fletching/gem_bolt_tips.yaml b/data/recipes/fletching/gem_bolt_tips.yaml
new file mode 100644
index 0000000..5482140
--- /dev/null
+++ b/data/recipes/fletching/gem_bolt_tips.yaml
@@ -0,0 +1,36 @@
+type: fletching
+tool: chisel
+wait: 5
+products:
+ - id: sapphire_bolt_tips
+ output: sapphire_bolt_tips
+ level: 63
+ xp: 4
+ output_qty: 12
+ consume:
+ - items: [uncut_sapphire]
+ quantity: 1
+ - id: diamond_bolt_tips
+ output: diamond_bolt_tips
+ level: 65
+ xp: 7
+ output_qty: 12
+ consume:
+ - items: [uncut_diamond]
+ quantity: 1
+ - id: emerald_bolt_tips
+ output: emerald_bolt_tips
+ level: 66
+ xp: 5
+ output_qty: 12
+ consume:
+ - items: [uncut_emerald]
+ quantity: 1
+ - id: ruby_bolt_tips
+ output: ruby_bolt_tips
+ level: 73
+ xp: 6
+ output_qty: 12
+ consume:
+ - items: [uncut_ruby]
+ quantity: 1
diff --git a/data/recipes/fletching/gem_tipped_bolts.yaml b/data/recipes/fletching/gem_tipped_bolts.yaml
new file mode 100644
index 0000000..7ed5adc
--- /dev/null
+++ b/data/recipes/fletching/gem_tipped_bolts.yaml
@@ -0,0 +1,43 @@
+type: fletching
+wait: 2
+products:
+ - id: sapphire_bolts
+ output: sapphire_bolts
+ level: 63
+ xp: 4
+ output_qty: 10
+ consume:
+ - items: [mithril_bolts]
+ quantity: 10
+ - items: [sapphire_bolt_tips]
+ quantity: 10
+ - id: diamond_bolts
+ output: diamond_bolts
+ level: 65
+ xp: 7
+ output_qty: 10
+ consume:
+ - items: [adamant_bolts]
+ quantity: 10
+ - items: [diamond_bolt_tips]
+ quantity: 10
+ - id: emerald_bolts
+ output: emerald_bolts
+ level: 66
+ xp: 5
+ output_qty: 10
+ consume:
+ - items: [mithril_bolts]
+ quantity: 10
+ - items: [emerald_bolt_tips]
+ quantity: 10
+ - id: ruby_bolts
+ output: ruby_bolts
+ level: 73
+ xp: 6
+ output_qty: 10
+ consume:
+ - items: [adamant_bolts]
+ quantity: 10
+ - items: [ruby_bolt_tips]
+ quantity: 10
diff --git a/data/recipes/fletching/fletch_headless_arrow.yaml b/data/recipes/fletching/headless_arrow.yaml
index 84d88f5..0c1e363 100644
--- a/data/recipes/fletching/fletch_headless_arrow.yaml
+++ b/data/recipes/fletching/headless_arrow.yaml
@@ -1,4 +1,4 @@
-id: fletch_headless_arrow
+id: headless_arrow
type: fletching
level: 1
xp: 1
diff --git a/data/recipes/fletching/strung_bows.yaml b/data/recipes/fletching/strung_bows.yaml
new file mode 100644
index 0000000..c7e2c3d
--- /dev/null
+++ b/data/recipes/fletching/strung_bows.yaml
@@ -0,0 +1,111 @@
+type: fletching
+wait: 2
+products:
+ - id: shortbow
+ output: shortbow
+ level: 5
+ xp: 5
+ consume:
+ - items: [shortbow_u]
+ quantity: 1
+ - items: [bowstring]
+ quantity: 1
+ - id: longbow
+ output: longbow
+ level: 10
+ xp: 10
+ consume:
+ - items: [longbow_u]
+ quantity: 1
+ - items: [bowstring]
+ quantity: 1
+ - id: oak_shortbow
+ output: oak_shortbow
+ level: 20
+ xp: 16
+ consume:
+ - items: [oak_shortbow_u]
+ quantity: 1
+ - items: [bowstring]
+ quantity: 1
+ - id: oak_longbow
+ output: oak_longbow
+ level: 25
+ xp: 25
+ consume:
+ - items: [oak_longbow_u]
+ quantity: 1
+ - items: [bowstring]
+ quantity: 1
+ - id: willow_shortbow
+ output: willow_shortbow
+ level: 35
+ xp: 33
+ consume:
+ - items: [willow_shortbow_u]
+ quantity: 1
+ - items: [bowstring]
+ quantity: 1
+ - id: willow_longbow
+ output: willow_longbow
+ level: 40
+ xp: 41
+ consume:
+ - items: [willow_longbow_u]
+ quantity: 1
+ - items: [bowstring]
+ quantity: 1
+ - id: maple_shortbow
+ output: maple_shortbow
+ level: 50
+ xp: 50
+ consume:
+ - items: [maple_shortbow_u]
+ quantity: 1
+ - items: [bowstring]
+ quantity: 1
+ - id: maple_longbow
+ output: maple_longbow
+ level: 55
+ xp: 58
+ consume:
+ - items: [maple_longbow_u]
+ quantity: 1
+ - items: [bowstring]
+ quantity: 1
+ - id: yew_shortbow
+ output: yew_shortbow
+ level: 60
+ xp: 67
+ consume:
+ - items: [yew_shortbow_u]
+ quantity: 1
+ - items: [bowstring]
+ quantity: 1
+ - id: yew_longbow
+ output: yew_longbow
+ level: 65
+ xp: 75
+ consume:
+ - items: [yew_longbow_u]
+ quantity: 1
+ - items: [bowstring]
+ quantity: 1
+ - id: magic_shortbow
+ output: magic_shortbow
+ level: 80
+ xp: 83
+ consume:
+ - items: [magic_shortbow_u]
+ quantity: 1
+ - items: [bowstring]
+ quantity: 1
+ - id: magic_longbow
+ output: magic_longbow
+ level: 85
+ xp: 91
+ consume:
+ - items: [magic_longbow_u]
+ quantity: 1
+ - items: [bowstring]
+ quantity: 1
diff --git a/data/recipes/fletching/tipped_arrows.yaml b/data/recipes/fletching/tipped_arrows.yaml
new file mode 100644
index 0000000..8e4530b
--- /dev/null
+++ b/data/recipes/fletching/tipped_arrows.yaml
@@ -0,0 +1,63 @@
+type: fletching
+wait: 2
+products:
+ - id: bronze_arrow
+ output: bronze_arrow
+ level: 1
+ xp: 1
+ output_qty: 15
+ consume:
+ - items: [headless_arrow]
+ quantity: 15
+ - items: [bronze_arrowtips]
+ quantity: 15
+ - id: iron_arrow
+ output: iron_arrow
+ level: 15
+ xp: 2
+ output_qty: 15
+ consume:
+ - items: [headless_arrow]
+ quantity: 15
+ - items: [iron_arrowtips]
+ quantity: 15
+ - id: steel_arrow
+ output: steel_arrow
+ level: 30
+ xp: 5
+ output_qty: 15
+ consume:
+ - items: [headless_arrow]
+ quantity: 15
+ - items: [steel_arrowtips]
+ quantity: 15
+ - id: mithril_arrow
+ output: mithril_arrow
+ level: 45
+ xp: 7
+ output_qty: 15
+ consume:
+ - items: [headless_arrow]
+ quantity: 15
+ - items: [mithril_arrowtips]
+ quantity: 15
+ - id: adamant_arrow
+ output: adamant_arrow
+ level: 60
+ xp: 10
+ output_qty: 15
+ consume:
+ - items: [headless_arrow]
+ quantity: 15
+ - items: [adamant_arrowtips]
+ quantity: 15
+ - id: rune_arrow
+ output: rune_arrow
+ level: 75
+ xp: 12
+ output_qty: 15
+ consume:
+ - items: [headless_arrow]
+ quantity: 15
+ - items: [rune_arrowtips]
+ quantity: 15
diff --git a/data/recipes/fletching/unstrung_bows.yaml b/data/recipes/fletching/unstrung_bows.yaml
new file mode 100644
index 0000000..3195c8d
--- /dev/null
+++ b/data/recipes/fletching/unstrung_bows.yaml
@@ -0,0 +1,88 @@
+type: fletching
+tool: knife
+wait: 3
+products:
+ - id: shortbow_u
+ output: shortbow_u
+ level: 5
+ xp: 5
+ consume:
+ - items: [logs]
+ quantity: 1
+ - id: longbow_u
+ output: longbow_u
+ level: 10
+ xp: 10
+ consume:
+ - items: [logs]
+ quantity: 1
+ - id: oak_shortbow_u
+ output: oak_shortbow_u
+ level: 20
+ xp: 16
+ consume:
+ - items: [oak_logs]
+ quantity: 1
+ - id: oak_longbow_u
+ output: oak_longbow_u
+ level: 25
+ xp: 25
+ consume:
+ - items: [oak_logs]
+ quantity: 1
+ - id: willow_shortbow_u
+ output: willow_shortbow_u
+ level: 35
+ xp: 33
+ consume:
+ - items: [willow_logs]
+ quantity: 1
+ - id: willow_longbow_u
+ output: willow_longbow_u
+ level: 40
+ xp: 41
+ consume:
+ - items: [willow_logs]
+ quantity: 1
+ - id: maple_shortbow_u
+ output: maple_shortbow_u
+ level: 50
+ xp: 50
+ consume:
+ - items: [maple_logs]
+ quantity: 1
+ - id: maple_longbow_u
+ output: maple_longbow_u
+ level: 55
+ xp: 58
+ consume:
+ - items: [maple_logs]
+ quantity: 1
+ - id: yew_shortbow_u
+ output: yew_shortbow_u
+ level: 60
+ xp: 67
+ consume:
+ - items: [yew_logs]
+ quantity: 1
+ - id: yew_longbow_u
+ output: yew_longbow_u
+ level: 65
+ xp: 75
+ consume:
+ - items: [yew_logs]
+ quantity: 1
+ - id: magic_shortbow_u
+ output: magic_shortbow_u
+ level: 80
+ xp: 83
+ consume:
+ - items: [magic_logs]
+ quantity: 1
+ - id: magic_longbow_u
+ output: magic_longbow_u
+ level: 85
+ xp: 91
+ consume:
+ - items: [magic_logs]
+ quantity: 1
diff --git a/data/recipes/mixing/mix_amp_potion.yaml b/data/recipes/mixing/amp_potion.yaml
index 6145296..40aeb84 100644
--- a/data/recipes/mixing/mix_amp_potion.yaml
+++ b/data/recipes/mixing/amp_potion.yaml
@@ -1,4 +1,4 @@
-id: mix_amp_potion
+id: amp_potion
type: pharmacy
skill: pharmacy
level: 12
diff --git a/data/recipes/mixing/mix_bio_serum.yaml b/data/recipes/mixing/bio_serum.yaml
index 6f5a3ea..f7f728a 100644
--- a/data/recipes/mixing/mix_bio_serum.yaml
+++ b/data/recipes/mixing/bio_serum.yaml
@@ -1,4 +1,4 @@
-id: mix_bio_serum
+id: bio_serum
type: pharmacy
skill: pharmacy
level: 5
diff --git a/data/recipes/mixing/mix_full_restore.yaml b/data/recipes/mixing/full_restore.yaml
index b9e320e..c5ed30b 100644
--- a/data/recipes/mixing/mix_full_restore.yaml
+++ b/data/recipes/mixing/full_restore.yaml
@@ -1,4 +1,4 @@
-id: mix_full_restore
+id: full_restore
type: pharmacy
skill: pharmacy
level: 63
diff --git a/data/recipes/mixing/mix_nano_restore.yaml b/data/recipes/mixing/nano_restore.yaml
index 4a5ebb0..c14f151 100644
--- a/data/recipes/mixing/mix_nano_restore.yaml
+++ b/data/recipes/mixing/nano_restore.yaml
@@ -1,4 +1,4 @@
-id: mix_nano_restore
+id: nano_restore
type: pharmacy
skill: pharmacy
level: 22
diff --git a/data/recipes/mixing/mix_restoration_compound.yaml b/data/recipes/mixing/restoration_compound.yaml
index c945cac..0613d47 100644
--- a/data/recipes/mixing/mix_restoration_compound.yaml
+++ b/data/recipes/mixing/restoration_compound.yaml
@@ -1,4 +1,4 @@
-id: mix_restoration_compound
+id: restoration_compound
type: pharmacy
skill: pharmacy
level: 81
diff --git a/data/recipes/mixing/mix_science_serum.yaml b/data/recipes/mixing/science_serum.yaml
index b65f2d2..311f3c4 100644
--- a/data/recipes/mixing/mix_science_serum.yaml
+++ b/data/recipes/mixing/science_serum.yaml
@@ -1,4 +1,4 @@
-id: mix_science_serum
+id: science_serum
type: pharmacy
skill: pharmacy
level: 76
diff --git a/data/recipes/mixing/mix_shield_potion.yaml b/data/recipes/mixing/shield_potion.yaml
index fc44abf..f334b5a 100644
--- a/data/recipes/mixing/mix_shield_potion.yaml
+++ b/data/recipes/mixing/shield_potion.yaml
@@ -1,4 +1,4 @@
-id: mix_shield_potion
+id: shield_potion
type: pharmacy
skill: pharmacy
level: 30
diff --git a/data/recipes/mixing/mix_stim_cell.yaml b/data/recipes/mixing/stim_cell.yaml
index ccca53a..ddf37f8 100644
--- a/data/recipes/mixing/mix_stim_cell.yaml
+++ b/data/recipes/mixing/stim_cell.yaml
@@ -1,4 +1,4 @@
-id: mix_stim_cell
+id: stim_cell
type: pharmacy
skill: pharmacy
level: 26
diff --git a/data/recipes/mixing/mix_stim_potion.yaml b/data/recipes/mixing/stim_potion.yaml
index 437b985..ad6abd3 100644
--- a/data/recipes/mixing/mix_stim_potion.yaml
+++ b/data/recipes/mixing/stim_potion.yaml
@@ -1,4 +1,4 @@
-id: mix_stim_potion
+id: stim_potion
type: pharmacy
skill: pharmacy
level: 3
diff --git a/data/recipes/mixing/mix_super_amp.yaml b/data/recipes/mixing/super_amp.yaml
index 5e05019..dc4ef81 100644
--- a/data/recipes/mixing/mix_super_amp.yaml
+++ b/data/recipes/mixing/super_amp.yaml
@@ -1,4 +1,4 @@
-id: mix_super_amp
+id: super_amp
type: pharmacy
skill: pharmacy
level: 55
diff --git a/data/recipes/mixing/mix_super_bio_serum.yaml b/data/recipes/mixing/super_bio_serum.yaml
index 96e6632..3c763a6 100644
--- a/data/recipes/mixing/mix_super_bio_serum.yaml
+++ b/data/recipes/mixing/super_bio_serum.yaml
@@ -1,4 +1,4 @@
-id: mix_super_bio_serum
+id: super_bio_serum
type: pharmacy
skill: pharmacy
level: 48
diff --git a/data/recipes/mixing/mix_super_shield.yaml b/data/recipes/mixing/super_shield.yaml
index 61b3686..706d402 100644
--- a/data/recipes/mixing/mix_super_shield.yaml
+++ b/data/recipes/mixing/super_shield.yaml
@@ -1,4 +1,4 @@
-id: mix_super_shield
+id: super_shield
type: pharmacy
skill: pharmacy
level: 66
diff --git a/data/recipes/mixing/mix_super_stim.yaml b/data/recipes/mixing/super_stim.yaml
index bac9f8f..8ef6819 100644
--- a/data/recipes/mixing/mix_super_stim.yaml
+++ b/data/recipes/mixing/super_stim.yaml
@@ -1,4 +1,4 @@
-id: mix_super_stim
+id: super_stim
type: pharmacy
skill: pharmacy
level: 45
diff --git a/data/recipes/mixing/mix_super_stim_cell.yaml b/data/recipes/mixing/super_stim_cell.yaml
index 26640ea..ec4a515 100644
--- a/data/recipes/mixing/mix_super_stim_cell.yaml
+++ b/data/recipes/mixing/super_stim_cell.yaml
@@ -1,4 +1,4 @@
-id: mix_super_stim_cell
+id: super_stim_cell
type: pharmacy
skill: pharmacy
level: 52
diff --git a/data/recipes/mixing/mix_targeting_serum.yaml b/data/recipes/mixing/targeting_serum.yaml
index 06f8421..e91e20e 100644
--- a/data/recipes/mixing/mix_targeting_serum.yaml
+++ b/data/recipes/mixing/targeting_serum.yaml
@@ -1,4 +1,4 @@
-id: mix_targeting_serum
+id: targeting_serum
type: pharmacy
skill: pharmacy
level: 72
diff --git a/data/recipes/mixing/mix_tech_serum.yaml b/data/recipes/mixing/tech_serum.yaml
index a57d2f9..3a21619 100644
--- a/data/recipes/mixing/mix_tech_serum.yaml
+++ b/data/recipes/mixing/tech_serum.yaml
@@ -1,4 +1,4 @@
-id: mix_tech_serum
+id: tech_serum
type: pharmacy
skill: pharmacy
level: 38
diff --git a/data/recipes/smelting/smelt_adamantite.yaml b/data/recipes/smelting/adamantite.yaml
index 5ff675b..af9283d 100644
--- a/data/recipes/smelting/smelt_adamantite.yaml
+++ b/data/recipes/smelting/adamantite.yaml
@@ -1,4 +1,4 @@
-id: smelt_adamantite
+id: adamantite
type: smelting
skill: smithing
level: 65
diff --git a/data/recipes/smelting/smelt_bronze.yaml b/data/recipes/smelting/bronze.yaml
index bdb33af..af28ca0 100644
--- a/data/recipes/smelting/smelt_bronze.yaml
+++ b/data/recipes/smelting/bronze.yaml
@@ -1,4 +1,4 @@
-id: smelt_bronze
+id: bronze
type: smelting
skill: smithing
level: 1
diff --git a/data/recipes/smelting/smelt_gold.yaml b/data/recipes/smelting/gold.yaml
index 91229e8..9ebb96b 100644
--- a/data/recipes/smelting/smelt_gold.yaml
+++ b/data/recipes/smelting/gold.yaml
@@ -1,4 +1,4 @@
-id: smelt_gold
+id: gold
type: smelting
skill: smithing
level: 45
diff --git a/data/recipes/smelting/smelt_iron.yaml b/data/recipes/smelting/iron.yaml
index ce0ca11..fa210d6 100644
--- a/data/recipes/smelting/smelt_iron.yaml
+++ b/data/recipes/smelting/iron.yaml
@@ -1,4 +1,4 @@
-id: smelt_iron
+id: iron
type: smelting
skill: smithing
level: 20
diff --git a/data/recipes/smelting/smelt_mithril.yaml b/data/recipes/smelting/mithril.yaml
index 6285c1b..8e11e61 100644
--- a/data/recipes/smelting/smelt_mithril.yaml
+++ b/data/recipes/smelting/mithril.yaml
@@ -1,4 +1,4 @@
-id: smelt_mithril
+id: mithril
type: smelting
skill: smithing
level: 50
diff --git a/data/recipes/smelting/smelt_runite.yaml b/data/recipes/smelting/runite.yaml
index b683221..36d44a4 100644
--- a/data/recipes/smelting/smelt_runite.yaml
+++ b/data/recipes/smelting/runite.yaml
@@ -1,4 +1,4 @@
-id: smelt_runite
+id: runite
type: smelting
skill: smithing
level: 80
diff --git a/data/recipes/smelting/smelt_silver.yaml b/data/recipes/smelting/silver.yaml
index f381bd0..05a76ef 100644
--- a/data/recipes/smelting/smelt_silver.yaml
+++ b/data/recipes/smelting/silver.yaml
@@ -1,4 +1,4 @@
-id: smelt_silver
+id: silver
type: smelting
skill: smithing
level: 25
diff --git a/data/recipes/smelting/smelt_steel.yaml b/data/recipes/smelting/steel.yaml
index ec1c2df..86ee366 100644
--- a/data/recipes/smelting/smelt_steel.yaml
+++ b/data/recipes/smelting/steel.yaml
@@ -1,4 +1,4 @@
-id: smelt_steel
+id: steel
type: smelting
skill: smithing
level: 40
diff --git a/data/recipes/smithing/adamant.yaml b/data/recipes/smithing/adamant.yaml
new file mode 100644
index 0000000..bd9debe
--- /dev/null
+++ b/data/recipes/smithing/adamant.yaml
@@ -0,0 +1,48 @@
+type: smithing
+station: [anvil]
+wait: 4
+consume:
+ - items: [adamantite_bar]
+ quantity: 1
+products:
+ - id: adamant_dagger
+ output: adamant_dagger
+ level: 70
+ xp: 62
+ - id: adamant_med_helm
+ output: adamant_med_helm
+ level: 73
+ xp: 62
+ - id: adamant_sword
+ output: adamant_sword
+ level: 74
+ xp: 62
+ - id: adamant_nails
+ output: adamant_nails
+ level: 74
+ xp: 62
+ output_qty: 15
+ - id: adamant_arrowtips
+ output: adamant_arrowtips
+ level: 75
+ xp: 62
+ output_qty: 15
+ - id: adamant_bolts_unf
+ output: adamant_bolts_unf
+ level: 76
+ xp: 62
+ output_qty: 10
+ - id: adamant_full_helm
+ output: adamant_full_helm
+ level: 77
+ xp: 125
+ consume:
+ - items: [adamantite_bar]
+ quantity: 2
+ - id: adamant_platebody
+ output: adamant_platebody
+ level: 88
+ xp: 312
+ consume:
+ - items: [adamantite_bar]
+ quantity: 5
diff --git a/data/recipes/smithing/bronze.yaml b/data/recipes/smithing/bronze.yaml
new file mode 100644
index 0000000..da1b55f
--- /dev/null
+++ b/data/recipes/smithing/bronze.yaml
@@ -0,0 +1,48 @@
+type: smithing
+station: [anvil]
+wait: 4
+consume:
+ - items: [bronze_bar]
+ quantity: 1
+products:
+ - id: bronze_dagger
+ output: bronze_dagger
+ level: 1
+ xp: 12
+ - id: bronze_med_helm
+ output: bronze_med_helm
+ level: 3
+ xp: 12
+ - id: bronze_sword
+ output: bronze_sword
+ level: 4
+ xp: 12
+ - id: bronze_nails
+ output: bronze_nails
+ level: 4
+ xp: 12
+ output_qty: 15
+ - id: bronze_arrowtips
+ output: bronze_arrowtips
+ level: 5
+ xp: 12
+ output_qty: 15
+ - id: bronze_bolts_unf
+ output: bronze_bolts_unf
+ level: 6
+ xp: 12
+ output_qty: 10
+ - id: bronze_full_helm
+ output: bronze_full_helm
+ level: 7
+ xp: 25
+ consume:
+ - items: [bronze_bar]
+ quantity: 2
+ - id: bronze_platebody
+ output: bronze_platebody
+ level: 18
+ xp: 62
+ consume:
+ - items: [bronze_bar]
+ quantity: 5
diff --git a/data/recipes/smithing/iron.yaml b/data/recipes/smithing/iron.yaml
new file mode 100644
index 0000000..e39facd
--- /dev/null
+++ b/data/recipes/smithing/iron.yaml
@@ -0,0 +1,48 @@
+type: smithing
+station: [anvil]
+wait: 4
+consume:
+ - items: [iron_bar]
+ quantity: 1
+products:
+ - id: iron_dagger
+ output: iron_dagger
+ level: 20
+ xp: 25
+ - id: iron_med_helm
+ output: iron_med_helm
+ level: 22
+ xp: 25
+ - id: iron_sword
+ output: iron_sword
+ level: 24
+ xp: 25
+ - id: iron_nails
+ output: iron_nails
+ level: 24
+ xp: 25
+ output_qty: 15
+ - id: iron_arrowtips
+ output: iron_arrowtips
+ level: 25
+ xp: 25
+ output_qty: 15
+ - id: iron_bolts_unf
+ output: iron_bolts_unf
+ level: 26
+ xp: 25
+ output_qty: 10
+ - id: iron_full_helm
+ output: iron_full_helm
+ level: 27
+ xp: 50
+ consume:
+ - items: [iron_bar]
+ quantity: 2
+ - id: iron_platebody
+ output: iron_platebody
+ level: 33
+ xp: 125
+ consume:
+ - items: [iron_bar]
+ quantity: 5
diff --git a/data/recipes/smithing/mithril.yaml b/data/recipes/smithing/mithril.yaml
new file mode 100644
index 0000000..dfa4a6d
--- /dev/null
+++ b/data/recipes/smithing/mithril.yaml
@@ -0,0 +1,48 @@
+type: smithing
+station: [anvil]
+wait: 4
+consume:
+ - items: [mithril_bar]
+ quantity: 1
+products:
+ - id: mithril_dagger
+ output: mithril_dagger
+ level: 50
+ xp: 50
+ - id: mithril_med_helm
+ output: mithril_med_helm
+ level: 53
+ xp: 50
+ - id: mithril_sword
+ output: mithril_sword
+ level: 54
+ xp: 50
+ - id: mithril_nails
+ output: mithril_nails
+ level: 54
+ xp: 50
+ output_qty: 15
+ - id: mithril_arrowtips
+ output: mithril_arrowtips
+ level: 55
+ xp: 50
+ output_qty: 15
+ - id: mithril_bolts_unf
+ output: mithril_bolts_unf
+ level: 56
+ xp: 50
+ output_qty: 10
+ - id: mithril_full_helm
+ output: mithril_full_helm
+ level: 57
+ xp: 100
+ consume:
+ - items: [mithril_bar]
+ quantity: 2
+ - id: mithril_platebody
+ output: mithril_platebody
+ level: 68
+ xp: 250
+ consume:
+ - items: [mithril_bar]
+ quantity: 5
diff --git a/data/recipes/smithing/rune.yaml b/data/recipes/smithing/rune.yaml
new file mode 100644
index 0000000..fdffafc
--- /dev/null
+++ b/data/recipes/smithing/rune.yaml
@@ -0,0 +1,48 @@
+type: smithing
+station: [anvil]
+wait: 4
+consume:
+ - items: [runite_bar]
+ quantity: 1
+products:
+ - id: rune_dagger
+ output: rune_dagger
+ level: 85
+ xp: 75
+ - id: rune_med_helm
+ output: rune_med_helm
+ level: 88
+ xp: 75
+ - id: rune_sword
+ output: rune_sword
+ level: 89
+ xp: 75
+ - id: rune_nails
+ output: rune_nails
+ level: 89
+ xp: 75
+ output_qty: 15
+ - id: rune_arrowtips
+ output: rune_arrowtips
+ level: 90
+ xp: 75
+ output_qty: 15
+ - id: rune_bolts_unf
+ output: rune_bolts_unf
+ level: 91
+ xp: 75
+ output_qty: 10
+ - id: rune_full_helm
+ output: rune_full_helm
+ level: 92
+ xp: 150
+ consume:
+ - items: [runite_bar]
+ quantity: 2
+ - id: rune_platebody
+ output: rune_platebody
+ level: 99
+ xp: 375
+ consume:
+ - items: [runite_bar]
+ quantity: 5
diff --git a/data/recipes/smithing/smith_adamant_arrowtips.yaml b/data/recipes/smithing/smith_adamant_arrowtips.yaml
deleted file mode 100644
index 466cfa2..0000000
--- a/data/recipes/smithing/smith_adamant_arrowtips.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: smith_adamant_arrowtips
-type: smithing
-level: 75
-xp: 62
-wait: 4
-station: [anvil]
-consume:
- - items: [adamantite_bar]
- quantity: 1
-output: adamant_arrowtips
-output_qty: 15
-message: "You hammer out some adamant arrowtips."
diff --git a/data/recipes/smithing/smith_adamant_bolts_unf.yaml b/data/recipes/smithing/smith_adamant_bolts_unf.yaml
deleted file mode 100644
index 2fe1730..0000000
--- a/data/recipes/smithing/smith_adamant_bolts_unf.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: smith_adamant_bolts_unf
-type: smithing
-level: 76
-xp: 62
-wait: 4
-station: [anvil]
-consume:
- - items: [adamantite_bar]
- quantity: 1
-output: adamant_bolts_unf
-output_qty: 10
-message: "You hammer out some adamant bolts."
diff --git a/data/recipes/smithing/smith_adamant_dagger.yaml b/data/recipes/smithing/smith_adamant_dagger.yaml
deleted file mode 100644
index fb68dbf..0000000
--- a/data/recipes/smithing/smith_adamant_dagger.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_adamant_dagger
-type: smithing
-level: 70
-xp: 62
-wait: 4
-station: [anvil]
-consume:
- - items: [adamantite_bar]
- quantity: 1
-output: adamant_dagger
-message: "You hammer out an adamant dagger."
diff --git a/data/recipes/smithing/smith_adamant_full_helm.yaml b/data/recipes/smithing/smith_adamant_full_helm.yaml
deleted file mode 100644
index 2d849e5..0000000
--- a/data/recipes/smithing/smith_adamant_full_helm.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_adamant_full_helm
-type: smithing
-level: 77
-xp: 125
-wait: 4
-station: [anvil]
-consume:
- - items: [adamantite_bar]
- quantity: 2
-output: adamant_full_helm
-message: "You hammer out an adamant full helm."
diff --git a/data/recipes/smithing/smith_adamant_med_helm.yaml b/data/recipes/smithing/smith_adamant_med_helm.yaml
deleted file mode 100644
index 505691c..0000000
--- a/data/recipes/smithing/smith_adamant_med_helm.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_adamant_med_helm
-type: smithing
-level: 73
-xp: 62
-wait: 4
-station: [anvil]
-consume:
- - items: [adamantite_bar]
- quantity: 1
-output: adamant_med_helm
-message: "You hammer out an adamant med helm."
diff --git a/data/recipes/smithing/smith_adamant_nails.yaml b/data/recipes/smithing/smith_adamant_nails.yaml
deleted file mode 100644
index 2273b67..0000000
--- a/data/recipes/smithing/smith_adamant_nails.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: smith_adamant_nails
-type: smithing
-level: 74
-xp: 62
-wait: 4
-station: [anvil]
-consume:
- - items: [adamantite_bar]
- quantity: 1
-output: adamant_nails
-output_qty: 15
-message: "You hammer out some adamant nails."
diff --git a/data/recipes/smithing/smith_adamant_platebody.yaml b/data/recipes/smithing/smith_adamant_platebody.yaml
deleted file mode 100644
index 33dc400..0000000
--- a/data/recipes/smithing/smith_adamant_platebody.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_adamant_platebody
-type: smithing
-level: 88
-xp: 312
-wait: 4
-station: [anvil]
-consume:
- - items: [adamantite_bar]
- quantity: 5
-output: adamant_platebody
-message: "You hammer out an adamant platebody."
diff --git a/data/recipes/smithing/smith_adamant_sword.yaml b/data/recipes/smithing/smith_adamant_sword.yaml
deleted file mode 100644
index e853ea6..0000000
--- a/data/recipes/smithing/smith_adamant_sword.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_adamant_sword
-type: smithing
-level: 74
-xp: 62
-wait: 4
-station: [anvil]
-consume:
- - items: [adamantite_bar]
- quantity: 1
-output: adamant_sword
-message: "You hammer out an adamant sword."
diff --git a/data/recipes/smithing/smith_bronze_arrowtips.yaml b/data/recipes/smithing/smith_bronze_arrowtips.yaml
deleted file mode 100644
index 77d724f..0000000
--- a/data/recipes/smithing/smith_bronze_arrowtips.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: smith_bronze_arrowtips
-type: smithing
-level: 5
-xp: 12
-wait: 4
-station: [anvil]
-consume:
- - items: [bronze_bar]
- quantity: 1
-output: bronze_arrowtips
-output_qty: 15
-message: "You hammer out some bronze arrowtips."
diff --git a/data/recipes/smithing/smith_bronze_bolts_unf.yaml b/data/recipes/smithing/smith_bronze_bolts_unf.yaml
deleted file mode 100644
index 340d14f..0000000
--- a/data/recipes/smithing/smith_bronze_bolts_unf.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: smith_bronze_bolts_unf
-type: smithing
-level: 6
-xp: 12
-wait: 4
-station: [anvil]
-consume:
- - items: [bronze_bar]
- quantity: 1
-output: bronze_bolts_unf
-output_qty: 10
-message: "You hammer out some bronze bolts."
diff --git a/data/recipes/smithing/smith_bronze_dagger.yaml b/data/recipes/smithing/smith_bronze_dagger.yaml
deleted file mode 100644
index 4842927..0000000
--- a/data/recipes/smithing/smith_bronze_dagger.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_bronze_dagger
-type: smithing
-level: 1
-xp: 12
-wait: 4
-station: [anvil]
-consume:
- - items: [bronze_bar]
- quantity: 1
-output: bronze_dagger
-message: "You hammer out a bronze dagger."
diff --git a/data/recipes/smithing/smith_bronze_full_helm.yaml b/data/recipes/smithing/smith_bronze_full_helm.yaml
deleted file mode 100644
index 6ef58ed..0000000
--- a/data/recipes/smithing/smith_bronze_full_helm.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_bronze_full_helm
-type: smithing
-level: 7
-xp: 25
-wait: 4
-station: [anvil]
-consume:
- - items: [bronze_bar]
- quantity: 2
-output: bronze_full_helm
-message: "You hammer out a bronze full helm."
diff --git a/data/recipes/smithing/smith_bronze_med_helm.yaml b/data/recipes/smithing/smith_bronze_med_helm.yaml
deleted file mode 100644
index 6abbc60..0000000
--- a/data/recipes/smithing/smith_bronze_med_helm.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_bronze_med_helm
-type: smithing
-level: 3
-xp: 12
-wait: 4
-station: [anvil]
-consume:
- - items: [bronze_bar]
- quantity: 1
-output: bronze_med_helm
-message: "You hammer out a bronze med helm."
diff --git a/data/recipes/smithing/smith_bronze_nails.yaml b/data/recipes/smithing/smith_bronze_nails.yaml
deleted file mode 100644
index c75a957..0000000
--- a/data/recipes/smithing/smith_bronze_nails.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: smith_bronze_nails
-type: smithing
-level: 4
-xp: 12
-wait: 4
-station: [anvil]
-consume:
- - items: [bronze_bar]
- quantity: 1
-output: bronze_nails
-output_qty: 15
-message: "You hammer out some bronze nails."
diff --git a/data/recipes/smithing/smith_bronze_platebody.yaml b/data/recipes/smithing/smith_bronze_platebody.yaml
deleted file mode 100644
index c9d7d39..0000000
--- a/data/recipes/smithing/smith_bronze_platebody.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_bronze_platebody
-type: smithing
-level: 18
-xp: 62
-wait: 4
-station: [anvil]
-consume:
- - items: [bronze_bar]
- quantity: 5
-output: bronze_platebody
-message: "You hammer out a bronze platebody."
diff --git a/data/recipes/smithing/smith_bronze_sword.yaml b/data/recipes/smithing/smith_bronze_sword.yaml
deleted file mode 100644
index 322ca9f..0000000
--- a/data/recipes/smithing/smith_bronze_sword.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_bronze_sword
-type: smithing
-level: 4
-xp: 12
-wait: 4
-station: [anvil]
-consume:
- - items: [bronze_bar]
- quantity: 1
-output: bronze_sword
-message: "You hammer out a bronze sword."
diff --git a/data/recipes/smithing/smith_iron_arrowtips.yaml b/data/recipes/smithing/smith_iron_arrowtips.yaml
deleted file mode 100644
index d11286e..0000000
--- a/data/recipes/smithing/smith_iron_arrowtips.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: smith_iron_arrowtips
-type: smithing
-level: 25
-xp: 25
-wait: 4
-station: [anvil]
-consume:
- - items: [iron_bar]
- quantity: 1
-output: iron_arrowtips
-output_qty: 15
-message: "You hammer out some iron arrowtips."
diff --git a/data/recipes/smithing/smith_iron_bolts_unf.yaml b/data/recipes/smithing/smith_iron_bolts_unf.yaml
deleted file mode 100644
index 9529cab..0000000
--- a/data/recipes/smithing/smith_iron_bolts_unf.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: smith_iron_bolts_unf
-type: smithing
-level: 26
-xp: 25
-wait: 4
-station: [anvil]
-consume:
- - items: [iron_bar]
- quantity: 1
-output: iron_bolts_unf
-output_qty: 10
-message: "You hammer out some iron bolts."
diff --git a/data/recipes/smithing/smith_iron_dagger.yaml b/data/recipes/smithing/smith_iron_dagger.yaml
deleted file mode 100644
index 3694ca5..0000000
--- a/data/recipes/smithing/smith_iron_dagger.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_iron_dagger
-type: smithing
-level: 20
-xp: 25
-wait: 4
-station: [anvil]
-consume:
- - items: [iron_bar]
- quantity: 1
-output: iron_dagger
-message: "You hammer out an iron dagger."
diff --git a/data/recipes/smithing/smith_iron_full_helm.yaml b/data/recipes/smithing/smith_iron_full_helm.yaml
deleted file mode 100644
index cfa27f5..0000000
--- a/data/recipes/smithing/smith_iron_full_helm.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_iron_full_helm
-type: smithing
-level: 27
-xp: 50
-wait: 4
-station: [anvil]
-consume:
- - items: [iron_bar]
- quantity: 2
-output: iron_full_helm
-message: "You hammer out an iron full helm."
diff --git a/data/recipes/smithing/smith_iron_med_helm.yaml b/data/recipes/smithing/smith_iron_med_helm.yaml
deleted file mode 100644
index 54b6654..0000000
--- a/data/recipes/smithing/smith_iron_med_helm.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_iron_med_helm
-type: smithing
-level: 22
-xp: 25
-wait: 4
-station: [anvil]
-consume:
- - items: [iron_bar]
- quantity: 1
-output: iron_med_helm
-message: "You hammer out an iron med helm."
diff --git a/data/recipes/smithing/smith_iron_nails.yaml b/data/recipes/smithing/smith_iron_nails.yaml
deleted file mode 100644
index cb6ad89..0000000
--- a/data/recipes/smithing/smith_iron_nails.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: smith_iron_nails
-type: smithing
-level: 24
-xp: 25
-wait: 4
-station: [anvil]
-consume:
- - items: [iron_bar]
- quantity: 1
-output: iron_nails
-output_qty: 15
-message: "You hammer out some iron nails."
diff --git a/data/recipes/smithing/smith_iron_platebody.yaml b/data/recipes/smithing/smith_iron_platebody.yaml
deleted file mode 100644
index dd8a7d6..0000000
--- a/data/recipes/smithing/smith_iron_platebody.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_iron_platebody
-type: smithing
-level: 33
-xp: 125
-wait: 4
-station: [anvil]
-consume:
- - items: [iron_bar]
- quantity: 5
-output: iron_platebody
-message: "You hammer out an iron platebody."
diff --git a/data/recipes/smithing/smith_iron_sword.yaml b/data/recipes/smithing/smith_iron_sword.yaml
deleted file mode 100644
index c2d4bc5..0000000
--- a/data/recipes/smithing/smith_iron_sword.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_iron_sword
-type: smithing
-level: 24
-xp: 25
-wait: 4
-station: [anvil]
-consume:
- - items: [iron_bar]
- quantity: 1
-output: iron_sword
-message: "You hammer out an iron sword."
diff --git a/data/recipes/smithing/smith_mithril_arrowtips.yaml b/data/recipes/smithing/smith_mithril_arrowtips.yaml
deleted file mode 100644
index 2d27604..0000000
--- a/data/recipes/smithing/smith_mithril_arrowtips.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: smith_mithril_arrowtips
-type: smithing
-level: 55
-xp: 50
-wait: 4
-station: [anvil]
-consume:
- - items: [mithril_bar]
- quantity: 1
-output: mithril_arrowtips
-output_qty: 15
-message: "You hammer out some mithril arrowtips."
diff --git a/data/recipes/smithing/smith_mithril_bolts_unf.yaml b/data/recipes/smithing/smith_mithril_bolts_unf.yaml
deleted file mode 100644
index 03afe7f..0000000
--- a/data/recipes/smithing/smith_mithril_bolts_unf.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: smith_mithril_bolts_unf
-type: smithing
-level: 56
-xp: 50
-wait: 4
-station: [anvil]
-consume:
- - items: [mithril_bar]
- quantity: 1
-output: mithril_bolts_unf
-output_qty: 10
-message: "You hammer out some mithril bolts."
diff --git a/data/recipes/smithing/smith_mithril_dagger.yaml b/data/recipes/smithing/smith_mithril_dagger.yaml
deleted file mode 100644
index a437969..0000000
--- a/data/recipes/smithing/smith_mithril_dagger.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_mithril_dagger
-type: smithing
-level: 50
-xp: 50
-wait: 4
-station: [anvil]
-consume:
- - items: [mithril_bar]
- quantity: 1
-output: mithril_dagger
-message: "You hammer out a mithril dagger."
diff --git a/data/recipes/smithing/smith_mithril_full_helm.yaml b/data/recipes/smithing/smith_mithril_full_helm.yaml
deleted file mode 100644
index f9d6b74..0000000
--- a/data/recipes/smithing/smith_mithril_full_helm.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_mithril_full_helm
-type: smithing
-level: 57
-xp: 100
-wait: 4
-station: [anvil]
-consume:
- - items: [mithril_bar]
- quantity: 2
-output: mithril_full_helm
-message: "You hammer out a mithril full helm."
diff --git a/data/recipes/smithing/smith_mithril_med_helm.yaml b/data/recipes/smithing/smith_mithril_med_helm.yaml
deleted file mode 100644
index c4ebc30..0000000
--- a/data/recipes/smithing/smith_mithril_med_helm.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_mithril_med_helm
-type: smithing
-level: 53
-xp: 50
-wait: 4
-station: [anvil]
-consume:
- - items: [mithril_bar]
- quantity: 1
-output: mithril_med_helm
-message: "You hammer out a mithril med helm."
diff --git a/data/recipes/smithing/smith_mithril_nails.yaml b/data/recipes/smithing/smith_mithril_nails.yaml
deleted file mode 100644
index 6c9f225..0000000
--- a/data/recipes/smithing/smith_mithril_nails.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: smith_mithril_nails
-type: smithing
-level: 54
-xp: 50
-wait: 4
-station: [anvil]
-consume:
- - items: [mithril_bar]
- quantity: 1
-output: mithril_nails
-output_qty: 15
-message: "You hammer out some mithril nails."
diff --git a/data/recipes/smithing/smith_mithril_platebody.yaml b/data/recipes/smithing/smith_mithril_platebody.yaml
deleted file mode 100644
index 751a7ea..0000000
--- a/data/recipes/smithing/smith_mithril_platebody.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_mithril_platebody
-type: smithing
-level: 68
-xp: 250
-wait: 4
-station: [anvil]
-consume:
- - items: [mithril_bar]
- quantity: 5
-output: mithril_platebody
-message: "You hammer out a mithril platebody."
diff --git a/data/recipes/smithing/smith_mithril_sword.yaml b/data/recipes/smithing/smith_mithril_sword.yaml
deleted file mode 100644
index fdf96ff..0000000
--- a/data/recipes/smithing/smith_mithril_sword.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_mithril_sword
-type: smithing
-level: 54
-xp: 50
-wait: 4
-station: [anvil]
-consume:
- - items: [mithril_bar]
- quantity: 1
-output: mithril_sword
-message: "You hammer out a mithril sword."
diff --git a/data/recipes/smithing/smith_rune_arrowtips.yaml b/data/recipes/smithing/smith_rune_arrowtips.yaml
deleted file mode 100644
index 93554a9..0000000
--- a/data/recipes/smithing/smith_rune_arrowtips.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: smith_rune_arrowtips
-type: smithing
-level: 90
-xp: 75
-wait: 4
-station: [anvil]
-consume:
- - items: [runite_bar]
- quantity: 1
-output: rune_arrowtips
-output_qty: 15
-message: "You hammer out some rune arrowtips."
diff --git a/data/recipes/smithing/smith_rune_bolts_unf.yaml b/data/recipes/smithing/smith_rune_bolts_unf.yaml
deleted file mode 100644
index 93047ce..0000000
--- a/data/recipes/smithing/smith_rune_bolts_unf.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: smith_rune_bolts_unf
-type: smithing
-level: 91
-xp: 75
-wait: 4
-station: [anvil]
-consume:
- - items: [runite_bar]
- quantity: 1
-output: rune_bolts_unf
-output_qty: 10
-message: "You hammer out some rune bolts."
diff --git a/data/recipes/smithing/smith_rune_dagger.yaml b/data/recipes/smithing/smith_rune_dagger.yaml
deleted file mode 100644
index a87b356..0000000
--- a/data/recipes/smithing/smith_rune_dagger.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_rune_dagger
-type: smithing
-level: 85
-xp: 75
-wait: 4
-station: [anvil]
-consume:
- - items: [runite_bar]
- quantity: 1
-output: rune_dagger
-message: "You hammer out a rune dagger."
diff --git a/data/recipes/smithing/smith_rune_full_helm.yaml b/data/recipes/smithing/smith_rune_full_helm.yaml
deleted file mode 100644
index 79ae3b0..0000000
--- a/data/recipes/smithing/smith_rune_full_helm.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_rune_full_helm
-type: smithing
-level: 92
-xp: 150
-wait: 4
-station: [anvil]
-consume:
- - items: [runite_bar]
- quantity: 2
-output: rune_full_helm
-message: "You hammer out a rune full helm."
diff --git a/data/recipes/smithing/smith_rune_med_helm.yaml b/data/recipes/smithing/smith_rune_med_helm.yaml
deleted file mode 100644
index 74e639a..0000000
--- a/data/recipes/smithing/smith_rune_med_helm.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_rune_med_helm
-type: smithing
-level: 88
-xp: 75
-wait: 4
-station: [anvil]
-consume:
- - items: [runite_bar]
- quantity: 1
-output: rune_med_helm
-message: "You hammer out a rune med helm."
diff --git a/data/recipes/smithing/smith_rune_nails.yaml b/data/recipes/smithing/smith_rune_nails.yaml
deleted file mode 100644
index 2681a57..0000000
--- a/data/recipes/smithing/smith_rune_nails.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: smith_rune_nails
-type: smithing
-level: 89
-xp: 75
-wait: 4
-station: [anvil]
-consume:
- - items: [runite_bar]
- quantity: 1
-output: rune_nails
-output_qty: 15
-message: "You hammer out some rune nails."
diff --git a/data/recipes/smithing/smith_rune_platebody.yaml b/data/recipes/smithing/smith_rune_platebody.yaml
deleted file mode 100644
index 4a36082..0000000
--- a/data/recipes/smithing/smith_rune_platebody.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_rune_platebody
-type: smithing
-level: 99
-xp: 375
-wait: 4
-station: [anvil]
-consume:
- - items: [runite_bar]
- quantity: 5
-output: rune_platebody
-message: "You hammer out a rune platebody."
diff --git a/data/recipes/smithing/smith_rune_sword.yaml b/data/recipes/smithing/smith_rune_sword.yaml
deleted file mode 100644
index 20f94f6..0000000
--- a/data/recipes/smithing/smith_rune_sword.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_rune_sword
-type: smithing
-level: 89
-xp: 75
-wait: 4
-station: [anvil]
-consume:
- - items: [runite_bar]
- quantity: 1
-output: rune_sword
-message: "You hammer out a rune sword."
diff --git a/data/recipes/smithing/smith_steel_arrowtips.yaml b/data/recipes/smithing/smith_steel_arrowtips.yaml
deleted file mode 100644
index 1ec292f..0000000
--- a/data/recipes/smithing/smith_steel_arrowtips.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: smith_steel_arrowtips
-type: smithing
-level: 35
-xp: 37
-wait: 4
-station: [anvil]
-consume:
- - items: [steel_bar]
- quantity: 1
-output: steel_arrowtips
-output_qty: 15
-message: "You hammer out some steel arrowtips."
diff --git a/data/recipes/smithing/smith_steel_bolts_unf.yaml b/data/recipes/smithing/smith_steel_bolts_unf.yaml
deleted file mode 100644
index cc071ef..0000000
--- a/data/recipes/smithing/smith_steel_bolts_unf.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: smith_steel_bolts_unf
-type: smithing
-level: 36
-xp: 37
-wait: 4
-station: [anvil]
-consume:
- - items: [steel_bar]
- quantity: 1
-output: steel_bolts_unf
-output_qty: 10
-message: "You hammer out some steel bolts."
diff --git a/data/recipes/smithing/smith_steel_dagger.yaml b/data/recipes/smithing/smith_steel_dagger.yaml
deleted file mode 100644
index 6e2f1c4..0000000
--- a/data/recipes/smithing/smith_steel_dagger.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_steel_dagger
-type: smithing
-level: 30
-xp: 37
-wait: 4
-station: [anvil]
-consume:
- - items: [steel_bar]
- quantity: 1
-output: steel_dagger
-message: "You hammer out a steel dagger."
diff --git a/data/recipes/smithing/smith_steel_full_helm.yaml b/data/recipes/smithing/smith_steel_full_helm.yaml
deleted file mode 100644
index c95d0d0..0000000
--- a/data/recipes/smithing/smith_steel_full_helm.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_steel_full_helm
-type: smithing
-level: 37
-xp: 75
-wait: 4
-station: [anvil]
-consume:
- - items: [steel_bar]
- quantity: 2
-output: steel_full_helm
-message: "You hammer out a steel full helm."
diff --git a/data/recipes/smithing/smith_steel_med_helm.yaml b/data/recipes/smithing/smith_steel_med_helm.yaml
deleted file mode 100644
index 3415ec8..0000000
--- a/data/recipes/smithing/smith_steel_med_helm.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_steel_med_helm
-type: smithing
-level: 33
-xp: 37
-wait: 4
-station: [anvil]
-consume:
- - items: [steel_bar]
- quantity: 1
-output: steel_med_helm
-message: "You hammer out a steel med helm."
diff --git a/data/recipes/smithing/smith_steel_nails.yaml b/data/recipes/smithing/smith_steel_nails.yaml
deleted file mode 100644
index 2da69c8..0000000
--- a/data/recipes/smithing/smith_steel_nails.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: smith_steel_nails
-type: smithing
-level: 34
-xp: 37
-wait: 4
-station: [anvil]
-consume:
- - items: [steel_bar]
- quantity: 1
-output: steel_nails
-output_qty: 15
-message: "You hammer out some steel nails."
diff --git a/data/recipes/smithing/smith_steel_platebody.yaml b/data/recipes/smithing/smith_steel_platebody.yaml
deleted file mode 100644
index da11d11..0000000
--- a/data/recipes/smithing/smith_steel_platebody.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_steel_platebody
-type: smithing
-level: 48
-xp: 187
-wait: 4
-station: [anvil]
-consume:
- - items: [steel_bar]
- quantity: 5
-output: steel_platebody
-message: "You hammer out a steel platebody."
diff --git a/data/recipes/smithing/smith_steel_sword.yaml b/data/recipes/smithing/smith_steel_sword.yaml
deleted file mode 100644
index 63270bc..0000000
--- a/data/recipes/smithing/smith_steel_sword.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: smith_steel_sword
-type: smithing
-level: 34
-xp: 37
-wait: 4
-station: [anvil]
-consume:
- - items: [steel_bar]
- quantity: 1
-output: steel_sword
-message: "You hammer out a steel sword."
diff --git a/data/recipes/smithing/steel.yaml b/data/recipes/smithing/steel.yaml
new file mode 100644
index 0000000..204b427
--- /dev/null
+++ b/data/recipes/smithing/steel.yaml
@@ -0,0 +1,48 @@
+type: smithing
+station: [anvil]
+wait: 4
+consume:
+ - items: [steel_bar]
+ quantity: 1
+products:
+ - id: steel_dagger
+ output: steel_dagger
+ level: 30
+ xp: 37
+ - id: steel_med_helm
+ output: steel_med_helm
+ level: 33
+ xp: 37
+ - id: steel_sword
+ output: steel_sword
+ level: 34
+ xp: 37
+ - id: steel_nails
+ output: steel_nails
+ level: 34
+ xp: 37
+ output_qty: 15
+ - id: steel_arrowtips
+ output: steel_arrowtips
+ level: 35
+ xp: 37
+ output_qty: 15
+ - id: steel_bolts_unf
+ output: steel_bolts_unf
+ level: 36
+ xp: 37
+ output_qty: 10
+ - id: steel_full_helm
+ output: steel_full_helm
+ level: 37
+ xp: 75
+ consume:
+ - items: [steel_bar]
+ quantity: 2
+ - id: steel_platebody
+ output: steel_platebody
+ level: 48
+ xp: 187
+ consume:
+ - items: [steel_bar]
+ quantity: 5
diff --git a/data/rooms/town/1.yaml b/data/rooms/town/1.yaml
index ec97af2..ba5212d 100644
--- a/data/rooms/town/1.yaml
+++ b/data/rooms/town/1.yaml
@@ -36,3 +36,6 @@ item_spawns:
- id: graceful_cape
quantity: 1
respawn_ticks: 60
+ - id: deck
+ quantity: 1
+ respawn_ticks: 60
diff --git a/data/rooms/town/9.yaml b/data/rooms/town/9.yaml
index 9a19943..7a24fda 100644
--- a/data/rooms/town/9.yaml
+++ b/data/rooms/town/9.yaml
@@ -6,7 +6,6 @@ exits:
west: 8
north: 150
south: 151
- northeast: 152
objects:
- id: scrap_pile
- id: scrap_pile
diff --git a/internal/action/recipe.go b/internal/action/recipe.go
index 5a69195..8779d53 100644
--- a/internal/action/recipe.go
+++ b/internal/action/recipe.go
@@ -1,6 +1,8 @@
package action
import (
+ "fmt"
+ "os"
"path/filepath"
"gopkg.in/yaml.v3"
@@ -30,13 +32,68 @@ type RecipeDef struct {
Success *SuccessFormula `yaml:"success"`
}
+type consolidatedRecipeDef struct {
+ Type string `yaml:"type"`
+ Skill string `yaml:"skill"`
+ Station []string `yaml:"station"`
+ Tool string `yaml:"tool"`
+ Wait float64 `yaml:"wait"`
+ Consume []ConsumeEntry `yaml:"consume"`
+ Products []recipeProduct `yaml:"products"`
+}
+
+type recipeProduct struct {
+ ID string `yaml:"id"`
+ Output string `yaml:"output"`
+ OutputQty int `yaml:"output_qty"`
+ Level int `yaml:"level"`
+ XP int `yaml:"xp"`
+ Consume []ConsumeEntry `yaml:"consume"`
+ Message string `yaml:"message"`
+ FailMessage string `yaml:"fail_message"`
+ Fail string `yaml:"fail"`
+ Success *SuccessFormula `yaml:"success"`
+}
+
type RecipeStore struct {
dataDir string
cache map[string][]RecipeDef
+ byID map[string]*RecipeDef
}
func NewRecipeStore(dataDir string) *RecipeStore {
- return &RecipeStore{dataDir: dataDir, cache: make(map[string][]RecipeDef)}
+ return &RecipeStore{
+ dataDir: dataDir,
+ cache: make(map[string][]RecipeDef),
+ byID: make(map[string]*RecipeDef),
+ }
+}
+
+func (s *RecipeStore) GetByID(recipeID string) *RecipeDef {
+ if r, ok := s.byID[recipeID]; ok {
+ return r
+ }
+ s.ensureLoaded()
+ if r, ok := s.byID[recipeID]; ok {
+ return r
+ }
+ return nil
+}
+
+func (s *RecipeStore) ensureLoaded() {
+ if _, ok := s.cache["_all"]; ok {
+ return
+ }
+ all, err := s.loadAllRaw()
+ if err != nil {
+ return
+ }
+ s.cache["_all"] = all
+ for i := range all {
+ if all[i].ID != "" {
+ s.byID[all[i].ID] = &all[i]
+ }
+ }
}
func (s *RecipeStore) LoadAll() ([]RecipeDef, error) {
@@ -53,13 +110,10 @@ func (s *RecipeStore) LoadByType(recipeType string) ([]RecipeDef, error) {
return result, nil
}
- all, err := s.loadAllRaw()
- if err != nil {
- return nil, err
- }
+ s.ensureLoaded()
+ all := s.cache["_all"]
if recipeType == "_all" {
- s.cache[recipeType] = all
result := make([]RecipeDef, len(all))
copy(result, all)
return result, nil
@@ -82,15 +136,52 @@ func (s *RecipeStore) loadAllRaw() ([]RecipeDef, error) {
var recipes []RecipeDef
WalkYAMLDir(dir, func(path, id string, data []byte) error {
var r RecipeDef
- if err := yaml.Unmarshal(data, &r); err != nil {
+ if err := yaml.Unmarshal(data, &r); err == nil && r.Type != "" {
+ recipes = append(recipes, r)
return nil
}
- recipes = append(recipes, r)
+
+ var cr consolidatedRecipeDef
+ if err := yaml.Unmarshal(data, &cr); err != nil {
+ fmt.Fprintf(os.Stderr, "recipe parse error %s: %v\n", path, err)
+ return nil
+ }
+ if cr.Type == "" || len(cr.Products) == 0 {
+ return nil
+ }
+
+ for _, p := range cr.Products {
+ recipes = append(recipes, s.expandProduct(cr, p))
+ }
return nil
})
return recipes, nil
}
+func (s *RecipeStore) expandProduct(cr consolidatedRecipeDef, p recipeProduct) RecipeDef {
+ r := RecipeDef{
+ ID: p.ID,
+ Output: p.Output,
+ OutputQty: p.OutputQty,
+ Type: cr.Type,
+ Skill: cr.Skill,
+ Level: p.Level,
+ XP: p.XP,
+ Station: cr.Station,
+ Tool: cr.Tool,
+ Wait: cr.Wait,
+ Consume: p.Consume,
+ Message: p.Message,
+ FailMessage: p.FailMessage,
+ Fail: p.Fail,
+ Success: p.Success,
+ }
+ if len(r.Consume) == 0 {
+ r.Consume = cr.Consume
+ }
+ return r
+}
+
func (r *RecipeDef) MatchesEntry(itemID string) bool {
for _, e := range r.Consume {
for _, id := range e.Items {
diff --git a/internal/color/color.go b/internal/color/color.go
index 9496a1d..58c040c 100644
--- a/internal/color/color.go
+++ b/internal/color/color.go
@@ -87,7 +87,7 @@ func nearestANSIBg(index int) int {
return fg - 30 + 40
}
-func nearestCubeComponent(v int) int {
+func nearestCube(v int) int {
if v < 48 {
return 0
}
@@ -107,9 +107,9 @@ func nearestCubeComponent(v int) int {
}
func NearestXterm256(r, g, b int) int {
- ri := nearestCubeComponent(r)
- gi := nearestCubeComponent(g)
- bi := nearestCubeComponent(b)
+ ri := nearestCube(r)
+ gi := nearestCube(g)
+ bi := nearestCube(b)
cubeIdx := 16 + ri*36 + gi*6 + bi
cubeDist := colorDist(r, g, b, cubeValues[ri], cubeValues[gi], cubeValues[bi])
@@ -325,10 +325,10 @@ func renderGradient(mode string, spec ColorSpec, text string) string {
}
func ExpandTags(mode string, text string) string {
- return ExpandTagsWithDefault(mode, NoColor(), text)
+ return ExpandTagsDefault(mode, NoColor(), text)
}
-func ExpandTagsWithDefault(mode string, def ColorSpec, text string) string {
+func ExpandTagsDefault(mode string, def ColorSpec, text string) string {
if !strings.Contains(text, "{") {
if !def.Empty() {
return Render(mode, def, text)
diff --git a/internal/config/config.go b/internal/config/config.go
index 124d45d..cbf15c5 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -61,10 +61,10 @@ func DefaultColors() ColorsConfig {
type GameConfig struct {
TickLength int `yaml:"tick_length"`
- StartupValidation StartupValidationConfig `yaml:"startup_validation"`
+ StartupValidation ValidationConfig `yaml:"startup_validation"`
}
-type StartupValidationConfig struct {
+type ValidationConfig struct {
CheckSources []int `yaml:"check_sources"`
IgnoreUnreachable []int `yaml:"ignore_unreachable"`
}
@@ -97,7 +97,7 @@ func Default() *Config {
return &Config{
Game: GameConfig{
TickLength: 600,
- StartupValidation: StartupValidationConfig{
+ StartupValidation: ValidationConfig{
CheckSources: []int{1},
IgnoreUnreachable: []int{},
},
diff --git a/internal/game/action.go b/internal/game/action.go
index f25cabc..2f5dbaf 100644
--- a/internal/game/action.go
+++ b/internal/game/action.go
@@ -57,7 +57,7 @@ func (g *Game) startAction(sess *net.Session, verb, target string) {
if p.Action != nil {
g.cancelAction(p)
}
- g.cancelBackgroundAction(p)
+ g.cancelBgAction(p)
lower := strings.ToLower(target)
instanceIdx := -1
@@ -206,7 +206,7 @@ func (g *Game) cancelAction(p *player.Player) {
p.ClearMoveState()
}
-func (g *Game) cancelBackgroundAction(p *player.Player) {
+func (g *Game) cancelBgAction(p *player.Player) {
p.BackgroundAction = nil
p.BackgroundActionState = nil
}
diff --git a/internal/game/action_agility.go b/internal/game/action_agility.go
index 37fbab9..43c9cfd 100644
--- a/internal/game/action_agility.go
+++ b/internal/game/action_agility.go
@@ -52,13 +52,7 @@ func (g *Game) startObstacle(sess *net.Session, p *player.Player, info *Obstacle
TargetName: info.CourseName,
}
- if g.Hub != nil {
- for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
- if other != sess && other.Player != nil {
- other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s starts %s on the %s.", p.Name, gerund, info.CourseName)))
- }
- }
- }
+ g.broadcastAction(sess, "\n%s starts %s on the %s.", p.Name, gerund, info.CourseName)
}
func (g *Game) advanceObstacle(sess *net.Session, p *player.Player) {
diff --git a/internal/game/action_burn.go b/internal/game/action_burn.go
index c31d7d5..71b5a35 100644
--- a/internal/game/action_burn.go
+++ b/internal/game/action_burn.go
@@ -28,7 +28,7 @@ func (g *Game) doBurn(sess *net.Session, p *player.Player, input string) {
if p.Action != nil {
g.cancelAction(p)
}
- g.cancelBackgroundAction(p)
+ g.cancelBgAction(p)
itemID, fromGround, ambiguous := g.resolveBurnTarget(p, input)
if ambiguous {
@@ -47,7 +47,7 @@ func (g *Game) doStoke(sess *net.Session, p *player.Player, input string) {
if p.Action != nil {
g.cancelAction(p)
}
- g.cancelBackgroundAction(p)
+ g.cancelBgAction(p)
if !g.hasFireObject(p.RoomID) {
sess.WriteLine("There's no fire here to stoke.")
@@ -120,13 +120,7 @@ func (g *Game) startBurn(sess *net.Session, p *player.Player, itemID string, fro
p.ActionState = &ActionState{Type: ActionBurning}
- if g.Hub != nil {
- for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
- if other != sess && other.Player != nil {
- other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s starts building a fire.", p.Name)))
- }
- }
- }
+ g.broadcastAction(sess, "\n%s starts building a fire.", p.Name)
p.Action = &action.Action{
Type: "burn",
@@ -166,13 +160,7 @@ func (g *Game) startStoke(sess *net.Session, p *player.Player, itemID string) {
p.ActionState = &ActionState{Type: ActionStoking}
- if g.Hub != nil {
- for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
- if other != sess && other.Player != nil {
- other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s tends to the fire.", p.Name)))
- }
- }
- }
+ g.broadcastAction(sess, "\n%s tends to the fire.", p.Name)
p.Action = &action.Action{
Type: "stoke",
@@ -260,11 +248,7 @@ func (g *Game) advanceBurn(sess *net.Session, p *player.Player) {
}
sess.WriteLine(msg)
- for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
- if other != sess {
- other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s started a fire!", p.Name)))
- }
- }
+ g.broadcastAction(sess, "\n%s started a fire!", p.Name)
return
}
@@ -458,12 +442,12 @@ func (g *Game) cancelStokers(roomID int) {
func (g *Game) resolveBurnTarget(p *player.Player, input string) (itemID string, fromGround bool, ambiguous bool) {
if input != "" {
- return g.resolveNamedBurnTarget(p, input)
+ return g.namedBurnTarget(p, input)
}
- return g.resolveDefaultBurnTarget(p)
+ return g.defaultBurnTarget(p)
}
-func (g *Game) collectBurnableGround(roomID int, name string) []string {
+func (g *Game) burnableGround(roomID int, name string) []string {
var out []string
for itemID := range g.World.GroundItems(roomID) {
def, err := g.ItemStore.Load(itemID)
@@ -477,7 +461,7 @@ func (g *Game) collectBurnableGround(roomID int, name string) []string {
return out
}
-func (g *Game) collectBurnableInv(p *player.Player, name string) []string {
+func (g *Game) burnableInv(p *player.Player, name string) []string {
var out []string
seen := make(map[string]bool)
for _, slot := range p.Inventory {
@@ -496,8 +480,8 @@ func (g *Game) collectBurnableInv(p *player.Player, name string) []string {
return out
}
-func (g *Game) resolveNamedBurnTarget(p *player.Player, input string) (itemID string, fromGround bool, ambiguous bool) {
- groundMatches := g.collectBurnableGround(p.RoomID, input)
+func (g *Game) namedBurnTarget(p *player.Player, input string) (itemID string, fromGround bool, ambiguous bool) {
+ groundMatches := g.burnableGround(p.RoomID, input)
if len(groundMatches) > 1 {
return "", false, true
}
@@ -505,7 +489,7 @@ func (g *Game) resolveNamedBurnTarget(p *player.Player, input string) (itemID st
return groundMatches[0], true, false
}
- invMatches := g.collectBurnableInv(p, input)
+ invMatches := g.burnableInv(p, input)
if len(invMatches) > 1 {
return "", false, true
}
@@ -516,8 +500,8 @@ func (g *Game) resolveNamedBurnTarget(p *player.Player, input string) (itemID st
return "", false, false
}
-func (g *Game) resolveDefaultBurnTarget(p *player.Player) (itemID string, fromGround bool, ambiguous bool) {
- groundBurnable := g.collectBurnableGround(p.RoomID, "")
+func (g *Game) defaultBurnTarget(p *player.Player) (itemID string, fromGround bool, ambiguous bool) {
+ groundBurnable := g.burnableGround(p.RoomID, "")
if len(groundBurnable) == 1 {
return groundBurnable[0], true, false
}
@@ -525,7 +509,7 @@ func (g *Game) resolveDefaultBurnTarget(p *player.Player) (itemID string, fromGr
return "", false, true
}
- invBurnable := g.collectBurnableInv(p, "")
+ invBurnable := g.burnableInv(p, "")
if len(invBurnable) == 1 {
return invBurnable[0], false, false
}
@@ -537,7 +521,7 @@ func (g *Game) resolveDefaultBurnTarget(p *player.Player) (itemID string, fromGr
}
func (g *Game) resolveStokeTarget(p *player.Player, input string) (string, bool) {
- invBurnable := g.collectBurnableInv(p, input)
+ invBurnable := g.burnableInv(p, input)
if len(invBurnable) == 1 {
return invBurnable[0], false
}
diff --git a/internal/game/action_clean.go b/internal/game/action_clean.go
index 104dcb0..7330883 100644
--- a/internal/game/action_clean.go
+++ b/internal/game/action_clean.go
@@ -21,7 +21,7 @@ func (g *Game) advanceClean(sess *net.Session, p *player.Player) {
allRecipes, err := g.RecipeStore.LoadAll()
if err != nil {
- g.cancelBackgroundAction(p)
+ g.cancelBgAction(p)
return
}
@@ -51,7 +51,7 @@ func (g *Game) advanceClean(sess *net.Session, p *player.Player) {
if recipe == nil {
sess.WriteLine("\nYou've finished cleaning herbs.")
- g.cancelBackgroundAction(p)
+ g.cancelBgAction(p)
return
}
@@ -105,7 +105,7 @@ func (g *Game) advanceClean(sess *net.Session, p *player.Player) {
if !hasMore {
sess.WriteLine("\nYou've finished cleaning herbs.")
- g.cancelBackgroundAction(p)
+ g.cancelBgAction(p)
return
}
diff --git a/internal/game/action_default_target.go b/internal/game/action_default_target.go
index 2ca0bd3..a184f32 100644
--- a/internal/game/action_default_target.go
+++ b/internal/game/action_default_target.go
@@ -8,7 +8,7 @@ func verbToSkill(verb string) string {
return verbSkill[verb]
}
-func (g *Game) resolveDefaultTarget(roomID int, verb string) string {
+func (g *Game) defaultTarget(roomID int, verb string) string {
skill := verbToSkill(verb)
if skill == "" {
return ""
diff --git a/internal/game/action_farm.go b/internal/game/action_farm.go
index d79150f..21604aa 100644
--- a/internal/game/action_farm.go
+++ b/internal/game/action_farm.go
@@ -51,7 +51,7 @@ func (g *Game) ensureFarmState(p *player.Player, prefix string) {
}
}
-func (g *Game) findActiveFarmPrefixes(p *player.Player) []string {
+func (g *Game) findFarmPrefixes(p *player.Player) []string {
seen := make(map[string]bool)
var prefixes []string
for key := range p.Flags {
@@ -94,7 +94,7 @@ func (g *Game) FarmTick() {
}
func (g *Game) advanceFarmGrowth(sess *net.Session, p *player.Player) {
- prefixes := g.findActiveFarmPrefixes(p)
+ prefixes := g.findFarmPrefixes(p)
for _, prefix := range prefixes {
seedID, _ := p.Flags[prefix+"_seed"].(string)
if seedID == "" {
@@ -492,7 +492,7 @@ func (g *Game) farmMatchPrefix(p *player.Player, input string) (prefix string, d
return "", "", -1
}
-func (g *Game) farmLookSuffixForObj(p *player.Player, defID string, index int) string {
+func (g *Game) farmObjSuffix(p *player.Player, defID string, index int) string {
if defID == "tool_shed" {
return g.toolShedLookSuffix(p)
}
@@ -538,7 +538,7 @@ func (g *Game) showFarmPatches(sess *net.Session, p *player.Player) {
g.ensureFarmState(p, prefix)
idxStr := ""
- if obj.Index > 0 || g.countFarmPatchesOfType(objInstances, obj.DefID) > 1 {
+ if obj.Index > 0 || g.countPatchesByType(objInstances, obj.DefID) > 1 {
idxStr = fmt.Sprintf(" %d", obj.Index+1)
}
@@ -618,7 +618,7 @@ func (g *Game) showFarmPatches(sess *net.Session, p *player.Player) {
}
}
-func (g *Game) countFarmPatchesOfType(instances []world.ObjState, defID string) int {
+func (g *Game) countPatchesByType(instances []world.ObjState, defID string) int {
count := 0
for _, obj := range instances {
if obj.DefID == defID && farmPatchDefIDs[obj.DefID] {
diff --git a/internal/game/action_fletch.go b/internal/game/action_fletch.go
index aa5dfc7..3d8c30f 100644
--- a/internal/game/action_fletch.go
+++ b/internal/game/action_fletch.go
@@ -115,13 +115,7 @@ func (g *Game) startTimedFletch(sess *net.Session, p *player.Player, recipe *act
}
p.BackgroundActionState = &ActionState{Type: ActionFletching, TargetName: outputName}
- if g.Hub != nil {
- for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
- if other != sess && other.Player != nil {
- other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s starts fletching.", p.Name)))
- }
- }
- }
+ g.broadcastAction(sess, "\n%s starts fletching.", p.Name)
sess.WriteLine(fmt.Sprintf("\nYou begin fletching %s.", outputName))
}
@@ -132,9 +126,9 @@ func (g *Game) advanceFletch(sess *net.Session, p *player.Player) {
wait, _ := p.BackgroundAction.Data["wait"].(float64)
remaining, _ := p.BackgroundAction.Data["remaining"].(int)
- recipe := g.loadProductionRecipe(recipeID)
+ recipe := g.loadRecipe(recipeID)
if recipe == nil {
- g.cancelBackgroundAction(p)
+ g.cancelBgAction(p)
return
}
@@ -146,7 +140,7 @@ func (g *Game) advanceFletch(sess *net.Session, p *player.Player) {
if !g.canDoFletchRecipe(p, recipe) {
sess.WriteLine("\nYou've run out of fletching materials.")
- g.cancelBackgroundAction(p)
+ g.cancelBgAction(p)
return
}
@@ -175,7 +169,7 @@ func (g *Game) advanceFletch(sess *net.Session, p *player.Player) {
freeSlot := p.FirstFreeSlot()
if freeSlot == -1 {
sess.WriteLine("\nYour inventory is too full!")
- g.cancelBackgroundAction(p)
+ g.cancelBgAction(p)
return
}
p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: recipe.Output, Quantity: outputQty})
@@ -205,14 +199,14 @@ func (g *Game) advanceFletch(sess *net.Session, p *player.Player) {
p.BackgroundAction.Data["remaining"] = remaining
if remaining <= 0 {
sess.WriteLine("\nYou've finished fletching.")
- g.cancelBackgroundAction(p)
+ g.cancelBgAction(p)
return
}
}
if !g.canContinueProduction(p, recipe) {
sess.WriteLine("\nYou've run out of fletching materials.")
- g.cancelBackgroundAction(p)
+ g.cancelBgAction(p)
return
}
diff --git a/internal/game/action_gather.go b/internal/game/action_gather.go
index e169beb..9b47de2 100644
--- a/internal/game/action_gather.go
+++ b/internal/game/action_gather.go
@@ -53,42 +53,11 @@ func (g *Game) startGather(sess *net.Session, p *player.Player, obj *object.Obje
}
wait := cfg.BaseWait
+ var toolName string
if len(cfg.Tools) > 0 {
- var toolSpeed float64 = -1
- if itemID, ok := p.Equipment[object.SlotMainHand]; ok {
- def, err := g.ItemStore.Load(itemID)
- if err == nil {
- for _, t := range cfg.Tools {
- if def.ToolType == t {
- toolSpeed = def.ToolSpeed
- break
- }
- }
- }
- }
- if toolSpeed < 0 {
- for i := 0; i < 28; i++ {
- slot := p.InvSlot(i)
- if slot == nil {
- continue
- }
- def, err := g.ItemStore.Load(slot.ItemID)
- if err != nil {
- continue
- }
- for _, t := range cfg.Tools {
- if def.ToolType == t {
- toolSpeed = def.ToolSpeed
- break
- }
- }
- if toolSpeed >= 0 {
- break
- }
- }
- }
- if toolSpeed < 0 {
+ toolSpeed, name, found := g.findTool(p, cfg.Tools)
+ if !found {
names := make([]string, len(cfg.Tools))
for i, t := range cfg.Tools {
names[i] = strings.ReplaceAll(t, "_", " ")
@@ -97,6 +66,7 @@ func (g *Game) startGather(sess *net.Session, p *player.Player, obj *object.Obje
sess.WriteLine(fmt.Sprintf("You need a %s to do that.", toolList))
return
}
+ toolName = name
wait = cfg.BaseWait - toolSpeed
if wait < 1 {
wait = 1
@@ -118,38 +88,6 @@ func (g *Game) startGather(sess *net.Session, p *player.Player, obj *object.Obje
sess.WriteLine("Your inventory is too full!")
return
}
-
- toolName := ""
- if itemID, ok := p.Equipment[object.SlotMainHand]; ok {
- if def, err := g.ItemStore.Load(itemID); err == nil {
- for _, t := range cfg.Tools {
- if def.ToolType == t {
- toolName = def.Name
- break
- }
- }
- }
- }
- if toolName == "" {
- for _, slot := range p.Inventory {
- if slot == nil || slot.Quantity <= 0 {
- continue
- }
- def, err := g.ItemStore.Load(slot.ItemID)
- if err != nil {
- continue
- }
- for _, t := range cfg.Tools {
- if def.ToolType == t {
- toolName = def.Name
- break
- }
- }
- if toolName != "" {
- break
- }
- }
- }
verb := cfg.Skill
switch cfg.Skill {
case "woodcutting":
@@ -157,13 +95,7 @@ func (g *Game) startGather(sess *net.Session, p *player.Player, obj *object.Obje
}
p.ActionState = &ActionState{Type: ActionGathering, TargetName: obj.Name, ToolName: toolName, Verb: verb}
- if g.Hub != nil {
- for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
- if other != sess && other.Player != nil {
- other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s starts %s the %s.", p.Name, verb, obj.Name)))
- }
- }
- }
+ g.broadcastAction(sess, "\n%s starts %s the %s.", p.Name, verb, obj.Name)
data := map[string]any{
"obj_def_id": obj.ID,
diff --git a/internal/game/action_search.go b/internal/game/action_search.go
index d7dabe0..5cb8644 100644
--- a/internal/game/action_search.go
+++ b/internal/game/action_search.go
@@ -35,13 +35,7 @@ func (g *Game) startSearch(sess *net.Session, p *player.Player, itemID string, s
p.ActionState = &ActionState{Type: ActionSearching, TargetName: def.Name}
- if g.Hub != nil {
- for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
- if other != sess && other.Player != nil {
- other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s searches a %s.", p.Name, def.Name)))
- }
- }
- }
+ g.broadcastAction(sess, "\n%s searches a %s.", p.Name, def.Name)
}
func (g *Game) advanceSearch(sess *net.Session, p *player.Player) {
diff --git a/internal/game/action_steal.go b/internal/game/action_steal.go
index 3510ce7..a95be25 100644
--- a/internal/game/action_steal.go
+++ b/internal/game/action_steal.go
@@ -259,13 +259,7 @@ func (g *Game) startSteal(sess *net.Session, p *player.Player, targetType string
sess.WriteLine(fmt.Sprintf("You attempt to steal from the %s...", targetName))
p.ActionState = &ActionState{Type: ActionStealing, TargetName: targetName}
- if g.Hub != nil {
- for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
- if other != sess && other.Player != nil {
- other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s glances around the room.", p.Name)))
- }
- }
- }
+ g.broadcastAction(sess, "\n%s glances around the room.", p.Name)
p.Action = &action.Action{
Type: "steal",
diff --git a/internal/game/action_use.go b/internal/game/action_use.go
index 368162b..a3858a9 100644
--- a/internal/game/action_use.go
+++ b/internal/game/action_use.go
@@ -36,13 +36,7 @@ func (g *Game) startUse(sess *net.Session, p *player.Player, obj *object.ObjectD
p.ActionState = &ActionState{Type: ActionUsing, TargetName: obj.Name}
- if g.Hub != nil {
- for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
- if other != sess && other.Player != nil {
- other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s uses the %s.", p.Name, obj.Name)))
- }
- }
- }
+ g.broadcastAction(sess, "\n%s uses the %s.", p.Name, obj.Name)
p.Action = &action.Action{
Type: "use",
diff --git a/internal/game/cmd_agility.go b/internal/game/cmd_agility.go
index 084c5cf..e347322 100644
--- a/internal/game/cmd_agility.go
+++ b/internal/game/cmd_agility.go
@@ -31,7 +31,7 @@ func (g *Game) doObstacle(sess *net.Session, verb string) {
if p.Action != nil {
g.cancelAction(p)
}
- g.cancelBackgroundAction(p)
+ g.cancelBgAction(p)
g.startObstacle(sess, p, info)
}
diff --git a/internal/game/cmd_attack.go b/internal/game/cmd_attack.go
index f9cfba2..89cde46 100644
--- a/internal/game/cmd_attack.go
+++ b/internal/game/cmd_attack.go
@@ -126,7 +126,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)
+ g.cancelBgAction(p)
combat.EnterCombat(p.Name, mob.InstanceID)
p.AttackTimer = 0
@@ -162,13 +162,7 @@ func (g *Game) startCombat(sess *net.Session, p *player.Player, mob *world.MobIn
}
p.ActionState = &ActionState{Type: ActionCombating, TargetName: mob.Name}
- if g.Hub != nil {
- for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
- if other != sess && other.Player != nil {
- other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s attacks %s!", p.Name, mobDisplayName(mob, true))))
- }
- }
- }
+ g.broadcastAction(sess, "\n%s attacks %s!", p.Name, mobDisplayName(mob, true))
g.Ticks.Subscribe(1, func() bool {
cs := combat.GetCombat(p.Name)
@@ -567,7 +561,7 @@ func (g *Game) endCombat(sess *net.Session, p *player.Player, mob *world.MobInst
}
if mob.Drops.Remains != "" {
- g.World.AddReservedGroundItem(p.RoomID, mob.Drops.Remains, 1, p.Name)
+ g.World.AddReservedItem(p.RoomID, mob.Drops.Remains, 1, p.Name)
def, _ := g.ItemStore.Load(mob.Drops.Remains)
name := mob.Drops.Remains
if def != nil {
@@ -588,7 +582,7 @@ func (g *Game) endCombat(sess *net.Session, p *player.Player, mob *world.MobInst
if qty <= 0 {
qty = 1
}
- g.World.AddReservedGroundItem(p.RoomID, entry.ItemID, qty, p.Name)
+ g.World.AddReservedItem(p.RoomID, entry.ItemID, qty, p.Name)
def, _ := g.ItemStore.Load(entry.ItemID)
name := entry.ItemID
if def != nil {
diff --git a/internal/game/cmd_clean.go b/internal/game/cmd_clean.go
index 47896bb..951b41d 100644
--- a/internal/game/cmd_clean.go
+++ b/internal/game/cmd_clean.go
@@ -1,7 +1,6 @@
package game
import (
- "fmt"
"strings"
"thehouseoficarus/internal/action"
@@ -54,7 +53,7 @@ func (g *Game) doClean(sess *net.Session, input string) {
return
}
- g.cancelBackgroundAction(p)
+ g.cancelBgAction(p)
p.BackgroundAction = &action.Action{
Type: "clean",
@@ -67,13 +66,7 @@ func (g *Game) doClean(sess *net.Session, input string) {
}
p.BackgroundActionState = &ActionState{Type: ActionCleaning}
- if g.Hub != nil {
- for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
- if other != sess && other.Player != nil {
- other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s starts cleaning herbs.", p.Name)))
- }
- }
- }
+ g.broadcastAction(sess, "\n%s starts cleaning herbs.", p.Name)
sess.WriteLine("\nYou begin cleaning herbs.")
}
diff --git a/internal/game/cmd_consume.go b/internal/game/cmd_consume.go
index 0a7d335..3782237 100644
--- a/internal/game/cmd_consume.go
+++ b/internal/game/cmd_consume.go
@@ -283,11 +283,5 @@ func (g *Game) applyPotion(sess *net.Session, p *player.Player, itemID string, d
p.ConsumeCooldown = 3
p.ActionState = &ActionState{Type: ActionEating}
- if g.Hub != nil {
- for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
- if other != sess && other.Player != nil {
- other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s drinks a %s.", p.Name, def.Name)))
- }
- }
- }
+ g.broadcastAction(sess, "\n%s drinks a %s.", p.Name, def.Name)
}
diff --git a/internal/game/cmd_cook.go b/internal/game/cmd_cook.go
index 288afb0..04c98bb 100644
--- a/internal/game/cmd_cook.go
+++ b/internal/game/cmd_cook.go
@@ -61,28 +61,13 @@ func (g *Game) doCook(sess *net.Session, input string) {
}
}
- if len(recipes) == 0 {
- sess.WriteLine("You can't cook that.")
- return
- }
-
- if len(recipes) == 1 {
- g.promptHowMany(sess, recipes[0].ID)
- return
- }
-
- sess.PendingMenu = recipeMenuData(recipeDefIDs(recipes))
- sess.State = net.StateRecipeChoice
- var names []string
- for _, r := range recipes {
- names = append(names, g.recipeName(sess, &r))
- }
- g.showMenuTable(sess, fmt.Sprintf("What would you like to cook on the %s?", stationName), names)
+ title := fmt.Sprintf("What would you like to cook on the %s?", stationName)
+ g.showRecipeChoice(sess, p, recipes, "You can't cook that.", title, "")
}
func (g *Game) showCookMenu(sess *net.Session, p *player.Player, allRecipes []action.RecipeDef, stationDefID, stationName string) {
seen := make(map[string]bool)
- var entries []recipeEntry
+ var recipes []action.RecipeDef
for _, r := range allRecipes {
if r.Type != "cooking" {
@@ -98,41 +83,9 @@ func (g *Game) showCookMenu(sess *net.Session, p *player.Player, allRecipes []ac
continue
}
seen[r.ID] = true
- entries = append(entries, recipeEntry{g.recipeName(sess, &r), r})
- }
-
- if len(entries) == 0 {
- sess.WriteLine("You don't have anything you can cook.")
- return
+ recipes = append(recipes, r)
}
- 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
- }
-
- sess.State = net.StateRecipeChoice
- var names []string
- for _, e := range entries {
- names = append(names, e.ItemName)
- }
- g.showMenuTable(sess, fmt.Sprintf("What would you like to cook on the %s?", stationName), names)
- ids := make([]string, len(entries))
- for i, e := range entries {
- ids[i] = e.Recipe.ID
- }
- sess.PendingMenu = recipeMenuData(ids)
-}
-
-func (g *Game) recipeName(sess *net.Session, r *action.RecipeDef) string {
- if r.Output != "" {
- if def, err := g.ItemStore.Load(r.Output); err == nil {
- return g.itemColorize(sess, def, def.Name)
- }
- }
- return r.DisplayName()
+ title := fmt.Sprintf("What would you like to cook on the %s?", stationName)
+ g.showRecipeChoice(sess, p, recipes, "You don't have anything you can cook.", title, "cook_all")
}
diff --git a/internal/game/cmd_craft.go b/internal/game/cmd_craft.go
index 447d980..d061a9c 100644
--- a/internal/game/cmd_craft.go
+++ b/internal/game/cmd_craft.go
@@ -17,14 +17,14 @@ func (g *Game) executeConstruct(sess *net.Session, args []string, rawInput strin
}
func (g *Game) doCraft(sess *net.Session, input string) {
- g.doGenericStationSkill(sess, input, "crafting", player.Crafting, "last_craft", "Crafting", "craft", "Craft")
+ g.doStationSkill(sess, input, "crafting", player.Crafting, "last_craft", "Crafting", "craft", "Craft")
}
func (g *Game) doConstruct(sess *net.Session, input string) {
- g.doGenericStationSkill(sess, input, "construction", player.Construction, "last_construct", "Construction", "construct", "Construct")
+ g.doStationSkill(sess, input, "construction", player.Construction, "last_construct", "Construction", "construct", "Construct")
}
-func (g *Game) doGenericStationSkill(sess *net.Session, input string, recipeType string, skill player.SkillName, flagKey, label, verbPast, verbCap string) {
+func (g *Game) doStationSkill(sess *net.Session, input string, recipeType string, skill player.SkillName, flagKey, label, verbPast, verbCap string) {
p := sess.Player
g.cancelAction(p)
@@ -39,14 +39,14 @@ func (g *Game) doGenericStationSkill(sess *net.Session, input string, recipeType
if r.Type != recipeType {
continue
}
- if !g.genericRecipeVisible(p, &r) {
+ if !g.recipeVisible(p, &r) {
continue
}
recipes = append(recipes, r)
}
if input != "" {
- g.doGenericWithInput(sess, p, recipes, input, skill, flagKey, label, verbCap)
+ g.doWithInput(sess, p, recipes, input, skill, flagKey, label, verbCap)
return
}
@@ -55,8 +55,8 @@ func (g *Game) doGenericStationSkill(sess *net.Session, input string, recipeType
for i := range recipes {
if recipes[i].ID == lastRecipeID {
r := &recipes[i]
- if g.canDoGenericRecipe(p, r, skill) {
- g.startGenericRecipe(sess, p, r, 0, flagKey)
+ if g.canDoRecipe(p, r, skill) {
+ g.startRecipe(sess, p, r, 0, flagKey)
return
}
break
@@ -64,7 +64,7 @@ func (g *Game) doGenericStationSkill(sess *net.Session, input string, recipeType
}
}
- available := g.availableGenericRecipes(p, recipes)
+ available := g.availableRecipes(p, recipes)
if len(available) == 0 {
sess.WriteLine("You don't have any materials to " + verbPast + ".")
return
@@ -72,14 +72,14 @@ func (g *Game) doGenericStationSkill(sess *net.Session, input string, recipeType
optionKey := verbPast + "_all"
if p.OptionBool(optionKey) && len(available) == 1 {
- g.startGenericRecipe(sess, p, &available[0], 0, flagKey)
+ g.startRecipe(sess, p, &available[0], 0, flagKey)
return
}
g.showProductionTable(sess, p, available, label, recipeType, flagKey, verbCap, false)
}
-func (g *Game) doGenericWithInput(sess *net.Session, p *player.Player, recipes []action.RecipeDef, input string, skill player.SkillName, flagKey, label, verbCap string) {
+func (g *Game) doWithInput(sess *net.Session, p *player.Player, recipes []action.RecipeDef, input string, skill player.SkillName, flagKey, label, verbCap string) {
qty, productName := parseQty(input)
productName = strings.TrimSpace(productName)
@@ -102,7 +102,7 @@ func (g *Game) doGenericWithInput(sess *net.Session, p *player.Player, recipes [
var available []action.RecipeDef
for _, r := range matched {
- if g.canDoGenericRecipe(p, &r, skill) {
+ if g.canDoRecipe(p, &r, skill) {
available = append(available, r)
}
}
@@ -113,14 +113,14 @@ func (g *Game) doGenericWithInput(sess *net.Session, p *player.Player, recipes [
}
if len(available) == 1 {
- g.startGenericRecipe(sess, p, &available[0], qty, flagKey)
+ g.startRecipe(sess, p, &available[0], qty, flagKey)
return
}
g.showProductionTable(sess, p, available, label, label, flagKey, verbCap, false)
}
-func (g *Game) genericRecipeVisible(p *player.Player, r *action.RecipeDef) bool {
+func (g *Game) recipeVisible(p *player.Player, r *action.RecipeDef) bool {
if len(r.Station) > 0 {
stID, _ := g.findStation(p.RoomID, r.Station)
if stID == "" {
@@ -133,7 +133,7 @@ func (g *Game) genericRecipeVisible(p *player.Player, r *action.RecipeDef) bool
return true
}
-func (g *Game) canDoGenericRecipe(p *player.Player, r *action.RecipeDef, skill player.SkillName) bool {
+func (g *Game) canDoRecipe(p *player.Player, r *action.RecipeDef, skill player.SkillName) bool {
if p.Level(skill) < r.Level {
return false
}
@@ -152,7 +152,7 @@ func (g *Game) canDoGenericRecipe(p *player.Player, r *action.RecipeDef, skill p
return true
}
-func (g *Game) availableGenericRecipes(p *player.Player, allRecipes []action.RecipeDef) []action.RecipeDef {
+func (g *Game) availableRecipes(p *player.Player, allRecipes []action.RecipeDef) []action.RecipeDef {
var result []action.RecipeDef
for _, r := range allRecipes {
if r.HasAllItemsQty(p.CountItem) {
@@ -162,7 +162,7 @@ func (g *Game) availableGenericRecipes(p *player.Player, allRecipes []action.Rec
return result
}
-func (g *Game) startGenericRecipe(sess *net.Session, p *player.Player, recipe *action.RecipeDef, count int, flagKey string) {
+func (g *Game) startRecipe(sess *net.Session, p *player.Player, recipe *action.RecipeDef, count int, flagKey string) {
p.EnsureFlags()
p.Flags[flagKey] = recipe.ID
g.AccountStore.SaveCharacter(p)
@@ -170,7 +170,7 @@ func (g *Game) startGenericRecipe(sess *net.Session, p *player.Player, recipe *a
g.startProductionFromRecipe(sess, p, recipe, count)
}
-func (g *Game) findCraftRecipesForItems(p *player.Player, itemAID, itemBID string) []action.RecipeDef {
+func (g *Game) findCraftRecipes(p *player.Player, itemAID, itemBID string) []action.RecipeDef {
allRecipes, err := g.RecipeStore.LoadAll()
if err != nil {
return nil
diff --git a/internal/game/cmd_description.go b/internal/game/cmd_description.go
index 5d0c5c1..1fc24ae 100644
--- a/internal/game/cmd_description.go
+++ b/internal/game/cmd_description.go
@@ -20,10 +20,10 @@ func (g *Game) doDescription(sess *net.Session) {
}
sess.WriteLine("")
sess.Write("Enter a new description (or press enter to keep current): ")
- sess.State = net.StateChangeDescription
+ sess.State = net.StateChangeDesc
}
-func (g *Game) handleDescriptionChange(sess *net.Session, input string) {
+func (g *Game) handleDescChange(sess *net.Session, input string) {
if input != "" {
p := sess.Player
p.Description = input
diff --git a/internal/game/cmd_drop.go b/internal/game/cmd_drop.go
index 551b014..ca3c244 100644
--- a/internal/game/cmd_drop.go
+++ b/internal/game/cmd_drop.go
@@ -194,7 +194,7 @@ func (g *Game) executeDrop(sess *net.Session, args []string, rawInput string) {
}
}
-func (g *Game) handleDropAllConfirm(sess *net.Session, input string) {
+func (g *Game) handleDropAll(sess *net.Session, input string) {
p := sess.Player
if p == nil {
sess.State = net.StateGame
diff --git a/internal/game/cmd_fletch.go b/internal/game/cmd_fletch.go
index e218dca..5a4bc19 100644
--- a/internal/game/cmd_fletch.go
+++ b/internal/game/cmd_fletch.go
@@ -184,7 +184,7 @@ func (g *Game) availableFletchRecipes(p *player.Player, allFletch []action.Recip
return result
}
-func (g *Game) findFletchRecipesForItems(p *player.Player, itemAID, itemBID string) []action.RecipeDef {
+func (g *Game) findFletchRecipes(p *player.Player, itemAID, itemBID string) []action.RecipeDef {
allRecipes, err := g.RecipeStore.LoadAll()
if err != nil {
return nil
diff --git a/internal/game/cmd_get.go b/internal/game/cmd_get.go
index f3485fe..30bc8c2 100644
--- a/internal/game/cmd_get.go
+++ b/internal/game/cmd_get.go
@@ -77,7 +77,7 @@ func (g *Game) doGet(sess *net.Session, input string) {
for i := 0; i < 28; i++ {
slot := p.InvSlot(i)
if slot != nil && slot.ItemID == itemID {
- if _, ok := g.World.RemoveReservedGroundItem(p.RoomID, itemID, qty, p.Name); !ok {
+ if _, ok := g.World.RemoveReservedItem(p.RoomID, itemID, qty, p.Name); !ok {
sess.WriteLine("That's not yours!")
return
}
@@ -92,7 +92,7 @@ func (g *Game) doGet(sess *net.Session, input string) {
sess.WriteLine("Your inventory is full.")
return
}
- if _, ok := g.World.RemoveReservedGroundItem(p.RoomID, itemID, qty, p.Name); !ok {
+ if _, ok := g.World.RemoveReservedItem(p.RoomID, itemID, qty, p.Name); !ok {
sess.WriteLine("That's not yours!")
return
}
@@ -112,7 +112,7 @@ func (g *Game) doGet(sess *net.Session, input string) {
if fs == -1 {
break
}
- if _, ok := g.World.RemoveReservedGroundItem(p.RoomID, itemID, 1, p.Name); !ok {
+ if _, ok := g.World.RemoveReservedItem(p.RoomID, itemID, 1, p.Name); !ok {
break
}
p.SetInvSlot(fs, &player.InventorySlot{ItemID: itemID, Quantity: 1})
@@ -193,7 +193,7 @@ func (g *Game) doGetAllNamed(sess *net.Session, input string) {
for i := 0; i < 28; i++ {
slot := p.InvSlot(i)
if slot != nil && slot.ItemID == itemID {
- removed, ok := g.World.RemoveReservedGroundItem(p.RoomID, itemID, wanted, p.Name)
+ removed, ok := g.World.RemoveReservedItem(p.RoomID, itemID, wanted, p.Name)
if !ok {
sess.WriteLine("That's not yours!")
return
@@ -209,7 +209,7 @@ func (g *Game) doGetAllNamed(sess *net.Session, input string) {
sess.WriteLine("Your inventory is full.")
return
}
- removed, ok := g.World.RemoveReservedGroundItem(p.RoomID, itemID, wanted, p.Name)
+ removed, ok := g.World.RemoveReservedItem(p.RoomID, itemID, wanted, p.Name)
if !ok {
sess.WriteLine("That's not yours!")
return
@@ -230,7 +230,7 @@ func (g *Game) doGetAllNamed(sess *net.Session, input string) {
if fs == -1 {
break
}
- _, ok := g.World.RemoveReservedGroundItem(p.RoomID, itemID, 1, p.Name)
+ _, ok := g.World.RemoveReservedItem(p.RoomID, itemID, 1, p.Name)
if !ok {
break
}
@@ -266,7 +266,7 @@ func (g *Game) doGetAll(sess *net.Session) {
continue
}
if itemID == "credits" {
- taken, ok := g.World.RemoveReservedGroundItem(p.RoomID, itemID, 999999, p.Name)
+ taken, ok := g.World.RemoveReservedItem(p.RoomID, itemID, 999999, p.Name)
if !ok {
continue
}
@@ -286,7 +286,7 @@ func (g *Game) doGetAll(sess *net.Session) {
for i := 0; i < 28; i++ {
slot := p.InvSlot(i)
if slot != nil && slot.ItemID == itemID {
- _, ok := g.World.RemoveReservedGroundItem(p.RoomID, itemID, qty, p.Name)
+ _, ok := g.World.RemoveReservedItem(p.RoomID, itemID, qty, p.Name)
if !ok {
qty = 0
stacked = true
@@ -313,7 +313,7 @@ func (g *Game) doGetAll(sess *net.Session) {
g.AccountStore.SaveCharacter(p)
return
}
- _, ok := g.World.RemoveReservedGroundItem(p.RoomID, itemID, qty, p.Name)
+ _, ok := g.World.RemoveReservedItem(p.RoomID, itemID, qty, p.Name)
if !ok {
break
}
@@ -339,7 +339,7 @@ func (g *Game) doGetAll(sess *net.Session) {
}
take := 1
- _, ok := g.World.RemoveReservedGroundItem(p.RoomID, itemID, take, p.Name)
+ _, ok := g.World.RemoveReservedItem(p.RoomID, itemID, take, p.Name)
if !ok {
break
}
@@ -364,7 +364,7 @@ func (g *Game) doGetAll(sess *net.Session) {
}
func (g *Game) pickupCredits(sess *net.Session, p *player.Player, itemID string) {
- qty, ok := g.World.RemoveReservedGroundItem(p.RoomID, itemID, 999999, p.Name)
+ qty, ok := g.World.RemoveReservedItem(p.RoomID, itemID, 999999, p.Name)
if !ok {
sess.WriteLine("That's not yours!")
return
diff --git a/internal/game/cmd_jack.go b/internal/game/cmd_jack.go
index 16f8b2a..f33ea85 100644
--- a/internal/game/cmd_jack.go
+++ b/internal/game/cmd_jack.go
@@ -52,7 +52,7 @@ func (g *Game) doJack(sess *net.Session, target string) {
}
g.cancelAction(p)
- g.cancelBackgroundAction(p)
+ g.cancelBgAction(p)
minigame := tDef.Minigame()
display := minigame.Init(hackLevel)
@@ -68,13 +68,7 @@ func (g *Game) doJack(sess *net.Session, target string) {
p.ActionState = &ActionState{Type: ActionHacking, TargetName: tDef.Name}
sess.State = net.StateHacking
- if g.Hub != nil {
- for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
- if other != sess {
- other.WriteLine(fmt.Sprintf("\n%s jacks into a terminal.", p.Name))
- }
- }
- }
+ g.broadcastAction(sess, "\n%s jacks into a terminal.", p.Name)
sess.WriteLine(display)
sess.Write("\nhack> ")
diff --git a/internal/game/cmd_look.go b/internal/game/cmd_look.go
index 6913333..505f7a6 100644
--- a/internal/game/cmd_look.go
+++ b/internal/game/cmd_look.go
@@ -48,7 +48,7 @@ func (g *Game) showRoomDescription(sess *net.Session, p *player.Player, room *wo
roomDescSpec := g.resolveColor(sess, "room_desc")
descLines := make([]string, len(rawLines))
for i, l := range rawLines {
- descLines[i] = color.ExpandTagsWithDefault(mode, roomDescSpec, l)
+ descLines[i] = color.ExpandTagsDefault(mode, roomDescSpec, l)
}
wroteDesc := false
if p.OptionString("tiny_map") != "off" {
@@ -546,7 +546,7 @@ func (g *Game) doLookTarget(sess *net.Session, input string) {
}
}
- farmSuffix := g.farmLookSuffixForObj(p, st.DefID, st.Index)
+ farmSuffix := g.farmObjSuffix(p, st.DefID, st.Index)
if farmSuffix != "" {
sess.WriteLine(farmSuffix)
}
diff --git a/internal/game/cmd_mix.go b/internal/game/cmd_mix.go
index 7bf1f7b..337bb18 100644
--- a/internal/game/cmd_mix.go
+++ b/internal/game/cmd_mix.go
@@ -3,7 +3,6 @@ package game
import (
"strings"
- "thehouseoficarus/internal/action"
"thehouseoficarus/internal/net"
"thehouseoficarus/internal/player"
)
@@ -13,110 +12,5 @@ func (g *Game) executeMix(sess *net.Session, args []string, rawInput string) {
}
func (g *Game) doMix(sess *net.Session, input string) {
- p := sess.Player
- g.cancelAction(p)
-
- allRecipes, err := g.RecipeStore.LoadAll()
- if err != nil {
- sess.WriteLine("Error loading recipes.")
- return
- }
-
- if input == "" {
- g.showMixMenu(sess, p, allRecipes)
- return
- }
-
- qty, itemName := parseQty(input)
- _ = qty
-
- var matched []action.RecipeDef
- for _, r := range allRecipes {
- if r.Type != "pharmacy" {
- continue
- }
- outDef, _ := g.ItemStore.Load(r.Output)
- name := r.Output
- if outDef != nil {
- name = outDef.Name
- }
- if action.WordPrefixMatch(itemName, name) {
- matched = append(matched, r)
- }
- }
-
- if len(matched) == 0 {
- sess.WriteLine("You can't mix that.")
- return
- }
-
- var available []action.RecipeDef
- for _, r := range matched {
- if r.HasAllItems(p.HasItem) && p.Level(player.Pharmacy) >= r.Level {
- available = append(available, r)
- }
- }
-
- if len(available) == 0 {
- sess.WriteLine("You don't have the materials for that.")
- return
- }
-
- if len(available) == 1 {
- g.promptHowMany(sess, available[0].ID)
- return
- }
-
- sess.State = net.StateRecipeChoice
- var names []string
- for _, r := range available {
- names = append(names, g.recipeName(sess, &r))
- }
- g.showMenuTable(sess, "What would you like to mix?", names)
- sess.PendingMenu = recipeMenuData(recipeDefIDs(available))
-}
-
-func (g *Game) showMixMenu(sess *net.Session, p *player.Player, allRecipes []action.RecipeDef) {
- seen := make(map[string]bool)
- var entries []recipeEntry
-
- for _, r := range allRecipes {
- if r.Type != "pharmacy" {
- continue
- }
- if seen[r.ID] {
- continue
- }
- if !r.HasAllItems(p.HasItem) {
- continue
- }
- seen[r.ID] = true
- entries = append(entries, recipeEntry{g.recipeName(sess, &r), r})
- }
-
- if len(entries) == 0 {
- sess.WriteLine("You don't have anything you can mix.")
- return
- }
-
- if len(entries) == 1 {
- if p.OptionBool("mix_all") {
- g.startProductionFromRecipe(sess, p, &entries[0].Recipe, 0)
- return
- }
- g.promptHowMany(sess, entries[0].Recipe.ID)
- return
- }
-
- sess.State = net.StateRecipeChoice
- var names []string
- for _, e := range entries {
- names = append(names, e.ItemName)
- }
- g.showMenuTable(sess, "What would you like to mix?", names)
- ids := make([]string, len(entries))
- for i, e := range entries {
- ids[i] = e.Recipe.ID
- }
- sess.PendingMenu = recipeMenuData(ids)
+ g.doStationSkill(sess, input, "pharmacy", player.Pharmacy, "last_mix", "Mixing", "mix", "Mix")
}
diff --git a/internal/game/cmd_quit.go b/internal/game/cmd_quit.go
index 46403f4..187d766 100644
--- a/internal/game/cmd_quit.go
+++ b/internal/game/cmd_quit.go
@@ -1,8 +1,6 @@
package game
import (
- "fmt"
-
"thehouseoficarus/internal/combat"
"thehouseoficarus/internal/engine"
"thehouseoficarus/internal/net"
@@ -24,13 +22,7 @@ func (g *Game) doQuit(sess *net.Session) {
p.ActionState = &ActionState{Type: ActionResting}
g.cancelRest(p.Name)
- if g.Hub != nil {
- for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
- if other != sess && other.Player != nil {
- other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s sits down to rest.", p.Name)))
- }
- }
- }
+ g.broadcastAction(sess, "\n%s sits down to rest.", p.Name)
ticksLeft := 10
id := g.Ticks.Subscribe(engine.ToTicks(1), func() bool {
diff --git a/internal/game/cmd_registry.go b/internal/game/cmd_registry.go
index c4f7700..ea741d9 100644
--- a/internal/game/cmd_registry.go
+++ b/internal/game/cmd_registry.go
@@ -149,7 +149,7 @@ func (g *Game) executeCommand(sess *net.Session, cmd string, args []string, rawI
switch a {
case "gather":
if len(args) == 0 {
- target := g.resolveDefaultTarget(p.RoomID, cmd)
+ target := g.defaultTarget(p.RoomID, cmd)
if target == "" {
sess.WriteLine(fmt.Sprintf("%s what?", cmd))
return
@@ -176,7 +176,7 @@ func (g *Game) executeGather(sess *net.Session, args []string, rawInput string)
p := sess.Player
g.cancelAction(p)
if len(args) == 0 {
- target := g.resolveDefaultTarget(p.RoomID, verb)
+ target := g.defaultTarget(p.RoomID, verb)
if target == "" {
sess.WriteLine(fmt.Sprintf("%s what?", verb))
return
diff --git a/internal/game/cmd_smelt.go b/internal/game/cmd_smelt.go
index a72cf53..f1ff4e2 100644
--- a/internal/game/cmd_smelt.go
+++ b/internal/game/cmd_smelt.go
@@ -53,69 +53,29 @@ func (g *Game) doSmelt(sess *net.Session, input string) {
if r.Type != "smelting" || !stationMatch(stationDefID, r.Station) || !r.MatchesEntry(itemID) {
continue
}
- if !r.HasAllItemsQty(p.CountItem) || p.Level(player.SkillName(r.EffectiveSkill())) < r.Level {
+ if !r.HasAllItemsQty(p.CountItem) || p.Level(player.SkillName(r.EffectiveSkill())) < r.Level {
continue
}
recipes = append(recipes, r)
}
- if len(recipes) == 0 {
- sess.WriteLine("You can't smelt that.")
- return
- }
-
- if len(recipes) == 1 {
- g.promptHowMany(sess, recipes[0].ID)
- return
- }
-
- sess.PendingMenu = recipeMenuData(recipeDefIDs(recipes))
- sess.State = net.StateRecipeChoice
- var names []string
- for _, r := range recipes {
- names = append(names, g.recipeName(sess, &r))
- }
- g.showMenuTable(sess, "What would you like to smelt?", names)
+ g.showRecipeChoice(sess, p, recipes, "You can't smelt that.", "What would you like to smelt?", "")
}
func (g *Game) showSmeltMenu(sess *net.Session, p *player.Player, allRecipes []action.RecipeDef, stationDefID, stationName string) {
seen := make(map[string]bool)
- var entries []recipeEntry
+ var recipes []action.RecipeDef
for _, r := range allRecipes {
if r.Type != "smelting" || !stationMatch(stationDefID, r.Station) {
continue
}
- if seen[r.ID] || !r.HasAllItemsQty(p.CountItem) || p.Level(player.SkillName(r.EffectiveSkill())) < r.Level {
+ if seen[r.ID] || !r.HasAllItemsQty(p.CountItem) || p.Level(player.SkillName(r.EffectiveSkill())) < r.Level {
continue
}
seen[r.ID] = true
- entries = append(entries, recipeEntry{g.recipeName(sess, &r), r})
- }
-
- if len(entries) == 0 {
- sess.WriteLine("You don't have anything you can smelt.")
- return
- }
-
- 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
+ recipes = append(recipes, r)
}
- sess.State = net.StateRecipeChoice
- var names []string
- for _, e := range entries {
- names = append(names, e.ItemName)
- }
- g.showMenuTable(sess, "What would you like to smelt?", names)
- ids := make([]string, len(entries))
- for i, e := range entries {
- ids[i] = e.Recipe.ID
- }
- sess.PendingMenu = recipeMenuData(ids)
+ g.showRecipeChoice(sess, p, recipes, "You don't have anything you can smelt.", "What would you like to smelt?", "smelt_all")
}
diff --git a/internal/game/cmd_smith.go b/internal/game/cmd_smith.go
index a2eaad7..c7a32cd 100644
--- a/internal/game/cmd_smith.go
+++ b/internal/game/cmd_smith.go
@@ -63,7 +63,7 @@ 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 {
+ if recipe := g.findSmithRecipe(p, barTypes[0], allRecipes); recipe != nil {
g.startProductionFromRecipe(sess, p, recipe, 0)
return
}
@@ -148,7 +148,7 @@ func barMenuData(barIDs []string) []map[string]string {
return out
}
-func (g *Game) findSingleSmithRecipe(p *player.Player, barID string, allRecipes []action.RecipeDef) *action.RecipeDef {
+func (g *Game) findSmithRecipe(p *player.Player, barID string, allRecipes []action.RecipeDef) *action.RecipeDef {
var makeable []*action.RecipeDef
skillLevel := p.Level(player.Smithing)
for i, r := range allRecipes {
diff --git a/internal/game/cmd_trigger_combat.go b/internal/game/cmd_trigger_combat.go
index 62c5083..444f80d 100644
--- a/internal/game/cmd_trigger_combat.go
+++ b/internal/game/cmd_trigger_combat.go
@@ -23,7 +23,7 @@ func (g *Game) scienceAttack(sess *net.Session, p *player.Player, mob *world.Mob
g.consumeJunkCost(p, mod)
- equipSciBonus := g.totalEquipScienceAttack(p)
+ equipSciBonus := g.totalScienceAttack(p)
attRoll := (p.Level(player.Science) + g.buffLevelBonus(p, "science") + 8) * (equipSciBonus + 64)
mobSciDef := mob.ScienceDefense
diff --git a/internal/game/cmd_trigger_enchant.go b/internal/game/cmd_trigger_enchant.go
index 535616b..a1a6950 100644
--- a/internal/game/cmd_trigger_enchant.go
+++ b/internal/game/cmd_trigger_enchant.go
@@ -41,13 +41,9 @@ func (g *Game) triggerEnchant(sess *net.Session, p *player.Player, mod *ModDef,
g.cancelAction(p)
}
- g.consumeJunkCost(p, mod)
-
inv.ItemID = outputID
- sciXP := mod.BaseXP
- g.awardSkillXP(sess, p, player.Science, sciXP)
- g.AccountStore.SaveCharacter(p)
+ g.triggerModReward(sess, p, mod, 1.0)
outputDef, _ := g.ItemStore.Load(outputID)
outputName := outputID
@@ -61,9 +57,6 @@ func (g *Game) triggerEnchant(sess *net.Session, p *player.Player, mod *ModDef,
}
sess.WriteLine(fmt.Sprintf("You enchant the %s and it becomes a %s!", inputName, outputName))
- if p.OptionBool("xp_drops") {
- sess.WriteLine(g.colorize(sess, "xp", fmt.Sprintf("+%dxp sci", sciXP)))
- }
}
func (g *Game) triggerChipBolts(sess *net.Session, p *player.Player, mod *ModDef, chip chipEntry) {
@@ -82,8 +75,6 @@ func (g *Game) triggerChipBolts(sess *net.Session, p *player.Player, mod *ModDef
g.cancelAction(p)
}
- g.consumeJunkCost(p, mod)
-
p.RemoveItem(chip.Input, chip.Qty)
placed := false
for i := 0; i < 28; i++ {
@@ -99,9 +90,7 @@ func (g *Game) triggerChipBolts(sess *net.Session, p *player.Player, mod *ModDef
p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: chip.Output, Quantity: chip.Qty})
}
- sciXP := mod.BaseXP
- g.awardSkillXP(sess, p, player.Science, sciXP)
- g.AccountStore.SaveCharacter(p)
+ g.triggerModReward(sess, p, mod, 1.0)
inputDef, _ := g.ItemStore.Load(chip.Input)
name := chip.Input
@@ -110,7 +99,4 @@ func (g *Game) triggerChipBolts(sess *net.Session, p *player.Player, mod *ModDef
}
sess.WriteLine(fmt.Sprintf("You chip %d %s with arcane circuitry.", chip.Qty, name))
- if p.OptionBool("xp_drops") {
- sess.WriteLine(g.colorize(sess, "xp", fmt.Sprintf("+%dxp sci", sciXP)))
- }
}
diff --git a/internal/game/cmd_trigger_transport.go b/internal/game/cmd_trigger_transport.go
index 2ca8db2..2354c27 100644
--- a/internal/game/cmd_trigger_transport.go
+++ b/internal/game/cmd_trigger_transport.go
@@ -18,14 +18,7 @@ func (g *Game) triggerTransport(sess *net.Session, p *player.Player, mod *ModDef
g.cancelAction(p)
}
- g.consumeJunkCost(p, mod)
-
- sciXP := mod.BaseXP
- g.awardSkillXP(sess, p, player.Science, sciXP)
- if p.OptionBool("xp_drops") {
- sess.WriteLine(g.colorize(sess, "xp", fmt.Sprintf("+%dxp sci", sciXP)))
- }
- g.AccountStore.SaveCharacter(p)
+ g.triggerModReward(sess, p, mod, 1.0)
if g.Hub != nil {
for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
diff --git a/internal/game/cmd_trigger_utility.go b/internal/game/cmd_trigger_utility.go
index efb23e7..ce59975 100644
--- a/internal/game/cmd_trigger_utility.go
+++ b/internal/game/cmd_trigger_utility.go
@@ -14,7 +14,7 @@ func (g *Game) triggerUtility(sess *net.Session, p *player.Player, mod *ModDef,
case "low_process", "high_process":
g.triggerProcessing(sess, p, mod, targetArg)
case "bones_to_nutrients":
- g.triggerBonesToNutrients(sess, p, mod)
+ g.triggerBonesToNuts(sess, p, mod)
case "em_grab":
g.triggerEmGrab(sess, p, mod, targetArg)
case "superheat":
@@ -51,8 +51,6 @@ func (g *Game) triggerProcessing(sess *net.Session, p *player.Player, mod *ModDe
g.cancelAction(p)
}
- g.consumeJunkCost(p, mod)
-
creditValue := itemDef.Value
if mod.ID == "low_process" {
creditValue = itemDef.Value / 2
@@ -69,19 +67,13 @@ func (g *Game) triggerProcessing(sess *net.Session, p *player.Player, mod *ModDe
p.Credits += creditValue
- sciXP := mod.BaseXP
- g.awardSkillXP(sess, p, player.Science, sciXP)
- g.AccountStore.SaveCharacter(p)
+ g.triggerModReward(sess, p, mod, 1.0)
sess.WriteLine(fmt.Sprintf("You process the %s. You receive %s credits.",
itemDef.Name, g.colorize(sess, "credits_pickup", fmt.Sprint(creditValue))))
-
- if p.OptionBool("xp_drops") {
- sess.WriteLine(g.colorize(sess, "xp", fmt.Sprintf("+%dxp sci", sciXP)))
- }
}
-func (g *Game) triggerBonesToNutrients(sess *net.Session, p *player.Player, mod *ModDef) {
+func (g *Game) triggerBonesToNuts(sess *net.Session, p *player.Player, mod *ModDef) {
if combat.GetCombat(p.Name) != nil {
sess.WriteLine("You can't do that during combat!")
return
@@ -97,8 +89,6 @@ func (g *Game) triggerBonesToNutrients(sess *net.Session, p *player.Player, mod
g.cancelAction(p)
}
- g.consumeJunkCost(p, mod)
-
for i := 0; i < 28; i++ {
slot := p.InvSlot(i)
if slot != nil && slot.ItemID == "bones" {
@@ -106,14 +96,9 @@ func (g *Game) triggerBonesToNutrients(sess *net.Session, p *player.Player, mod
}
}
- sciXP := mod.BaseXP * boneCount
- g.awardSkillXP(sess, p, player.Science, sciXP)
- g.AccountStore.SaveCharacter(p)
+ g.triggerModReward(sess, p, mod, float64(boneCount))
sess.WriteLine(fmt.Sprintf("You convert %d bones into nutrient bars.", boneCount))
- if p.OptionBool("xp_drops") {
- sess.WriteLine(g.colorize(sess, "xp", fmt.Sprintf("+%dxp sci", sciXP)))
- }
}
func (g *Game) triggerEmGrab(sess *net.Session, p *player.Player, mod *ModDef, targetArg string) {
@@ -150,8 +135,6 @@ func (g *Game) triggerEmGrab(sess *net.Session, p *player.Player, mod *ModDef, t
g.cancelAction(p)
}
- g.consumeJunkCost(p, mod)
-
qty := groundItems[matchedID]
g.World.RemoveGroundItem(p.RoomID, matchedID, qty)
@@ -164,14 +147,9 @@ func (g *Game) triggerEmGrab(sess *net.Session, p *player.Player, mod *ModDef, t
freeSlot := p.FirstFreeSlot()
p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: matchedID, Quantity: qty})
- sciXP := mod.BaseXP
- g.awardSkillXP(sess, p, player.Science, sciXP)
- g.AccountStore.SaveCharacter(p)
+ g.triggerModReward(sess, p, mod, 1.0)
sess.WriteLine(fmt.Sprintf("You magnetically pull the %s toward you.", name))
- if p.OptionBool("xp_drops") {
- sess.WriteLine(g.colorize(sess, "xp", fmt.Sprintf("+%dxp sci", sciXP)))
- }
}
func (g *Game) triggerSuperheat(sess *net.Session, p *player.Player, mod *ModDef, targetArg string) {
@@ -224,8 +202,6 @@ func (g *Game) triggerSuperheat(sess *net.Session, p *player.Player, mod *ModDef
g.cancelAction(p)
}
- g.consumeJunkCost(p, mod)
-
for _, e := range recipe.Consume {
for _, itemID := range e.Items {
qty := e.Quantity
@@ -254,12 +230,11 @@ func (g *Game) triggerSuperheat(sess *net.Session, p *player.Player, mod *ModDef
p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: recipe.Output, Quantity: outputQty})
}
- sciXP := mod.BaseXP
- g.awardSkillXP(sess, p, player.Science, sciXP)
+ var extraGains []xpGain
if recipe.XP > 0 {
- g.awardSkillXP(sess, p, player.SkillName(recipe.Skill), recipe.XP)
+ extraGains = []xpGain{{recipe.Skill, recipe.XP}}
}
- g.AccountStore.SaveCharacter(p)
+ g.triggerModReward(sess, p, mod, 1.0, extraGains...)
outputDef, _ := g.ItemStore.Load(recipe.Output)
outputName := recipe.Output
@@ -273,13 +248,6 @@ func (g *Game) triggerSuperheat(sess *net.Session, p *player.Player, mod *ModDef
}
sess.WriteLine(fmt.Sprintf("You superheat the %s and produce a %s.", inputName, outputName))
- if p.OptionBool("xp_drops") {
- parts := []string{fmt.Sprintf("+%dxp sci", sciXP)}
- if recipe.XP > 0 {
- parts = append(parts, fmt.Sprintf("+%dxp %s", recipe.XP, player.SkillAbbr[player.SkillName(recipe.Skill)]))
- }
- sess.WriteLine(g.colorize(sess, "xp", "("+strings.Join(parts, ", ")+")"))
- }
}
func (g *Game) triggerPlankMake(sess *net.Session, p *player.Player, mod *ModDef, targetArg string) {
@@ -320,8 +288,6 @@ func (g *Game) triggerPlankMake(sess *net.Session, p *player.Player, mod *ModDef
g.cancelAction(p)
}
- g.consumeJunkCost(p, mod)
-
p.RemoveItem(inv.ItemID, 1)
p.Credits -= info.cost
@@ -332,16 +298,9 @@ func (g *Game) triggerPlankMake(sess *net.Session, p *player.Player, mod *ModDef
}
p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: info.plankID, Quantity: 1})
- sciXP := mod.BaseXP
- g.awardSkillXP(sess, p, player.Science, sciXP)
- g.awardSkillXP(sess, p, player.Construction, 10)
- g.AccountStore.SaveCharacter(p)
+ g.triggerModReward(sess, p, mod, 1.0, xpGain{Skill: string(player.Construction), XP: 10})
sess.WriteLine(fmt.Sprintf("You convert the log into %s.", info.name))
- if p.OptionBool("xp_drops") {
- parts := []string{fmt.Sprintf("+%dxp sci", sciXP), "+10xp con"}
- sess.WriteLine(g.colorize(sess, "xp", "("+strings.Join(parts, ", ")+")"))
- }
return
}
@@ -360,8 +319,6 @@ func (g *Game) triggerPlankMake(sess *net.Session, p *player.Player, mod *ModDef
g.cancelAction(p)
}
- g.consumeJunkCost(p, mod)
-
p.RemoveItem(logID, qty)
p.Credits -= totalCost
@@ -376,20 +333,12 @@ func (g *Game) triggerPlankMake(sess *net.Session, p *player.Player, mod *ModDef
processed++
}
- sciXP := mod.BaseXP * qty
- g.awardSkillXP(sess, p, player.Science, sciXP)
- conXP := 10 * qty
- g.awardSkillXP(sess, p, player.Construction, conXP)
- g.AccountStore.SaveCharacter(p)
+ g.triggerModReward(sess, p, mod, float64(qty), xpGain{Skill: string(player.Construction), XP: 10 * qty})
sess.WriteLine(fmt.Sprintf("You convert %d logs into %s for %d credits.", processed, info.name, totalCost))
if processed < qty {
sess.WriteLine(fmt.Sprintf("Your inventory is full. The remaining %d planks fall to the ground.", qty-processed))
}
- if p.OptionBool("xp_drops") {
- parts := []string{fmt.Sprintf("+%dxp sci", sciXP), fmt.Sprintf("+%dxp con", conXP)}
- sess.WriteLine(g.colorize(sess, "xp", "("+strings.Join(parts, ", ")+")"))
- }
return
}
diff --git a/internal/game/cmd_use.go b/internal/game/cmd_use.go
index 4f830a8..e653c93 100644
--- a/internal/game/cmd_use.go
+++ b/internal/game/cmd_use.go
@@ -83,7 +83,7 @@ func (g *Game) useRoomObject(sess *net.Session, p *player.Player, input string)
}
recipes := g.stationRecipes(p, objSt.DefID)
if len(recipes) > 0 {
- g.showStationRecipeMenu(sess, p, recipes, def.Name)
+ g.showRecipeMenu(sess, p, recipes, def.Name)
return true
}
bt := def.BehaviorType()
@@ -105,13 +105,7 @@ func (g *Game) useRoomObject(sess *net.Session, p *player.Player, input string)
g.applyNodeAction(sess, ui.Action)
}
p.ActionState = &ActionState{Type: ActionUsing, TargetName: def.Name}
- if g.Hub != nil {
- for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
- if other != sess && other.Player != nil {
- other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s uses the %s.", p.Name, def.Name)))
- }
- }
- }
+ g.broadcastAction(sess, "\n%s uses the %s.", p.Name, def.Name)
return true
}
if len(def.UseInteractions) > 0 {
@@ -160,7 +154,7 @@ func (g *Game) stationRecipes(p *player.Player, stationDefID string) []action.Re
return results
}
-func (g *Game) showStationRecipeMenu(sess *net.Session, p *player.Player, recipes []action.RecipeDef, stationName string) {
+func (g *Game) showRecipeMenu(sess *net.Session, p *player.Player, recipes []action.RecipeDef, stationName string) {
if len(recipes) == 0 {
sess.WriteLine(fmt.Sprintf("You don't have anything you can make at the %s.", stationName))
return
@@ -320,8 +314,8 @@ func (g *Game) doUseItemOnTarget(sess *net.Session, p *player.Player, itemAName,
itemBID := matchesB[0].ID
- craftRecipes := g.findCraftRecipesForItems(p, itemAID, itemBID)
- fletchRecipes := g.findFletchRecipesForItems(p, itemAID, itemBID)
+ craftRecipes := g.findCraftRecipes(p, itemAID, itemBID)
+ fletchRecipes := g.findFletchRecipes(p, itemAID, itemBID)
if len(craftRecipes) > 0 && len(fletchRecipes) > 0 {
var menu []map[string]string
@@ -353,12 +347,12 @@ func (g *Game) doUseItemOnTarget(sess *net.Session, p *player.Player, itemAName,
if len(craftRecipes) > 0 {
var available []action.RecipeDef
for _, r := range craftRecipes {
- if g.canDoGenericRecipe(p, &r, player.Crafting) {
+ if g.canDoRecipe(p, &r, player.Crafting) {
available = append(available, r)
}
}
if len(available) == 1 {
- g.startGenericRecipe(sess, p, &available[0], 0, "last_craft")
+ g.startRecipe(sess, p, &available[0], 0, "last_craft")
return
}
if len(available) > 1 {
@@ -429,7 +423,7 @@ func (g *Game) findCombineResults(sess *net.Session, p *player.Player, itemAID,
}
}
}
- if aEntry >= 0 && bEntry >= 0 && aEntry != bEntry && madeFromItemsAvailable(p, def.MadeFrom) {
+ if aEntry >= 0 && bEntry >= 0 && aEntry != bEntry && madeFromAvailable(p, def.MadeFrom) {
matched = append(matched, def)
}
}
@@ -444,7 +438,7 @@ func combineMenuData(defs []*object.ItemDef) []map[string]string {
return out
}
-func madeFromItemsAvailable(p *player.Player, madeFrom []object.MadeFromEntry) bool {
+func madeFromAvailable(p *player.Player, madeFrom []object.MadeFromEntry) bool {
for _, entry := range madeFrom {
found := false
for _, id := range entry.Items {
diff --git a/internal/game/core_login_account.go b/internal/game/core_login_account.go
index 87f8c62..a2c1bd8 100644
--- a/internal/game/core_login_account.go
+++ b/internal/game/core_login_account.go
@@ -83,7 +83,7 @@ func (g *Game) handlePassword(sess *net.Session, input string) {
g.showMenu(sess)
}
-func (g *Game) handleNewAccountPass(sess *net.Session, input string) {
+func (g *Game) handleNewPass(sess *net.Session, input string) {
input = strings.TrimSpace(input)
if input == "" {
sess.Conn.SetEcho(true)
@@ -98,11 +98,11 @@ func (g *Game) handleNewAccountPass(sess *net.Session, input string) {
return
}
sess.PendingPass = input
- sess.State = net.StateNewAccountConfirm
+ sess.State = net.StateNewAccount
sess.Write("Confirm password: ")
}
-func (g *Game) handleNewAccountConfirm(sess *net.Session, input string) {
+func (g *Game) handleNewAccount(sess *net.Session, input string) {
input = strings.TrimSpace(input)
if input == "" {
sess.Conn.SetEcho(true)
diff --git a/internal/game/core_messages.go b/internal/game/core_messages.go
deleted file mode 100644
index 1c9e85a..0000000
--- a/internal/game/core_messages.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package game
-
-const (
- MsgErrLoadRecipes = "Error loading recipes."
- MsgNoCombat = "You can't do that during combat!"
- MsgNoDontHave = "You don't have any '%s'."
- MsgNeverMind = "Never mind."
- MsgSomethingWrongObject = "Something is wrong with this object."
- MsgSomethingWrongThat = "Something is wrong with that."
- MsgInventoryFull = "Your inventory is too full!"
- MsgAmbiguous = "That's ambiguous, which one?"
- MsgWhichOne = "Which one?"
-)
diff --git a/internal/game/core_production.go b/internal/game/core_production.go
index 227c4ad..59517d5 100644
--- a/internal/game/core_production.go
+++ b/internal/game/core_production.go
@@ -18,7 +18,7 @@ import (
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)
+ g.cancelBgAction(p)
skill := recipe.EffectiveSkill()
if skill != "" {
@@ -65,13 +65,7 @@ func (g *Game) startProduction(sess *net.Session, p *player.Player, recipe *acti
p.ActionState = &ActionState{Type: ActionProducing, TargetName: outputName, Verb: displayVerb}
- if g.Hub != nil {
- for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
- if other != sess && other.Player != nil {
- other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s starts %s.", p.Name, displayVerb)))
- }
- }
- }
+ g.broadcastAction(sess, "\n%s starts %s.", p.Name, displayVerb)
}
func (g *Game) startProductionFromRecipe(sess *net.Session, p *player.Player, recipe *action.RecipeDef, count int) {
@@ -106,14 +100,9 @@ func (g *Game) startProductionFromRecipe(sess *net.Session, p *player.Player, re
g.startProduction(sess, p, recipe, actionType, displayVerb, startMsg, endMsg, count)
}
-func (g *Game) loadProductionRecipe(recipeID string) *action.RecipeDef {
- all, err := g.RecipeStore.LoadAll()
- if err == nil {
- for _, r := range all {
- if r.ID == recipeID {
- return &r
- }
- }
+func (g *Game) loadRecipe(recipeID string) *action.RecipeDef {
+ if r := g.RecipeStore.GetByID(recipeID); r != nil {
+ return r
}
if strings.HasPrefix(recipeID, "combine_") {
itemID := strings.TrimPrefix(recipeID, "combine_")
@@ -132,7 +121,7 @@ func (g *Game) advanceProduction(sess *net.Session, p *player.Player) bool {
endMsg, _ := p.Action.Data["end_msg"].(string)
remaining, _ := p.Action.Data["remaining"].(int)
- recipe := g.loadProductionRecipe(recipeID)
+ recipe := g.loadRecipe(recipeID)
if recipe == nil {
g.cancelAction(p)
return false
@@ -350,25 +339,19 @@ func (g *Game) handleRecipeChoice(sess *net.Session, input string) {
func (g *Game) menuEntryName(entry map[string]string) string {
if rid, ok := entry["recipe_id"]; ok {
- all, _ := g.RecipeStore.LoadAll()
- for _, r := range all {
- if r.ID == rid {
- if def, err := g.ItemStore.Load(r.Output); err == nil {
- return def.Name
- }
- return r.Output
+ if r := g.RecipeStore.GetByID(rid); r != nil {
+ if def, err := g.ItemStore.Load(r.Output); err == nil {
+ return def.Name
}
+ return r.Output
}
}
if rid, ok := entry["fletch_recipe_id"]; ok {
- all, _ := g.RecipeStore.LoadAll()
- for _, r := range all {
- if r.ID == rid {
- if def, err := g.ItemStore.Load(r.Output); err == nil {
- return def.Name
- }
- return r.Output
+ if r := g.RecipeStore.GetByID(rid); r != nil {
+ if def, err := g.ItemStore.Load(r.Output); err == nil {
+ return def.Name
}
+ return r.Output
}
}
if barID, ok := entry["bar_id"]; ok {
@@ -399,12 +382,9 @@ func (g *Game) dispatchMenuEntry(sess *net.Session, entry map[string]string) {
}
if rid, ok := entry["fletch_recipe_id"]; ok {
p := sess.Player
- allRecipes, _ := g.RecipeStore.LoadAll()
- for _, r := range allRecipes {
- if r.ID == rid {
- g.startFletchAction(sess, p, &r, 0)
- return
- }
+ if r := g.RecipeStore.GetByID(rid); r != nil {
+ g.startFletchAction(sess, p, r, 0)
+ return
}
g.reprompt(sess)
return
@@ -416,7 +396,7 @@ func (g *Game) dispatchMenuEntry(sess *net.Session, entry map[string]string) {
g.reprompt(sess)
}
-func (g *Game) formatRecipeMaterials(r *action.RecipeDef) string {
+func (g *Game) formatMaterials(r *action.RecipeDef) string {
var parts []string
for _, e := range r.Consume {
itemName := e.Items[0]
@@ -461,7 +441,7 @@ func (g *Game) showProductionTable(sess *net.Session, p *player.Player, recipes
productName = fmt.Sprintf("%s x%d", productName, outputQty)
}
- matStr := g.formatRecipeMaterials(&r)
+ matStr := g.formatMaterials(&r)
levelStr := fmt.Sprint(r.Level)
numStr := fmt.Sprintf("%d", i+1)
@@ -524,20 +504,11 @@ func (g *Game) handleProductChoice(sess *net.Session, input string) {
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 r := g.RecipeStore.GetByID(rid); r != nil {
+ recipes = append(recipes, *r)
}
}
@@ -587,10 +558,15 @@ func (g *Game) handleProductChoice(sess *net.Session, input string) {
}
}
-func (g *Game) hasToolType(p *player.Player, toolType string) bool {
+func (g *Game) findTool(p *player.Player, toolTypes []string) (toolSpeed float64, toolName string, found bool) {
+ toolSpeed = -1
if itemID, ok := p.Equipment[object.SlotMainHand]; ok {
- if def, err := g.ItemStore.Load(itemID); err == nil && def.ToolType == toolType {
- return true
+ if def, err := g.ItemStore.Load(itemID); err == nil {
+ for _, t := range toolTypes {
+ if def.ToolType == t {
+ return def.ToolSpeed, def.Name, true
+ }
+ }
}
}
for i := 0; i < 28; i++ {
@@ -598,11 +574,22 @@ func (g *Game) hasToolType(p *player.Player, toolType string) bool {
if slot == nil {
continue
}
- if def, err := g.ItemStore.Load(slot.ItemID); err == nil && def.ToolType == toolType {
- return true
+ def, err := g.ItemStore.Load(slot.ItemID)
+ if err != nil {
+ continue
+ }
+ for _, t := range toolTypes {
+ if def.ToolType == t {
+ return def.ToolSpeed, def.Name, true
+ }
}
}
- return false
+ return -1, "", false
+}
+
+func (g *Game) hasToolType(p *player.Player, toolType string) bool {
+ _, _, found := g.findTool(p, []string{toolType})
+ return found
}
type recipeEntry struct {
ItemName string
@@ -729,6 +716,37 @@ func (g *Game) showMenuTable(sess *net.Session, title string, names []string) {
}
}
+func (g *Game) recipeName(sess *net.Session, r *action.RecipeDef) string {
+ if r.Output != "" {
+ if def, err := g.ItemStore.Load(r.Output); err == nil {
+ return g.itemColorize(sess, def, def.Name)
+ }
+ }
+ return r.DisplayName()
+}
+
+func (g *Game) showRecipeChoice(sess *net.Session, p *player.Player, recipes []action.RecipeDef, emptyMsg, title string, autoOption string) {
+ if len(recipes) == 0 {
+ sess.WriteLine(emptyMsg)
+ return
+ }
+ if len(recipes) == 1 {
+ if autoOption != "" && p.OptionBool(autoOption) {
+ g.startProductionFromRecipe(sess, p, &recipes[0], 0)
+ return
+ }
+ g.promptHowMany(sess, recipes[0].ID)
+ return
+ }
+ sess.State = net.StateRecipeChoice
+ sess.PendingMenu = recipeMenuData(recipeDefIDs(recipes))
+ var names []string
+ for i := range recipes {
+ names = append(names, g.recipeName(sess, &recipes[i]))
+ }
+ g.showMenuTable(sess, title, names)
+}
+
func (g *Game) handleHowMany(sess *net.Session, input string) {
p := sess.Player
recipeID := sess.PendingRecipeID
@@ -758,17 +776,7 @@ func (g *Game) handleHowMany(sess *net.Session, input string) {
}
recipe = g.buildCombineRecipe(itemDef)
} else {
- all, err := g.RecipeStore.LoadAll()
- if err != nil {
- g.reprompt(sess)
- return
- }
- for _, r := range all {
- if r.ID == recipeID {
- recipe = &r
- break
- }
- }
+ recipe = g.RecipeStore.GetByID(recipeID)
}
if recipe == nil {
diff --git a/internal/game/core_startup.go b/internal/game/core_startup.go
index ab52762..ef33ff7 100644
--- a/internal/game/core_startup.go
+++ b/internal/game/core_startup.go
@@ -8,6 +8,7 @@ import (
"strings"
"thehouseoficarus/internal/action"
+ "thehouseoficarus/internal/world"
)
type StartupIssue struct {
@@ -19,29 +20,29 @@ type StartupIssue struct {
func (g *Game) ValidateStartup() []StartupIssue {
var issues []StartupIssue
- issues = append(issues, validateDuplicateIDs(g.DataDir, "items", "item")...)
- issues = append(issues, validateDuplicateIDs(g.DataDir, "rooms", "room")...)
- issues = append(issues, validateDuplicateIDs(g.DataDir, "mobs", "mob")...)
- issues = append(issues, validateDuplicateIDs(g.DataDir, "objects", "object")...)
- issues = append(issues, validateDuplicateIDs(g.DataDir, "drops", "drop table")...)
- issues = append(issues, validateDuplicateIDs(g.DataDir, "recipes", "recipe")...)
- issues = append(issues, validateDuplicateIDs(g.DataDir, "courses", "course")...)
- issues = append(issues, validateDuplicateIDs(g.DataDir, "modules", "module")...)
- issues = append(issues, validateDuplicateIDs(g.DataDir, "help", "help topic")...)
-
- issues = append(issues, validateRoomReferences(g)...)
- issues = append(issues, validateMobReferences(g)...)
- issues = append(issues, validateObjectReferences(g)...)
- issues = append(issues, validateItemReferences(g)...)
- issues = append(issues, validateRecipeReferences(g)...)
- issues = append(issues, validateDropTableReferences(g)...)
- issues = append(issues, validateCourseReferences(g)...)
+ issues = append(issues, validateIDs(g.DataDir, "items", "item")...)
+ issues = append(issues, validateIDs(g.DataDir, "rooms", "room")...)
+ issues = append(issues, validateIDs(g.DataDir, "mobs", "mob")...)
+ issues = append(issues, validateIDs(g.DataDir, "objects", "object")...)
+ issues = append(issues, validateIDs(g.DataDir, "drops", "drop table")...)
+ issues = append(issues, validateIDs(g.DataDir, "recipes", "recipe")...)
+ issues = append(issues, validateIDs(g.DataDir, "courses", "course")...)
+ issues = append(issues, validateIDs(g.DataDir, "modules", "module")...)
+ issues = append(issues, validateIDs(g.DataDir, "help", "help topic")...)
+
+ issues = append(issues, validateRooms(g)...)
+ issues = append(issues, validateMobs(g)...)
+ issues = append(issues, validateObjects(g)...)
+ issues = append(issues, validateItems(g)...)
+ issues = append(issues, validateRecipes(g)...)
+ issues = append(issues, validateDropTables(g)...)
+ issues = append(issues, validateCourses(g)...)
issues = append(issues, validateRoomWiring(g)...)
return issues
}
-func validateDuplicateIDs(dataDir, subDir, label string) []StartupIssue {
+func validateIDs(dataDir, subDir, label string) []StartupIssue {
dir := filepath.Join(dataDir, subDir)
dups := action.CheckDuplicateIDs(dir)
var issues []StartupIssue
@@ -60,7 +61,7 @@ func validateDuplicateIDs(dataDir, subDir, label string) []StartupIssue {
return issues
}
-func validateRoomReferences(g *Game) []StartupIssue {
+func validateRooms(g *Game) []StartupIssue {
var issues []StartupIssue
roomIndex := g.World.RoomIndex()
itemIDs := g.ItemStore.IDSet()
@@ -86,6 +87,16 @@ func validateRoomReferences(g *Game) []StartupIssue {
}
for dir, exit := range room.Exits {
+ switch dir {
+ case world.North, world.South, world.East, world.West, world.Up, world.Down:
+ default:
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Room %d: invalid exit direction %q (only north/south/east/west/up/down supported)", id, dir),
+ })
+ continue
+ }
if exit.Room <= 0 {
continue
}
@@ -159,7 +170,7 @@ func validateRoomReferences(g *Game) []StartupIssue {
return issues
}
-func validateMobReferences(g *Game) []StartupIssue {
+func validateMobs(g *Game) []StartupIssue {
var issues []StartupIssue
itemIDs := g.ItemStore.IDSet()
dropIDs := dropTableIDSet(g.DataDir)
@@ -233,7 +244,7 @@ func validateMobReferences(g *Game) []StartupIssue {
return issues
}
-func validateObjectReferences(g *Game) []StartupIssue {
+func validateObjects(g *Game) []StartupIssue {
var issues []StartupIssue
objIDs := g.ObjectStore.IDSet()
itemIDs := g.ItemStore.IDSet()
@@ -301,7 +312,7 @@ func validateObjectReferences(g *Game) []StartupIssue {
}
if obj.Gather != nil {
- issues = append(issues, validateGatherConfig(fmt.Sprintf("Object %q: gather", id), obj.Gather, itemIDs, dropIDs)...)
+ issues = append(issues, validateGather(fmt.Sprintf("Object %q: gather", id), obj.Gather, itemIDs, dropIDs)...)
}
if obj.Talk != nil {
@@ -316,7 +327,7 @@ func validateObjectReferences(g *Game) []StartupIssue {
return issues
}
-func validateItemReferences(g *Game) []StartupIssue {
+func validateItems(g *Game) []StartupIssue {
var issues []StartupIssue
itemIDs := g.ItemStore.IDSet()
dropIDs := dropTableIDSet(g.DataDir)
@@ -393,7 +404,7 @@ func validateItemReferences(g *Game) []StartupIssue {
return issues
}
-func validateRecipeReferences(g *Game) []StartupIssue {
+func validateRecipes(g *Game) []StartupIssue {
var issues []StartupIssue
itemIDs := g.ItemStore.IDSet()
@@ -456,7 +467,7 @@ func validateRecipeReferences(g *Game) []StartupIssue {
return issues
}
-func validateDropTableReferences(g *Game) []StartupIssue {
+func validateDropTables(g *Game) []StartupIssue {
var issues []StartupIssue
dropIDs := dropTableIDSet(g.DataDir)
itemIDs := g.ItemStore.IDSet()
@@ -494,7 +505,7 @@ func validateDropTableReferences(g *Game) []StartupIssue {
return issues
}
-func validateCourseReferences(g *Game) []StartupIssue {
+func validateCourses(g *Game) []StartupIssue {
var issues []StartupIssue
roomIndex := g.World.RoomIndex()
@@ -582,7 +593,7 @@ func validateRoomWiring(g *Game) []StartupIssue {
return issues
}
-func validateGatherConfig(prefix string, cfg *action.GatherConfig, itemIDs map[string]bool, dropIDs map[string]bool) []StartupIssue {
+func validateGather(prefix string, cfg *action.GatherConfig, itemIDs map[string]bool, dropIDs map[string]bool) []StartupIssue {
var issues []StartupIssue
if cfg.Bait != "" && !itemIDs[cfg.Bait] {
diff --git a/internal/game/core_types.go b/internal/game/core_types.go
index d41b18a..898078b 100644
--- a/internal/game/core_types.go
+++ b/internal/game/core_types.go
@@ -22,7 +22,7 @@ type xpGain struct {
type deathDrop struct {
itemID string
quantity int
- totalVal int
+ totalValue int
isEquip bool
equipSlot object.EquipSlot
invSlot int
diff --git a/internal/game/core_utils.go b/internal/game/core_utils.go
index 409422e..c169580 100644
--- a/internal/game/core_utils.go
+++ b/internal/game/core_utils.go
@@ -104,6 +104,22 @@ func formatPickupList(sess *net.Session, picked []string) {
}
}
+func (g *Game) broadcastAction(sess *net.Session, format string, args ...interface{}) {
+ if g.Hub == nil {
+ return
+ }
+ p := sess.Player
+ if p == nil {
+ return
+ }
+ msg := fmt.Sprintf(format, args...)
+ for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
+ if other != sess && other.Player != nil {
+ other.WriteLine(g.colorize(other, "broadcast", msg))
+ }
+ }
+}
+
func (g *Game) newInventorySlot(itemID string, qty int) *player.InventorySlot {
slot := &player.InventorySlot{ItemID: itemID, Quantity: qty}
if def, err := g.ItemStore.Load(itemID); err == nil && def.MaxQuality > 0 {
diff --git a/internal/game/game.go b/internal/game/game.go
index 5d6e21e..ac8622e 100644
--- a/internal/game/game.go
+++ b/internal/game/game.go
@@ -47,7 +47,7 @@ type Game struct {
WorldFlags map[string]any
ColorConfig *config.ColorsConfig
DataDir string
- ValidationConfig config.StartupValidationConfig
+ ValidationConfig config.ValidationConfig
restTimers map[string]uint64
charsMu sync.Mutex
loggedInChars map[string]*net.Session
@@ -60,7 +60,7 @@ type Game struct {
hackingStates map[string]*hacking.Session
}
-func New(dataDir string, colorConfig *config.ColorsConfig, valConfig config.StartupValidationConfig) *Game {
+func New(dataDir string, colorConfig *config.ColorsConfig, valConfig config.ValidationConfig) *Game {
g := &Game{
World: world.New(dataDir),
ObjectStore: object.NewObjectStore(dataDir),
@@ -110,9 +110,9 @@ func (g *Game) HandleSession(sess *net.Session, input string) {
case net.StatePassword:
g.handlePassword(sess, input)
case net.StateNewAccountPass:
- g.handleNewAccountPass(sess, input)
- case net.StateNewAccountConfirm:
- g.handleNewAccountConfirm(sess, input)
+ g.handleNewPass(sess, input)
+ case net.StateNewAccount:
+ g.handleNewAccount(sess, input)
case net.StateMenu:
g.handleMenu(sess, input)
case net.StateNewCharName:
@@ -129,8 +129,8 @@ func (g *Game) HandleSession(sess *net.Session, input string) {
g.handlePurgeAccount(sess, input)
case net.StateGame:
g.handleGameCommand(sess, input)
- case net.StateChangeDescription:
- g.handleDescriptionChange(sess, input)
+ case net.StateChangeDesc:
+ g.handleDescChange(sess, input)
case net.StateTalk:
g.handleTalkInput(sess, input)
case net.StateShop:
@@ -138,7 +138,7 @@ func (g *Game) HandleSession(sess *net.Session, input string) {
case net.StateBank:
g.handleBankInput(sess, input)
case net.StateDropAllConfirm:
- g.handleDropAllConfirm(sess, input)
+ g.handleDropAll(sess, input)
case net.StateRecipeChoice:
g.handleRecipeChoice(sess, input)
case net.StateHowMany:
diff --git a/internal/game/hacking/hacking_liars_dice.go b/internal/game/hacking/liars_dice.go
index bcfbd40..bcfbd40 100644
--- a/internal/game/hacking/hacking_liars_dice.go
+++ b/internal/game/hacking/liars_dice.go
diff --git a/internal/game/hacking/hacking_mastermind.go b/internal/game/hacking/mastermind.go
index 8d4eb1c..8d4eb1c 100644
--- a/internal/game/hacking/hacking_mastermind.go
+++ b/internal/game/hacking/mastermind.go
diff --git a/internal/game/hacking/hacking_wumpus.go b/internal/game/hacking/wumpus.go
index 91b9243..91b9243 100644
--- a/internal/game/hacking/hacking_wumpus.go
+++ b/internal/game/hacking/wumpus.go
diff --git a/internal/game/sys_combat.go b/internal/game/sys_combat.go
index 030bed1..702871c 100644
--- a/internal/game/sys_combat.go
+++ b/internal/game/sys_combat.go
@@ -31,7 +31,7 @@ func (g *Game) dropItemsOnDeath(p *player.Player) {
items = append(items, deathDrop{
itemID: inv.ItemID,
quantity: inv.Quantity,
- totalVal: val,
+ totalValue: val,
invSlot: slot,
})
}
@@ -44,7 +44,7 @@ func (g *Game) dropItemsOnDeath(p *player.Player) {
items = append(items, deathDrop{
itemID: itemID,
quantity: 1,
- totalVal: val,
+ totalValue: val,
isEquip: true,
equipSlot: eqSlot,
})
@@ -55,7 +55,7 @@ func (g *Game) dropItemsOnDeath(p *player.Player) {
}
sort.Slice(items, func(i, j int) bool {
- return items[i].totalVal > items[j].totalVal
+ return items[i].totalValue > items[j].totalValue
})
for i := 3; i < len(items); i++ {
diff --git a/internal/game/sys_science.go b/internal/game/sys_science.go
index e35105a..376d7c7 100644
--- a/internal/game/sys_science.go
+++ b/internal/game/sys_science.go
@@ -9,6 +9,7 @@ import (
"gopkg.in/yaml.v3"
"thehouseoficarus/internal/action"
+ "thehouseoficarus/internal/net"
"thehouseoficarus/internal/object"
"thehouseoficarus/internal/player"
)
@@ -182,7 +183,7 @@ func (g *Game) hasDeckEquipped(p *player.Player) bool {
return def.WeaponType == object.WeaponScience
}
-func (g *Game) equippedProvidesJunk(p *player.Player) string {
+func (g *Game) providesJunk(p *player.Player) string {
itemID, ok := p.Equipment[object.SlotMainHand]
if !ok {
return ""
@@ -206,7 +207,7 @@ func (g *Game) effectiveJunkCost(p *player.Player, mod *ModDef) map[string]int {
delete(cost, "scrap_metal")
}
- providesJunk := g.equippedProvidesJunk(p)
+ providesJunk := g.providesJunk(p)
if providesJunk != "" {
delete(cost, providesJunk)
}
@@ -235,6 +236,31 @@ func (g *Game) consumeJunkCost(p *player.Player, mod *ModDef) bool {
return true
}
+func (g *Game) triggerModReward(sess *net.Session, p *player.Player, mod *ModDef, multiplier float64, extraGains ...xpGain) int {
+ g.consumeJunkCost(p, mod)
+
+ sciXP := int(float64(mod.BaseXP) * multiplier)
+ if sciXP < 1 {
+ sciXP = 1
+ }
+ g.awardSkillXP(sess, p, player.Science, sciXP)
+
+ for _, gain := range extraGains {
+ g.awardSkillXP(sess, p, player.SkillName(gain.Skill), gain.XP)
+ }
+
+ g.AccountStore.SaveCharacter(p)
+
+ if p.OptionBool("xp_drops") {
+ parts := []string{fmt.Sprintf("+%dxp %s", sciXP, player.SkillAbbr[player.Science])}
+ for _, gain := range extraGains {
+ parts = append(parts, fmt.Sprintf("+%dxp %s", gain.XP, player.SkillAbbr[player.SkillName(gain.Skill)]))
+ }
+ sess.WriteLine(g.colorize(sess, "xp", "("+strings.Join(parts, ", ")+")"))
+ }
+ return sciXP
+}
+
func (g *Game) junkCostString(p *player.Player, mod *ModDef) string {
cost := g.effectiveJunkCost(p, mod)
delete(cost, "scrap_metal")
@@ -259,7 +285,7 @@ func (g *Game) junkCostString(p *player.Player, mod *ModDef) string {
return strings.Join(parts, ", ")
}
-func (g *Game) totalEquipScienceAttack(p *player.Player) int {
+func (g *Game) totalScienceAttack(p *player.Player) int {
total := 0
for _, itemID := range p.Equipment {
def, err := g.ItemStore.Load(itemID)
diff --git a/internal/net/server.go b/internal/net/server.go
index f82f82b..6706a3a 100644
--- a/internal/net/server.go
+++ b/internal/net/server.go
@@ -18,7 +18,7 @@ const (
StateAccountName SessionState = iota
StatePassword
StateNewAccountPass
- StateNewAccountConfirm
+ StateNewAccount
StateMenu
StateNewCharName
StateNewCharConfirm
@@ -28,7 +28,7 @@ const (
StateDeleteChar
StatePurgeAccount
StateGame
- StateChangeDescription
+ StateChangeDesc
StateTalk
StateDropAllConfirm
StateRecipeChoice
diff --git a/internal/player/validate.go b/internal/player/validate.go
index d836089..1a2a5b9 100644
--- a/internal/player/validate.go
+++ b/internal/player/validate.go
@@ -6,10 +6,10 @@ import (
"unicode"
)
-var validNameRE = regexp.MustCompile(`^[A-Za-z0-9 ]{1,30}$`)
+var validNameRe = regexp.MustCompile(`^[A-Za-z0-9 ]{1,30}$`)
func ValidName(name string) error {
- if !validNameRE.MatchString(name) {
+ if !validNameRe.MatchString(name) {
return fmt.Errorf("name must be 1-30 alphanumeric characters or spaces")
}
return nil
diff --git a/internal/world/ground.go b/internal/world/ground.go
index 6c3ecf3..879cc71 100644
--- a/internal/world/ground.go
+++ b/internal/world/ground.go
@@ -63,7 +63,7 @@ func (w *World) GroundItems(roomID int) map[string]int {
return out
}
-func (w *World) AddReservedGroundItem(roomID int, itemID string, qty int, owner string) {
+func (w *World) AddReservedItem(roomID int, itemID string, qty int, owner string) {
w.mu.Lock()
defer w.mu.Unlock()
e := &groundEntry{
@@ -87,7 +87,7 @@ func (w *World) AddGroundItem(roomID int, itemID string, qty int) {
w.groundItems[roomID] = append(w.groundItems[roomID], e)
}
-func (w *World) RemoveReservedGroundItem(roomID int, itemID string, qty int, owner string) (int, bool) {
+func (w *World) RemoveReservedItem(roomID int, itemID string, qty int, owner string) (int, bool) {
w.mu.Lock()
defer w.mu.Unlock()
removed := 0
@@ -151,14 +151,14 @@ func (w *World) RemoveGroundItem(roomID int, itemID string, qty int) int {
return removed
}
-func (w *World) tickGroundItemsLocked() {
+func (w *World) tickGroundItems() {
for _, entries := range w.groundItems {
for _, e := range entries {
if e.respawnTimer > 0 {
e.respawnTimer--
if e.respawnTimer <= 0 {
e.quantity = e.respawnQty
- w.mergeNearbyItemsLocked(entries, e)
+ w.mergeNearbyItems(entries, e)
}
}
if e.despawnTimer > 0 {
@@ -177,7 +177,7 @@ func (w *World) tickGroundItemsLocked() {
}
}
-func (w *World) mergeNearbyItemsLocked(entries []*groundEntry, target *groundEntry) {
+func (w *World) mergeNearbyItems(entries []*groundEntry, target *groundEntry) {
for _, other := range entries {
if other != target && other.quantity > 0 && strings.EqualFold(other.itemID, target.itemID) &&
(other.reserveTimer <= 0 || other.reservedFor == "") &&
diff --git a/internal/world/objects.go b/internal/world/objects.go
index 568ea40..72e4a6d 100644
--- a/internal/world/objects.go
+++ b/internal/world/objects.go
@@ -39,10 +39,10 @@ func (w *World) ObjStateKey(roomID int, defID string, index int) string {
func (w *World) EnsureObjectStates(roomID int, defIDs []string) {
w.mu.Lock()
defer w.mu.Unlock()
- w.ensureObjectStatesLocked(roomID, defIDs)
+ w.ensureObjStates(roomID, defIDs)
}
-func (w *World) ensureObjectStatesLocked(roomID int, defIDs []string) {
+func (w *World) ensureObjStates(roomID int, defIDs []string) {
if w.objStates == nil {
w.objStates = make(map[string]*ObjState)
}
diff --git a/internal/world/room.go b/internal/world/room.go
index f006acc..9226a16 100644
--- a/internal/world/room.go
+++ b/internal/world/room.go
@@ -35,9 +35,7 @@ var OppositeExit = map[ExitDir]ExitDir{
}
var ExitOrder = []ExitDir{
- "northwest", North, "northeast",
- East, "southeast", South, "southwest",
- West, Up, Down,
+ North, South, East, West, Up, Down,
}
type SpawnDef struct {
diff --git a/internal/world/world.go b/internal/world/world.go
index 0b40918..b305c4b 100644
--- a/internal/world/world.go
+++ b/internal/world/world.go
@@ -124,6 +124,6 @@ func (w *World) RoomIndex() map[int]bool {
func (w *World) Tick() {
w.mu.Lock()
defer w.mu.Unlock()
- w.tickGroundItemsLocked()
+ w.tickGroundItems()
w.tickObjStatesLocked()
}
diff --git a/worldbuilding_guide/recipes.md b/worldbuilding_guide/recipes.md
index 29b513a..335a6cc 100644
--- a/worldbuilding_guide/recipes.md
+++ b/worldbuilding_guide/recipes.md
@@ -4,7 +4,7 @@
Recipes define how items are processed on stations to produce new items. They are the foundation for cooking, smithing, crafting, and fletching.
-Recipe files live in `data/recipes/<type>/<id>.yaml` (e.g., `data/recipes/smithing/smith_bronze_dagger.yaml`).
+Recipe files live in `data/recipes/<type>/<id>.yaml`. Two formats are supported: **individual** (one recipe per file) and **consolidated** (multiple related recipes per file). Consolidated format is preferred when many recipes share the same type, station, and materials (e.g., all bronze smithing products in one file).
### Recipe vs MadeFrom
@@ -51,9 +51,71 @@ consume:
quantity: 1
byproducts: [empty_bucket, ""] # bucket returns empty, vial is consumed entirely
- items: [herb]
- quantity: 1 # no byproducts — herb is consumed entirely
+ quantity: 1 # no byproducts — herb is consumed entirely
```
+### Consolidated Format
+
+For recipe categories where many recipes share the same `type`, `station`, and `wait` (e.g., smithing all bronze items from bronze bars, fletching all arrow shafts from logs), use a **consolidated** file. Shared fields are set once at the top level; each product entry provides only the fields that differ.
+
+**Loader behavior:** At load time, the loader expands each `products` entry into an individual `RecipeDef` with the same shape as the individual format. The production engine sees no difference — all lookup, matching, and execution code works identically.
+
+**Inheritance:** Child fields override parent. `consume` at the top level provides a default ingredient list; any product with different `consume` (e.g., a platebody needing 5 bars instead of 1) specifies its own. `output_qty`, `message`, `fail`, and `level` are per-product.
+
+```yaml
+# data/recipes/smithing/bronze.yaml — consolidated: 8 products in one file
+
+type: smithing # shared by all products
+station: [anvil] # shared by all products
+wait: 4 # shared by all products
+consume: # default ingredient list — overridden per-product if needed
+ - items: [bronze_bar]
+ quantity: 1
+
+products:
+ - id: smith_bronze_dagger
+ output: bronze_dagger
+ level: 1
+ xp: 12
+ # inherits consume: 1 bronze_bar from parent
+
+ - id: smith_bronze_nails
+ output: bronze_nails
+ level: 4
+ xp: 12
+ output_qty: 15 # stackable output, 15 per cycle
+
+ - id: smith_bronze_full_helm
+ output: bronze_full_helm
+ level: 7
+ xp: 25
+ consume: # overrides parent — 2 bars instead of 1
+ - items: [bronze_bar]
+ quantity: 2
+
+ - id: smith_bronze_platebody
+ output: bronze_platebody
+ level: 18
+ xp: 62
+ consume: # overrides parent — 5 bars
+ - items: [bronze_bar]
+ quantity: 5
+
+ # ... dagger, sword, med_helm, arrowtips, bolts_unf ...
+```
+
+**Use consolidated files when:**
+- Many recipes share the same `type`, `station`, and `material`
+- Adding a new tier means copy-pasting identical consume/station/wait fields
+- The relationship between recipes is easier to understand as a group
+
+**Use individual files when:**
+- The recipe is unique and doesn't share structure with others (e.g., compound smelting recipes with different ore ratios)
+- You want fine-grained version control on a single recipe
+- The recipe is complex enough that a consolidated file would be harder to read
+
+Individual and consolidated files coexist — the loader handles both transparently.
+
### Station-based recipe (cooking on a fire)
```yaml
@@ -127,6 +189,10 @@ success:
Smithing recipes use `type: smithing` and `station: [anvil]`. The `smith` command or `use <bar> on anvil` triggers them. Requires a hammer (`tool_type: hammer`) in inventory.
+**Recommended:** Use consolidated format (one file per metal type) since all products for a given metal share the same bar, station, and wait. See [Consolidated Format](#consolidated-format) above.
+
+The individual format is also supported:
+
```yaml
id: smith_steel_platebody
type: smithing