aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorworkhorse <workhorse@localhost.localdomain>2026-06-19 20:24:52 -0400
committerworkhorse <workhorse@localhost.localdomain>2026-06-19 20:24:52 -0400
commit8ee8982b8759bcae116647cc993867f4c8b0a6ce (patch)
treeee01f7b1d745cbc94d9981108a1209527487b3e5
parent2bec24859af8f03c80a0a58e3240519942450167 (diff)
downloadthehouseoficarus-8ee8982b8759bcae116647cc993867f4c8b0a6ce.tar.gz
reorganized items, objects, and rooms in directories. oranized module names. added startup checks.
-rw-r--r--AGENTS.md635
-rw-r--r--cmd/mud/main.go2
-rw-r--r--data/items/ammo/adamant_arrow.yaml (renamed from data/items/adamant_arrow.yaml)0
-rw-r--r--data/items/ammo/adamant_arrowtips.yaml (renamed from data/items/adamant_arrowtips.yaml)0
-rw-r--r--data/items/ammo/adamant_bolts.yaml (renamed from data/items/adamant_bolts.yaml)0
-rw-r--r--data/items/ammo/adamant_bolts_unf.yaml (renamed from data/items/adamant_bolts_unf.yaml)0
-rw-r--r--data/items/ammo/arrow_shaft.yaml (renamed from data/items/arrow_shaft.yaml)0
-rw-r--r--data/items/ammo/bronze_arrow.yaml (renamed from data/items/bronze_arrow.yaml)0
-rw-r--r--data/items/ammo/bronze_arrowtips.yaml (renamed from data/items/bronze_arrowtips.yaml)0
-rw-r--r--data/items/ammo/bronze_bolts.yaml (renamed from data/items/bronze_bolts.yaml)0
-rw-r--r--data/items/ammo/bronze_bolts_unf.yaml (renamed from data/items/bronze_bolts_unf.yaml)0
-rw-r--r--data/items/ammo/diamond_bolt_tips.yaml (renamed from data/items/diamond_bolt_tips.yaml)0
-rw-r--r--data/items/ammo/diamond_bolts.yaml (renamed from data/items/diamond_bolts.yaml)0
-rw-r--r--data/items/ammo/diamond_bolts_e.yaml (renamed from data/items/diamond_bolts_e.yaml)0
-rw-r--r--data/items/ammo/emerald_bolt_tips.yaml (renamed from data/items/emerald_bolt_tips.yaml)0
-rw-r--r--data/items/ammo/emerald_bolts.yaml (renamed from data/items/emerald_bolts.yaml)0
-rw-r--r--data/items/ammo/emerald_bolts_e.yaml (renamed from data/items/emerald_bolts_e.yaml)0
-rw-r--r--data/items/ammo/headless_arrow.yaml (renamed from data/items/headless_arrow.yaml)0
-rw-r--r--data/items/ammo/iron_arrow.yaml (renamed from data/items/iron_arrow.yaml)0
-rw-r--r--data/items/ammo/iron_arrowtips.yaml (renamed from data/items/iron_arrowtips.yaml)0
-rw-r--r--data/items/ammo/iron_bolts.yaml (renamed from data/items/iron_bolts.yaml)0
-rw-r--r--data/items/ammo/iron_bolts_unf.yaml (renamed from data/items/iron_bolts_unf.yaml)0
-rw-r--r--data/items/ammo/mithril_arrow.yaml (renamed from data/items/mithril_arrow.yaml)0
-rw-r--r--data/items/ammo/mithril_arrowtips.yaml (renamed from data/items/mithril_arrowtips.yaml)0
-rw-r--r--data/items/ammo/mithril_bolts.yaml (renamed from data/items/mithril_bolts.yaml)0
-rw-r--r--data/items/ammo/mithril_bolts_unf.yaml (renamed from data/items/mithril_bolts_unf.yaml)0
-rw-r--r--data/items/ammo/ruby_bolt_tips.yaml (renamed from data/items/ruby_bolt_tips.yaml)0
-rw-r--r--data/items/ammo/ruby_bolts.yaml (renamed from data/items/ruby_bolts.yaml)0
-rw-r--r--data/items/ammo/ruby_bolts_e.yaml (renamed from data/items/ruby_bolts_e.yaml)0
-rw-r--r--data/items/ammo/rune_arrow.yaml (renamed from data/items/rune_arrow.yaml)0
-rw-r--r--data/items/ammo/rune_arrowtips.yaml (renamed from data/items/rune_arrowtips.yaml)0
-rw-r--r--data/items/ammo/rune_bolts.yaml (renamed from data/items/rune_bolts.yaml)0
-rw-r--r--data/items/ammo/rune_bolts_unf.yaml (renamed from data/items/rune_bolts_unf.yaml)0
-rw-r--r--data/items/ammo/sapphire_bolt_tips.yaml (renamed from data/items/sapphire_bolt_tips.yaml)0
-rw-r--r--data/items/ammo/sapphire_bolts.yaml (renamed from data/items/sapphire_bolts.yaml)0
-rw-r--r--data/items/ammo/sapphire_bolts_e.yaml (renamed from data/items/sapphire_bolts_e.yaml)0
-rw-r--r--data/items/ammo/steel_arrow.yaml (renamed from data/items/steel_arrow.yaml)0
-rw-r--r--data/items/ammo/steel_arrowtips.yaml (renamed from data/items/steel_arrowtips.yaml)0
-rw-r--r--data/items/ammo/steel_bolts.yaml (renamed from data/items/steel_bolts.yaml)0
-rw-r--r--data/items/ammo/steel_bolts_unf.yaml (renamed from data/items/steel_bolts_unf.yaml)0
-rw-r--r--data/items/avantoe_potion_unf.yaml12
-rw-r--r--data/items/bread_dough.yaml14
-rw-r--r--data/items/cadantine_potion_unf.yaml12
-rw-r--r--data/items/consumables/acid_vial.yaml (renamed from data/items/acid_vial.yaml)0
-rw-r--r--data/items/consumables/amp_potion.yaml (renamed from data/items/amp_potion.yaml)0
-rw-r--r--data/items/consumables/anchovies.yaml (renamed from data/items/anchovies.yaml)0
-rw-r--r--data/items/consumables/antitoxin_powder.yaml (renamed from data/items/antitoxin_powder.yaml)0
-rw-r--r--data/items/consumables/apple.yaml (renamed from data/items/apple.yaml)0
-rw-r--r--data/items/consumables/avantoe_potion_unf.yaml12
-rw-r--r--data/items/consumables/bio_serum.yaml (renamed from data/items/bio_serum.yaml)0
-rw-r--r--data/items/consumables/blight_spore.yaml (renamed from data/items/blight_spore.yaml)0
-rw-r--r--data/items/consumables/bread.yaml (renamed from data/items/bread.yaml)0
-rw-r--r--data/items/consumables/bread_dough.yaml14
-rw-r--r--data/items/consumables/bucket_of_water.yaml (renamed from data/items/bucket_of_water.yaml)0
-rw-r--r--data/items/consumables/burnt_fish.yaml (renamed from data/items/burnt_fish.yaml)0
-rw-r--r--data/items/consumables/burnt_meat.yaml (renamed from data/items/burnt_meat.yaml)0
-rw-r--r--data/items/consumables/cabbage.yaml (renamed from data/items/cabbage.yaml)0
-rw-r--r--data/items/consumables/cadantine_potion_unf.yaml12
-rw-r--r--data/items/consumables/catalyst_wine.yaml (renamed from data/items/catalyst_wine.yaml)0
-rw-r--r--data/items/consumables/cheese.yaml (renamed from data/items/cheese.yaml)0
-rw-r--r--data/items/consumables/dwarf_weed_potion_unf.yaml12
-rw-r--r--data/items/consumables/full_restore.yaml (renamed from data/items/full_restore.yaml)0
-rw-r--r--data/items/consumables/guam_potion_unf.yaml12
-rw-r--r--data/items/consumables/harralander_potion_unf.yaml12
-rw-r--r--data/items/consumables/herring.yaml (renamed from data/items/herring.yaml)0
-rw-r--r--data/items/consumables/irit_potion_unf.yaml12
-rw-r--r--data/items/consumables/jug_of_water.yaml (renamed from data/items/jug_of_water.yaml)0
-rw-r--r--data/items/consumables/kwuarm_potion_unf.yaml12
-rw-r--r--data/items/consumables/lantadyme_potion_unf.yaml12
-rw-r--r--data/items/consumables/marrentill_potion_unf.yaml12
-rw-r--r--data/items/consumables/nano_restore.yaml (renamed from data/items/nano_restore.yaml)0
-rw-r--r--data/items/consumables/onion.yaml (renamed from data/items/onion.yaml)0
-rw-r--r--data/items/consumables/pastry_dough.yaml11
-rw-r--r--data/items/consumables/pie_dish.yaml (renamed from data/items/pie_dish.yaml)0
-rw-r--r--data/items/consumables/pizza_dough.yaml11
-rw-r--r--data/items/consumables/plant_cure.yaml (renamed from data/items/plant_cure.yaml)0
-rw-r--r--data/items/consumables/pot_of_flour.yaml (renamed from data/items/pot_of_flour.yaml)0
-rw-r--r--data/items/consumables/potato.yaml (renamed from data/items/potato.yaml)0
-rw-r--r--data/items/consumables/ranarr_potion_unf.yaml12
-rw-r--r--data/items/consumables/raw_anchovies.yaml (renamed from data/items/raw_anchovies.yaml)0
-rw-r--r--data/items/consumables/raw_anglerfish.yaml (renamed from data/items/raw_anglerfish.yaml)0
-rw-r--r--data/items/consumables/raw_bass.yaml (renamed from data/items/raw_bass.yaml)0
-rw-r--r--data/items/consumables/raw_beef.yaml (renamed from data/items/raw_beef.yaml)0
-rw-r--r--data/items/consumables/raw_cave_eel.yaml (renamed from data/items/raw_cave_eel.yaml)0
-rw-r--r--data/items/consumables/raw_cod.yaml (renamed from data/items/raw_cod.yaml)0
-rw-r--r--data/items/consumables/raw_herring.yaml (renamed from data/items/raw_herring.yaml)0
-rw-r--r--data/items/consumables/raw_lobster.yaml (renamed from data/items/raw_lobster.yaml)0
-rw-r--r--data/items/consumables/raw_mackerel.yaml (renamed from data/items/raw_mackerel.yaml)0
-rw-r--r--data/items/consumables/raw_pike.yaml (renamed from data/items/raw_pike.yaml)0
-rw-r--r--data/items/consumables/raw_rainbow_fish.yaml (renamed from data/items/raw_rainbow_fish.yaml)0
-rw-r--r--data/items/consumables/raw_salmon.yaml (renamed from data/items/raw_salmon.yaml)0
-rw-r--r--data/items/consumables/raw_sardine.yaml (renamed from data/items/raw_sardine.yaml)0
-rw-r--r--data/items/consumables/raw_shark.yaml (renamed from data/items/raw_shark.yaml)0
-rw-r--r--data/items/consumables/raw_shrimps.yaml (renamed from data/items/raw_shrimps.yaml)0
-rw-r--r--data/items/consumables/raw_slimy_eel.yaml (renamed from data/items/raw_slimy_eel.yaml)0
-rw-r--r--data/items/consumables/raw_swordfish.yaml (renamed from data/items/raw_swordfish.yaml)0
-rw-r--r--data/items/consumables/raw_trout.yaml (renamed from data/items/raw_trout.yaml)0
-rw-r--r--data/items/consumables/raw_tuna.yaml (renamed from data/items/raw_tuna.yaml)0
-rw-r--r--data/items/consumables/restoration_compound.yaml (renamed from data/items/restoration_compound.yaml)0
-rw-r--r--data/items/consumables/salmon.yaml (renamed from data/items/salmon.yaml)0
-rw-r--r--data/items/consumables/sardine.yaml (renamed from data/items/sardine.yaml)0
-rw-r--r--data/items/consumables/science_serum.yaml (renamed from data/items/science_serum.yaml)0
-rw-r--r--data/items/consumables/shield_potion.yaml (renamed from data/items/shield_potion.yaml)0
-rw-r--r--data/items/consumables/shrimps.yaml (renamed from data/items/shrimps.yaml)0
-rw-r--r--data/items/consumables/snapdragon_potion_unf.yaml12
-rw-r--r--data/items/consumables/stim_potion.yaml (renamed from data/items/stim_potion.yaml)0
-rw-r--r--data/items/consumables/strawberry.yaml (renamed from data/items/strawberry.yaml)0
-rw-r--r--data/items/consumables/super_amp.yaml (renamed from data/items/super_amp.yaml)0
-rw-r--r--data/items/consumables/super_bio_serum.yaml (renamed from data/items/super_bio_serum.yaml)0
-rw-r--r--data/items/consumables/super_shield.yaml (renamed from data/items/super_shield.yaml)0
-rw-r--r--data/items/consumables/super_stim.yaml (renamed from data/items/super_stim.yaml)0
-rw-r--r--data/items/consumables/super_stim_cell.yaml (renamed from data/items/super_stim_cell.yaml)0
-rw-r--r--data/items/consumables/sweetcorn.yaml (renamed from data/items/sweetcorn.yaml)0
-rw-r--r--data/items/consumables/targeting_serum.yaml (renamed from data/items/targeting_serum.yaml)0
-rw-r--r--data/items/consumables/tarromin_potion_unf.yaml12
-rw-r--r--data/items/consumables/tech_serum.yaml (renamed from data/items/tech_serum.yaml)0
-rw-r--r--data/items/consumables/toadflax_potion_unf.yaml12
-rw-r--r--data/items/consumables/tomato.yaml (renamed from data/items/tomato.yaml)0
-rw-r--r--data/items/consumables/torstol_potion_unf.yaml12
-rw-r--r--data/items/consumables/trout.yaml (renamed from data/items/trout.yaml)0
-rw-r--r--data/items/consumables/vial_of_water.yaml (renamed from data/items/vial_of_water.yaml)0
-rw-r--r--data/items/consumables/watermelon.yaml (renamed from data/items/watermelon.yaml)0
-rw-r--r--data/items/decks/advanced_bio_deck.yaml (renamed from data/items/advanced_bio_deck.yaml)0
-rw-r--r--data/items/decks/advanced_eco_deck.yaml (renamed from data/items/advanced_eco_deck.yaml)0
-rw-r--r--data/items/decks/advanced_hydro_deck.yaml (renamed from data/items/advanced_hydro_deck.yaml)0
-rw-r--r--data/items/decks/advanced_solar_deck.yaml (renamed from data/items/advanced_solar_deck.yaml)0
-rw-r--r--data/items/decks/basic_deck.yaml (renamed from data/items/basic_deck.yaml)0
-rw-r--r--data/items/decks/bio_deck.yaml (renamed from data/items/bio_deck.yaml)0
-rw-r--r--data/items/decks/bio_identifier.yaml (renamed from data/items/bio_identifier.yaml)0
-rw-r--r--data/items/decks/biojunk.yaml (renamed from data/items/biojunk.yaml)0
-rw-r--r--data/items/decks/blood_identifier.yaml (renamed from data/items/blood_identifier.yaml)0
-rw-r--r--data/items/decks/bloodjunk.yaml (renamed from data/items/bloodjunk.yaml)0
-rw-r--r--data/items/decks/chaos_identifier.yaml (renamed from data/items/chaos_identifier.yaml)0
-rw-r--r--data/items/decks/chaosjunk.yaml (renamed from data/items/chaosjunk.yaml)0
-rw-r--r--data/items/decks/cosmic_identifier.yaml (renamed from data/items/cosmic_identifier.yaml)0
-rw-r--r--data/items/decks/cosmicjunk.yaml (renamed from data/items/cosmicjunk.yaml)0
-rw-r--r--data/items/decks/death_identifier.yaml (renamed from data/items/death_identifier.yaml)0
-rw-r--r--data/items/decks/deathjunk.yaml (renamed from data/items/deathjunk.yaml)0
-rw-r--r--data/items/decks/eco_deck.yaml (renamed from data/items/eco_deck.yaml)0
-rw-r--r--data/items/decks/eco_identifier.yaml (renamed from data/items/eco_identifier.yaml)0
-rw-r--r--data/items/decks/ecojunk.yaml (renamed from data/items/ecojunk.yaml)0
-rw-r--r--data/items/decks/hydro_deck.yaml (renamed from data/items/hydro_deck.yaml)0
-rw-r--r--data/items/decks/hydro_identifier.yaml (renamed from data/items/hydro_identifier.yaml)0
-rw-r--r--data/items/decks/hydrojunk.yaml (renamed from data/items/hydrojunk.yaml)0
-rw-r--r--data/items/decks/law_identifier.yaml (renamed from data/items/law_identifier.yaml)0
-rw-r--r--data/items/decks/lawjunk.yaml (renamed from data/items/lawjunk.yaml)0
-rw-r--r--data/items/decks/nature_identifier.yaml (renamed from data/items/nature_identifier.yaml)0
-rw-r--r--data/items/decks/naturejunk.yaml (renamed from data/items/naturejunk.yaml)0
-rw-r--r--data/items/decks/solar_deck.yaml (renamed from data/items/solar_deck.yaml)0
-rw-r--r--data/items/decks/solar_identifier.yaml (renamed from data/items/solar_identifier.yaml)0
-rw-r--r--data/items/decks/solarjunk.yaml (renamed from data/items/solarjunk.yaml)0
-rw-r--r--data/items/dwarf_weed_potion_unf.yaml12
-rw-r--r--data/items/equipment/abyssal_bracelet.yaml (renamed from data/items/abyssal_bracelet.yaml)0
-rw-r--r--data/items/equipment/adamant_dagger.yaml (renamed from data/items/adamant_dagger.yaml)0
-rw-r--r--data/items/equipment/adamant_full_helm.yaml (renamed from data/items/adamant_full_helm.yaml)0
-rw-r--r--data/items/equipment/adamant_med_helm.yaml (renamed from data/items/adamant_med_helm.yaml)0
-rw-r--r--data/items/equipment/adamant_platebody.yaml (renamed from data/items/adamant_platebody.yaml)0
-rw-r--r--data/items/equipment/adamant_sword.yaml (renamed from data/items/adamant_sword.yaml)0
-rw-r--r--data/items/equipment/binding_necklace.yaml (renamed from data/items/binding_necklace.yaml)0
-rw-r--r--data/items/equipment/black_axe.yaml (renamed from data/items/black_axe.yaml)0
-rw-r--r--data/items/equipment/bracelet_of_clay.yaml (renamed from data/items/bracelet_of_clay.yaml)0
-rw-r--r--data/items/equipment/bracelet_of_slaughter.yaml (renamed from data/items/bracelet_of_slaughter.yaml)0
-rw-r--r--data/items/equipment/bronze_dagger.yaml (renamed from data/items/bronze_dagger.yaml)0
-rw-r--r--data/items/equipment/bronze_full_helm.yaml (renamed from data/items/bronze_full_helm.yaml)0
-rw-r--r--data/items/equipment/bronze_med_helm.yaml (renamed from data/items/bronze_med_helm.yaml)0
-rw-r--r--data/items/equipment/bronze_platebody.yaml (renamed from data/items/bronze_platebody.yaml)0
-rw-r--r--data/items/equipment/bronze_sword.yaml (renamed from data/items/bronze_sword.yaml)0
-rw-r--r--data/items/equipment/capacitor_gloves.yaml (renamed from data/items/capacitor_gloves.yaml)0
-rw-r--r--data/items/equipment/cape_of_agility.yaml (renamed from data/items/cape_of_agility.yaml)0
-rw-r--r--data/items/equipment/chitin_plate.yaml (renamed from data/items/chitin_plate.yaml)0
-rw-r--r--data/items/equipment/coif.yaml (renamed from data/items/coif.yaml)0
-rw-r--r--data/items/equipment/diamond_amulet_u.yaml (renamed from data/items/diamond_amulet_u.yaml)0
-rw-r--r--data/items/equipment/diamond_bracelet.yaml (renamed from data/items/diamond_bracelet.yaml)0
-rw-r--r--data/items/equipment/diamond_necklace.yaml (renamed from data/items/diamond_necklace.yaml)0
-rw-r--r--data/items/equipment/diamond_ring.yaml (renamed from data/items/diamond_ring.yaml)0
-rw-r--r--data/items/equipment/digsite_pendant.yaml (renamed from data/items/digsite_pendant.yaml)0
-rw-r--r--data/items/equipment/dragon_axe.yaml (renamed from data/items/dragon_axe.yaml)0
-rw-r--r--data/items/equipment/emerald_amulet_u.yaml (renamed from data/items/emerald_amulet_u.yaml)0
-rw-r--r--data/items/equipment/emerald_bracelet.yaml (renamed from data/items/emerald_bracelet.yaml)0
-rw-r--r--data/items/equipment/emerald_necklace.yaml (renamed from data/items/emerald_necklace.yaml)0
-rw-r--r--data/items/equipment/emerald_ring.yaml (renamed from data/items/emerald_ring.yaml)0
-rw-r--r--data/items/equipment/gold_amulet_u.yaml (renamed from data/items/gold_amulet_u.yaml)0
-rw-r--r--data/items/equipment/gold_bracelet.yaml (renamed from data/items/gold_bracelet.yaml)0
-rw-r--r--data/items/equipment/gold_necklace.yaml (renamed from data/items/gold_necklace.yaml)0
-rw-r--r--data/items/equipment/gold_ring.yaml (renamed from data/items/gold_ring.yaml)0
-rw-r--r--data/items/equipment/graceful_boots.yaml (renamed from data/items/graceful_boots.yaml)0
-rw-r--r--data/items/equipment/graceful_cape.yaml (renamed from data/items/graceful_cape.yaml)0
-rw-r--r--data/items/equipment/graceful_gloves.yaml (renamed from data/items/graceful_gloves.yaml)0
-rw-r--r--data/items/equipment/graceful_hat.yaml (renamed from data/items/graceful_hat.yaml)0
-rw-r--r--data/items/equipment/graceful_legs.yaml (renamed from data/items/graceful_legs.yaml)0
-rw-r--r--data/items/equipment/graceful_torso.yaml (renamed from data/items/graceful_torso.yaml)0
-rw-r--r--data/items/equipment/hard_leather_body.yaml (renamed from data/items/hard_leather_body.yaml)0
-rw-r--r--data/items/equipment/hard_leather_shield.yaml (renamed from data/items/hard_leather_shield.yaml)0
-rw-r--r--data/items/equipment/inoculation_bracelet.yaml (renamed from data/items/inoculation_bracelet.yaml)0
-rw-r--r--data/items/equipment/insulated_gloves.yaml (renamed from data/items/insulated_gloves.yaml)0
-rw-r--r--data/items/equipment/iron_dagger.yaml (renamed from data/items/iron_dagger.yaml)0
-rw-r--r--data/items/equipment/iron_full_helm.yaml (renamed from data/items/iron_full_helm.yaml)0
-rw-r--r--data/items/equipment/iron_med_helm.yaml (renamed from data/items/iron_med_helm.yaml)0
-rw-r--r--data/items/equipment/iron_platebody.yaml (renamed from data/items/iron_platebody.yaml)0
-rw-r--r--data/items/equipment/iron_sword.yaml (renamed from data/items/iron_sword.yaml)0
-rw-r--r--data/items/equipment/leather_body.yaml (renamed from data/items/leather_body.yaml)0
-rw-r--r--data/items/equipment/leather_boots.yaml (renamed from data/items/leather_boots.yaml)0
-rw-r--r--data/items/equipment/leather_chaps.yaml (renamed from data/items/leather_chaps.yaml)0
-rw-r--r--data/items/equipment/leather_cowl.yaml (renamed from data/items/leather_cowl.yaml)0
-rw-r--r--data/items/equipment/leather_gloves.yaml (renamed from data/items/leather_gloves.yaml)0
-rw-r--r--data/items/equipment/leather_vambraces.yaml (renamed from data/items/leather_vambraces.yaml)0
-rw-r--r--data/items/equipment/longbow.yaml (renamed from data/items/longbow.yaml)0
-rw-r--r--data/items/equipment/longbow_u.yaml (renamed from data/items/longbow_u.yaml)0
-rw-r--r--data/items/equipment/magic_longbow.yaml (renamed from data/items/magic_longbow.yaml)0
-rw-r--r--data/items/equipment/magic_longbow_u.yaml (renamed from data/items/magic_longbow_u.yaml)0
-rw-r--r--data/items/equipment/magic_shortbow.yaml (renamed from data/items/magic_shortbow.yaml)0
-rw-r--r--data/items/equipment/magic_shortbow_u.yaml (renamed from data/items/magic_shortbow_u.yaml)0
-rw-r--r--data/items/equipment/maple_longbow.yaml (renamed from data/items/maple_longbow.yaml)0
-rw-r--r--data/items/equipment/maple_longbow_u.yaml (renamed from data/items/maple_longbow_u.yaml)0
-rw-r--r--data/items/equipment/maple_shortbow.yaml (renamed from data/items/maple_shortbow.yaml)0
-rw-r--r--data/items/equipment/maple_shortbow_u.yaml (renamed from data/items/maple_shortbow_u.yaml)0
-rw-r--r--data/items/equipment/mithril_dagger.yaml (renamed from data/items/mithril_dagger.yaml)0
-rw-r--r--data/items/equipment/mithril_full_helm.yaml (renamed from data/items/mithril_full_helm.yaml)0
-rw-r--r--data/items/equipment/mithril_med_helm.yaml (renamed from data/items/mithril_med_helm.yaml)0
-rw-r--r--data/items/equipment/mithril_platebody.yaml (renamed from data/items/mithril_platebody.yaml)0
-rw-r--r--data/items/equipment/mithril_sword.yaml (renamed from data/items/mithril_sword.yaml)0
-rw-r--r--data/items/equipment/necklace_of_passage.yaml (renamed from data/items/necklace_of_passage.yaml)0
-rw-r--r--data/items/equipment/neural_headband.yaml (renamed from data/items/neural_headband.yaml)0
-rw-r--r--data/items/equipment/oak_longbow.yaml (renamed from data/items/oak_longbow.yaml)0
-rw-r--r--data/items/equipment/oak_longbow_u.yaml (renamed from data/items/oak_longbow_u.yaml)0
-rw-r--r--data/items/equipment/oak_shortbow.yaml (renamed from data/items/oak_shortbow.yaml)0
-rw-r--r--data/items/equipment/oak_shortbow_u.yaml (renamed from data/items/oak_shortbow_u.yaml)0
-rw-r--r--data/items/equipment/party_hat_blue.yaml (renamed from data/items/party_hat_blue.yaml)0
-rw-r--r--data/items/equipment/party_hat_green.yaml (renamed from data/items/party_hat_green.yaml)0
-rw-r--r--data/items/equipment/party_hat_pink.yaml (renamed from data/items/party_hat_pink.yaml)0
-rw-r--r--data/items/equipment/party_hat_red.yaml (renamed from data/items/party_hat_red.yaml)0
-rw-r--r--data/items/equipment/party_hat_white.yaml (renamed from data/items/party_hat_white.yaml)0
-rw-r--r--data/items/equipment/party_hat_yellow.yaml (renamed from data/items/party_hat_yellow.yaml)0
-rw-r--r--data/items/equipment/phoenix_necklace.yaml (renamed from data/items/phoenix_necklace.yaml)0
-rw-r--r--data/items/equipment/power_amulet.yaml (renamed from data/items/power_amulet.yaml)0
-rw-r--r--data/items/equipment/ring_of_dueling.yaml (renamed from data/items/ring_of_dueling.yaml)0
-rw-r--r--data/items/equipment/ring_of_forging.yaml (renamed from data/items/ring_of_forging.yaml)0
-rw-r--r--data/items/equipment/ring_of_life.yaml (renamed from data/items/ring_of_life.yaml)0
-rw-r--r--data/items/equipment/ring_of_recoil.yaml (renamed from data/items/ring_of_recoil.yaml)0
-rw-r--r--data/items/equipment/ruby_amulet_u.yaml (renamed from data/items/ruby_amulet_u.yaml)0
-rw-r--r--data/items/equipment/ruby_bracelet.yaml (renamed from data/items/ruby_bracelet.yaml)0
-rw-r--r--data/items/equipment/ruby_necklace.yaml (renamed from data/items/ruby_necklace.yaml)0
-rw-r--r--data/items/equipment/ruby_ring.yaml (renamed from data/items/ruby_ring.yaml)0
-rw-r--r--data/items/equipment/rune_dagger.yaml (renamed from data/items/rune_dagger.yaml)0
-rw-r--r--data/items/equipment/rune_full_helm.yaml (renamed from data/items/rune_full_helm.yaml)0
-rw-r--r--data/items/equipment/rune_med_helm.yaml (renamed from data/items/rune_med_helm.yaml)0
-rw-r--r--data/items/equipment/rune_platebody.yaml (renamed from data/items/rune_platebody.yaml)0
-rw-r--r--data/items/equipment/rune_sword.yaml (renamed from data/items/rune_sword.yaml)0
-rw-r--r--data/items/equipment/sapphire_amulet_u.yaml (renamed from data/items/sapphire_amulet_u.yaml)0
-rw-r--r--data/items/equipment/sapphire_bracelet.yaml (renamed from data/items/sapphire_bracelet.yaml)0
-rw-r--r--data/items/equipment/sapphire_necklace.yaml (renamed from data/items/sapphire_necklace.yaml)0
-rw-r--r--data/items/equipment/sapphire_ring.yaml (renamed from data/items/sapphire_ring.yaml)0
-rw-r--r--data/items/equipment/shortbow.yaml (renamed from data/items/shortbow.yaml)0
-rw-r--r--data/items/equipment/shortbow_u.yaml (renamed from data/items/shortbow_u.yaml)0
-rw-r--r--data/items/equipment/spectral_visor.yaml (renamed from data/items/spectral_visor.yaml)0
-rw-r--r--data/items/equipment/steel_dagger.yaml (renamed from data/items/steel_dagger.yaml)0
-rw-r--r--data/items/equipment/steel_full_helm.yaml (renamed from data/items/steel_full_helm.yaml)0
-rw-r--r--data/items/equipment/steel_med_helm.yaml (renamed from data/items/steel_med_helm.yaml)0
-rw-r--r--data/items/equipment/steel_platebody.yaml (renamed from data/items/steel_platebody.yaml)0
-rw-r--r--data/items/equipment/steel_sword.yaml (renamed from data/items/steel_sword.yaml)0
-rw-r--r--data/items/equipment/willow_longbow.yaml (renamed from data/items/willow_longbow.yaml)0
-rw-r--r--data/items/equipment/willow_longbow_u.yaml (renamed from data/items/willow_longbow_u.yaml)0
-rw-r--r--data/items/equipment/willow_shortbow.yaml (renamed from data/items/willow_shortbow.yaml)0
-rw-r--r--data/items/equipment/willow_shortbow_u.yaml (renamed from data/items/willow_shortbow_u.yaml)0
-rw-r--r--data/items/equipment/yew_longbow.yaml (renamed from data/items/yew_longbow.yaml)0
-rw-r--r--data/items/equipment/yew_longbow_u.yaml (renamed from data/items/yew_longbow_u.yaml)0
-rw-r--r--data/items/equipment/yew_shortbow.yaml (renamed from data/items/yew_shortbow.yaml)0
-rw-r--r--data/items/equipment/yew_shortbow_u.yaml (renamed from data/items/yew_shortbow_u.yaml)0
-rw-r--r--data/items/guam_potion_unf.yaml12
-rw-r--r--data/items/harralander_potion_unf.yaml12
-rw-r--r--data/items/irit_potion_unf.yaml12
-rw-r--r--data/items/kwuarm_potion_unf.yaml12
-rw-r--r--data/items/lantadyme_potion_unf.yaml12
-rw-r--r--data/items/marrentill_potion_unf.yaml12
-rw-r--r--data/items/materials/adamant_nails.yaml (renamed from data/items/adamant_nails.yaml)0
-rw-r--r--data/items/materials/adamantite_bar.yaml (renamed from data/items/adamantite_bar.yaml)0
-rw-r--r--data/items/materials/adamantite_ore.yaml (renamed from data/items/adamantite_ore.yaml)0
-rw-r--r--data/items/materials/arachnid_enzyme.yaml (renamed from data/items/arachnid_enzyme.yaml)0
-rw-r--r--data/items/materials/ashes.yaml (renamed from data/items/ashes.yaml)0
-rw-r--r--data/items/materials/avantoe.yaml (renamed from data/items/avantoe.yaml)0
-rw-r--r--data/items/materials/ball_of_wool.yaml (renamed from data/items/ball_of_wool.yaml)0
-rw-r--r--data/items/materials/bones.yaml (renamed from data/items/bones.yaml)0
-rw-r--r--data/items/materials/bowstring.yaml (renamed from data/items/bowstring.yaml)0
-rw-r--r--data/items/materials/bronze_bar.yaml (renamed from data/items/bronze_bar.yaml)0
-rw-r--r--data/items/materials/bronze_nails.yaml (renamed from data/items/bronze_nails.yaml)0
-rw-r--r--data/items/materials/cadantine.yaml (renamed from data/items/cadantine.yaml)0
-rw-r--r--data/items/materials/chocolate_dust.yaml (renamed from data/items/chocolate_dust.yaml)0
-rw-r--r--data/items/materials/clay.yaml (renamed from data/items/clay.yaml)0
-rw-r--r--data/items/materials/coal.yaml (renamed from data/items/coal.yaml)0
-rw-r--r--data/items/materials/copper_ore.yaml (renamed from data/items/copper_ore.yaml)0
-rw-r--r--data/items/materials/cowhide.yaml (renamed from data/items/cowhide.yaml)0
-rw-r--r--data/items/materials/crushed_nest.yaml (renamed from data/items/crushed_nest.yaml)0
-rw-r--r--data/items/materials/diamond.yaml (renamed from data/items/diamond.yaml)0
-rw-r--r--data/items/materials/dwarf_weed.yaml (renamed from data/items/dwarf_weed.yaml)0
-rw-r--r--data/items/materials/ectoplasm.yaml (renamed from data/items/ectoplasm.yaml)0
-rw-r--r--data/items/materials/emerald.yaml (renamed from data/items/emerald.yaml)0
-rw-r--r--data/items/materials/eye_of_newt.yaml (renamed from data/items/eye_of_newt.yaml)0
-rw-r--r--data/items/materials/feather.yaml (renamed from data/items/feather.yaml)0
-rw-r--r--data/items/materials/flax.yaml (renamed from data/items/flax.yaml)0
-rw-r--r--data/items/materials/gold_bar.yaml (renamed from data/items/gold_bar.yaml)0
-rw-r--r--data/items/materials/gold_ore.yaml (renamed from data/items/gold_ore.yaml)0
-rw-r--r--data/items/materials/grimy_avantoe.yaml (renamed from data/items/grimy_avantoe.yaml)0
-rw-r--r--data/items/materials/grimy_cadantine.yaml (renamed from data/items/grimy_cadantine.yaml)0
-rw-r--r--data/items/materials/grimy_dwarf_weed.yaml (renamed from data/items/grimy_dwarf_weed.yaml)0
-rw-r--r--data/items/materials/grimy_guam.yaml (renamed from data/items/grimy_guam.yaml)0
-rw-r--r--data/items/materials/grimy_harralander.yaml (renamed from data/items/grimy_harralander.yaml)0
-rw-r--r--data/items/materials/grimy_irit.yaml (renamed from data/items/grimy_irit.yaml)0
-rw-r--r--data/items/materials/grimy_kwuarm.yaml (renamed from data/items/grimy_kwuarm.yaml)0
-rw-r--r--data/items/materials/grimy_lantadyme.yaml (renamed from data/items/grimy_lantadyme.yaml)0
-rw-r--r--data/items/materials/grimy_marrentill.yaml (renamed from data/items/grimy_marrentill.yaml)0
-rw-r--r--data/items/materials/grimy_ranarr.yaml (renamed from data/items/grimy_ranarr.yaml)0
-rw-r--r--data/items/materials/grimy_snapdragon.yaml (renamed from data/items/grimy_snapdragon.yaml)0
-rw-r--r--data/items/materials/grimy_tarromin.yaml (renamed from data/items/grimy_tarromin.yaml)0
-rw-r--r--data/items/materials/grimy_toadflax.yaml (renamed from data/items/grimy_toadflax.yaml)0
-rw-r--r--data/items/materials/grimy_torstol.yaml (renamed from data/items/grimy_torstol.yaml)0
-rw-r--r--data/items/materials/guam.yaml (renamed from data/items/guam.yaml)0
-rw-r--r--data/items/materials/hard_leather.yaml (renamed from data/items/hard_leather.yaml)0
-rw-r--r--data/items/materials/harralander.yaml (renamed from data/items/harralander.yaml)0
-rw-r--r--data/items/materials/irit.yaml (renamed from data/items/irit.yaml)0
-rw-r--r--data/items/materials/iron_bar.yaml (renamed from data/items/iron_bar.yaml)0
-rw-r--r--data/items/materials/iron_nails.yaml (renamed from data/items/iron_nails.yaml)0
-rw-r--r--data/items/materials/iron_ore.yaml (renamed from data/items/iron_ore.yaml)0
-rw-r--r--data/items/materials/kwuarm.yaml (renamed from data/items/kwuarm.yaml)0
-rw-r--r--data/items/materials/lantadyme.yaml (renamed from data/items/lantadyme.yaml)0
-rw-r--r--data/items/materials/leather.yaml (renamed from data/items/leather.yaml)0
-rw-r--r--data/items/materials/limpwurt_root.yaml (renamed from data/items/limpwurt_root.yaml)0
-rw-r--r--data/items/materials/logs.yaml (renamed from data/items/logs.yaml)0
-rw-r--r--data/items/materials/magic_logs.yaml (renamed from data/items/magic_logs.yaml)0
-rw-r--r--data/items/materials/mahogany_logs.yaml (renamed from data/items/mahogany_logs.yaml)0
-rw-r--r--data/items/materials/mahogany_planks.yaml (renamed from data/items/mahogany_planks.yaml)0
-rw-r--r--data/items/materials/maple_logs.yaml (renamed from data/items/maple_logs.yaml)0
-rw-r--r--data/items/materials/marrentill.yaml (renamed from data/items/marrentill.yaml)0
-rw-r--r--data/items/materials/mithril_bar.yaml (renamed from data/items/mithril_bar.yaml)0
-rw-r--r--data/items/materials/mithril_nails.yaml (renamed from data/items/mithril_nails.yaml)0
-rw-r--r--data/items/materials/mithril_ore.yaml (renamed from data/items/mithril_ore.yaml)0
-rw-r--r--data/items/materials/oak_logs.yaml (renamed from data/items/oak_logs.yaml)0
-rw-r--r--data/items/materials/oak_planks.yaml (renamed from data/items/oak_planks.yaml)0
-rw-r--r--data/items/materials/planks.yaml (renamed from data/items/planks.yaml)0
-rw-r--r--data/items/materials/potato_cactus.yaml (renamed from data/items/potato_cactus.yaml)0
-rw-r--r--data/items/materials/ranarr.yaml (renamed from data/items/ranarr.yaml)0
-rw-r--r--data/items/materials/redwood_logs.yaml (renamed from data/items/redwood_logs.yaml)0
-rw-r--r--data/items/materials/ruby.yaml (renamed from data/items/ruby.yaml)0
-rw-r--r--data/items/materials/rune_nails.yaml (renamed from data/items/rune_nails.yaml)0
-rw-r--r--data/items/materials/runite_bar.yaml (renamed from data/items/runite_bar.yaml)0
-rw-r--r--data/items/materials/runite_ore.yaml (renamed from data/items/runite_ore.yaml)0
-rw-r--r--data/items/materials/sacred_eel.yaml (renamed from data/items/sacred_eel.yaml)0
-rw-r--r--data/items/materials/salt.yaml (renamed from data/items/salt.yaml)0
-rw-r--r--data/items/materials/sapphire.yaml (renamed from data/items/sapphire.yaml)0
-rw-r--r--data/items/materials/silver_bar.yaml (renamed from data/items/silver_bar.yaml)0
-rw-r--r--data/items/materials/silver_ore.yaml (renamed from data/items/silver_ore.yaml)0
-rw-r--r--data/items/materials/slug_mucus.yaml (renamed from data/items/slug_mucus.yaml)0
-rw-r--r--data/items/materials/snapdragon.yaml (renamed from data/items/snapdragon.yaml)0
-rw-r--r--data/items/materials/snape_grass.yaml (renamed from data/items/snape_grass.yaml)0
-rw-r--r--data/items/materials/soft_clay.yaml13
-rw-r--r--data/items/materials/steel_bar.yaml (renamed from data/items/steel_bar.yaml)0
-rw-r--r--data/items/materials/steel_nails.yaml (renamed from data/items/steel_nails.yaml)0
-rw-r--r--data/items/materials/stim_cell.yaml (renamed from data/items/stim_cell.yaml)0
-rw-r--r--data/items/materials/tarromin.yaml (renamed from data/items/tarromin.yaml)0
-rw-r--r--data/items/materials/teak_logs.yaml (renamed from data/items/teak_logs.yaml)0
-rw-r--r--data/items/materials/teak_planks.yaml (renamed from data/items/teak_planks.yaml)0
-rw-r--r--data/items/materials/thread.yaml (renamed from data/items/thread.yaml)0
-rw-r--r--data/items/materials/tin_ore.yaml (renamed from data/items/tin_ore.yaml)0
-rw-r--r--data/items/materials/toadflax.yaml (renamed from data/items/toadflax.yaml)0
-rw-r--r--data/items/materials/torstol.yaml (renamed from data/items/torstol.yaml)0
-rw-r--r--data/items/materials/uncut_diamond.yaml (renamed from data/items/uncut_diamond.yaml)0
-rw-r--r--data/items/materials/uncut_emerald.yaml (renamed from data/items/uncut_emerald.yaml)0
-rw-r--r--data/items/materials/uncut_ruby.yaml (renamed from data/items/uncut_ruby.yaml)0
-rw-r--r--data/items/materials/uncut_sapphire.yaml (renamed from data/items/uncut_sapphire.yaml)0
-rw-r--r--data/items/materials/vial.yaml (renamed from data/items/vial.yaml)0
-rw-r--r--data/items/materials/white_berries.yaml (renamed from data/items/white_berries.yaml)0
-rw-r--r--data/items/materials/willow_logs.yaml (renamed from data/items/willow_logs.yaml)0
-rw-r--r--data/items/materials/wool.yaml (renamed from data/items/wool.yaml)0
-rw-r--r--data/items/materials/yew_logs.yaml (renamed from data/items/yew_logs.yaml)0
-rw-r--r--data/items/misc/birds_nest.yaml (renamed from data/items/birds_nest.yaml)0
-rw-r--r--data/items/misc/christmas_cracker.yaml (renamed from data/items/christmas_cracker.yaml)0
-rw-r--r--data/items/misc/circuit_board.yaml (renamed from data/items/circuit_board.yaml)0
-rw-r--r--data/items/misc/credit_stick.yaml (renamed from data/items/credit_stick.yaml)0
-rw-r--r--data/items/misc/credits.yaml (renamed from data/items/credits.yaml)0
-rw-r--r--data/items/misc/mahogany_table.yaml (renamed from data/items/mahogany_table.yaml)0
-rw-r--r--data/items/misc/nutrient_bar.yaml (renamed from data/items/nutrient_bar.yaml)0
-rw-r--r--data/items/misc/oak_shelf.yaml (renamed from data/items/oak_shelf.yaml)0
-rw-r--r--data/items/misc/oak_table.yaml (renamed from data/items/oak_table.yaml)0
-rw-r--r--data/items/misc/pass_stub.yaml (renamed from data/items/pass_stub.yaml)0
-rw-r--r--data/items/misc/scrap_metal.yaml (renamed from data/items/scrap_metal.yaml)0
-rw-r--r--data/items/misc/teak_table.yaml (renamed from data/items/teak_table.yaml)0
-rw-r--r--data/items/misc/wooden_chair.yaml (renamed from data/items/wooden_chair.yaml)0
-rw-r--r--data/items/misc/wooden_shelf.yaml (renamed from data/items/wooden_shelf.yaml)0
-rw-r--r--data/items/misc/wooden_table.yaml (renamed from data/items/wooden_table.yaml)0
-rw-r--r--data/items/pastry_dough.yaml11
-rw-r--r--data/items/pizza_dough.yaml11
-rw-r--r--data/items/ranarr_potion_unf.yaml12
-rw-r--r--data/items/seeds/avantoe_seed.yaml (renamed from data/items/avantoe_seed.yaml)0
-rw-r--r--data/items/seeds/cabbage_seed.yaml (renamed from data/items/cabbage_seed.yaml)0
-rw-r--r--data/items/seeds/cadantine_seed.yaml (renamed from data/items/cadantine_seed.yaml)0
-rw-r--r--data/items/seeds/dwarf_weed_seed.yaml (renamed from data/items/dwarf_weed_seed.yaml)0
-rw-r--r--data/items/seeds/guam_seed.yaml (renamed from data/items/guam_seed.yaml)0
-rw-r--r--data/items/seeds/harralander_seed.yaml (renamed from data/items/harralander_seed.yaml)0
-rw-r--r--data/items/seeds/irit_seed.yaml (renamed from data/items/irit_seed.yaml)0
-rw-r--r--data/items/seeds/kwuarm_seed.yaml (renamed from data/items/kwuarm_seed.yaml)0
-rw-r--r--data/items/seeds/lantadyme_seed.yaml (renamed from data/items/lantadyme_seed.yaml)0
-rw-r--r--data/items/seeds/marrentill_seed.yaml (renamed from data/items/marrentill_seed.yaml)0
-rw-r--r--data/items/seeds/onion_seed.yaml (renamed from data/items/onion_seed.yaml)0
-rw-r--r--data/items/seeds/potato_seed.yaml (renamed from data/items/potato_seed.yaml)0
-rw-r--r--data/items/seeds/ranarr_seed.yaml (renamed from data/items/ranarr_seed.yaml)0
-rw-r--r--data/items/seeds/snapdragon_seed.yaml (renamed from data/items/snapdragon_seed.yaml)0
-rw-r--r--data/items/seeds/strawberry_seed.yaml (renamed from data/items/strawberry_seed.yaml)0
-rw-r--r--data/items/seeds/sweetcorn_seed.yaml (renamed from data/items/sweetcorn_seed.yaml)0
-rw-r--r--data/items/seeds/tarromin_seed.yaml (renamed from data/items/tarromin_seed.yaml)0
-rw-r--r--data/items/seeds/toadflax_seed.yaml (renamed from data/items/toadflax_seed.yaml)0
-rw-r--r--data/items/seeds/tomato_seed.yaml (renamed from data/items/tomato_seed.yaml)0
-rw-r--r--data/items/seeds/torstol_seed.yaml (renamed from data/items/torstol_seed.yaml)0
-rw-r--r--data/items/seeds/watermelon_seed.yaml (renamed from data/items/watermelon_seed.yaml)0
-rw-r--r--data/items/snapdragon_potion_unf.yaml12
-rw-r--r--data/items/soft_clay.yaml13
-rw-r--r--data/items/tarromin_potion_unf.yaml12
-rw-r--r--data/items/toadflax_potion_unf.yaml12
-rw-r--r--data/items/tools/adamant_axe.yaml (renamed from data/items/adamant_axe.yaml)0
-rw-r--r--data/items/tools/adamant_pickaxe.yaml (renamed from data/items/adamant_pickaxe.yaml)0
-rw-r--r--data/items/tools/amulet_mould.yaml (renamed from data/items/amulet_mould.yaml)0
-rw-r--r--data/items/tools/big_fishing_net.yaml (renamed from data/items/big_fishing_net.yaml)0
-rw-r--r--data/items/tools/black_pickaxe.yaml (renamed from data/items/black_pickaxe.yaml)0
-rw-r--r--data/items/tools/bracelet_mould.yaml (renamed from data/items/bracelet_mould.yaml)0
-rw-r--r--data/items/tools/bronze_axe.yaml (renamed from data/items/bronze_axe.yaml)0
-rw-r--r--data/items/tools/bronze_pickaxe.yaml (renamed from data/items/bronze_pickaxe.yaml)0
-rw-r--r--data/items/tools/chisel.yaml (renamed from data/items/chisel.yaml)0
-rw-r--r--data/items/tools/dragon_pickaxe.yaml (renamed from data/items/dragon_pickaxe.yaml)0
-rw-r--r--data/items/tools/empty_bucket.yaml (renamed from data/items/empty_bucket.yaml)0
-rw-r--r--data/items/tools/empty_jug.yaml (renamed from data/items/empty_jug.yaml)0
-rw-r--r--data/items/tools/empty_pot.yaml (renamed from data/items/empty_pot.yaml)0
-rw-r--r--data/items/tools/firesteel.yaml (renamed from data/items/firesteel.yaml)0
-rw-r--r--data/items/tools/fishing_bait.yaml (renamed from data/items/fishing_bait.yaml)0
-rw-r--r--data/items/tools/fishing_rod.yaml (renamed from data/items/fishing_rod.yaml)0
-rw-r--r--data/items/tools/fly_fishing_rod.yaml (renamed from data/items/fly_fishing_rod.yaml)0
-rw-r--r--data/items/tools/hammer.yaml (renamed from data/items/hammer.yaml)0
-rw-r--r--data/items/tools/harpoon.yaml (renamed from data/items/harpoon.yaml)0
-rw-r--r--data/items/tools/iron_axe.yaml (renamed from data/items/iron_axe.yaml)0
-rw-r--r--data/items/tools/iron_pickaxe.yaml (renamed from data/items/iron_pickaxe.yaml)0
-rw-r--r--data/items/tools/knife.yaml (renamed from data/items/knife.yaml)0
-rw-r--r--data/items/tools/lighter.yaml (renamed from data/items/lighter.yaml)0
-rw-r--r--data/items/tools/lobster_pot.yaml (renamed from data/items/lobster_pot.yaml)0
-rw-r--r--data/items/tools/matches.yaml (renamed from data/items/matches.yaml)0
-rw-r--r--data/items/tools/mithril_axe.yaml (renamed from data/items/mithril_axe.yaml)0
-rw-r--r--data/items/tools/mithril_pickaxe.yaml (renamed from data/items/mithril_pickaxe.yaml)0
-rw-r--r--data/items/tools/necklace_mould.yaml (renamed from data/items/necklace_mould.yaml)0
-rw-r--r--data/items/tools/needle.yaml (renamed from data/items/needle.yaml)0
-rw-r--r--data/items/tools/plant_pot.yaml (renamed from data/items/plant_pot.yaml)0
-rw-r--r--data/items/tools/rake.yaml (renamed from data/items/rake.yaml)0
-rw-r--r--data/items/tools/ring_mould.yaml (renamed from data/items/ring_mould.yaml)0
-rw-r--r--data/items/tools/rune_axe.yaml (renamed from data/items/rune_axe.yaml)0
-rw-r--r--data/items/tools/rune_pickaxe.yaml (renamed from data/items/rune_pickaxe.yaml)0
-rw-r--r--data/items/tools/saw.yaml (renamed from data/items/saw.yaml)0
-rw-r--r--data/items/tools/shears.yaml (renamed from data/items/shears.yaml)0
-rw-r--r--data/items/tools/small_fishing_net.yaml (renamed from data/items/small_fishing_net.yaml)0
-rw-r--r--data/items/tools/spade.yaml (renamed from data/items/spade.yaml)0
-rw-r--r--data/items/tools/steel_axe.yaml (renamed from data/items/steel_axe.yaml)0
-rw-r--r--data/items/tools/steel_pickaxe.yaml (renamed from data/items/steel_pickaxe.yaml)0
-rw-r--r--data/items/tools/unfired_pie_dish.yaml (renamed from data/items/unfired_pie_dish.yaml)0
-rw-r--r--data/items/tools/unfired_plant_pot.yaml (renamed from data/items/unfired_plant_pot.yaml)0
-rw-r--r--data/items/tools/unfired_pot.yaml (renamed from data/items/unfired_pot.yaml)0
-rw-r--r--data/items/tools/watering_can.yaml (renamed from data/items/watering_can.yaml)0
-rw-r--r--data/items/torstol_potion_unf.yaml12
-rw-r--r--data/mobs/estate_broker.yaml8
-rw-r--r--data/objects/anvil.yaml4
-rw-r--r--data/recipes/clean/clean_avantoe.yaml11
-rw-r--r--data/recipes/clean/clean_cadantine.yaml11
-rw-r--r--data/recipes/clean/clean_dwarf_weed.yaml11
-rw-r--r--data/recipes/clean/clean_guam.yaml11
-rw-r--r--data/recipes/clean/clean_harralander.yaml11
-rw-r--r--data/recipes/clean/clean_irit.yaml11
-rw-r--r--data/recipes/clean/clean_kwuarm.yaml11
-rw-r--r--data/recipes/clean/clean_lantadyme.yaml11
-rw-r--r--data/recipes/clean/clean_marrentill.yaml11
-rw-r--r--data/recipes/clean/clean_ranarr.yaml11
-rw-r--r--data/recipes/clean/clean_snapdragon.yaml11
-rw-r--r--data/recipes/clean/clean_tarromin.yaml11
-rw-r--r--data/recipes/clean/clean_toadflax.yaml11
-rw-r--r--data/recipes/clean/clean_torstol.yaml11
-rw-r--r--data/recipes/clean_avantoe.yaml11
-rw-r--r--data/recipes/clean_cadantine.yaml11
-rw-r--r--data/recipes/clean_dwarf_weed.yaml11
-rw-r--r--data/recipes/clean_guam.yaml11
-rw-r--r--data/recipes/clean_harralander.yaml11
-rw-r--r--data/recipes/clean_irit.yaml11
-rw-r--r--data/recipes/clean_kwuarm.yaml11
-rw-r--r--data/recipes/clean_lantadyme.yaml11
-rw-r--r--data/recipes/clean_marrentill.yaml11
-rw-r--r--data/recipes/clean_ranarr.yaml11
-rw-r--r--data/recipes/clean_snapdragon.yaml11
-rw-r--r--data/recipes/clean_tarromin.yaml11
-rw-r--r--data/recipes/clean_toadflax.yaml11
-rw-r--r--data/recipes/clean_torstol.yaml11
-rw-r--r--data/recipes/construct_mahogany_planks.yaml12
-rw-r--r--data/recipes/construct_mahogany_table.yaml11
-rw-r--r--data/recipes/construct_oak_planks.yaml12
-rw-r--r--data/recipes/construct_oak_shelf.yaml11
-rw-r--r--data/recipes/construct_oak_table.yaml11
-rw-r--r--data/recipes/construct_planks.yaml12
-rw-r--r--data/recipes/construct_teak_planks.yaml12
-rw-r--r--data/recipes/construct_teak_table.yaml11
-rw-r--r--data/recipes/construct_wooden_chair.yaml11
-rw-r--r--data/recipes/construct_wooden_shelf.yaml11
-rw-r--r--data/recipes/construct_wooden_table.yaml11
-rw-r--r--data/recipes/construction/construct_mahogany_planks.yaml12
-rw-r--r--data/recipes/construction/construct_mahogany_table.yaml11
-rw-r--r--data/recipes/construction/construct_oak_planks.yaml12
-rw-r--r--data/recipes/construction/construct_oak_shelf.yaml11
-rw-r--r--data/recipes/construction/construct_oak_table.yaml11
-rw-r--r--data/recipes/construction/construct_planks.yaml12
-rw-r--r--data/recipes/construction/construct_teak_planks.yaml12
-rw-r--r--data/recipes/construction/construct_teak_table.yaml11
-rw-r--r--data/recipes/construction/construct_wooden_chair.yaml11
-rw-r--r--data/recipes/construction/construct_wooden_shelf.yaml11
-rw-r--r--data/recipes/construction/construct_wooden_table.yaml11
-rw-r--r--data/recipes/cook_anchovies.yaml13
-rw-r--r--data/recipes/cook_bread.yaml13
-rw-r--r--data/recipes/cook_herring.yaml13
-rw-r--r--data/recipes/cook_salmon.yaml13
-rw-r--r--data/recipes/cook_sardine.yaml13
-rw-r--r--data/recipes/cook_shrimps.yaml13
-rw-r--r--data/recipes/cook_trout.yaml13
-rw-r--r--data/recipes/cooking/cook_anchovies.yaml13
-rw-r--r--data/recipes/cooking/cook_bread.yaml13
-rw-r--r--data/recipes/cooking/cook_herring.yaml13
-rw-r--r--data/recipes/cooking/cook_salmon.yaml13
-rw-r--r--data/recipes/cooking/cook_sardine.yaml13
-rw-r--r--data/recipes/cooking/cook_shrimps.yaml13
-rw-r--r--data/recipes/cooking/cook_trout.yaml13
-rw-r--r--data/recipes/craft_ball_of_wool.yaml11
-rw-r--r--data/recipes/craft_bowstring.yaml11
-rw-r--r--data/recipes/craft_coif.yaml13
-rw-r--r--data/recipes/craft_cut_diamond.yaml11
-rw-r--r--data/recipes/craft_cut_emerald.yaml11
-rw-r--r--data/recipes/craft_cut_ruby.yaml11
-rw-r--r--data/recipes/craft_cut_sapphire.yaml11
-rw-r--r--data/recipes/craft_diamond_amulet_u.yaml16
-rw-r--r--data/recipes/craft_diamond_bracelet.yaml16
-rw-r--r--data/recipes/craft_diamond_necklace.yaml16
-rw-r--r--data/recipes/craft_diamond_ring.yaml16
-rw-r--r--data/recipes/craft_emerald_amulet_u.yaml16
-rw-r--r--data/recipes/craft_emerald_bracelet.yaml16
-rw-r--r--data/recipes/craft_emerald_necklace.yaml16
-rw-r--r--data/recipes/craft_emerald_ring.yaml16
-rw-r--r--data/recipes/craft_gold_amulet_u.yaml14
-rw-r--r--data/recipes/craft_gold_bracelet.yaml14
-rw-r--r--data/recipes/craft_gold_necklace.yaml14
-rw-r--r--data/recipes/craft_gold_ring.yaml14
-rw-r--r--data/recipes/craft_hard_leather_body.yaml13
-rw-r--r--data/recipes/craft_hard_leather_shield.yaml13
-rw-r--r--data/recipes/craft_leather_body.yaml13
-rw-r--r--data/recipes/craft_leather_boots.yaml13
-rw-r--r--data/recipes/craft_leather_chaps.yaml13
-rw-r--r--data/recipes/craft_leather_cowl.yaml13
-rw-r--r--data/recipes/craft_leather_gloves.yaml13
-rw-r--r--data/recipes/craft_leather_vambraces.yaml13
-rw-r--r--data/recipes/craft_pie_dish.yaml16
-rw-r--r--data/recipes/craft_plant_pot.yaml16
-rw-r--r--data/recipes/craft_pot.yaml16
-rw-r--r--data/recipes/craft_ruby_amulet_u.yaml16
-rw-r--r--data/recipes/craft_ruby_bracelet.yaml16
-rw-r--r--data/recipes/craft_ruby_necklace.yaml16
-rw-r--r--data/recipes/craft_ruby_ring.yaml16
-rw-r--r--data/recipes/craft_sapphire_amulet_u.yaml16
-rw-r--r--data/recipes/craft_sapphire_bracelet.yaml16
-rw-r--r--data/recipes/craft_sapphire_necklace.yaml16
-rw-r--r--data/recipes/craft_sapphire_ring.yaml16
-rw-r--r--data/recipes/craft_unfired_pie_dish.yaml11
-rw-r--r--data/recipes/craft_unfired_plant_pot.yaml11
-rw-r--r--data/recipes/craft_unfired_pot.yaml11
-rw-r--r--data/recipes/crafting/craft_ball_of_wool.yaml11
-rw-r--r--data/recipes/crafting/craft_bowstring.yaml11
-rw-r--r--data/recipes/crafting/craft_coif.yaml13
-rw-r--r--data/recipes/crafting/craft_cut_diamond.yaml11
-rw-r--r--data/recipes/crafting/craft_cut_emerald.yaml11
-rw-r--r--data/recipes/crafting/craft_cut_ruby.yaml11
-rw-r--r--data/recipes/crafting/craft_cut_sapphire.yaml11
-rw-r--r--data/recipes/crafting/craft_diamond_amulet_u.yaml16
-rw-r--r--data/recipes/crafting/craft_diamond_bracelet.yaml16
-rw-r--r--data/recipes/crafting/craft_diamond_necklace.yaml16
-rw-r--r--data/recipes/crafting/craft_diamond_ring.yaml16
-rw-r--r--data/recipes/crafting/craft_emerald_amulet_u.yaml16
-rw-r--r--data/recipes/crafting/craft_emerald_bracelet.yaml16
-rw-r--r--data/recipes/crafting/craft_emerald_necklace.yaml16
-rw-r--r--data/recipes/crafting/craft_emerald_ring.yaml16
-rw-r--r--data/recipes/crafting/craft_gold_amulet_u.yaml14
-rw-r--r--data/recipes/crafting/craft_gold_bracelet.yaml14
-rw-r--r--data/recipes/crafting/craft_gold_necklace.yaml14
-rw-r--r--data/recipes/crafting/craft_gold_ring.yaml14
-rw-r--r--data/recipes/crafting/craft_hard_leather_body.yaml13
-rw-r--r--data/recipes/crafting/craft_hard_leather_shield.yaml13
-rw-r--r--data/recipes/crafting/craft_leather_body.yaml13
-rw-r--r--data/recipes/crafting/craft_leather_boots.yaml13
-rw-r--r--data/recipes/crafting/craft_leather_chaps.yaml13
-rw-r--r--data/recipes/crafting/craft_leather_cowl.yaml13
-rw-r--r--data/recipes/crafting/craft_leather_gloves.yaml13
-rw-r--r--data/recipes/crafting/craft_leather_vambraces.yaml13
-rw-r--r--data/recipes/crafting/craft_pie_dish.yaml16
-rw-r--r--data/recipes/crafting/craft_plant_pot.yaml16
-rw-r--r--data/recipes/crafting/craft_pot.yaml16
-rw-r--r--data/recipes/crafting/craft_ruby_amulet_u.yaml16
-rw-r--r--data/recipes/crafting/craft_ruby_bracelet.yaml16
-rw-r--r--data/recipes/crafting/craft_ruby_necklace.yaml16
-rw-r--r--data/recipes/crafting/craft_ruby_ring.yaml16
-rw-r--r--data/recipes/crafting/craft_sapphire_amulet_u.yaml16
-rw-r--r--data/recipes/crafting/craft_sapphire_bracelet.yaml16
-rw-r--r--data/recipes/crafting/craft_sapphire_necklace.yaml16
-rw-r--r--data/recipes/crafting/craft_sapphire_ring.yaml16
-rw-r--r--data/recipes/crafting/craft_unfired_pie_dish.yaml11
-rw-r--r--data/recipes/crafting/craft_unfired_plant_pot.yaml11
-rw-r--r--data/recipes/crafting/craft_unfired_pot.yaml11
-rw-r--r--data/recipes/fletch_adamant_arrow.yaml13
-rw-r--r--data/recipes/fletch_adamant_bolts.yaml13
-rw-r--r--data/recipes/fletch_arrow_shaft.yaml12
-rw-r--r--data/recipes/fletch_arrow_shaft_magic.yaml12
-rw-r--r--data/recipes/fletch_arrow_shaft_maple.yaml12
-rw-r--r--data/recipes/fletch_arrow_shaft_oak.yaml12
-rw-r--r--data/recipes/fletch_arrow_shaft_willow.yaml12
-rw-r--r--data/recipes/fletch_arrow_shaft_yew.yaml12
-rw-r--r--data/recipes/fletch_bronze_arrow.yaml13
-rw-r--r--data/recipes/fletch_bronze_bolts.yaml13
-rw-r--r--data/recipes/fletch_diamond_bolt_tips.yaml12
-rw-r--r--data/recipes/fletch_diamond_bolts.yaml13
-rw-r--r--data/recipes/fletch_emerald_bolt_tips.yaml12
-rw-r--r--data/recipes/fletch_emerald_bolts.yaml13
-rw-r--r--data/recipes/fletch_headless_arrow.yaml13
-rw-r--r--data/recipes/fletch_iron_arrow.yaml13
-rw-r--r--data/recipes/fletch_iron_bolts.yaml13
-rw-r--r--data/recipes/fletch_longbow.yaml12
-rw-r--r--data/recipes/fletch_longbow_u.yaml11
-rw-r--r--data/recipes/fletch_magic_longbow.yaml12
-rw-r--r--data/recipes/fletch_magic_longbow_u.yaml11
-rw-r--r--data/recipes/fletch_magic_shortbow.yaml12
-rw-r--r--data/recipes/fletch_magic_shortbow_u.yaml11
-rw-r--r--data/recipes/fletch_maple_longbow.yaml12
-rw-r--r--data/recipes/fletch_maple_longbow_u.yaml11
-rw-r--r--data/recipes/fletch_maple_shortbow.yaml12
-rw-r--r--data/recipes/fletch_maple_shortbow_u.yaml11
-rw-r--r--data/recipes/fletch_mithril_arrow.yaml13
-rw-r--r--data/recipes/fletch_mithril_bolts.yaml13
-rw-r--r--data/recipes/fletch_oak_longbow.yaml12
-rw-r--r--data/recipes/fletch_oak_longbow_u.yaml11
-rw-r--r--data/recipes/fletch_oak_shortbow.yaml12
-rw-r--r--data/recipes/fletch_oak_shortbow_u.yaml11
-rw-r--r--data/recipes/fletch_ruby_bolt_tips.yaml12
-rw-r--r--data/recipes/fletch_ruby_bolts.yaml13
-rw-r--r--data/recipes/fletch_rune_arrow.yaml13
-rw-r--r--data/recipes/fletch_rune_bolts.yaml13
-rw-r--r--data/recipes/fletch_sapphire_bolt_tips.yaml12
-rw-r--r--data/recipes/fletch_sapphire_bolts.yaml13
-rw-r--r--data/recipes/fletch_shortbow.yaml12
-rw-r--r--data/recipes/fletch_shortbow_u.yaml11
-rw-r--r--data/recipes/fletch_steel_arrow.yaml13
-rw-r--r--data/recipes/fletch_steel_bolts.yaml13
-rw-r--r--data/recipes/fletch_willow_longbow.yaml12
-rw-r--r--data/recipes/fletch_willow_longbow_u.yaml11
-rw-r--r--data/recipes/fletch_willow_shortbow.yaml12
-rw-r--r--data/recipes/fletch_willow_shortbow_u.yaml11
-rw-r--r--data/recipes/fletch_yew_longbow.yaml12
-rw-r--r--data/recipes/fletch_yew_longbow_u.yaml11
-rw-r--r--data/recipes/fletch_yew_shortbow.yaml12
-rw-r--r--data/recipes/fletch_yew_shortbow_u.yaml11
-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_headless_arrow.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/mix_amp_potion.yaml13
-rw-r--r--data/recipes/mix_bio_serum.yaml13
-rw-r--r--data/recipes/mix_full_restore.yaml13
-rw-r--r--data/recipes/mix_nano_restore.yaml13
-rw-r--r--data/recipes/mix_restoration_compound.yaml13
-rw-r--r--data/recipes/mix_science_serum.yaml13
-rw-r--r--data/recipes/mix_shield_potion.yaml13
-rw-r--r--data/recipes/mix_stim_cell.yaml13
-rw-r--r--data/recipes/mix_stim_potion.yaml13
-rw-r--r--data/recipes/mix_super_amp.yaml13
-rw-r--r--data/recipes/mix_super_bio_serum.yaml13
-rw-r--r--data/recipes/mix_super_shield.yaml13
-rw-r--r--data/recipes/mix_super_stim.yaml13
-rw-r--r--data/recipes/mix_super_stim_cell.yaml13
-rw-r--r--data/recipes/mix_targeting_serum.yaml13
-rw-r--r--data/recipes/mix_tech_serum.yaml13
-rw-r--r--data/recipes/mixing/mix_amp_potion.yaml13
-rw-r--r--data/recipes/mixing/mix_bio_serum.yaml13
-rw-r--r--data/recipes/mixing/mix_full_restore.yaml13
-rw-r--r--data/recipes/mixing/mix_nano_restore.yaml13
-rw-r--r--data/recipes/mixing/mix_restoration_compound.yaml13
-rw-r--r--data/recipes/mixing/mix_science_serum.yaml13
-rw-r--r--data/recipes/mixing/mix_shield_potion.yaml13
-rw-r--r--data/recipes/mixing/mix_stim_cell.yaml13
-rw-r--r--data/recipes/mixing/mix_stim_potion.yaml13
-rw-r--r--data/recipes/mixing/mix_super_amp.yaml13
-rw-r--r--data/recipes/mixing/mix_super_bio_serum.yaml13
-rw-r--r--data/recipes/mixing/mix_super_shield.yaml13
-rw-r--r--data/recipes/mixing/mix_super_stim.yaml13
-rw-r--r--data/recipes/mixing/mix_super_stim_cell.yaml13
-rw-r--r--data/recipes/mixing/mix_targeting_serum.yaml13
-rw-r--r--data/recipes/mixing/mix_tech_serum.yaml13
-rw-r--r--data/recipes/smelt_adamantite.yaml15
-rw-r--r--data/recipes/smelt_bronze.yaml15
-rw-r--r--data/recipes/smelt_gold.yaml13
-rw-r--r--data/recipes/smelt_iron.yaml17
-rw-r--r--data/recipes/smelt_mithril.yaml15
-rw-r--r--data/recipes/smelt_runite.yaml15
-rw-r--r--data/recipes/smelt_silver.yaml13
-rw-r--r--data/recipes/smelt_steel.yaml15
-rw-r--r--data/recipes/smelting/smelt_adamantite.yaml15
-rw-r--r--data/recipes/smelting/smelt_bronze.yaml15
-rw-r--r--data/recipes/smelting/smelt_gold.yaml13
-rw-r--r--data/recipes/smelting/smelt_iron.yaml17
-rw-r--r--data/recipes/smelting/smelt_mithril.yaml15
-rw-r--r--data/recipes/smelting/smelt_runite.yaml15
-rw-r--r--data/recipes/smelting/smelt_silver.yaml13
-rw-r--r--data/recipes/smelting/smelt_steel.yaml15
-rw-r--r--data/recipes/smith_adamant_arrowtips.yaml12
-rw-r--r--data/recipes/smith_adamant_bolts_unf.yaml12
-rw-r--r--data/recipes/smith_adamant_dagger.yaml11
-rw-r--r--data/recipes/smith_adamant_full_helm.yaml11
-rw-r--r--data/recipes/smith_adamant_med_helm.yaml11
-rw-r--r--data/recipes/smith_adamant_nails.yaml12
-rw-r--r--data/recipes/smith_adamant_platebody.yaml11
-rw-r--r--data/recipes/smith_adamant_sword.yaml11
-rw-r--r--data/recipes/smith_bronze_arrowtips.yaml12
-rw-r--r--data/recipes/smith_bronze_bolts_unf.yaml12
-rw-r--r--data/recipes/smith_bronze_dagger.yaml11
-rw-r--r--data/recipes/smith_bronze_full_helm.yaml11
-rw-r--r--data/recipes/smith_bronze_med_helm.yaml11
-rw-r--r--data/recipes/smith_bronze_nails.yaml12
-rw-r--r--data/recipes/smith_bronze_platebody.yaml11
-rw-r--r--data/recipes/smith_bronze_sword.yaml11
-rw-r--r--data/recipes/smith_iron_arrowtips.yaml12
-rw-r--r--data/recipes/smith_iron_bolts_unf.yaml12
-rw-r--r--data/recipes/smith_iron_dagger.yaml11
-rw-r--r--data/recipes/smith_iron_full_helm.yaml11
-rw-r--r--data/recipes/smith_iron_med_helm.yaml11
-rw-r--r--data/recipes/smith_iron_nails.yaml12
-rw-r--r--data/recipes/smith_iron_platebody.yaml11
-rw-r--r--data/recipes/smith_iron_sword.yaml11
-rw-r--r--data/recipes/smith_mithril_arrowtips.yaml12
-rw-r--r--data/recipes/smith_mithril_bolts_unf.yaml12
-rw-r--r--data/recipes/smith_mithril_dagger.yaml11
-rw-r--r--data/recipes/smith_mithril_full_helm.yaml11
-rw-r--r--data/recipes/smith_mithril_med_helm.yaml11
-rw-r--r--data/recipes/smith_mithril_nails.yaml12
-rw-r--r--data/recipes/smith_mithril_platebody.yaml11
-rw-r--r--data/recipes/smith_mithril_sword.yaml11
-rw-r--r--data/recipes/smith_rune_arrowtips.yaml12
-rw-r--r--data/recipes/smith_rune_bolts_unf.yaml12
-rw-r--r--data/recipes/smith_rune_dagger.yaml11
-rw-r--r--data/recipes/smith_rune_full_helm.yaml11
-rw-r--r--data/recipes/smith_rune_med_helm.yaml11
-rw-r--r--data/recipes/smith_rune_nails.yaml12
-rw-r--r--data/recipes/smith_rune_platebody.yaml11
-rw-r--r--data/recipes/smith_rune_sword.yaml11
-rw-r--r--data/recipes/smith_steel_arrowtips.yaml12
-rw-r--r--data/recipes/smith_steel_bolts_unf.yaml12
-rw-r--r--data/recipes/smith_steel_dagger.yaml11
-rw-r--r--data/recipes/smith_steel_full_helm.yaml11
-rw-r--r--data/recipes/smith_steel_med_helm.yaml11
-rw-r--r--data/recipes/smith_steel_nails.yaml12
-rw-r--r--data/recipes/smith_steel_platebody.yaml11
-rw-r--r--data/recipes/smith_steel_sword.yaml11
-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/rooms/1.yaml38
-rw-r--r--data/rooms/11.yaml17
-rw-r--r--data/rooms/12.yaml28
-rw-r--r--data/rooms/13.yaml46
-rw-r--r--data/rooms/14.yaml18
-rw-r--r--data/rooms/150.yaml32
-rw-r--r--data/rooms/2.yaml17
-rw-r--r--data/rooms/3.yaml15
-rw-r--r--data/rooms/4.yaml29
-rw-r--r--data/rooms/5.yaml12
-rw-r--r--data/rooms/6.yaml16
-rw-r--r--data/rooms/9.yaml17
-rw-r--r--data/rooms/agility/200.yaml (renamed from data/rooms/200.yaml)0
-rw-r--r--data/rooms/agility/201.yaml (renamed from data/rooms/201.yaml)0
-rw-r--r--data/rooms/agility/202.yaml (renamed from data/rooms/202.yaml)0
-rw-r--r--data/rooms/agility/203.yaml (renamed from data/rooms/203.yaml)0
-rw-r--r--data/rooms/agility/204.yaml (renamed from data/rooms/204.yaml)0
-rw-r--r--data/rooms/agility/205.yaml (renamed from data/rooms/205.yaml)0
-rw-r--r--data/rooms/agility/210.yaml (renamed from data/rooms/210.yaml)0
-rw-r--r--data/rooms/agility/211.yaml (renamed from data/rooms/211.yaml)0
-rw-r--r--data/rooms/agility/212.yaml (renamed from data/rooms/212.yaml)0
-rw-r--r--data/rooms/agility/213.yaml (renamed from data/rooms/213.yaml)0
-rw-r--r--data/rooms/agility/214.yaml (renamed from data/rooms/214.yaml)0
-rw-r--r--data/rooms/agility/215.yaml (renamed from data/rooms/215.yaml)0
-rw-r--r--data/rooms/agility/220.yaml (renamed from data/rooms/220.yaml)0
-rw-r--r--data/rooms/agility/221.yaml (renamed from data/rooms/221.yaml)0
-rw-r--r--data/rooms/agility/222.yaml (renamed from data/rooms/222.yaml)0
-rw-r--r--data/rooms/agility/223.yaml (renamed from data/rooms/223.yaml)0
-rw-r--r--data/rooms/agility/224.yaml (renamed from data/rooms/224.yaml)0
-rw-r--r--data/rooms/agility/225.yaml (renamed from data/rooms/225.yaml)0
-rw-r--r--data/rooms/agility/226.yaml (renamed from data/rooms/226.yaml)0
-rw-r--r--data/rooms/player_housing/200.yaml8
-rw-r--r--data/rooms/player_housing/201.yaml8
-rw-r--r--data/rooms/player_housing/300.yaml8
-rw-r--r--data/rooms/player_housing/301.yaml8
-rw-r--r--data/rooms/town/1.yaml38
-rw-r--r--data/rooms/town/10.yaml (renamed from data/rooms/10.yaml)0
-rw-r--r--data/rooms/town/11.yaml17
-rw-r--r--data/rooms/town/12.yaml28
-rw-r--r--data/rooms/town/13.yaml46
-rw-r--r--data/rooms/town/14.yaml18
-rw-r--r--data/rooms/town/15.yaml (renamed from data/rooms/15.yaml)0
-rw-r--r--data/rooms/town/16.yaml (renamed from data/rooms/16.yaml)0
-rw-r--r--data/rooms/town/17.yaml (renamed from data/rooms/17.yaml)0
-rw-r--r--data/rooms/town/18.yaml (renamed from data/rooms/18.yaml)0
-rw-r--r--data/rooms/town/19.yaml (renamed from data/rooms/19.yaml)0
-rw-r--r--data/rooms/town/2.yaml17
-rw-r--r--data/rooms/town/20.yaml (renamed from data/rooms/20.yaml)0
-rw-r--r--data/rooms/town/21.yaml (renamed from data/rooms/21.yaml)0
-rw-r--r--data/rooms/town/22.yaml (renamed from data/rooms/22.yaml)0
-rw-r--r--data/rooms/town/23.yaml (renamed from data/rooms/23.yaml)0
-rw-r--r--data/rooms/town/24.yaml (renamed from data/rooms/24.yaml)0
-rw-r--r--data/rooms/town/25.yaml (renamed from data/rooms/25.yaml)0
-rw-r--r--data/rooms/town/26.yaml (renamed from data/rooms/26.yaml)0
-rw-r--r--data/rooms/town/27.yaml (renamed from data/rooms/27.yaml)0
-rw-r--r--data/rooms/town/28.yaml (renamed from data/rooms/28.yaml)0
-rw-r--r--data/rooms/town/3.yaml15
-rw-r--r--data/rooms/town/30.yaml (renamed from data/rooms/30.yaml)0
-rw-r--r--data/rooms/town/31.yaml (renamed from data/rooms/31.yaml)0
-rw-r--r--data/rooms/town/4.yaml29
-rw-r--r--data/rooms/town/5.yaml12
-rw-r--r--data/rooms/town/6.yaml16
-rw-r--r--data/rooms/town/7.yaml (renamed from data/rooms/7.yaml)0
-rw-r--r--data/rooms/town/8.yaml (renamed from data/rooms/8.yaml)0
-rw-r--r--data/rooms/town/9.yaml14
-rw-r--r--data/rooms/underground/50.yaml (renamed from data/rooms/50.yaml)0
-rw-r--r--data/rooms/underground/51.yaml (renamed from data/rooms/51.yaml)0
-rw-r--r--data/rooms/underground/52.yaml (renamed from data/rooms/52.yaml)0
-rw-r--r--data/rooms/underground/53.yaml (renamed from data/rooms/53.yaml)0
-rw-r--r--data/rooms/underground/54.yaml (renamed from data/rooms/54.yaml)0
-rw-r--r--data/rooms/underground/55.yaml (renamed from data/rooms/55.yaml)0
-rw-r--r--data/rooms/underground/56.yaml (renamed from data/rooms/56.yaml)0
-rw-r--r--data/rooms/underground/57.yaml (renamed from data/rooms/57.yaml)0
-rw-r--r--data/rooms/underground/58.yaml (renamed from data/rooms/58.yaml)0
-rw-r--r--data/rooms/underground/59.yaml (renamed from data/rooms/59.yaml)0
-rw-r--r--data/rooms/underground/60.yaml (renamed from data/rooms/60.yaml)0
-rw-r--r--data/rooms/underground/61.yaml (renamed from data/rooms/61.yaml)0
-rw-r--r--data/rooms/underground/62.yaml (renamed from data/rooms/62.yaml)0
-rw-r--r--data/rooms/underground/63.yaml (renamed from data/rooms/63.yaml)0
-rw-r--r--data/rooms/underground/64.yaml (renamed from data/rooms/64.yaml)0
-rw-r--r--data/rooms/underground/65.yaml (renamed from data/rooms/65.yaml)0
-rw-r--r--data/rooms/underground/66.yaml (renamed from data/rooms/66.yaml)0
-rw-r--r--data/rooms/underground/67.yaml (renamed from data/rooms/67.yaml)0
-rw-r--r--data/rooms/underground/68.yaml (renamed from data/rooms/68.yaml)0
-rw-r--r--data/rooms/wilderness/150.yaml32
-rw-r--r--data/rooms/wilderness/151.yaml (renamed from data/rooms/151.yaml)0
-rw-r--r--data/rooms/wilderness/152.yaml (renamed from data/rooms/152.yaml)0
-rw-r--r--data/rooms/wilderness/160.yaml (renamed from data/rooms/160.yaml)0
-rw-r--r--data/rooms/wilderness/161.yaml (renamed from data/rooms/161.yaml)0
-rw-r--r--data/rooms/wilderness/162.yaml (renamed from data/rooms/162.yaml)0
-rw-r--r--data/rooms/wilderness/170.yaml (renamed from data/rooms/170.yaml)0
-rw-r--r--data/rooms/wilderness/171.yaml (renamed from data/rooms/171.yaml)0
-rw-r--r--internal/action/recipe.go39
-rw-r--r--internal/action/store.go89
-rw-r--r--internal/config/config.go12
-rw-r--r--internal/game/action_burn.go13
-rw-r--r--internal/game/action_steal.go10
-rw-r--r--internal/game/aggro.go53
-rw-r--r--internal/game/cmd_alias.go8
-rw-r--r--internal/game/cmd_attack.go14
-rw-r--r--internal/game/cmd_autotrigger.go4
-rw-r--r--internal/game/cmd_bank.go4
-rw-r--r--internal/game/cmd_clean.go4
-rw-r--r--internal/game/cmd_color.go4
-rw-r--r--internal/game/cmd_colortable.go4
-rw-r--r--internal/game/cmd_consume.go293
-rw-r--r--internal/game/cmd_cook.go5
-rw-r--r--internal/game/cmd_craft.go8
-rw-r--r--internal/game/cmd_description.go4
-rw-r--r--internal/game/cmd_drink.go150
-rw-r--r--internal/game/cmd_drop.go14
-rw-r--r--internal/game/cmd_eat.go145
-rw-r--r--internal/game/cmd_estate_directory.go2
-rw-r--r--internal/game/cmd_farm.go58
-rw-r--r--internal/game/cmd_fletch.go4
-rw-r--r--internal/game/cmd_get.go15
-rw-r--r--internal/game/cmd_handlers.go370
-rw-r--r--internal/game/cmd_id.go5
-rw-r--r--internal/game/cmd_inventory.go4
-rw-r--r--internal/game/cmd_jack.go6
-rw-r--r--internal/game/cmd_look.go12
-rw-r--r--internal/game/cmd_map.go4
-rw-r--r--internal/game/cmd_mix.go6
-rw-r--r--internal/game/cmd_mods.go5
-rw-r--r--internal/game/cmd_move.go5
-rw-r--r--internal/game/cmd_option.go4
-rw-r--r--internal/game/cmd_prompt.go14
-rw-r--r--internal/game/cmd_queued.go4
-rw-r--r--internal/game/cmd_quit.go4
-rw-r--r--internal/game/cmd_registry.go26
-rw-r--r--internal/game/cmd_remove.go4
-rw-r--r--internal/game/cmd_say.go14
-rw-r--r--internal/game/cmd_score.go4
-rw-r--r--internal/game/cmd_search.go10
-rw-r--r--internal/game/cmd_smelt.go5
-rw-r--r--internal/game/cmd_smith.go4
-rw-r--r--internal/game/cmd_sneak.go4
-rw-r--r--internal/game/cmd_stats.go4
-rw-r--r--internal/game/cmd_style.go8
-rw-r--r--internal/game/cmd_task.go4
-rw-r--r--internal/game/cmd_tech.go4
-rw-r--r--internal/game/cmd_trigger.go637
-rw-r--r--internal/game/cmd_trigger_combat.go104
-rw-r--r--internal/game/cmd_trigger_enchant.go116
-rw-r--r--internal/game/cmd_trigger_transport.go54
-rw-r--r--internal/game/cmd_trigger_utility.go397
-rw-r--r--internal/game/cmd_use.go7
-rw-r--r--internal/game/cmd_walk.go4
-rw-r--r--internal/game/cmd_wear.go4
-rw-r--r--internal/game/core_course.go135
-rw-r--r--internal/game/core_equip.go (renamed from internal/game/equip_stats.go)0
-rw-r--r--internal/game/core_flags.go (renamed from internal/game/player_flags.go)0
-rw-r--r--internal/game/core_hacking.go (renamed from internal/game/hacking.go)0
-rw-r--r--internal/game/core_login_account.go (renamed from internal/game/login_account.go)0
-rw-r--r--internal/game/core_login_char.go (renamed from internal/game/login_char.go)0
-rw-r--r--internal/game/core_messages.go (renamed from internal/game/messages.go)0
-rw-r--r--internal/game/core_production.go803
-rw-r--r--internal/game/core_skill.go15
-rw-r--r--internal/game/core_startup.go770
-rw-r--r--internal/game/core_stations.go (renamed from internal/game/stations.go)0
-rw-r--r--internal/game/core_types.go (renamed from internal/game/types.go)0
-rw-r--r--internal/game/core_utils.go116
-rw-r--r--internal/game/course.go141
-rw-r--r--internal/game/death.go66
-rw-r--r--internal/game/game.go29
-rw-r--r--internal/game/help.go155
-rw-r--r--internal/game/production_core.go606
-rw-r--r--internal/game/production_menu.go209
-rw-r--r--internal/game/science.go231
-rw-r--r--internal/game/sys_assassin.go (renamed from internal/game/assassin.go)0
-rw-r--r--internal/game/sys_buff.go (renamed from internal/game/buff.go)0
-rw-r--r--internal/game/sys_combat.go114
-rw-r--r--internal/game/sys_construction.go (renamed from internal/game/construction.go)0
-rw-r--r--internal/game/sys_science.go220
-rw-r--r--internal/game/sys_technology.go (renamed from internal/game/tech.go)0
-rw-r--r--internal/game/ui_color.go (renamed from internal/game/color.go)0
-rw-r--r--internal/game/ui_help.go152
-rw-r--r--internal/game/ui_map.go (renamed from internal/game/map.go)0
-rw-r--r--internal/game/ui_prompt.go (renamed from internal/game/prompt.go)0
-rw-r--r--internal/game/ui_table.go (renamed from internal/game/table.go)0
-rw-r--r--internal/game/utils.go121
-rw-r--r--internal/object/item.go2
-rw-r--r--internal/object/item_store.go56
-rw-r--r--internal/object/object.go2
-rw-r--r--internal/object/store.go28
-rw-r--r--internal/world/mob.go15
-rw-r--r--internal/world/room.go6
-rw-r--r--internal/world/world.go52
-rw-r--r--worldbuilding_guide/construction.md4
-rw-r--r--worldbuilding_guide/items.md12
-rw-r--r--worldbuilding_guide/objects.md12
-rw-r--r--worldbuilding_guide/recipes.md24
-rw-r--r--worldbuilding_guide/rooms.md8
1048 files changed, 7145 insertions, 6324 deletions
diff --git a/AGENTS.md b/AGENTS.md
index 67b653e..7464f3d 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -7,87 +7,52 @@ Set on a terraformed asteroid where technology has regressed. "Technology" repla
## Build & Run
```bash
-make build # go build -o tc ./cmd/mud
+make build # go build -o thoi ./cmd/mud
make run # build + run with DATA_DIR=./data
-make test # go test ./... -timeout 60s
+make test # DATA_DIR=$(PWD)/data go test ./... -timeout 60s
make vet # go vet ./...
```
-Binary accepts `--config <path>` (defaults to `config.yaml`). If no config found, starts telnet on :4000.
+Binary accepts `--config <path>` (defaults to `config.yaml`). If no config found it creates one with defaults (telnet :4000, http :8888, tick 600ms).
Dependencies: `gopkg.in/yaml.v3`, `github.com/gorilla/websocket`.
-Config:
-```yaml
-game:
- tick_length: 600 # ms per game tick (default 600, min 50)
-```
-
## Package Map
```
-cmd/mud/main.go Entry point — config loading, multi-listener, tick engine
+cmd/mud/main.go Entry point — config loading, multi-listener, tick subscriber registration
internal/
- config/ YAML config (telnet, http, https listeners)
- net/ Conn interface (tcp + websocket), session states, hub, IAC echo
- game/ Login flow, command dispatch, input sanitization, all game logic
- action/ Behavior structs (GatherConfig, TalkConfig, etc.), Action, drop tables, SuccessChance
- player/ Player struct, skills, inventory, XP (RSC table), name/password validation, accounts
- world/ Room, MobDef, MobInstance, ObjState, ground items, wandering, drop tables
- combat/ Combat state tracking, OSRS-style combat formulas
- object/ ItemDef, ObjectDef, item/object YAML loaders, EquipSlot, WeaponType, ItemStats
+ config/ YAML config (telnet, telnet_tls, http, https listeners, color targets)
+ net/ Conn interface (tcpConn, wsConn), Hub, Session, session states, IAC echo
+ game/ Game struct, HandleSession state machine, command registry, all game logic
+ hacking/ Minigame interface + 3 implementations (Wumpus, Mastermind, Liar's Dice)
+ action/ Behavior structs (GatherConfig, TalkConfig, UseConfig), RecipeStore, SuccessChance
+ player/ Player struct, skills, inventory, bank, equipment, accounts, XP, validation
+ world/ World, RoomDef, MobDef, MobInstance, ObjState, ground items, wandering
+ combat/ Combat state tracking, OSRS-style formulas (HitChance, MaxHit, AttackRoll)
+ object/ ItemDef, ObjectDef, ItemStore, ObjectStore, EquipSlot, WeaponType, ItemStats
engine/ Tick scheduler — configurable ms interval, subscriber pattern, ToTicks()
- color/ xterm-256 color system with auto ANSI downgrade, numeric palette (0-255), gradients, inline tags
+ color/ xterm-256 color system with auto ANSI downgrade, gradients, inline tags
```
-`internal/game/` is the largest package. Key files:
+`internal/game/` is organized by file name prefix for easy navigation:
-| File | Purpose |
-|---|---|
-| `game.go` | Game struct, HandleSession state machine, command dispatch, ProcessQueuedCommands |
-| `login_account.go` | Account auth, creation, password handling, main menu, account rename/purge |
-| `login_char.go` | Character creation, connection, rename, delete |
-| `cmd_*.go` | Command handlers for each command (see Commands section) |
-| `action.go` | StartAction, CancelAction, AdvanceActions, checkCondition, verbAliases, verbSkill |
-| `action_*.go` | Action lifecycle per type (gather, talk, use, production) |
-| `action_burn.go` | Burn/stoke firemaking (standalone, not behavior-YAML-driven) |
-| `action_search.go` | Search bird's nests for loot |
-| `action_room.go` | RunEnterSteps (on_enter scripts), BroadcastRespawns |
-| `action_state.go` | ActionType consts, ActionState struct, Description() for look display |
-| `action_default_target.go` | Smart auto-selection for gather/attack |
-| `tick.go` | DisconnectTick, RegenTick, WanderTick, SharedDepletionTick, MoveTick, VisualTick, TechTick |
-| `map.go` | BFS graph builder, tiny map, full map, display utilities (strip, trim) |
-| `types.go` | EquipSlots, itemMatch, xpGain, deathDrop |
-| `utils.go` | Helper functions (plural, parseQty, parseChoiceIndex, formatPickupList, etc.) |
-| `help.go` | Help topic YAML loading and display |
-| `color.go` | colorMode(), colorize(), colorTag() helpers |
-| `table.go` | Unicode/ASCII table rendering for score/option/help/crafting menus |
-| `tech.go` | TechDef struct, 25 hardcoded tech definitions, techLevelBonus, applyTechProtection, totalTechBonus |
-| `cmd_tech.go` | doTech, toggleTech, doTechList, doTechQuick — technology command handlers |
-| `cmd_stats.go` | doStats — equipment bonus totals, attack roll, max hit display |
-| `cmd_clean.go` | doClean — background herb cleaning (Pharmacy) |
-| `cmd_mix.go` | doMix — potion mixing via production system (Pharmacy) |
-| `cmd_shop.go` | handleShopInput — buy/sell/browse/leave in shop interface |
-| `action_steal.go` | doSteal, startSteal, advanceSteal — thieving action lifecycle |
-| `cmd_sneak.go` | doSneak, SneakTick — sneak toggle + guard watch notifications |
-| `action_clean.go` | advanceClean — background clean action lifecycle |
-| `equip_stats.go` | playerEquipBonuses — sums all equipped items into one ItemStats |
-| `aggro.go` | checkAggro — aggressive mob attack on room enter |
-| `cmd_bank.go` | handleBankInput — deposit/withdraw/browse in bank interface |
-| `cmd_construct.go` | doConstruct — construction production command at workbench |
-| `cmd_estate_directory.go` | startEstateDirectory, lookEstateDirectory — player house directory, homeowner scanning, teleport |
-| `course.go` | CourseStore, CourseConfig, ObstacleDef, ObstacleInfo — agility course YAML loader, room-to-obstacle lookup, obstacleVerbs |
-| `cmd_agility.go` | doObstacle — agility obstacle command handler |
-| `action_agility.go` | startObstacle, advanceObstacle, obstacleFail, calcFailChance — 3-phase obstacle lifecycle |
-| `hacking.go` | HackingMinigame interface, HackingSession, terminalDefs, handleHackingInput, endHacking, hackingXP |
-| `hacking_wumpus.go` | WumpusGame — Hunt the Rogue AI (20-node dodecahedron, probes, hazards) |
-| `hacking_mastermind.go` | MastermindGame — Code Breaker (4-digit cipher, 10 guesses, feedback) |
-| `hacking_liars_dice.go` | LiarsDiceGame — Signal Bluff (hidden dice bidding vs AI, wilds, exact calls) |
-| `cmd_jack.go` | doJack, findTerminalInRoom — jack into terminals command handler |
+| Prefix | Purpose | Count |
+|---|---|---|
+| `game.go`, `cmd_registry.go`, `tick.go` | Core state machine, command dispatch, tick callbacks | 3 |
+| `core_*.go` | Infrastructure — types, utils, login, production engine, skill XP, flags, courses, equipment, stations, messages, startup validation | 13 |
+| `action_*.go` | Tick-based action lifecycles — gather, firemaking, talk, use, search, thieving, farming, agility, scavenging, assassin, fletching, cleaning, room scripts, targeting | 16 |
+| `cmd_*.go` | Command handlers — one file per command or command group | 49 |
+| `sys_*.go` | Game subsystems — technology, science, combat (death + aggro), assassin tasks, buffs, construction | 6 |
+| `ui_*.go` | Display/output — color, prompt, table, map, help | 5 |
+| `hacking/` | Hacking sub-package — Minigame interface, TerminalDef, CalcXP, WumpusGame, MastermindGame, LiarsDiceGame | 4 |
+
+**To add a new command:** create `cmd_<name>.go` with the handler, add one line to `cmd_registry.go`.
+**To add a new skill:** create `action_<skill>.go` with `start*`/`advance*` methods dispatched from `action.go`.
## Key Conventions
-**YAML-driven.** Rooms, items, objects, mobs, behaviors, drops are YAML files under `data/`. Read from disk on every access — edit a room description and it takes effect immediately, no restart.
+**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.
**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.
@@ -95,11 +60,11 @@ internal/
**Transport-agnostic.** The `Conn` interface (`internal/net/conn.go`) abstracts the transport layer. `tcpConn` wraps a raw `net.Conn` for telnet. `wsConn` wraps `gorilla/websocket.Conn` for the web client. Game code works with `*Session` regardless of transport.
-**Input sanitization.** `HandleSession` strips Unicode control characters from every input line via `player.StripControlCharacters` before dispatch. Account/character names validated to `[A-Za-z0-9 ]{1,30}`. Input truncated to 1024 bytes. Password echo suppressed via IAC ECHO (telnet) and OSC sequences (web). WebSocket origin checked against host header + loopback.
+**Input sanitization.** `HandleSession` strips Unicode control characters from every input line via `player.StripControlCharacters`. Account/character names validated to `[A-Za-z0-9 ]{1,30}`. Input truncated to 1024 bytes. Password echo suppressed via IAC ECHO (telnet) and OSC sequences (web). WebSocket origin checked against host header + loopback.
-**Tick-driven.** The world runs on a configurable tick (default 600ms). Combat, gathering, regen, wandering, respawns, shared depletion, fire ticks all advance per tick.
+**Tick-driven.** The world runs on a configurable tick (default 600ms, min 50ms). All game advancement happens per tick.
-**Fractional ticks.** All YAML timer fields (tool_speed, speed, base_wait, deplete_timer, etc.) accept `float64` values. `engine.ToTicks(base)` probabilistically rounds to an integer — a tool_speed of 4.5 means each action cycle has a 50% chance of 4 ticks and 50% of 5 ticks.
+**Fractional ticks.** All YAML timer fields accept `float64` values. `engine.ToTicks(base)` probabilistically rounds to an integer — a tool_speed of 4.5 means each action cycle has a 50% chance of 4 ticks and 50% of 5 ticks.
**Concurrency.** Every state-bearing package uses its own `sync.Mutex`:
- `world.World` — groundItems, objStates, seeded, objMoves
@@ -119,181 +84,163 @@ The pattern is: lock, snapshot/unmarshal, unlock, then process. The engine creat
## Data Files
```
-data/rooms/<id>.yaml Room definitions (exits, objects, mobs, spawns, on_enter)
+data/rooms/<id>.yaml Room definitions (exits, objects, mobs, item_spawns, on_enter)
+data/rooms/player_housing/ Player house rooms
data/items/<id>.yaml Item definitions (stats, equip_slot, tool_type, burn_ticks, search_table, etc.)
-data/mobs/<id>.yaml Mob definitions (combat stats, drops, behavior — NO wander config)
-data/objects/<id>.yaml Object definitions (name, behavior, hidden, inroom_description, removal_item, props)
-data/behaviors/<id>.yaml Behaviors (gather, talk, use)
-data/drops/<id>.yaml Shared drop tables (weighted item lists)
+data/mobs/<id>.yaml Mob definitions (combat stats, drops, behavior, steal config)
+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, crafting, smithing, etc.)
+data/recipes/<id>.yaml Recipe definitions (cooking, smithing, crafting, fletching, etc.)
+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)
data/players/characters/ Character YAML (gitignored)
```
-Wander config (`wander_rooms`, `wander_interval`) is set per-instance in room YAML — mob defs are generic. Mobs wander via legal (unconditioned) room exits; objects teleport between rooms in their list.
+Wander config (`wander_rooms`, `wander_interval`) is set per-instance in room YAML — mob defs are generic. Mobs wander via legal (unconditioned) room exits; objects teleport between rooms in their assigned list.
See `worldbuilding_guide/` for full YAML format reference with examples.
+## Startup Validation
+
+At startup, the server runs comprehensive integrity checks on all data files. Errors cause the server to exit; warnings are informational.
+
+**Duplicate ID checks** across all data directories (items, rooms, mobs, objects, drops, recipes, courses, modules, help). Detects collision from two files with the same ID in different subdirectories.
+
+**Referential integrity checks:**
+- Room exits → target rooms exist
+- Room mobs/objects/spawns → mob defs/object defs/items exist
+- Mob drops, steal tables, finishing blow items → referenced items/drop tables exist
+- Object removal items, steal tables, guard mobs, use_interaction items → exist
+- Object gather config drops → items/tables exist; bait items exist
+- Object talk config node actions (give_item, take_item, teleport, shop items) → exist
+- Item search tables, made_from items/byproducts, farm_product → exist
+- Recipe consume items, output, fail product → exist
+- Drop table sub-references → exist
+- Course start_room and obstacle rooms → exist
+
+**World wiring checks** (config-driven):
+- Orphan rooms (no exit from any room leads to them)
+
+### Config: `startup_validation`
+
+```yaml
+game:
+ tick_length: 600
+ startup_validation:
+ check_sources: [1] # rooms to start reachability walk from
+ ignore_unreachable: [] # suppress "no exit leads here" for these rooms
+```
+
+Defaults: `check_sources: [1]`, ignore list empty. Set `check_sources` to include rooms accessible only via teleport (e.g., `[1, 1000]`) to suppress false orphan warnings. Use `ignore_unreachable_to` for rooms that are intentionally isolated.
+
## Inline Color Tags
-Room descriptions (and prompts) support inline color tags using `{spec}text{/}` syntax. Untagged text in room descriptions uses the `room_desc` color target.
+Room descriptions and prompts support inline color tags using `{spec}text{/}` syntax.
```yaml
description: "On the table lies a {182 bold}mysterious vase{/} with a rose in it."
```
-Tag spec format matches the color command: `{<0-255> [bold] [dim] [underline]}text{/}`.
+Tag spec format: `{<0-255> [bold] [dim] [underline]}text{/}`.
-Gradients are supported: `{g:196,82}gradient text{/}`. Multi-stop: `{g:45,39,59}three stops{/}`.
+Gradients: `{g:196,82}gradient text{/}`. Multi-stop: `{g:45,39,59}three stops{/}`. Item and object YAML `color` fields also support gradient syntax: `color: "g:196,208,226"`.
Gradients interpolate in RGB space across the xterm-256 palette. In ANSI mode, each character maps to the nearest ANSI color.
-Item and object YAML `color` fields also support gradient syntax: `color: "g:196,208,226"`.
-
-## Commands
-
-### Instant (execute immediately, no queue)
-| Command | Description |
-|---------|-------------|
-| `say <text>` | Room chat (preserves case from raw input) |
-| `score` / `sc` | Character stats and skills |
-| `inventory` / `i` / `inv` | Inventory contents (28 slots) |
-| `equip` / `eq` / `equipment` / `wear` / `wield` | View or change equipped items (no args = view, with args = equip) |
-| `look` / `l` | Room view (mobs, objects, ground items, exits, players) |
-| `look <target>` / `l <target>` | Examine mobs, objects, items, players, directions |
-| `exits` | List available exits |
-| `help [topic]` | Help topics from data/help/ |
-| `map` | BFS ASCII map centered on player |
-| `option` / `options [name] [value]` | Account-wide settings (see Option System) |
-| `alias <name> <cmd>` | Account-wide command alias with arg passthrough |
-| `unalias <name>` | Remove an alias |
-| `description` / `desc` | Set custom player description |
-| `prompt <value>` | Set custom per-character command prompt |
-| `queued` | Show pending tick actions |
-| `color` / `colors [target] [value]` | Customize display colors (account-wide) |
-| `color reset <target>` | Reset a color target to server default |
-| `color <target> off` | Disable color for a specific target |
-| `colortable` | Display xterm-256 color reference chart |
-| `style <name>` | Set combat style: accurate, aggressive, defensive, balanced (prefix match) |
-| `stats` | Show total equipment bonuses, attack type, attack roll, max hit |
-| `tech [name]` / `t` | Toggle tech on/off, `tech list` shows all, `tech quick <name>` sets quick tech |
-
-### Free (stackable, execute before active)
-| Command | Description |
-|---------|-------------|
-| `equip` / `wear` / `wield <item>` | Equip item to correct slot. `equip all` auto-equips best per slot |
-| `remove` / `unwear` / `unwield <item>` | Unequip item back to inventory. `remove all` unequips everything |
-| `eat <item>` | Eat food to heal (3-tick cooldown, can eat during combat) |
-| `fletch [item]` | Fletch logs/items into bows, arrows, etc. (background action) |
-| `clean [herb]` | Clean grimy herbs (background action, Pharmacy skill) |
-
-### Active (replaces previous active, queued per tick)
-| Command | Description |
-|---------|-------------|
-| `n` / `s` / `e` / `w` / `u` / `d` | Movement (multi-tick, equipment-dependent speed, flee during combat) |
-| `get` / `take` / `grab` / `pick <item>` | Pick up item from ground (reservation-aware) |
-| `get all` / `get all <name>` | Pick up all (or matching) items |
-| `drop <item>` | Drop item to ground |
-| `drop all` / `drop all <name>` | Drop all (or matching) items |
-| `attack` / `kill <mob>` | Combat with numbered targeting and smart default |
-| `mine` / `chop` / `cut` / `fish <object>` | Gathering with smart default (verbAliases map to "gather") |
-| `use <item> [on/with] <target>` | Universal item interaction: recipes, crafting, gathering shortcuts |
-| `cook <item>` | Cook raw food on a fire or cooking range (Cooking skill) |
-| `smelt [item]` | Smelt ore into bars at a furnace (Smithing skill) |
-| `smith [item]` | Smith bars into items at an anvil with a hammer (Smithing skill) |
-| `mix [item]` | Mix potions from unfinished potions and reagents (Pharmacy skill) |
-| `talk` / `speak` / `ask <target>` | NPC conversations (objects or mobs with behavior) |
-| `pull` / `push <object>` | Interact with objects (shortcut for use) |
-| `search <item>` | Search bird's nests / searchable items (rolls on search_table) |
-| `burn [item]` | Start a fire (smart default: auto-select single log type) |
-| `stoke [item]` | Add logs to existing fire for XP |
-| `walk <room_number>` | BFS pathfinding to a room by number |
-| `walk <directions>` | Direction sequence like `3n2e1u` (capped by agility level) |
-| `construct` / `make [item]` | Construction: make planks or furniture at a workbench (Active, production system) |
-| `bank` / `use bank` | Open bank interface at a bank booth (deposit, withdraw, browse, leave) |
-| `scramble` / `jump` / `swing` / `balance` / `climb` / `crawl` / `vault` / `leap` / `slide` | Agility obstacle verbs (Active, per-room validation) |
-| `jack` / `jackin [terminal]` | Jack into a terminal for hacking minigames (Active) |
-| `quit` | 10-tick rest sequence, then return to menu (blocked during combat) |
-
## Command Dispatch
-`handleGameCommand()` in `internal/game/game.go`:
-1. Cancel rest timer
-2. Resolve account aliases (alias expansion BEFORE command lookup — can shadow built-ins)
-3. Classify command (Instant / Free / Active / Unknown)
-4. Instant runs immediately; Free/Active queue for next tick
+`handleGameCommand()` in `game.go`:
+1. Expands account aliases (aliases can shadow built-in commands)
+2. Splits input, lowercases the command word
+3. Special-cases `equip`/`wear`/`wield` with no args → display equipment directly
+4. Calls `classifyCommand(cmd)` from `cmd_registry.go` → `ClassInstant` / `ClassFree` / `ClassActive` / `ClassUnknown`
+5. ClassInstant executes `executeCommand()` immediately + writes prompt
+6. ClassFree appends to `freeQueue[playerName]` for next tick
+7. ClassActive replaces entry in `activeQueue[playerName]`, sorted by timestamp on execution
+8. ClassUnknown falls through to `verbAliases` and `obstacleVerbs` maps, then prints error
## Tick-Based Action Queue
-`ProcessQueuedCommands` runs each tick:
-1. Clears stale ActionState from one-tick actions (move, get, drop)
+`ProcessQueuedCommands()` runs each tick:
+1. Clears stale `ActionState` from one-tick actions (move, get, drop)
2. Executes all free commands per player in queued order
3. Executes all active commands sorted globally by timestamp (first-to-queue wins conflicts)
4. Advances walk sequences (one step per tick)
5. Flushes pending shared depletions
-**Conflict resolution:** multi-player attack on same mob or get on same item — first to queue wins (timestamp-order). Shared depletion defers to award all successful gatherers on depletion tick.
+**Conflict resolution:** multi-player attack on same mob, get on same item, etc. — first to queue wins (timestamp-order). Shared depletion awards all successful gatherers on depletion tick.
-**ActionState** tracks player activity for `look` display. Timed actions (gather, combat, burn, stoke, use, rest, walk) persist until interrupted. One-tick actions (move, get, drop) clear after one tick.
+**ActionState** tracks player activity for `look` display. Timed actions persist until interrupted; one-tick actions (move, get, drop) clear after one tick.
-Queue feedback controlled by `queue_silently` option (default on).
+Queue feedback is suppressed by default via the `queue_silently` option.
## Action System
-Actions that interact with objects/mobs route through `StartAction()` in `action.go`:
+Actions that interact with objects/mobs route through `startAction()` in `action.go`:
1. Normalizes verb via `verbAliases` map (mine/chop/fish/cut → gather, talk/speak/ask → talk)
2. Multiple object types → "That's ambiguous, which one?"
3. Searches object instances in room (numbered targeting via `N.name`)
4. Falls back to mob instances if no object matches
5. Loads the behavior from YAML
-6. Routes to type-specific handler (startGather, startTalk, startUse)
+6. Routes to type-specific handler (startGather, startMobTalk, startTalk, startUse)
The `verbSkill` map maps verb → skill name for default target resolution:
- mine → mining, chop/cut → woodcutting, fish → fishing
-### Action Types
+### Action Types (from action_state.go)
-| ActionType | Trigger | Description |
+| ActionType | Trigger | Type |
|---|---|---|
-| `gathering` | mine/chop/cut/fish | Resource gathering with skill checks, tool reqs, depletion |
-| `combating` | attack/kill | Combat with attack/defense rolls |
-| `using` | use | Crafting — consume items, produce output |
-| `talking` | talk/speak/ask | NPC conversation trees with choices/conditions/actions |
-| `burning` | burn | Firemaking startup (3 phases) |
-| `stoking` | stoke | Adding logs to existing fire |
-| `searching` | search | Search bird's nests / items with search_table |
-| `resting` | quit | 10-tick rest before disconnect |
-| `walking` | walk | Multi-step movement (one dir per tick) |
-| `moving` | n/s/e/w/u/d | Multi-tick movement state |
-| `picking_up` | get | One-tick pickup state |
-| `dropping` | drop | One-tick drop state |
-| `cooking` | cook | Cooking food on fire/range |
-| `smelting` | smelt | Smelting ore into bars at furnace |
-| `smithing` | smith | Smithing bars into items at anvil |
-| `cleaning` | clean | Cleaning grimy herbs (background action) |
-| `mixing` | mix | Mixing potions (Pharmacy) |
-| `constructing` | construct/make | Construction via production system (workbench) |
-
-### Behavior Types (YAML-driven)
-
-| Type | Verbs | Use |
+| `gathering` | mine/chop/cut/fish | Active |
+| `combating` | attack/kill | Active |
+| `using` | use / pull / push | Active |
+| `talking` | talk/speak/ask | Active |
+| `burning` | burn | Active |
+| `stoking` | stoke | Active |
+| `searching` | search | Active |
+| `resting` | quit | Active |
+| `walking` | walk | Active |
+| `moving` | n/s/e/w/u/d | Active |
+| `picking_up` | get | Active |
+| `dropping` | drop | Active |
+| `producing` | cook/smelt/smith/craft/mix/construct | Active |
+| `fletching` | fletch | Free (background) |
+| `eating` | eat | Free |
+| `cleaning` | clean | Free (background) |
+| `identifying` | identify/id | Active |
+| `stealing` | steal/thieve | Active |
+| `planting` | plant | Active |
+| `harvesting_crop` | harvest | Active |
+| `raking` | rake | Active |
+| `watering` | water | Active |
+| `curing` | cure | Active |
+| `traversing` | obstacle verbs (scramble/jump/etc.) | Active |
+| `hacking` | jack/jackin | Active |
+
+### Behavior Types (YAML-driven in behavior.go)
+
+| Type | Verbs | Description |
|---|---|---|
| `gather` | mine, chop, cut, fish | Resource gathering with skill checks, tool requirements, depletion/respawn |
| `use` | use | Crafting stations — consume items, produce output, optional skill checks |
-| `talk` | talk, speak, ask | NPC conversation trees with choices, conditions, actions |
+| `talk` | talk, speak, ask | NPC conversation trees with choices, conditions, node actions |
-### Behaviors Not in YAML (hardcoded)
+### Hardcoded Actions (not YAML-driven)
| Action | File | Description |
|---|---|---|
-| burn | `action_burn.go` | 3-phase firemaking: drop log → try burn → skill check → create fire object |
-| stoke | `action_burn.go` | Add logs to fire, XP per log, increments fire quality |
+| burn/stoke | `action_burn.go` | 3-phase firemaking: drop log → try → skill check → create fire. Stoke adds logs. |
| search | `action_search.go` | Search items with search_table, consumes one, rolls on drop table |
-| cook | `action_production.go` | Recipe-driven cooking on fire/range stations |
-| smelt | `action_production.go` | Recipe-driven smelting of ore into bars at furnace |
-| smith | `action_production.go` | Recipe-driven smithing of bars into items at anvil (requires hammer) |
-| steal | `action_steal.go` | Thieving: pickpocket mobs, steal from objects, guard watching, sneak mode |
+| cook/smelt/smith/craft/mix | `production_core.go` | Unified recipe-driven production — station checks, timed loops, success/fail, XP, byproducts |
+| fletch | `action_fletch.go` | Background fletching — instant or timed, stackable output |
+| clean | `action_clean.go` | Background herb cleaning via recipe matching |
+| steal | `action_steal.go` | Thieving — pickpocket, stall stealing, guard watching, sneak mode |
+| agility | `action_agility.go` | 3-phase obstacle traversal with fail chance, course lap tracking |
+| farm | `action_farm.go` | Planting, growth stages (500-tick), watering, disease, harvesting |
+| identify | `action_identify.go` | Scavenging — scrap_metal → junk at altars |
+| finishing blow | `action_finishing_blow.go` | Assassin special item on mob at 1 HP |
### GatherConfig Fields
@@ -305,7 +252,7 @@ The `verbSkill` map maps verb → skill name for default target resolution:
| `base_wait` | Base ticks per action cycle |
| `tools` | Required tool_type list |
| `bait` | Required bait item |
-| `success` | SuccessFormula (base + per_level, capped) |
+| `success` | SuccessFormula (base + per_level * (level - required), capped) |
| `drops` | Weighted drop entries (item_id, table ref, depletes, quantity, message, level, xp) |
| `respawn_timer` | Ticks until depleted object respawns |
| `deplete_timer` | Ticks for shared depletion (trees) |
@@ -318,8 +265,7 @@ The `verbSkill` map maps verb → skill name for default target resolution:
### SuccessChance Formula
-`SuccessChance(cfg SuccessFormula, level int, requiredLevel int) float64`:
-```
+```go
chance = cfg.Base + (level - requiredLevel) * cfg.PerLevel
clamped to [0, cfg.Cap]
```
@@ -335,7 +281,9 @@ clamped to [0, cfg.Cap]
| `teleport` | Moves player to a room ID |
| `heal` | Restores hitpoints |
| `cost` | Deducts credits from player |
-| `shop` | Opens a buy/sell interface (see Shop section) |
+| `shop` | Opens a buy/sell interface |
+| `assign_task` / `skip_task` / `extend_task` | Assassin task management |
+| `sawmill` | Opens sawmill interface (construction) |
### Condition System
@@ -343,80 +291,96 @@ Used by exits, talk options, on-enter scripts, and use_interactions checks:
| Field | Scope | Example |
|---|---|---|
-| `flag` | World (shared by all players) | `flag: gate_open` |
-| `player_flag` | Per-character (quest progress) | `player_flag: finished_tutorial` |
-| `has_item` | Player inventory check | `has_item: bronze_key` |
-| `all_of` | All sub-conditions must pass | Nested list |
+| `flag` | World (shared by all) | `flag: gate_open` |
+| `player_flag` | Per-character | `player_flag: finished_tutorial` |
+| `has_item` | Player inventory | `has_item: bronze_key` |
+| `min_credits` | Player credits | `min_credits: 100` |
+| `all_of` | All sub-conditions pass | Nested list |
| `any_of` | Any sub-condition passes | Nested list |
| `not` | Invert the check | `not: true` |
-### Firemaking (burn/stoke)
-
-See `internal/game/action_burn.go`.
-
-**Burn flow (3 phases):**
-- Phase 0 (inventory only): Drops 1 log to ground, outputs "You drop the X and begin to make a fire..."
-- Phase 1: "You try burning the X on the ground..." (waits tool_speed ticks)
-- Phase 2: Skill check (chance = 0.5 + (level - fireLevel) * 0.02, capped [0.05, 0.95]). On success creates `fire` object (quality = log's burn_ticks), gives XP, broadcasts. On fail retries Phase 1.
-
-**Stoke flow:** 10-tick loop → "You throw X onto the fire", gives XP, adds burn_ticks/2 to fire quality. Ends when out of item.
-
-**Fire objects:** Created via `World.AddObjInstance()`, tracked by `ObjState.Quality`. Each tick `FireTick()` decrements quality; at 0, `RemoveObjInstance()` cleans up and drops `removal_item` (e.g., ashes).
-
-**Fire tools:** `matches` (stackable, consumed), `firesteel` (non-consumable), `lighter` (quality-based, `MaxQuality: 100`, decreases per use). Tool search: main hand → inventory.
-
-**Item fields for firemaking:** `burn_ticks`, `fire_level`, `fire_xp`, `quality`, `max_quality`.
-
## Two Depletion Mechanics
**Per-drop depletion** (mining, regular trees): Set `depletes: true` on a drop entry. The resource depletes on first successful gather of that drop.
-**Shared depletion** (higher-tier trees): Set `deplete_timer: <ticks>` on the behavior. A shared timer counts down while anyone is chopping; when it hits 0, the next successful gather depletes the tree for all players. Timer regenerates when no one is chopping. Managed by `SharedDepletionTick` in `tick.go`.
+**Shared depletion** (higher-tier trees): Set `deplete_timer: <ticks>` on the behavior. A shared timer counts down while anyone is chopping; when it hits 0, the next successful gather depletes the tree for all players. Timer regenerates when no one is chopping. Managed by `SharedDepletionTick`.
## Ground Item System
- `DropDespawnTicks = 1000` — dropped items despawn after 1000 ticks
- `ReserveTicks = 100` — mob loot is reserved for the killer for 100 ticks
-- Items with same ID in the same room are NOT merged — each entry has independent despawn timer
-- Spawn items respawn after their respawn delay; nearby matching items are merged on respawn
+- Items with same ID in the same room are NOT merged — independent despawn timers
+- Spawn items respawn after their delay; nearby matching items are merged on respawn
- Despawn timers visible via `despawn` option; reserve info via `reserve` option
## Combat System
-**Attack types.** Weapons have an `attack_type` field (stab/slash/crush/ranged/science). The attack type determines which of the player's 5 attack bonuses is used for accuracy and which of the mob's 5 defense bonuses is used. Equipment stats use per-type fields: `stab_attack`, `slash_attack`, `crush_attack`, `science_attack`, `ranged_attack` (and matching defense fields).
-
-**OSRS accuracy formula.** `HitChance(attackRoll, defenseRoll)` uses a continuous probability curve: if `a > d`, chance = `1 - (d+2)/(2*(a+1))`; otherwise `a/(2*(d+1))`. Equal rolls ≈ 50% hit chance.
+**Attack types.** Weapons have an `attack_type` field (stab/slash/crush/ranged/science). The attack type determines which player attack bonus and which mob defense bonus to use.
-**Ranged combat.** When `weapon_type` is "ranged", uses Ranged level for attack rolls, `ranged_attack` for accuracy, `ranged_strength` from ammo for max hit. Consumes 1 ammo per attack. If ammo runs out, combat ends. XP: 75% Ranged, 25% Hitpoints (all styles).
+**OSRS accuracy formula.** `HitChance(a, d)`: if `a > d` → `1 - (d+2)/(2*(a+1))`, else `a/(2*(d+1))`. Equal rolls ≈ 50%.
-**Aggressive mobs.** Mobs with `aggressive: true` auto-attack players entering their room (1-tick delay). Only aggros if `playerLevel <= mobLevel * 2` (OSRS rule). Checked on room enter and login via `checkAggro()`.
+**Ranged combat.** Weapon type "ranged" uses Ranged level for attack rolls, consumes 1 ammo per attack. If ammo runs out, combat ends. XP: 75% Ranged, 25% Hitpoints.
-**Equipment requirements.** Items can have `requirements: { attack: 20, defense: 10 }`. Checked in `doWear` and `doWearAll` before equipping. Displayed when examining items.
+**Agile mobs.** Mobs with `aggressive: true` auto-attack players entering their room (1-tick delay) if `playerLevel <= mobLevel * 2`.
-**Technology in combat.** Active tech boosts (Clarity, Amplifier, etc.) add percentage-based level bonuses to attack/strength/defense/ranged. Protection techs (Kinetic Barrier, Projectile Screen, Neural Firewall) reduce incoming damage by 40%. Dead Man's Switch deals 25% max HP damage to the mob on player death.
+**Equipment requirements.** Items can have `requirements: { attack: 20, defense: 10 }` — checked before equipping.
-**Fleeing combat:** Players can move in any direction to attempt to flee. Movement takes the normal tick delay (default 4, reduced by equipment). During the countdown, the mob continues attacking. If the mob lands a hit, the escape fails ("Can't escape!") and combat resumes. If no hit lands, the player escapes. With Cape of Agility, fleeing is instant. The `run_countdown` option shows tick-by-tick countdown messages.
+**Fleeing combat:** Movement during combat is a flee attempt. Mobs continue attacking during the movement countdown; a hit aborts the flee ("Can't escape!"). Cape of Agility makes fleeing instant.
-**Death mechanic:** Player drops credits to ground. If player has >3 items (inventory + equipment), the 3 most valuable are kept and rest are dropped. Player teleports to room 1 at full HP. All active techs deactivated on death.
+**Death mechanic:** Player drops credits to ground. If >3 items total (inventory + equipment), the 3 most valuable are kept, rest dropped. Player teleports to room 1 at full HP. All active techs deactivated.
**XP formula:** `baseXP = dmg * 4`, distributed by attack style:
-- Accurate: 75% Attack, 25% Hitpoints
-- Aggressive: 75% Strength, 25% Hitpoints
-- Defensive: 75% Defense, 25% Hitpoints
-- Balanced: 25% each Attack/Strength/Defense/Hitpoints
-- Ranged (all styles): 75% Ranged, 25% Hitpoints
+- Accurate: +3 attack, 75% Attack XP / 25% HP
+- Aggressive: +3 strength, 75% Strength XP / 25% HP
+- Defensive: +3 defense, 75% Defense XP / 25% HP
+- Balanced: +1 each, 25% each Attack/Strength/Defense/HP
+- Ranged styles: varied bonus, 75% Ranged XP / 25% HP
-**Mob combat level:** `base = (def + hp) / 4`, `offensive = max(melee=(atk+str)/4, ranged=rng*3/8, science=sci*3/8)`, `level = floor(base + offensive)`.
+**Mob combat level:** `floor((def+hp)/4 + max((atk+str)/4, rng*3/8, sci*3/8))`.
-**Player combat level:** Ranged-or-melee dominant calculation. Includes Technology (0.25) and Science (0.125).
-
-**Mob respawn:** Default 30 ticks. Broadcasts spawn messages if `mob_spawn` option is on.
+**Player combat level:** Ranged-or-melee dominant, includes Technology (0.25) and Science (0.125).
**Mob DropTable:** `Remains` (guaranteed drop item), `Loot` (weighted DropEntry table).
+## Technology System (tech.go)
+
+25 hardcoded techs in 4 categories, unlocked by Technology level:
+- **Attack/Strength/Defense/Ranged/Science tiers:** I/II/III at 5%/10%/15% level boost
+- **Protection techs:** Kinetic Barrier (melee), Projectile Screen (ranged), Neural Firewall (science) — 40% damage reduction each
+- **Utility:** Nano Repair (2x HP regen), Rapid Repair (4x), Power Saver (20% drain reduction), Dead Man's Switch (25% max HP retribution on death)
+- **Combo:** Overclock (15% atk+str), Fortify (15% def + 2x regen)
+
+Battery = Technology level (float64). Each tech has a drain rate (0.05–0.25 per tick). Equipment TechnologyBonus reduces drain (capped at 50%). Active techs deactivate when battery hits 0. 1-tick "flicking" grace period supported.
+
+## Science System (science.go)
+
+Mods loaded from `data/modules/` — categories: mods, enchant, chip, processing, transport, combat. Junk cost for casting, deck equipment provides junk. Trigger command for casting, autotrigger for automation. Mod list viewable with `mods` command.
+
+## Hacking System (game/hacking/)
+
+Minigame interface: `Init(level) string`, `HandleInput(input) (output, done, won)`, `Name() string`.
+
+Three minigames at 3 terminal levels (1, 20, 40):
+- **WumpusGame** — Hunt the Rogue AI: 20-node dodecahedron, probes, hazards
+- **MastermindGame** — Code Breaker: 4-digit cipher, 10 guesses, feedback
+- **LiarsDiceGame** — Signal Bluff: hidden dice bidding vs AI, wilds, exact calls
+
+XP scales with Hacking level.
+
+## Skills (23 total, 1-99, RSC XP table)
+
+| Category | Skills |
+|---|---|
+| Combat | Attack, Strength, Defense, Hitpoints, Ranged, Science, Technology, Assassin |
+| Gathering | Fishing, Woodcutting, Mining, Hacking, Scavenging, Farming, Thieving |
+| Production | Cooking, Smithing, Crafting, Fletching, Pharmacy, Construction, Firemaking |
+| Utility | Agility |
+
+All fully implemented.
+
## Option System
-Account-wide options set via `option <name> <value>`. Options are stored in the account YAML and shared by all characters on the same account.
+Account-wide options set via `option <name> <value>`. Stored in account YAML, shared by all characters on the account.
| Option | Type | Default | Description |
|---|---|---|---|
@@ -424,34 +388,64 @@ Account-wide options set via `option <name> <value>`. Options are stored in the
| `tiny_map` | string | "right" | off/right/left — mini-map display |
| `xp_drops` | bool | true | XP drop messages |
| `exits` | bool | true | Long exit display in look |
-| `mob_enter` | bool | true | Messages when mobs enter the room |
-| `mob_leave` | bool | true | Messages when mobs leave the room |
+| `mob_enter` | bool | true | Messages when mobs enter |
+| `mob_leave` | bool | true | Messages when mobs leave |
| `mob_spawn` | bool | true | Messages when mobs spawn |
| `reserve` | bool | true | Show full reserved item details |
-| `depletion` | bool | false | Show depletion and despawn timers |
+| `depletion` | bool | false | Show depletion timers |
| `despawn` | bool | false | Show ground item despawn timers |
| `color` | string | "xterm256" | none/ansi/xterm256 — color output mode |
| `mapwidth` | int | 30 | Map viewport width |
| `mapheight` | int | 20 | Map viewport height |
| `mappadding` | string | "none" | none/x/y/xy — blank-row stripping |
-| `automap` | bool | false | Show map automatically after moving |
+| `automap` | bool | false | Auto-show map after moving |
| `queue_silently` | bool | true | Suppress queue messages |
| `room_desc_width` | int | 70 | Room desc line wrap width |
| `unicode` | bool | true | Unicode box-drawing characters |
-| `run_countdown` | bool | false | Show countdown messages when fleeing combat |
-| `visual_ticks` | bool | false | Display a tick marker every game tick |
-| `visual_tick_count` | int | 0 | Cycle length for tick counter (0 = no counter) |
-| `visual_tick_text` | string | "TICK" | Text displayed for visual ticks |
-
-Prompt is NOT an option — it is a per-character setting via the `prompt` command. Default: `"> "`. `prompt none` sets it to blank. Stored in character YAML as `prompt:`.
+| `run_countdown` | bool | false | Flee countdown messages |
+| `visual_ticks` | bool | false | Tick marker display |
+| `visual_tick_count` | int | 0 | Tick counter cycle length |
+| `visual_tick_text` | string | "Tick" | Text for visual tick marker |
+| `fletch_all` | bool | false | Auto-start fletching |
+| `smith_all` | bool | false | Auto-start smithing |
+| `cook_all` | bool | false | Auto-start cooking |
+| `smelt_all` | bool | false | Auto-start smelting |
+| `mix_all` | bool | false | Auto-start mixing |
+| `construct_all` | bool | false | Auto-start constructing |
+| `craft_all` | bool | false | Auto-start crafting |
+
+Prompt is NOT an option — per-character via the `prompt` command. Default: `"> "`. `prompt none` sets it to blank.
Prompt variables: `%h` (HP), `%H` (max HP), `%b` (battery), `%B` (max battery), `%c` (credits), `%i` (free slots), `%s` (style short), `%S` (style long), `%m` (mob HP), `%M` (mob max HP), `%X_<skill>` (XP), `%x_<skill>` (XP to next).
-New accounts are asked "Should I disable color? [y/N]" after password creation. Pressing enter defaults to No (xterm256 enabled). Choosing Y sets color to "none".
+## Session States (net/server.go)
-## ItemDef Fields
+| State | Purpose |
+|---|---|
+| `StateAccountName` | Entering account name |
+| `StatePassword` | Entering password |
+| `StateNewAccountPass` | Setting password for new account |
+| `StateNewAccountConfirm` | Confirming password |
+| `StateColorChoice` | New account: "Should I disable color?" |
+| `StateMenu` | Main menu |
+| `StateNewCharName` | Naming new character or selecting for connection |
+| `StateRenameAccount` | Renaming account |
+| `StateRenameChar` | Selecting character to rename |
+| `StateRenameCharName` | Entering new character name |
+| `StateDeleteChar` | Confirming character deletion |
+| `StatePurgeAccount` | Confirming account purge |
+| `StateGame` | In-game command mode |
+| `StateChangeDescription` | Setting player description |
+| `StateTalk` | NPC conversation input |
+| `StateDropAllConfirm` | Confirming `drop all` |
+| `StateRecipeChoice` | Recipe/product selection (by number or name) |
+| `StateHowMany` | "How many?" production count |
+| `StateSmithProduct` / `StateFletchProduct` / `StateCraftProduct` / `StateProductChoice` | Product selection table menus |
+| `StateShop` | Shop interface (buy, sell, browse, leave) |
+| `StateBank` | Bank interface (deposit, withdraw, browse, leave) |
+| `StateHacking` | Jacked into a terminal running a minigame |
-From `internal/object/item.go`:
+## ItemDef Fields (object/item.go)
| Field | Description |
|---|---|
@@ -462,88 +456,41 @@ From `internal/object/item.go`:
| `value` | Credit value |
| `stackable` | Can stack in inventory |
| `equip_slot` | head/neck/torso/legs/hands/feet/back/ammo/main_hand/off_hand/ring |
-| `weapon_type` | "melee" / "ranged" / "science" |
-| `attack_type` | "stab" / "slash" / "crush" / "ranged" / "science" |
-| `stats` | Per-type attack/defense bonuses, strength, ranged strength, science damage, technology bonus |
-| `speed` | Attack speed |
-| `requirements` | Skill level requirements to equip (e.g., `{attack: 20, defense: 10}`) |
+| `weapon_type` | melee / ranged / science |
+| `attack_type` | stab / slash / crush / ranged / science |
+| `stats` | Per-type attack/defense bonuses, strength, ranged_strength, science_damage, technology_bonus |
+| `speed` | Attack speed in ticks |
+| `requirements` | Skill level requirements to equip |
| `tool_type` | Tool type string for gather requirements |
| `tool_speed` | Speed bonus for gathering |
| `burn_ticks` | Burn duration for firemaking |
| `fire_level` | Required firemaking level |
| `fire_xp` | XP for burning |
-| `quality` | Default quality (e.g., lighter butane) |
-| `max_quality` | Maximum quality |
-| `search_table` | Drop table for searching this item |
-| `search_misc_table` | Secondary drop table |
+| `quality` / `max_quality` | Default and maximum quality |
+| `search_table` / `search_misc_table` | Drop tables for searching this item |
| `search_ticks` | Ticks per search action |
| `search_message` | Custom search message |
-| `made_from` | MadeFrom entries for item-on-item combinations (byproducts supported) |
+| `made_from` | MadeFrom entries for item-on-item combinations (with byproducts) |
| `ticks` | Ticks per production cycle for MadeFrom combines (default 2) |
+| `farming` | Farming-related fields (seed type, patch, yield, level, xp) |
+| `potion` | Potion properties (buff levels for attack/strength/defense/agility) |
-## ObjectDef Fields
-
-From `internal/object/object.go`:
-
-| Field | Description |
-|---|---|
-| `id` | Unique identifier |
-| `name` | Display name |
-| `behavior` | Behavior ID for interaction |
-| `hidden` | Interactable but not shown in room listing |
-| `inroom_description` | Custom "A X is here." text |
-| `removal_item` | Item dropped when instance is removed (fire → ashes) |
-| `description` | Text shown when examining the object (supports `{quality}` placeholder for quality-based objects) |
-| `use_interactions` | List of item-on-object interactions with conditions and actions (see worldbuilding guide) |
-
-## World ObjState Fields
-
-From `internal/world/world.go`:
-
-| Field | Description |
-|---|---|
-| `DefID` | Object definition ID |
-| `Name` | Display name |
-| `Index` | Index for duplicate objects in same room |
-| `RoomID` | Current room |
-| `Depleted` | Resource is depleted |
-| `DepleteTimer` | Ticks until respawn |
-| `SharedMax` | Max shared depletion timer |
-| `SharedTimer` | Current shared timer value |
-| `WanderRooms` | Rooms for object wandering |
-| `WanderInterval` | Ticks between wanders |
-| `Quality` | Fire quality, resource health, etc. |
-| `JustRespawned` | Flag for respawn broadcast |
-
-## Session States
-
-From `internal/net/server.go`:
+## Places to Be Careful
-| State | Purpose |
-|---|---|
-| `StateAccountName` | Entering account name |
-| `StatePassword` | Entering password |
-| `StateNewAccountPass` | Setting password for new account |
-| `StateNewAccountConfirm` | Confirming password |
-| `StateColorChoice` | New account: "Should I disable color?" |
-| `StateMenu` | Main menu |
-| `StateNewCharName` | Naming new character |
-| `StateRenameAccount` | Renaming account |
-| `StateRenameChar` | Selecting character to rename |
-| `StateRenameCharName` | Entering new character name |
-| `StateDeleteChar` | Confirming character deletion |
-| `StatePurgeAccount` | Confirming account purge |
-| `StateGame` | In-game command mode |
-| `StateChangeDescription` | Setting player description |
-| `StateTalk` | NPC conversation input |
-| `StateDropAllConfirm` | Confirming `drop all` |
-| `StateRecipeChoice` | Recipe/product selection table menu (number or name input, enter cancels) |
-| `StateHowMany` | "How many?" production count prompt |
-| `StateSmithProduct` | Smith product selection with numbered table (#, name, or partial name input) |
-| `StateShop` | Buy/sell/browse in shop (buy, sell, browse, leave commands) |
-| `StateBank` | Bank interface (deposit, withdraw, browse, leave commands) |
-| `StateHacking` | Jacked into a terminal running a minigame |
-| `StateColorChoice` | New account color preference |
+- `startAction` searches objects first, then mobs. A mob must have `behavior:` set on its YAML def to be interactable.
+- Exits changed from `map[ExitDir]int` to `map[ExitDir]ExitDef` — old `north: 2` still works via custom `UnmarshalYAML`. Same pattern for `RoomMob`.
+- `checkCondition` is the single condition evaluator — used by exits, talk options, on-enter scripts, and use_interactions.
+- Object `hidden: true` means the object doesn't appear in room listings but is still interactable.
+- Alias expansion happens before command dispatch and can shadow built-in commands.
+- `say` preserves case from raw input.
+- `get`, `drop`, and `quit` cancel the player's active action.
+- `findGroundMatches` and `findInventoryMatches` support bidirectional prefix matching — `"iron ax"` matches `"iron axe"`.
+- Mob wander config is per-instance in room YAML via `RoomMob`, not on `MobDef`.
+- Walk distance capped by agility level.
+- `wear all` equips the highest-value item per slot; `remove all` unequips everything.
+- Cape of Agility makes movement instant (1 tick). Graceful pieces each reduce by 0.25 ticks; full set +0.5 bonus for 2 ticks total. Cape overrides Graceful.
+- During combat, movement is a flee attempt. Mob hits during countdown abort the flee.
+- Level-up messages output `*** You are now level N <skill>! ***` across all skills that gain XP.
## Adding a New Skill
@@ -554,54 +501,20 @@ From `internal/net/server.go`:
5. Place the object in a room YAML in `data/rooms/`
6. Create any resource items in `data/items/`
-Set `xp` on the behavior to award XP on successful gathers. Set `deplete_timer` for tree-style depletion, or use per-drop `depletes: true` for rock-style depletion.
-
## Adding a New Production Skill
-Production skills (cooking, smelting, smithing) share a unified system in `action_production.go`:
+Production skills share a unified system in `core_production.go`:
-1. Create recipe YAMLs in `data/recipes/` with the new `type` (e.g., `type: fletching`)
+1. Create recipe YAMLs in `data/recipes/` with the new `type`
2. Create a station object in `data/objects/` if needed
-3. Add a `cmd_<skill>.go` command handler following the `cmd_cook.go` pattern
-4. Add the command to `classifyCommand()` and `executeCommand()` in `game.go`
-5. Add the action type string to `AdvanceActions` in `action.go`
-6. Add a case in `startProductionFromRecipe()` for default messages
+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
+5. Wire up the `auto_start` option if desired
-The unified production cycle handles: "How many?" prompt, start message, timed loops, success/fail rolls, XP, output quantity, stackable stacking, byproducts, and end message. Tool requirements (e.g., hammer for smithing, needle for crafting) are checked in the command handler before calling `promptHowMany`.
+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`.
## Adding a New Command
-1. Add a `cmd_*.go` file in `internal/game/` with the handler
-2. Add a case in `handleGameCommand()` and entry in `classifyCommand()` in `internal/game/game.go`
+1. Add a `cmd_*.go` file in `internal/game/` with the handler function `func (g *Game) executeXxx(sess *net.Session, args []string, rawInput string)`
+2. Add the command to `commandRegistry` in `cmd_registry.go` with the appropriate `CommandClass`
3. Update `data/help/` YAML files
-
-## Skills (23 total, 1-99, RSC XP table)
-
-| Category | Skills |
-|---|---|
-| Combat | Attack, Strength, Defense, Hitpoints, Ranged, Science, Technology, Assassin |
-| Gathering | Fishing, Woodcutting, Mining, Hacking, Scavenging, Farming, Thieving |
-| Production | Cooking, Smithing, Crafting, Fletching, Pharmacy, Construction, Firemaking |
-| Utility | Agility |
-
-Implemented: Mining, Fishing, Woodcutting, Firemaking, Cooking, Smithing (smelting + smithing), Fletching, Pharmacy (herb cleaning + potion mixing), Technology (25 techs, battery drain, 1-tick flicking, combat integration), Construction (planks, furniture, player houses), Agility (3 obstacle courses, 9 verbs, lap tracking), Hacking (3 minigames, terminals, StateHacking). The rest are stubs (skill defined, XP tracked, but no behaviors/objects/items yet).
-
-## Places to Be Careful
-
-- `StartAction` searches objects first, then mobs. A mob must have `behavior:` set on its YAML def to be interactable.
-- Exits changed from `map[ExitDir]int` to `map[ExitDir]ExitDef` — old `north: 2` still works via custom `UnmarshalYAML`. Same pattern for `RoomMob`.
-- `checkCondition` is the single condition evaluator — used by exits, talk options, on-enter scripts, and use_interactions checks.
-- Object `Hidden: true` means the object doesn't appear in room listings but is still interactable.
-- Alias expansion happens before command dispatch and can shadow built-in commands.
-- `say` preserves case (alias expansion preserves case, dispatch extracts original input text).
-- `get`, `drop`, and `quit` cancel the player's active action (gathering, use, etc.).
-- `findGroundMatches` and `findInventoryMatches` support bidirectional prefix matching — `"iron ax"` matches `"iron axe"`.
-- Mob wander config is per-instance in room YAML via `RoomMob`, not on `MobDef`.
-- Walk distance capped by agility level (`len(dirs) > agilityLevel`).
-- `wear all` auto-equips the highest-value item per slot from inventory.
-- `remove all` unequips everything if inventory has room.
-- Movement takes multiple ticks (default 4, reduced by Graceful set and Cape of Agility). Cape of Agility makes movement instant (1 tick). Graceful pieces each reduce by 0.25 ticks; full set bonus adds 0.5 for 2 ticks total. Cape overrides Graceful.
-- During combat, movement is a flee attempt. Mob hits during the countdown abort the flee ("Can't escape!").
-- Level-up messages output `*** You are now level N <skill>! ***` across all skills that gain XP.
-
-Skills provide specialized instructions and workflows for specific tasks.
diff --git a/cmd/mud/main.go b/cmd/mud/main.go
index 497f427..ef4a560 100644
--- a/cmd/mud/main.go
+++ b/cmd/mud/main.go
@@ -35,7 +35,7 @@ func main() {
}
}
- g := game.New(dataDir, &cfg.Colors)
+ g := game.New(dataDir, &cfg.Colors, cfg.Game.StartupValidation)
g.Ticks.Start(cfg.Game.TickLength)
defer g.Ticks.Stop()
diff --git a/data/items/adamant_arrow.yaml b/data/items/ammo/adamant_arrow.yaml
index bbcdcab..bbcdcab 100644
--- a/data/items/adamant_arrow.yaml
+++ b/data/items/ammo/adamant_arrow.yaml
diff --git a/data/items/adamant_arrowtips.yaml b/data/items/ammo/adamant_arrowtips.yaml
index 49ddb57..49ddb57 100644
--- a/data/items/adamant_arrowtips.yaml
+++ b/data/items/ammo/adamant_arrowtips.yaml
diff --git a/data/items/adamant_bolts.yaml b/data/items/ammo/adamant_bolts.yaml
index 47d088f..47d088f 100644
--- a/data/items/adamant_bolts.yaml
+++ b/data/items/ammo/adamant_bolts.yaml
diff --git a/data/items/adamant_bolts_unf.yaml b/data/items/ammo/adamant_bolts_unf.yaml
index db709db..db709db 100644
--- a/data/items/adamant_bolts_unf.yaml
+++ b/data/items/ammo/adamant_bolts_unf.yaml
diff --git a/data/items/arrow_shaft.yaml b/data/items/ammo/arrow_shaft.yaml
index 715a3e6..715a3e6 100644
--- a/data/items/arrow_shaft.yaml
+++ b/data/items/ammo/arrow_shaft.yaml
diff --git a/data/items/bronze_arrow.yaml b/data/items/ammo/bronze_arrow.yaml
index eb73a48..eb73a48 100644
--- a/data/items/bronze_arrow.yaml
+++ b/data/items/ammo/bronze_arrow.yaml
diff --git a/data/items/bronze_arrowtips.yaml b/data/items/ammo/bronze_arrowtips.yaml
index df42e59..df42e59 100644
--- a/data/items/bronze_arrowtips.yaml
+++ b/data/items/ammo/bronze_arrowtips.yaml
diff --git a/data/items/bronze_bolts.yaml b/data/items/ammo/bronze_bolts.yaml
index 0d3afae..0d3afae 100644
--- a/data/items/bronze_bolts.yaml
+++ b/data/items/ammo/bronze_bolts.yaml
diff --git a/data/items/bronze_bolts_unf.yaml b/data/items/ammo/bronze_bolts_unf.yaml
index 538d8ab..538d8ab 100644
--- a/data/items/bronze_bolts_unf.yaml
+++ b/data/items/ammo/bronze_bolts_unf.yaml
diff --git a/data/items/diamond_bolt_tips.yaml b/data/items/ammo/diamond_bolt_tips.yaml
index 1529b11..1529b11 100644
--- a/data/items/diamond_bolt_tips.yaml
+++ b/data/items/ammo/diamond_bolt_tips.yaml
diff --git a/data/items/diamond_bolts.yaml b/data/items/ammo/diamond_bolts.yaml
index efe8013..efe8013 100644
--- a/data/items/diamond_bolts.yaml
+++ b/data/items/ammo/diamond_bolts.yaml
diff --git a/data/items/diamond_bolts_e.yaml b/data/items/ammo/diamond_bolts_e.yaml
index 93ade4c..93ade4c 100644
--- a/data/items/diamond_bolts_e.yaml
+++ b/data/items/ammo/diamond_bolts_e.yaml
diff --git a/data/items/emerald_bolt_tips.yaml b/data/items/ammo/emerald_bolt_tips.yaml
index afd285b..afd285b 100644
--- a/data/items/emerald_bolt_tips.yaml
+++ b/data/items/ammo/emerald_bolt_tips.yaml
diff --git a/data/items/emerald_bolts.yaml b/data/items/ammo/emerald_bolts.yaml
index 1afdcd0..1afdcd0 100644
--- a/data/items/emerald_bolts.yaml
+++ b/data/items/ammo/emerald_bolts.yaml
diff --git a/data/items/emerald_bolts_e.yaml b/data/items/ammo/emerald_bolts_e.yaml
index 4cb7cbd..4cb7cbd 100644
--- a/data/items/emerald_bolts_e.yaml
+++ b/data/items/ammo/emerald_bolts_e.yaml
diff --git a/data/items/headless_arrow.yaml b/data/items/ammo/headless_arrow.yaml
index acf1384..acf1384 100644
--- a/data/items/headless_arrow.yaml
+++ b/data/items/ammo/headless_arrow.yaml
diff --git a/data/items/iron_arrow.yaml b/data/items/ammo/iron_arrow.yaml
index ebadaec..ebadaec 100644
--- a/data/items/iron_arrow.yaml
+++ b/data/items/ammo/iron_arrow.yaml
diff --git a/data/items/iron_arrowtips.yaml b/data/items/ammo/iron_arrowtips.yaml
index 54ffd11..54ffd11 100644
--- a/data/items/iron_arrowtips.yaml
+++ b/data/items/ammo/iron_arrowtips.yaml
diff --git a/data/items/iron_bolts.yaml b/data/items/ammo/iron_bolts.yaml
index ed81578..ed81578 100644
--- a/data/items/iron_bolts.yaml
+++ b/data/items/ammo/iron_bolts.yaml
diff --git a/data/items/iron_bolts_unf.yaml b/data/items/ammo/iron_bolts_unf.yaml
index 0a51ca0..0a51ca0 100644
--- a/data/items/iron_bolts_unf.yaml
+++ b/data/items/ammo/iron_bolts_unf.yaml
diff --git a/data/items/mithril_arrow.yaml b/data/items/ammo/mithril_arrow.yaml
index 23464e9..23464e9 100644
--- a/data/items/mithril_arrow.yaml
+++ b/data/items/ammo/mithril_arrow.yaml
diff --git a/data/items/mithril_arrowtips.yaml b/data/items/ammo/mithril_arrowtips.yaml
index 8f68a34..8f68a34 100644
--- a/data/items/mithril_arrowtips.yaml
+++ b/data/items/ammo/mithril_arrowtips.yaml
diff --git a/data/items/mithril_bolts.yaml b/data/items/ammo/mithril_bolts.yaml
index e160065..e160065 100644
--- a/data/items/mithril_bolts.yaml
+++ b/data/items/ammo/mithril_bolts.yaml
diff --git a/data/items/mithril_bolts_unf.yaml b/data/items/ammo/mithril_bolts_unf.yaml
index 8015f1a..8015f1a 100644
--- a/data/items/mithril_bolts_unf.yaml
+++ b/data/items/ammo/mithril_bolts_unf.yaml
diff --git a/data/items/ruby_bolt_tips.yaml b/data/items/ammo/ruby_bolt_tips.yaml
index 6ebe1a0..6ebe1a0 100644
--- a/data/items/ruby_bolt_tips.yaml
+++ b/data/items/ammo/ruby_bolt_tips.yaml
diff --git a/data/items/ruby_bolts.yaml b/data/items/ammo/ruby_bolts.yaml
index b83bec2..b83bec2 100644
--- a/data/items/ruby_bolts.yaml
+++ b/data/items/ammo/ruby_bolts.yaml
diff --git a/data/items/ruby_bolts_e.yaml b/data/items/ammo/ruby_bolts_e.yaml
index 14954c2..14954c2 100644
--- a/data/items/ruby_bolts_e.yaml
+++ b/data/items/ammo/ruby_bolts_e.yaml
diff --git a/data/items/rune_arrow.yaml b/data/items/ammo/rune_arrow.yaml
index b00e922..b00e922 100644
--- a/data/items/rune_arrow.yaml
+++ b/data/items/ammo/rune_arrow.yaml
diff --git a/data/items/rune_arrowtips.yaml b/data/items/ammo/rune_arrowtips.yaml
index c2d80e0..c2d80e0 100644
--- a/data/items/rune_arrowtips.yaml
+++ b/data/items/ammo/rune_arrowtips.yaml
diff --git a/data/items/rune_bolts.yaml b/data/items/ammo/rune_bolts.yaml
index 82ac597..82ac597 100644
--- a/data/items/rune_bolts.yaml
+++ b/data/items/ammo/rune_bolts.yaml
diff --git a/data/items/rune_bolts_unf.yaml b/data/items/ammo/rune_bolts_unf.yaml
index ecaf494..ecaf494 100644
--- a/data/items/rune_bolts_unf.yaml
+++ b/data/items/ammo/rune_bolts_unf.yaml
diff --git a/data/items/sapphire_bolt_tips.yaml b/data/items/ammo/sapphire_bolt_tips.yaml
index 7513fd3..7513fd3 100644
--- a/data/items/sapphire_bolt_tips.yaml
+++ b/data/items/ammo/sapphire_bolt_tips.yaml
diff --git a/data/items/sapphire_bolts.yaml b/data/items/ammo/sapphire_bolts.yaml
index a7102c9..a7102c9 100644
--- a/data/items/sapphire_bolts.yaml
+++ b/data/items/ammo/sapphire_bolts.yaml
diff --git a/data/items/sapphire_bolts_e.yaml b/data/items/ammo/sapphire_bolts_e.yaml
index dc378cc..dc378cc 100644
--- a/data/items/sapphire_bolts_e.yaml
+++ b/data/items/ammo/sapphire_bolts_e.yaml
diff --git a/data/items/steel_arrow.yaml b/data/items/ammo/steel_arrow.yaml
index e14171f..e14171f 100644
--- a/data/items/steel_arrow.yaml
+++ b/data/items/ammo/steel_arrow.yaml
diff --git a/data/items/steel_arrowtips.yaml b/data/items/ammo/steel_arrowtips.yaml
index 49000d2..49000d2 100644
--- a/data/items/steel_arrowtips.yaml
+++ b/data/items/ammo/steel_arrowtips.yaml
diff --git a/data/items/steel_bolts.yaml b/data/items/ammo/steel_bolts.yaml
index 7dfadf1..7dfadf1 100644
--- a/data/items/steel_bolts.yaml
+++ b/data/items/ammo/steel_bolts.yaml
diff --git a/data/items/steel_bolts_unf.yaml b/data/items/ammo/steel_bolts_unf.yaml
index b65d5fa..b65d5fa 100644
--- a/data/items/steel_bolts_unf.yaml
+++ b/data/items/ammo/steel_bolts_unf.yaml
diff --git a/data/items/avantoe_potion_unf.yaml b/data/items/avantoe_potion_unf.yaml
deleted file mode 100644
index 8b23f35..0000000
--- a/data/items/avantoe_potion_unf.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: avantoe_potion_unf
-name: "avantoe potion (unf)"
-color: "48"
-description: "An unfinished potion with avantoe floating in water."
-value: 50
-stackable: false
-made_from:
- - items: [avantoe]
- qty: 1
- - items: [vial_of_water]
- qty: 1
-ticks: 0
diff --git a/data/items/bread_dough.yaml b/data/items/bread_dough.yaml
deleted file mode 100644
index 98ab297..0000000
--- a/data/items/bread_dough.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
-id: bread_dough
-name: bread dough
-color: "222"
-description: "A ball of uncooked bread dough. It needs to be baked."
-value: 3
-stackable: false
-ticks: 2
-made_from:
- - items: [pot_of_flour]
- qty: 1
- byproducts: [empty_pot]
- - items: [bucket_of_water, jug_of_water]
- qty: 1
- byproducts: [empty_bucket, empty_jug]
diff --git a/data/items/cadantine_potion_unf.yaml b/data/items/cadantine_potion_unf.yaml
deleted file mode 100644
index 416d938..0000000
--- a/data/items/cadantine_potion_unf.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: cadantine_potion_unf
-name: "cadantine potion (unf)"
-color: "48"
-description: "An unfinished potion with cadantine floating in water."
-value: 72
-stackable: false
-made_from:
- - items: [cadantine]
- qty: 1
- - items: [vial_of_water]
- qty: 1
-ticks: 0
diff --git a/data/items/acid_vial.yaml b/data/items/consumables/acid_vial.yaml
index 7a49e2d..7a49e2d 100644
--- a/data/items/acid_vial.yaml
+++ b/data/items/consumables/acid_vial.yaml
diff --git a/data/items/amp_potion.yaml b/data/items/consumables/amp_potion.yaml
index 8167932..8167932 100644
--- a/data/items/amp_potion.yaml
+++ b/data/items/consumables/amp_potion.yaml
diff --git a/data/items/anchovies.yaml b/data/items/consumables/anchovies.yaml
index 0abd846..0abd846 100644
--- a/data/items/anchovies.yaml
+++ b/data/items/consumables/anchovies.yaml
diff --git a/data/items/antitoxin_powder.yaml b/data/items/consumables/antitoxin_powder.yaml
index dedba43..dedba43 100644
--- a/data/items/antitoxin_powder.yaml
+++ b/data/items/consumables/antitoxin_powder.yaml
diff --git a/data/items/apple.yaml b/data/items/consumables/apple.yaml
index 10c09c1..10c09c1 100644
--- a/data/items/apple.yaml
+++ b/data/items/consumables/apple.yaml
diff --git a/data/items/consumables/avantoe_potion_unf.yaml b/data/items/consumables/avantoe_potion_unf.yaml
new file mode 100644
index 0000000..62f2218
--- /dev/null
+++ b/data/items/consumables/avantoe_potion_unf.yaml
@@ -0,0 +1,12 @@
+id: avantoe_potion_unf
+name: "avantoe potion (unf)"
+color: "48"
+description: "An unfinished potion with avantoe floating in water."
+value: 50
+stackable: false
+made_from:
+ - items: [avantoe]
+ quantity: 1
+ - items: [vial_of_water]
+ quantity: 1
+ticks: 0
diff --git a/data/items/bio_serum.yaml b/data/items/consumables/bio_serum.yaml
index b397e46..b397e46 100644
--- a/data/items/bio_serum.yaml
+++ b/data/items/consumables/bio_serum.yaml
diff --git a/data/items/blight_spore.yaml b/data/items/consumables/blight_spore.yaml
index add0f6e..add0f6e 100644
--- a/data/items/blight_spore.yaml
+++ b/data/items/consumables/blight_spore.yaml
diff --git a/data/items/bread.yaml b/data/items/consumables/bread.yaml
index 68cefdc..68cefdc 100644
--- a/data/items/bread.yaml
+++ b/data/items/consumables/bread.yaml
diff --git a/data/items/consumables/bread_dough.yaml b/data/items/consumables/bread_dough.yaml
new file mode 100644
index 0000000..73ac998
--- /dev/null
+++ b/data/items/consumables/bread_dough.yaml
@@ -0,0 +1,14 @@
+id: bread_dough
+name: bread dough
+color: "222"
+description: "A ball of uncooked bread dough. It needs to be baked."
+value: 3
+stackable: false
+ticks: 2
+made_from:
+ - items: [pot_of_flour]
+ quantity: 1
+ byproducts: [empty_pot]
+ - items: [bucket_of_water, jug_of_water]
+ quantity: 1
+ byproducts: [empty_bucket, empty_jug]
diff --git a/data/items/bucket_of_water.yaml b/data/items/consumables/bucket_of_water.yaml
index 3449cdd..3449cdd 100644
--- a/data/items/bucket_of_water.yaml
+++ b/data/items/consumables/bucket_of_water.yaml
diff --git a/data/items/burnt_fish.yaml b/data/items/consumables/burnt_fish.yaml
index 7af9f03..7af9f03 100644
--- a/data/items/burnt_fish.yaml
+++ b/data/items/consumables/burnt_fish.yaml
diff --git a/data/items/burnt_meat.yaml b/data/items/consumables/burnt_meat.yaml
index 92c339c..92c339c 100644
--- a/data/items/burnt_meat.yaml
+++ b/data/items/consumables/burnt_meat.yaml
diff --git a/data/items/cabbage.yaml b/data/items/consumables/cabbage.yaml
index c04fd4e..c04fd4e 100644
--- a/data/items/cabbage.yaml
+++ b/data/items/consumables/cabbage.yaml
diff --git a/data/items/consumables/cadantine_potion_unf.yaml b/data/items/consumables/cadantine_potion_unf.yaml
new file mode 100644
index 0000000..d1f13c6
--- /dev/null
+++ b/data/items/consumables/cadantine_potion_unf.yaml
@@ -0,0 +1,12 @@
+id: cadantine_potion_unf
+name: "cadantine potion (unf)"
+color: "48"
+description: "An unfinished potion with cadantine floating in water."
+value: 72
+stackable: false
+made_from:
+ - items: [cadantine]
+ quantity: 1
+ - items: [vial_of_water]
+ quantity: 1
+ticks: 0
diff --git a/data/items/catalyst_wine.yaml b/data/items/consumables/catalyst_wine.yaml
index fcfe1a0..fcfe1a0 100644
--- a/data/items/catalyst_wine.yaml
+++ b/data/items/consumables/catalyst_wine.yaml
diff --git a/data/items/cheese.yaml b/data/items/consumables/cheese.yaml
index 79d9fe8..79d9fe8 100644
--- a/data/items/cheese.yaml
+++ b/data/items/consumables/cheese.yaml
diff --git a/data/items/consumables/dwarf_weed_potion_unf.yaml b/data/items/consumables/dwarf_weed_potion_unf.yaml
new file mode 100644
index 0000000..64f7faa
--- /dev/null
+++ b/data/items/consumables/dwarf_weed_potion_unf.yaml
@@ -0,0 +1,12 @@
+id: dwarf_weed_potion_unf
+name: "dwarf weed potion (unf)"
+color: "48"
+description: "An unfinished potion with dwarf weed floating in water."
+value: 88
+stackable: false
+made_from:
+ - items: [dwarf_weed]
+ quantity: 1
+ - items: [vial_of_water]
+ quantity: 1
+ticks: 0
diff --git a/data/items/full_restore.yaml b/data/items/consumables/full_restore.yaml
index 96f6036..96f6036 100644
--- a/data/items/full_restore.yaml
+++ b/data/items/consumables/full_restore.yaml
diff --git a/data/items/consumables/guam_potion_unf.yaml b/data/items/consumables/guam_potion_unf.yaml
new file mode 100644
index 0000000..ba81e93
--- /dev/null
+++ b/data/items/consumables/guam_potion_unf.yaml
@@ -0,0 +1,12 @@
+id: guam_potion_unf
+name: "guam potion (unf)"
+color: "48"
+description: "An unfinished potion with guam floating in water."
+value: 5
+stackable: false
+made_from:
+ - items: [guam]
+ quantity: 1
+ - items: [vial_of_water]
+ quantity: 1
+ticks: 0
diff --git a/data/items/consumables/harralander_potion_unf.yaml b/data/items/consumables/harralander_potion_unf.yaml
new file mode 100644
index 0000000..522f8ea
--- /dev/null
+++ b/data/items/consumables/harralander_potion_unf.yaml
@@ -0,0 +1,12 @@
+id: harralander_potion_unf
+name: "harralander potion (unf)"
+color: "48"
+description: "An unfinished potion with harralander floating in water."
+value: 18
+stackable: false
+made_from:
+ - items: [harralander]
+ quantity: 1
+ - items: [vial_of_water]
+ quantity: 1
+ticks: 0
diff --git a/data/items/herring.yaml b/data/items/consumables/herring.yaml
index adbbd95..adbbd95 100644
--- a/data/items/herring.yaml
+++ b/data/items/consumables/herring.yaml
diff --git a/data/items/consumables/irit_potion_unf.yaml b/data/items/consumables/irit_potion_unf.yaml
new file mode 100644
index 0000000..76a8577
--- /dev/null
+++ b/data/items/consumables/irit_potion_unf.yaml
@@ -0,0 +1,12 @@
+id: irit_potion_unf
+name: "irit potion (unf)"
+color: "48"
+description: "An unfinished potion with irit floating in water."
+value: 42
+stackable: false
+made_from:
+ - items: [irit]
+ quantity: 1
+ - items: [vial_of_water]
+ quantity: 1
+ticks: 0
diff --git a/data/items/jug_of_water.yaml b/data/items/consumables/jug_of_water.yaml
index 04d3b8f..04d3b8f 100644
--- a/data/items/jug_of_water.yaml
+++ b/data/items/consumables/jug_of_water.yaml
diff --git a/data/items/consumables/kwuarm_potion_unf.yaml b/data/items/consumables/kwuarm_potion_unf.yaml
new file mode 100644
index 0000000..0448145
--- /dev/null
+++ b/data/items/consumables/kwuarm_potion_unf.yaml
@@ -0,0 +1,12 @@
+id: kwuarm_potion_unf
+name: "kwuarm potion (unf)"
+color: "48"
+description: "An unfinished potion with kwuarm floating in water."
+value: 56
+stackable: false
+made_from:
+ - items: [kwuarm]
+ quantity: 1
+ - items: [vial_of_water]
+ quantity: 1
+ticks: 0
diff --git a/data/items/consumables/lantadyme_potion_unf.yaml b/data/items/consumables/lantadyme_potion_unf.yaml
new file mode 100644
index 0000000..3460a18
--- /dev/null
+++ b/data/items/consumables/lantadyme_potion_unf.yaml
@@ -0,0 +1,12 @@
+id: lantadyme_potion_unf
+name: "lantadyme potion (unf)"
+color: "48"
+description: "An unfinished potion with lantadyme floating in water."
+value: 80
+stackable: false
+made_from:
+ - items: [lantadyme]
+ quantity: 1
+ - items: [vial_of_water]
+ quantity: 1
+ticks: 0
diff --git a/data/items/consumables/marrentill_potion_unf.yaml b/data/items/consumables/marrentill_potion_unf.yaml
new file mode 100644
index 0000000..4424e5e
--- /dev/null
+++ b/data/items/consumables/marrentill_potion_unf.yaml
@@ -0,0 +1,12 @@
+id: marrentill_potion_unf
+name: "marrentill potion (unf)"
+color: "48"
+description: "An unfinished potion with marrentill floating in water."
+value: 8
+stackable: false
+made_from:
+ - items: [marrentill]
+ quantity: 1
+ - items: [vial_of_water]
+ quantity: 1
+ticks: 0
diff --git a/data/items/nano_restore.yaml b/data/items/consumables/nano_restore.yaml
index e430b49..e430b49 100644
--- a/data/items/nano_restore.yaml
+++ b/data/items/consumables/nano_restore.yaml
diff --git a/data/items/onion.yaml b/data/items/consumables/onion.yaml
index 0d0aca7..0d0aca7 100644
--- a/data/items/onion.yaml
+++ b/data/items/consumables/onion.yaml
diff --git a/data/items/consumables/pastry_dough.yaml b/data/items/consumables/pastry_dough.yaml
new file mode 100644
index 0000000..b607c83
--- /dev/null
+++ b/data/items/consumables/pastry_dough.yaml
@@ -0,0 +1,11 @@
+id: pastry_dough
+name: pastry dough
+color: "222"
+description: "A ball of uncooked pastry dough. It needs to be baked."
+value: 3
+stackable: false
+made_from:
+ - items: [pot_of_flour]
+ quantity: 1
+ - items: [bucket_of_water]
+ quantity: 1
diff --git a/data/items/pie_dish.yaml b/data/items/consumables/pie_dish.yaml
index bd6100b..bd6100b 100644
--- a/data/items/pie_dish.yaml
+++ b/data/items/consumables/pie_dish.yaml
diff --git a/data/items/consumables/pizza_dough.yaml b/data/items/consumables/pizza_dough.yaml
new file mode 100644
index 0000000..12d8b14
--- /dev/null
+++ b/data/items/consumables/pizza_dough.yaml
@@ -0,0 +1,11 @@
+id: pizza_dough
+name: pizza dough
+color: "222"
+description: "A ball of uncooked pizza dough. It needs to be baked."
+value: 3
+stackable: false
+made_from:
+ - items: [pot_of_flour]
+ quantity: 1
+ - items: [bucket_of_water]
+ quantity: 1
diff --git a/data/items/plant_cure.yaml b/data/items/consumables/plant_cure.yaml
index 64ebe91..64ebe91 100644
--- a/data/items/plant_cure.yaml
+++ b/data/items/consumables/plant_cure.yaml
diff --git a/data/items/pot_of_flour.yaml b/data/items/consumables/pot_of_flour.yaml
index c05c065..c05c065 100644
--- a/data/items/pot_of_flour.yaml
+++ b/data/items/consumables/pot_of_flour.yaml
diff --git a/data/items/potato.yaml b/data/items/consumables/potato.yaml
index d9f521a..d9f521a 100644
--- a/data/items/potato.yaml
+++ b/data/items/consumables/potato.yaml
diff --git a/data/items/consumables/ranarr_potion_unf.yaml b/data/items/consumables/ranarr_potion_unf.yaml
new file mode 100644
index 0000000..d2c475d
--- /dev/null
+++ b/data/items/consumables/ranarr_potion_unf.yaml
@@ -0,0 +1,12 @@
+id: ranarr_potion_unf
+name: "ranarr potion (unf)"
+color: "48"
+description: "An unfinished potion with ranarr floating in water."
+value: 38
+stackable: false
+made_from:
+ - items: [ranarr]
+ quantity: 1
+ - items: [vial_of_water]
+ quantity: 1
+ticks: 0
diff --git a/data/items/raw_anchovies.yaml b/data/items/consumables/raw_anchovies.yaml
index cf5b75f..cf5b75f 100644
--- a/data/items/raw_anchovies.yaml
+++ b/data/items/consumables/raw_anchovies.yaml
diff --git a/data/items/raw_anglerfish.yaml b/data/items/consumables/raw_anglerfish.yaml
index b1c7262..b1c7262 100644
--- a/data/items/raw_anglerfish.yaml
+++ b/data/items/consumables/raw_anglerfish.yaml
diff --git a/data/items/raw_bass.yaml b/data/items/consumables/raw_bass.yaml
index 34dddc1..34dddc1 100644
--- a/data/items/raw_bass.yaml
+++ b/data/items/consumables/raw_bass.yaml
diff --git a/data/items/raw_beef.yaml b/data/items/consumables/raw_beef.yaml
index 59695b0..59695b0 100644
--- a/data/items/raw_beef.yaml
+++ b/data/items/consumables/raw_beef.yaml
diff --git a/data/items/raw_cave_eel.yaml b/data/items/consumables/raw_cave_eel.yaml
index 000f93c..000f93c 100644
--- a/data/items/raw_cave_eel.yaml
+++ b/data/items/consumables/raw_cave_eel.yaml
diff --git a/data/items/raw_cod.yaml b/data/items/consumables/raw_cod.yaml
index fe94f35..fe94f35 100644
--- a/data/items/raw_cod.yaml
+++ b/data/items/consumables/raw_cod.yaml
diff --git a/data/items/raw_herring.yaml b/data/items/consumables/raw_herring.yaml
index 730eca5..730eca5 100644
--- a/data/items/raw_herring.yaml
+++ b/data/items/consumables/raw_herring.yaml
diff --git a/data/items/raw_lobster.yaml b/data/items/consumables/raw_lobster.yaml
index 908c931..908c931 100644
--- a/data/items/raw_lobster.yaml
+++ b/data/items/consumables/raw_lobster.yaml
diff --git a/data/items/raw_mackerel.yaml b/data/items/consumables/raw_mackerel.yaml
index 42e1d09..42e1d09 100644
--- a/data/items/raw_mackerel.yaml
+++ b/data/items/consumables/raw_mackerel.yaml
diff --git a/data/items/raw_pike.yaml b/data/items/consumables/raw_pike.yaml
index 625c019..625c019 100644
--- a/data/items/raw_pike.yaml
+++ b/data/items/consumables/raw_pike.yaml
diff --git a/data/items/raw_rainbow_fish.yaml b/data/items/consumables/raw_rainbow_fish.yaml
index 311c9f1..311c9f1 100644
--- a/data/items/raw_rainbow_fish.yaml
+++ b/data/items/consumables/raw_rainbow_fish.yaml
diff --git a/data/items/raw_salmon.yaml b/data/items/consumables/raw_salmon.yaml
index d5ae9bd..d5ae9bd 100644
--- a/data/items/raw_salmon.yaml
+++ b/data/items/consumables/raw_salmon.yaml
diff --git a/data/items/raw_sardine.yaml b/data/items/consumables/raw_sardine.yaml
index d27a155..d27a155 100644
--- a/data/items/raw_sardine.yaml
+++ b/data/items/consumables/raw_sardine.yaml
diff --git a/data/items/raw_shark.yaml b/data/items/consumables/raw_shark.yaml
index 999ccb9..999ccb9 100644
--- a/data/items/raw_shark.yaml
+++ b/data/items/consumables/raw_shark.yaml
diff --git a/data/items/raw_shrimps.yaml b/data/items/consumables/raw_shrimps.yaml
index 1127fda..1127fda 100644
--- a/data/items/raw_shrimps.yaml
+++ b/data/items/consumables/raw_shrimps.yaml
diff --git a/data/items/raw_slimy_eel.yaml b/data/items/consumables/raw_slimy_eel.yaml
index 40820e5..40820e5 100644
--- a/data/items/raw_slimy_eel.yaml
+++ b/data/items/consumables/raw_slimy_eel.yaml
diff --git a/data/items/raw_swordfish.yaml b/data/items/consumables/raw_swordfish.yaml
index e162460..e162460 100644
--- a/data/items/raw_swordfish.yaml
+++ b/data/items/consumables/raw_swordfish.yaml
diff --git a/data/items/raw_trout.yaml b/data/items/consumables/raw_trout.yaml
index c1d98aa..c1d98aa 100644
--- a/data/items/raw_trout.yaml
+++ b/data/items/consumables/raw_trout.yaml
diff --git a/data/items/raw_tuna.yaml b/data/items/consumables/raw_tuna.yaml
index 6079f92..6079f92 100644
--- a/data/items/raw_tuna.yaml
+++ b/data/items/consumables/raw_tuna.yaml
diff --git a/data/items/restoration_compound.yaml b/data/items/consumables/restoration_compound.yaml
index 913153f..913153f 100644
--- a/data/items/restoration_compound.yaml
+++ b/data/items/consumables/restoration_compound.yaml
diff --git a/data/items/salmon.yaml b/data/items/consumables/salmon.yaml
index 59b8237..59b8237 100644
--- a/data/items/salmon.yaml
+++ b/data/items/consumables/salmon.yaml
diff --git a/data/items/sardine.yaml b/data/items/consumables/sardine.yaml
index c11b42b..c11b42b 100644
--- a/data/items/sardine.yaml
+++ b/data/items/consumables/sardine.yaml
diff --git a/data/items/science_serum.yaml b/data/items/consumables/science_serum.yaml
index e78f915..e78f915 100644
--- a/data/items/science_serum.yaml
+++ b/data/items/consumables/science_serum.yaml
diff --git a/data/items/shield_potion.yaml b/data/items/consumables/shield_potion.yaml
index 4cd91e8..4cd91e8 100644
--- a/data/items/shield_potion.yaml
+++ b/data/items/consumables/shield_potion.yaml
diff --git a/data/items/shrimps.yaml b/data/items/consumables/shrimps.yaml
index 9c280c8..9c280c8 100644
--- a/data/items/shrimps.yaml
+++ b/data/items/consumables/shrimps.yaml
diff --git a/data/items/consumables/snapdragon_potion_unf.yaml b/data/items/consumables/snapdragon_potion_unf.yaml
new file mode 100644
index 0000000..ee8e366
--- /dev/null
+++ b/data/items/consumables/snapdragon_potion_unf.yaml
@@ -0,0 +1,12 @@
+id: snapdragon_potion_unf
+name: "snapdragon potion (unf)"
+color: "48"
+description: "An unfinished potion with snapdragon floating in water."
+value: 68
+stackable: false
+made_from:
+ - items: [snapdragon]
+ quantity: 1
+ - items: [vial_of_water]
+ quantity: 1
+ticks: 0
diff --git a/data/items/stim_potion.yaml b/data/items/consumables/stim_potion.yaml
index de51291..de51291 100644
--- a/data/items/stim_potion.yaml
+++ b/data/items/consumables/stim_potion.yaml
diff --git a/data/items/strawberry.yaml b/data/items/consumables/strawberry.yaml
index 337a65e..337a65e 100644
--- a/data/items/strawberry.yaml
+++ b/data/items/consumables/strawberry.yaml
diff --git a/data/items/super_amp.yaml b/data/items/consumables/super_amp.yaml
index 3bea59b..3bea59b 100644
--- a/data/items/super_amp.yaml
+++ b/data/items/consumables/super_amp.yaml
diff --git a/data/items/super_bio_serum.yaml b/data/items/consumables/super_bio_serum.yaml
index fbc5ebe..fbc5ebe 100644
--- a/data/items/super_bio_serum.yaml
+++ b/data/items/consumables/super_bio_serum.yaml
diff --git a/data/items/super_shield.yaml b/data/items/consumables/super_shield.yaml
index 7545b2e..7545b2e 100644
--- a/data/items/super_shield.yaml
+++ b/data/items/consumables/super_shield.yaml
diff --git a/data/items/super_stim.yaml b/data/items/consumables/super_stim.yaml
index c290c5f..c290c5f 100644
--- a/data/items/super_stim.yaml
+++ b/data/items/consumables/super_stim.yaml
diff --git a/data/items/super_stim_cell.yaml b/data/items/consumables/super_stim_cell.yaml
index 1f25597..1f25597 100644
--- a/data/items/super_stim_cell.yaml
+++ b/data/items/consumables/super_stim_cell.yaml
diff --git a/data/items/sweetcorn.yaml b/data/items/consumables/sweetcorn.yaml
index 32f5a70..32f5a70 100644
--- a/data/items/sweetcorn.yaml
+++ b/data/items/consumables/sweetcorn.yaml
diff --git a/data/items/targeting_serum.yaml b/data/items/consumables/targeting_serum.yaml
index b10ebc5..b10ebc5 100644
--- a/data/items/targeting_serum.yaml
+++ b/data/items/consumables/targeting_serum.yaml
diff --git a/data/items/consumables/tarromin_potion_unf.yaml b/data/items/consumables/tarromin_potion_unf.yaml
new file mode 100644
index 0000000..abc6438
--- /dev/null
+++ b/data/items/consumables/tarromin_potion_unf.yaml
@@ -0,0 +1,12 @@
+id: tarromin_potion_unf
+name: "tarromin potion (unf)"
+color: "48"
+description: "An unfinished potion with tarromin floating in water."
+value: 12
+stackable: false
+made_from:
+ - items: [tarromin]
+ quantity: 1
+ - items: [vial_of_water]
+ quantity: 1
+ticks: 0
diff --git a/data/items/tech_serum.yaml b/data/items/consumables/tech_serum.yaml
index aac3fdc..aac3fdc 100644
--- a/data/items/tech_serum.yaml
+++ b/data/items/consumables/tech_serum.yaml
diff --git a/data/items/consumables/toadflax_potion_unf.yaml b/data/items/consumables/toadflax_potion_unf.yaml
new file mode 100644
index 0000000..f05b406
--- /dev/null
+++ b/data/items/consumables/toadflax_potion_unf.yaml
@@ -0,0 +1,12 @@
+id: toadflax_potion_unf
+name: "toadflax potion (unf)"
+color: "48"
+description: "An unfinished potion with toadflax floating in water."
+value: 28
+stackable: false
+made_from:
+ - items: [toadflax]
+ quantity: 1
+ - items: [vial_of_water]
+ quantity: 1
+ticks: 0
diff --git a/data/items/tomato.yaml b/data/items/consumables/tomato.yaml
index d2ad349..d2ad349 100644
--- a/data/items/tomato.yaml
+++ b/data/items/consumables/tomato.yaml
diff --git a/data/items/consumables/torstol_potion_unf.yaml b/data/items/consumables/torstol_potion_unf.yaml
new file mode 100644
index 0000000..cf551df
--- /dev/null
+++ b/data/items/consumables/torstol_potion_unf.yaml
@@ -0,0 +1,12 @@
+id: torstol_potion_unf
+name: "torstol potion (unf)"
+color: "48"
+description: "An unfinished potion with torstol floating in water."
+value: 102
+stackable: false
+made_from:
+ - items: [torstol]
+ quantity: 1
+ - items: [vial_of_water]
+ quantity: 1
+ticks: 0
diff --git a/data/items/trout.yaml b/data/items/consumables/trout.yaml
index df6cdcc..df6cdcc 100644
--- a/data/items/trout.yaml
+++ b/data/items/consumables/trout.yaml
diff --git a/data/items/vial_of_water.yaml b/data/items/consumables/vial_of_water.yaml
index c3e2995..c3e2995 100644
--- a/data/items/vial_of_water.yaml
+++ b/data/items/consumables/vial_of_water.yaml
diff --git a/data/items/watermelon.yaml b/data/items/consumables/watermelon.yaml
index 3374687..3374687 100644
--- a/data/items/watermelon.yaml
+++ b/data/items/consumables/watermelon.yaml
diff --git a/data/items/advanced_bio_deck.yaml b/data/items/decks/advanced_bio_deck.yaml
index 1411a16..1411a16 100644
--- a/data/items/advanced_bio_deck.yaml
+++ b/data/items/decks/advanced_bio_deck.yaml
diff --git a/data/items/advanced_eco_deck.yaml b/data/items/decks/advanced_eco_deck.yaml
index c844f47..c844f47 100644
--- a/data/items/advanced_eco_deck.yaml
+++ b/data/items/decks/advanced_eco_deck.yaml
diff --git a/data/items/advanced_hydro_deck.yaml b/data/items/decks/advanced_hydro_deck.yaml
index 2910931..2910931 100644
--- a/data/items/advanced_hydro_deck.yaml
+++ b/data/items/decks/advanced_hydro_deck.yaml
diff --git a/data/items/advanced_solar_deck.yaml b/data/items/decks/advanced_solar_deck.yaml
index 002845c..002845c 100644
--- a/data/items/advanced_solar_deck.yaml
+++ b/data/items/decks/advanced_solar_deck.yaml
diff --git a/data/items/basic_deck.yaml b/data/items/decks/basic_deck.yaml
index ef2549a..ef2549a 100644
--- a/data/items/basic_deck.yaml
+++ b/data/items/decks/basic_deck.yaml
diff --git a/data/items/bio_deck.yaml b/data/items/decks/bio_deck.yaml
index 8de2036..8de2036 100644
--- a/data/items/bio_deck.yaml
+++ b/data/items/decks/bio_deck.yaml
diff --git a/data/items/bio_identifier.yaml b/data/items/decks/bio_identifier.yaml
index 1b5a941..1b5a941 100644
--- a/data/items/bio_identifier.yaml
+++ b/data/items/decks/bio_identifier.yaml
diff --git a/data/items/biojunk.yaml b/data/items/decks/biojunk.yaml
index 8832a20..8832a20 100644
--- a/data/items/biojunk.yaml
+++ b/data/items/decks/biojunk.yaml
diff --git a/data/items/blood_identifier.yaml b/data/items/decks/blood_identifier.yaml
index 3540179..3540179 100644
--- a/data/items/blood_identifier.yaml
+++ b/data/items/decks/blood_identifier.yaml
diff --git a/data/items/bloodjunk.yaml b/data/items/decks/bloodjunk.yaml
index b837137..b837137 100644
--- a/data/items/bloodjunk.yaml
+++ b/data/items/decks/bloodjunk.yaml
diff --git a/data/items/chaos_identifier.yaml b/data/items/decks/chaos_identifier.yaml
index 82a7ccf..82a7ccf 100644
--- a/data/items/chaos_identifier.yaml
+++ b/data/items/decks/chaos_identifier.yaml
diff --git a/data/items/chaosjunk.yaml b/data/items/decks/chaosjunk.yaml
index 99515df..99515df 100644
--- a/data/items/chaosjunk.yaml
+++ b/data/items/decks/chaosjunk.yaml
diff --git a/data/items/cosmic_identifier.yaml b/data/items/decks/cosmic_identifier.yaml
index 97cb89a..97cb89a 100644
--- a/data/items/cosmic_identifier.yaml
+++ b/data/items/decks/cosmic_identifier.yaml
diff --git a/data/items/cosmicjunk.yaml b/data/items/decks/cosmicjunk.yaml
index d9acc21..d9acc21 100644
--- a/data/items/cosmicjunk.yaml
+++ b/data/items/decks/cosmicjunk.yaml
diff --git a/data/items/death_identifier.yaml b/data/items/decks/death_identifier.yaml
index 778bca5..778bca5 100644
--- a/data/items/death_identifier.yaml
+++ b/data/items/decks/death_identifier.yaml
diff --git a/data/items/deathjunk.yaml b/data/items/decks/deathjunk.yaml
index 7dcaba8..7dcaba8 100644
--- a/data/items/deathjunk.yaml
+++ b/data/items/decks/deathjunk.yaml
diff --git a/data/items/eco_deck.yaml b/data/items/decks/eco_deck.yaml
index ef1a991..ef1a991 100644
--- a/data/items/eco_deck.yaml
+++ b/data/items/decks/eco_deck.yaml
diff --git a/data/items/eco_identifier.yaml b/data/items/decks/eco_identifier.yaml
index d0af516..d0af516 100644
--- a/data/items/eco_identifier.yaml
+++ b/data/items/decks/eco_identifier.yaml
diff --git a/data/items/ecojunk.yaml b/data/items/decks/ecojunk.yaml
index c050049..c050049 100644
--- a/data/items/ecojunk.yaml
+++ b/data/items/decks/ecojunk.yaml
diff --git a/data/items/hydro_deck.yaml b/data/items/decks/hydro_deck.yaml
index 0e417a2..0e417a2 100644
--- a/data/items/hydro_deck.yaml
+++ b/data/items/decks/hydro_deck.yaml
diff --git a/data/items/hydro_identifier.yaml b/data/items/decks/hydro_identifier.yaml
index 3eb7d98..3eb7d98 100644
--- a/data/items/hydro_identifier.yaml
+++ b/data/items/decks/hydro_identifier.yaml
diff --git a/data/items/hydrojunk.yaml b/data/items/decks/hydrojunk.yaml
index bfcd64a..bfcd64a 100644
--- a/data/items/hydrojunk.yaml
+++ b/data/items/decks/hydrojunk.yaml
diff --git a/data/items/law_identifier.yaml b/data/items/decks/law_identifier.yaml
index 25700e1..25700e1 100644
--- a/data/items/law_identifier.yaml
+++ b/data/items/decks/law_identifier.yaml
diff --git a/data/items/lawjunk.yaml b/data/items/decks/lawjunk.yaml
index b46bf9e..b46bf9e 100644
--- a/data/items/lawjunk.yaml
+++ b/data/items/decks/lawjunk.yaml
diff --git a/data/items/nature_identifier.yaml b/data/items/decks/nature_identifier.yaml
index dc441cf..dc441cf 100644
--- a/data/items/nature_identifier.yaml
+++ b/data/items/decks/nature_identifier.yaml
diff --git a/data/items/naturejunk.yaml b/data/items/decks/naturejunk.yaml
index 49ed1ff..49ed1ff 100644
--- a/data/items/naturejunk.yaml
+++ b/data/items/decks/naturejunk.yaml
diff --git a/data/items/solar_deck.yaml b/data/items/decks/solar_deck.yaml
index 72b7e9e..72b7e9e 100644
--- a/data/items/solar_deck.yaml
+++ b/data/items/decks/solar_deck.yaml
diff --git a/data/items/solar_identifier.yaml b/data/items/decks/solar_identifier.yaml
index 663bd9f..663bd9f 100644
--- a/data/items/solar_identifier.yaml
+++ b/data/items/decks/solar_identifier.yaml
diff --git a/data/items/solarjunk.yaml b/data/items/decks/solarjunk.yaml
index ab4d5d5..ab4d5d5 100644
--- a/data/items/solarjunk.yaml
+++ b/data/items/decks/solarjunk.yaml
diff --git a/data/items/dwarf_weed_potion_unf.yaml b/data/items/dwarf_weed_potion_unf.yaml
deleted file mode 100644
index 1a9516e..0000000
--- a/data/items/dwarf_weed_potion_unf.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: dwarf_weed_potion_unf
-name: "dwarf weed potion (unf)"
-color: "48"
-description: "An unfinished potion with dwarf weed floating in water."
-value: 88
-stackable: false
-made_from:
- - items: [dwarf_weed]
- qty: 1
- - items: [vial_of_water]
- qty: 1
-ticks: 0
diff --git a/data/items/abyssal_bracelet.yaml b/data/items/equipment/abyssal_bracelet.yaml
index 3b424c3..3b424c3 100644
--- a/data/items/abyssal_bracelet.yaml
+++ b/data/items/equipment/abyssal_bracelet.yaml
diff --git a/data/items/adamant_dagger.yaml b/data/items/equipment/adamant_dagger.yaml
index d78ecdc..d78ecdc 100644
--- a/data/items/adamant_dagger.yaml
+++ b/data/items/equipment/adamant_dagger.yaml
diff --git a/data/items/adamant_full_helm.yaml b/data/items/equipment/adamant_full_helm.yaml
index 3c36c62..3c36c62 100644
--- a/data/items/adamant_full_helm.yaml
+++ b/data/items/equipment/adamant_full_helm.yaml
diff --git a/data/items/adamant_med_helm.yaml b/data/items/equipment/adamant_med_helm.yaml
index 1fb6785..1fb6785 100644
--- a/data/items/adamant_med_helm.yaml
+++ b/data/items/equipment/adamant_med_helm.yaml
diff --git a/data/items/adamant_platebody.yaml b/data/items/equipment/adamant_platebody.yaml
index a37b781..a37b781 100644
--- a/data/items/adamant_platebody.yaml
+++ b/data/items/equipment/adamant_platebody.yaml
diff --git a/data/items/adamant_sword.yaml b/data/items/equipment/adamant_sword.yaml
index 035b615..035b615 100644
--- a/data/items/adamant_sword.yaml
+++ b/data/items/equipment/adamant_sword.yaml
diff --git a/data/items/binding_necklace.yaml b/data/items/equipment/binding_necklace.yaml
index e9e7559..e9e7559 100644
--- a/data/items/binding_necklace.yaml
+++ b/data/items/equipment/binding_necklace.yaml
diff --git a/data/items/black_axe.yaml b/data/items/equipment/black_axe.yaml
index e708645..e708645 100644
--- a/data/items/black_axe.yaml
+++ b/data/items/equipment/black_axe.yaml
diff --git a/data/items/bracelet_of_clay.yaml b/data/items/equipment/bracelet_of_clay.yaml
index d8cd881..d8cd881 100644
--- a/data/items/bracelet_of_clay.yaml
+++ b/data/items/equipment/bracelet_of_clay.yaml
diff --git a/data/items/bracelet_of_slaughter.yaml b/data/items/equipment/bracelet_of_slaughter.yaml
index 2bbbbec..2bbbbec 100644
--- a/data/items/bracelet_of_slaughter.yaml
+++ b/data/items/equipment/bracelet_of_slaughter.yaml
diff --git a/data/items/bronze_dagger.yaml b/data/items/equipment/bronze_dagger.yaml
index 95ec968..95ec968 100644
--- a/data/items/bronze_dagger.yaml
+++ b/data/items/equipment/bronze_dagger.yaml
diff --git a/data/items/bronze_full_helm.yaml b/data/items/equipment/bronze_full_helm.yaml
index ed006a8..ed006a8 100644
--- a/data/items/bronze_full_helm.yaml
+++ b/data/items/equipment/bronze_full_helm.yaml
diff --git a/data/items/bronze_med_helm.yaml b/data/items/equipment/bronze_med_helm.yaml
index c9635cf..c9635cf 100644
--- a/data/items/bronze_med_helm.yaml
+++ b/data/items/equipment/bronze_med_helm.yaml
diff --git a/data/items/bronze_platebody.yaml b/data/items/equipment/bronze_platebody.yaml
index 8e6dd2c..8e6dd2c 100644
--- a/data/items/bronze_platebody.yaml
+++ b/data/items/equipment/bronze_platebody.yaml
diff --git a/data/items/bronze_sword.yaml b/data/items/equipment/bronze_sword.yaml
index 0cfdd0e..0cfdd0e 100644
--- a/data/items/bronze_sword.yaml
+++ b/data/items/equipment/bronze_sword.yaml
diff --git a/data/items/capacitor_gloves.yaml b/data/items/equipment/capacitor_gloves.yaml
index e5af9ed..e5af9ed 100644
--- a/data/items/capacitor_gloves.yaml
+++ b/data/items/equipment/capacitor_gloves.yaml
diff --git a/data/items/cape_of_agility.yaml b/data/items/equipment/cape_of_agility.yaml
index 5567e73..5567e73 100644
--- a/data/items/cape_of_agility.yaml
+++ b/data/items/equipment/cape_of_agility.yaml
diff --git a/data/items/chitin_plate.yaml b/data/items/equipment/chitin_plate.yaml
index 4148a2a..4148a2a 100644
--- a/data/items/chitin_plate.yaml
+++ b/data/items/equipment/chitin_plate.yaml
diff --git a/data/items/coif.yaml b/data/items/equipment/coif.yaml
index 8d910d7..8d910d7 100644
--- a/data/items/coif.yaml
+++ b/data/items/equipment/coif.yaml
diff --git a/data/items/diamond_amulet_u.yaml b/data/items/equipment/diamond_amulet_u.yaml
index 55eaf45..55eaf45 100644
--- a/data/items/diamond_amulet_u.yaml
+++ b/data/items/equipment/diamond_amulet_u.yaml
diff --git a/data/items/diamond_bracelet.yaml b/data/items/equipment/diamond_bracelet.yaml
index 5ba5778..5ba5778 100644
--- a/data/items/diamond_bracelet.yaml
+++ b/data/items/equipment/diamond_bracelet.yaml
diff --git a/data/items/diamond_necklace.yaml b/data/items/equipment/diamond_necklace.yaml
index 5629b8c..5629b8c 100644
--- a/data/items/diamond_necklace.yaml
+++ b/data/items/equipment/diamond_necklace.yaml
diff --git a/data/items/diamond_ring.yaml b/data/items/equipment/diamond_ring.yaml
index 54023c3..54023c3 100644
--- a/data/items/diamond_ring.yaml
+++ b/data/items/equipment/diamond_ring.yaml
diff --git a/data/items/digsite_pendant.yaml b/data/items/equipment/digsite_pendant.yaml
index b28e3e3..b28e3e3 100644
--- a/data/items/digsite_pendant.yaml
+++ b/data/items/equipment/digsite_pendant.yaml
diff --git a/data/items/dragon_axe.yaml b/data/items/equipment/dragon_axe.yaml
index ddc9758..ddc9758 100644
--- a/data/items/dragon_axe.yaml
+++ b/data/items/equipment/dragon_axe.yaml
diff --git a/data/items/emerald_amulet_u.yaml b/data/items/equipment/emerald_amulet_u.yaml
index 87261d4..87261d4 100644
--- a/data/items/emerald_amulet_u.yaml
+++ b/data/items/equipment/emerald_amulet_u.yaml
diff --git a/data/items/emerald_bracelet.yaml b/data/items/equipment/emerald_bracelet.yaml
index 04e4979..04e4979 100644
--- a/data/items/emerald_bracelet.yaml
+++ b/data/items/equipment/emerald_bracelet.yaml
diff --git a/data/items/emerald_necklace.yaml b/data/items/equipment/emerald_necklace.yaml
index 2107ca4..2107ca4 100644
--- a/data/items/emerald_necklace.yaml
+++ b/data/items/equipment/emerald_necklace.yaml
diff --git a/data/items/emerald_ring.yaml b/data/items/equipment/emerald_ring.yaml
index 253f872..253f872 100644
--- a/data/items/emerald_ring.yaml
+++ b/data/items/equipment/emerald_ring.yaml
diff --git a/data/items/gold_amulet_u.yaml b/data/items/equipment/gold_amulet_u.yaml
index 8671941..8671941 100644
--- a/data/items/gold_amulet_u.yaml
+++ b/data/items/equipment/gold_amulet_u.yaml
diff --git a/data/items/gold_bracelet.yaml b/data/items/equipment/gold_bracelet.yaml
index 2c4534f..2c4534f 100644
--- a/data/items/gold_bracelet.yaml
+++ b/data/items/equipment/gold_bracelet.yaml
diff --git a/data/items/gold_necklace.yaml b/data/items/equipment/gold_necklace.yaml
index 4a22ac1..4a22ac1 100644
--- a/data/items/gold_necklace.yaml
+++ b/data/items/equipment/gold_necklace.yaml
diff --git a/data/items/gold_ring.yaml b/data/items/equipment/gold_ring.yaml
index 1a39165..1a39165 100644
--- a/data/items/gold_ring.yaml
+++ b/data/items/equipment/gold_ring.yaml
diff --git a/data/items/graceful_boots.yaml b/data/items/equipment/graceful_boots.yaml
index 71e8f84..71e8f84 100644
--- a/data/items/graceful_boots.yaml
+++ b/data/items/equipment/graceful_boots.yaml
diff --git a/data/items/graceful_cape.yaml b/data/items/equipment/graceful_cape.yaml
index 1d7c8f0..1d7c8f0 100644
--- a/data/items/graceful_cape.yaml
+++ b/data/items/equipment/graceful_cape.yaml
diff --git a/data/items/graceful_gloves.yaml b/data/items/equipment/graceful_gloves.yaml
index 311ded5..311ded5 100644
--- a/data/items/graceful_gloves.yaml
+++ b/data/items/equipment/graceful_gloves.yaml
diff --git a/data/items/graceful_hat.yaml b/data/items/equipment/graceful_hat.yaml
index faec1e3..faec1e3 100644
--- a/data/items/graceful_hat.yaml
+++ b/data/items/equipment/graceful_hat.yaml
diff --git a/data/items/graceful_legs.yaml b/data/items/equipment/graceful_legs.yaml
index 31eeebf..31eeebf 100644
--- a/data/items/graceful_legs.yaml
+++ b/data/items/equipment/graceful_legs.yaml
diff --git a/data/items/graceful_torso.yaml b/data/items/equipment/graceful_torso.yaml
index ec1edb7..ec1edb7 100644
--- a/data/items/graceful_torso.yaml
+++ b/data/items/equipment/graceful_torso.yaml
diff --git a/data/items/hard_leather_body.yaml b/data/items/equipment/hard_leather_body.yaml
index a5fe30f..a5fe30f 100644
--- a/data/items/hard_leather_body.yaml
+++ b/data/items/equipment/hard_leather_body.yaml
diff --git a/data/items/hard_leather_shield.yaml b/data/items/equipment/hard_leather_shield.yaml
index fd7dabf..fd7dabf 100644
--- a/data/items/hard_leather_shield.yaml
+++ b/data/items/equipment/hard_leather_shield.yaml
diff --git a/data/items/inoculation_bracelet.yaml b/data/items/equipment/inoculation_bracelet.yaml
index 9a3a149..9a3a149 100644
--- a/data/items/inoculation_bracelet.yaml
+++ b/data/items/equipment/inoculation_bracelet.yaml
diff --git a/data/items/insulated_gloves.yaml b/data/items/equipment/insulated_gloves.yaml
index 3e53614..3e53614 100644
--- a/data/items/insulated_gloves.yaml
+++ b/data/items/equipment/insulated_gloves.yaml
diff --git a/data/items/iron_dagger.yaml b/data/items/equipment/iron_dagger.yaml
index 8fa645c..8fa645c 100644
--- a/data/items/iron_dagger.yaml
+++ b/data/items/equipment/iron_dagger.yaml
diff --git a/data/items/iron_full_helm.yaml b/data/items/equipment/iron_full_helm.yaml
index 3a2fc0c..3a2fc0c 100644
--- a/data/items/iron_full_helm.yaml
+++ b/data/items/equipment/iron_full_helm.yaml
diff --git a/data/items/iron_med_helm.yaml b/data/items/equipment/iron_med_helm.yaml
index 6956283..6956283 100644
--- a/data/items/iron_med_helm.yaml
+++ b/data/items/equipment/iron_med_helm.yaml
diff --git a/data/items/iron_platebody.yaml b/data/items/equipment/iron_platebody.yaml
index 3c0be80..3c0be80 100644
--- a/data/items/iron_platebody.yaml
+++ b/data/items/equipment/iron_platebody.yaml
diff --git a/data/items/iron_sword.yaml b/data/items/equipment/iron_sword.yaml
index a5711fc..a5711fc 100644
--- a/data/items/iron_sword.yaml
+++ b/data/items/equipment/iron_sword.yaml
diff --git a/data/items/leather_body.yaml b/data/items/equipment/leather_body.yaml
index e69342c..e69342c 100644
--- a/data/items/leather_body.yaml
+++ b/data/items/equipment/leather_body.yaml
diff --git a/data/items/leather_boots.yaml b/data/items/equipment/leather_boots.yaml
index 7716e95..7716e95 100644
--- a/data/items/leather_boots.yaml
+++ b/data/items/equipment/leather_boots.yaml
diff --git a/data/items/leather_chaps.yaml b/data/items/equipment/leather_chaps.yaml
index 1a17c16..1a17c16 100644
--- a/data/items/leather_chaps.yaml
+++ b/data/items/equipment/leather_chaps.yaml
diff --git a/data/items/leather_cowl.yaml b/data/items/equipment/leather_cowl.yaml
index aa5965a..aa5965a 100644
--- a/data/items/leather_cowl.yaml
+++ b/data/items/equipment/leather_cowl.yaml
diff --git a/data/items/leather_gloves.yaml b/data/items/equipment/leather_gloves.yaml
index 7590fee..7590fee 100644
--- a/data/items/leather_gloves.yaml
+++ b/data/items/equipment/leather_gloves.yaml
diff --git a/data/items/leather_vambraces.yaml b/data/items/equipment/leather_vambraces.yaml
index fa59aa9..fa59aa9 100644
--- a/data/items/leather_vambraces.yaml
+++ b/data/items/equipment/leather_vambraces.yaml
diff --git a/data/items/longbow.yaml b/data/items/equipment/longbow.yaml
index f40c96d..f40c96d 100644
--- a/data/items/longbow.yaml
+++ b/data/items/equipment/longbow.yaml
diff --git a/data/items/longbow_u.yaml b/data/items/equipment/longbow_u.yaml
index 93e5ff0..93e5ff0 100644
--- a/data/items/longbow_u.yaml
+++ b/data/items/equipment/longbow_u.yaml
diff --git a/data/items/magic_longbow.yaml b/data/items/equipment/magic_longbow.yaml
index 07797fb..07797fb 100644
--- a/data/items/magic_longbow.yaml
+++ b/data/items/equipment/magic_longbow.yaml
diff --git a/data/items/magic_longbow_u.yaml b/data/items/equipment/magic_longbow_u.yaml
index 65682ed..65682ed 100644
--- a/data/items/magic_longbow_u.yaml
+++ b/data/items/equipment/magic_longbow_u.yaml
diff --git a/data/items/magic_shortbow.yaml b/data/items/equipment/magic_shortbow.yaml
index 893b287..893b287 100644
--- a/data/items/magic_shortbow.yaml
+++ b/data/items/equipment/magic_shortbow.yaml
diff --git a/data/items/magic_shortbow_u.yaml b/data/items/equipment/magic_shortbow_u.yaml
index 2075bd1..2075bd1 100644
--- a/data/items/magic_shortbow_u.yaml
+++ b/data/items/equipment/magic_shortbow_u.yaml
diff --git a/data/items/maple_longbow.yaml b/data/items/equipment/maple_longbow.yaml
index 1c86128..1c86128 100644
--- a/data/items/maple_longbow.yaml
+++ b/data/items/equipment/maple_longbow.yaml
diff --git a/data/items/maple_longbow_u.yaml b/data/items/equipment/maple_longbow_u.yaml
index 120bf1d..120bf1d 100644
--- a/data/items/maple_longbow_u.yaml
+++ b/data/items/equipment/maple_longbow_u.yaml
diff --git a/data/items/maple_shortbow.yaml b/data/items/equipment/maple_shortbow.yaml
index 38c27b5..38c27b5 100644
--- a/data/items/maple_shortbow.yaml
+++ b/data/items/equipment/maple_shortbow.yaml
diff --git a/data/items/maple_shortbow_u.yaml b/data/items/equipment/maple_shortbow_u.yaml
index 6fc6b84..6fc6b84 100644
--- a/data/items/maple_shortbow_u.yaml
+++ b/data/items/equipment/maple_shortbow_u.yaml
diff --git a/data/items/mithril_dagger.yaml b/data/items/equipment/mithril_dagger.yaml
index 8ffe595..8ffe595 100644
--- a/data/items/mithril_dagger.yaml
+++ b/data/items/equipment/mithril_dagger.yaml
diff --git a/data/items/mithril_full_helm.yaml b/data/items/equipment/mithril_full_helm.yaml
index d317705..d317705 100644
--- a/data/items/mithril_full_helm.yaml
+++ b/data/items/equipment/mithril_full_helm.yaml
diff --git a/data/items/mithril_med_helm.yaml b/data/items/equipment/mithril_med_helm.yaml
index f244a7c..f244a7c 100644
--- a/data/items/mithril_med_helm.yaml
+++ b/data/items/equipment/mithril_med_helm.yaml
diff --git a/data/items/mithril_platebody.yaml b/data/items/equipment/mithril_platebody.yaml
index 50e8ea0..50e8ea0 100644
--- a/data/items/mithril_platebody.yaml
+++ b/data/items/equipment/mithril_platebody.yaml
diff --git a/data/items/mithril_sword.yaml b/data/items/equipment/mithril_sword.yaml
index 1824f5b..1824f5b 100644
--- a/data/items/mithril_sword.yaml
+++ b/data/items/equipment/mithril_sword.yaml
diff --git a/data/items/necklace_of_passage.yaml b/data/items/equipment/necklace_of_passage.yaml
index b5630ed..b5630ed 100644
--- a/data/items/necklace_of_passage.yaml
+++ b/data/items/equipment/necklace_of_passage.yaml
diff --git a/data/items/neural_headband.yaml b/data/items/equipment/neural_headband.yaml
index e01e57d..e01e57d 100644
--- a/data/items/neural_headband.yaml
+++ b/data/items/equipment/neural_headband.yaml
diff --git a/data/items/oak_longbow.yaml b/data/items/equipment/oak_longbow.yaml
index 71e7c7c..71e7c7c 100644
--- a/data/items/oak_longbow.yaml
+++ b/data/items/equipment/oak_longbow.yaml
diff --git a/data/items/oak_longbow_u.yaml b/data/items/equipment/oak_longbow_u.yaml
index 6df0dab..6df0dab 100644
--- a/data/items/oak_longbow_u.yaml
+++ b/data/items/equipment/oak_longbow_u.yaml
diff --git a/data/items/oak_shortbow.yaml b/data/items/equipment/oak_shortbow.yaml
index 30ebbce..30ebbce 100644
--- a/data/items/oak_shortbow.yaml
+++ b/data/items/equipment/oak_shortbow.yaml
diff --git a/data/items/oak_shortbow_u.yaml b/data/items/equipment/oak_shortbow_u.yaml
index 5bebc38..5bebc38 100644
--- a/data/items/oak_shortbow_u.yaml
+++ b/data/items/equipment/oak_shortbow_u.yaml
diff --git a/data/items/party_hat_blue.yaml b/data/items/equipment/party_hat_blue.yaml
index 42d6bb5..42d6bb5 100644
--- a/data/items/party_hat_blue.yaml
+++ b/data/items/equipment/party_hat_blue.yaml
diff --git a/data/items/party_hat_green.yaml b/data/items/equipment/party_hat_green.yaml
index 82c3bc7..82c3bc7 100644
--- a/data/items/party_hat_green.yaml
+++ b/data/items/equipment/party_hat_green.yaml
diff --git a/data/items/party_hat_pink.yaml b/data/items/equipment/party_hat_pink.yaml
index 006e261..006e261 100644
--- a/data/items/party_hat_pink.yaml
+++ b/data/items/equipment/party_hat_pink.yaml
diff --git a/data/items/party_hat_red.yaml b/data/items/equipment/party_hat_red.yaml
index 005b3dd..005b3dd 100644
--- a/data/items/party_hat_red.yaml
+++ b/data/items/equipment/party_hat_red.yaml
diff --git a/data/items/party_hat_white.yaml b/data/items/equipment/party_hat_white.yaml
index c8e8f86..c8e8f86 100644
--- a/data/items/party_hat_white.yaml
+++ b/data/items/equipment/party_hat_white.yaml
diff --git a/data/items/party_hat_yellow.yaml b/data/items/equipment/party_hat_yellow.yaml
index 33a85f8..33a85f8 100644
--- a/data/items/party_hat_yellow.yaml
+++ b/data/items/equipment/party_hat_yellow.yaml
diff --git a/data/items/phoenix_necklace.yaml b/data/items/equipment/phoenix_necklace.yaml
index b53fafb..b53fafb 100644
--- a/data/items/phoenix_necklace.yaml
+++ b/data/items/equipment/phoenix_necklace.yaml
diff --git a/data/items/power_amulet.yaml b/data/items/equipment/power_amulet.yaml
index bd136a4..bd136a4 100644
--- a/data/items/power_amulet.yaml
+++ b/data/items/equipment/power_amulet.yaml
diff --git a/data/items/ring_of_dueling.yaml b/data/items/equipment/ring_of_dueling.yaml
index 0c11e87..0c11e87 100644
--- a/data/items/ring_of_dueling.yaml
+++ b/data/items/equipment/ring_of_dueling.yaml
diff --git a/data/items/ring_of_forging.yaml b/data/items/equipment/ring_of_forging.yaml
index d0792a1..d0792a1 100644
--- a/data/items/ring_of_forging.yaml
+++ b/data/items/equipment/ring_of_forging.yaml
diff --git a/data/items/ring_of_life.yaml b/data/items/equipment/ring_of_life.yaml
index ab0b6e3..ab0b6e3 100644
--- a/data/items/ring_of_life.yaml
+++ b/data/items/equipment/ring_of_life.yaml
diff --git a/data/items/ring_of_recoil.yaml b/data/items/equipment/ring_of_recoil.yaml
index c143f68..c143f68 100644
--- a/data/items/ring_of_recoil.yaml
+++ b/data/items/equipment/ring_of_recoil.yaml
diff --git a/data/items/ruby_amulet_u.yaml b/data/items/equipment/ruby_amulet_u.yaml
index 031bf8b..031bf8b 100644
--- a/data/items/ruby_amulet_u.yaml
+++ b/data/items/equipment/ruby_amulet_u.yaml
diff --git a/data/items/ruby_bracelet.yaml b/data/items/equipment/ruby_bracelet.yaml
index e5f3f70..e5f3f70 100644
--- a/data/items/ruby_bracelet.yaml
+++ b/data/items/equipment/ruby_bracelet.yaml
diff --git a/data/items/ruby_necklace.yaml b/data/items/equipment/ruby_necklace.yaml
index e181777..e181777 100644
--- a/data/items/ruby_necklace.yaml
+++ b/data/items/equipment/ruby_necklace.yaml
diff --git a/data/items/ruby_ring.yaml b/data/items/equipment/ruby_ring.yaml
index cb0cfde..cb0cfde 100644
--- a/data/items/ruby_ring.yaml
+++ b/data/items/equipment/ruby_ring.yaml
diff --git a/data/items/rune_dagger.yaml b/data/items/equipment/rune_dagger.yaml
index 9e8507b..9e8507b 100644
--- a/data/items/rune_dagger.yaml
+++ b/data/items/equipment/rune_dagger.yaml
diff --git a/data/items/rune_full_helm.yaml b/data/items/equipment/rune_full_helm.yaml
index 87d6973..87d6973 100644
--- a/data/items/rune_full_helm.yaml
+++ b/data/items/equipment/rune_full_helm.yaml
diff --git a/data/items/rune_med_helm.yaml b/data/items/equipment/rune_med_helm.yaml
index 895d257..895d257 100644
--- a/data/items/rune_med_helm.yaml
+++ b/data/items/equipment/rune_med_helm.yaml
diff --git a/data/items/rune_platebody.yaml b/data/items/equipment/rune_platebody.yaml
index d1e5fb1..d1e5fb1 100644
--- a/data/items/rune_platebody.yaml
+++ b/data/items/equipment/rune_platebody.yaml
diff --git a/data/items/rune_sword.yaml b/data/items/equipment/rune_sword.yaml
index e0bfb74..e0bfb74 100644
--- a/data/items/rune_sword.yaml
+++ b/data/items/equipment/rune_sword.yaml
diff --git a/data/items/sapphire_amulet_u.yaml b/data/items/equipment/sapphire_amulet_u.yaml
index 22ef39c..22ef39c 100644
--- a/data/items/sapphire_amulet_u.yaml
+++ b/data/items/equipment/sapphire_amulet_u.yaml
diff --git a/data/items/sapphire_bracelet.yaml b/data/items/equipment/sapphire_bracelet.yaml
index a2996eb..a2996eb 100644
--- a/data/items/sapphire_bracelet.yaml
+++ b/data/items/equipment/sapphire_bracelet.yaml
diff --git a/data/items/sapphire_necklace.yaml b/data/items/equipment/sapphire_necklace.yaml
index e553973..e553973 100644
--- a/data/items/sapphire_necklace.yaml
+++ b/data/items/equipment/sapphire_necklace.yaml
diff --git a/data/items/sapphire_ring.yaml b/data/items/equipment/sapphire_ring.yaml
index 40e8cc5..40e8cc5 100644
--- a/data/items/sapphire_ring.yaml
+++ b/data/items/equipment/sapphire_ring.yaml
diff --git a/data/items/shortbow.yaml b/data/items/equipment/shortbow.yaml
index ecd3630..ecd3630 100644
--- a/data/items/shortbow.yaml
+++ b/data/items/equipment/shortbow.yaml
diff --git a/data/items/shortbow_u.yaml b/data/items/equipment/shortbow_u.yaml
index 6ffcafc..6ffcafc 100644
--- a/data/items/shortbow_u.yaml
+++ b/data/items/equipment/shortbow_u.yaml
diff --git a/data/items/spectral_visor.yaml b/data/items/equipment/spectral_visor.yaml
index 851a929..851a929 100644
--- a/data/items/spectral_visor.yaml
+++ b/data/items/equipment/spectral_visor.yaml
diff --git a/data/items/steel_dagger.yaml b/data/items/equipment/steel_dagger.yaml
index e52b882..e52b882 100644
--- a/data/items/steel_dagger.yaml
+++ b/data/items/equipment/steel_dagger.yaml
diff --git a/data/items/steel_full_helm.yaml b/data/items/equipment/steel_full_helm.yaml
index 7152ded..7152ded 100644
--- a/data/items/steel_full_helm.yaml
+++ b/data/items/equipment/steel_full_helm.yaml
diff --git a/data/items/steel_med_helm.yaml b/data/items/equipment/steel_med_helm.yaml
index 9e15fdd..9e15fdd 100644
--- a/data/items/steel_med_helm.yaml
+++ b/data/items/equipment/steel_med_helm.yaml
diff --git a/data/items/steel_platebody.yaml b/data/items/equipment/steel_platebody.yaml
index f44dfa3..f44dfa3 100644
--- a/data/items/steel_platebody.yaml
+++ b/data/items/equipment/steel_platebody.yaml
diff --git a/data/items/steel_sword.yaml b/data/items/equipment/steel_sword.yaml
index e096dc9..e096dc9 100644
--- a/data/items/steel_sword.yaml
+++ b/data/items/equipment/steel_sword.yaml
diff --git a/data/items/willow_longbow.yaml b/data/items/equipment/willow_longbow.yaml
index e30528c..e30528c 100644
--- a/data/items/willow_longbow.yaml
+++ b/data/items/equipment/willow_longbow.yaml
diff --git a/data/items/willow_longbow_u.yaml b/data/items/equipment/willow_longbow_u.yaml
index 5e4e9da..5e4e9da 100644
--- a/data/items/willow_longbow_u.yaml
+++ b/data/items/equipment/willow_longbow_u.yaml
diff --git a/data/items/willow_shortbow.yaml b/data/items/equipment/willow_shortbow.yaml
index f75cfe9..f75cfe9 100644
--- a/data/items/willow_shortbow.yaml
+++ b/data/items/equipment/willow_shortbow.yaml
diff --git a/data/items/willow_shortbow_u.yaml b/data/items/equipment/willow_shortbow_u.yaml
index b467455..b467455 100644
--- a/data/items/willow_shortbow_u.yaml
+++ b/data/items/equipment/willow_shortbow_u.yaml
diff --git a/data/items/yew_longbow.yaml b/data/items/equipment/yew_longbow.yaml
index 1e7c0f5..1e7c0f5 100644
--- a/data/items/yew_longbow.yaml
+++ b/data/items/equipment/yew_longbow.yaml
diff --git a/data/items/yew_longbow_u.yaml b/data/items/equipment/yew_longbow_u.yaml
index ac4fdb2..ac4fdb2 100644
--- a/data/items/yew_longbow_u.yaml
+++ b/data/items/equipment/yew_longbow_u.yaml
diff --git a/data/items/yew_shortbow.yaml b/data/items/equipment/yew_shortbow.yaml
index c302a38..c302a38 100644
--- a/data/items/yew_shortbow.yaml
+++ b/data/items/equipment/yew_shortbow.yaml
diff --git a/data/items/yew_shortbow_u.yaml b/data/items/equipment/yew_shortbow_u.yaml
index 4c2124b..4c2124b 100644
--- a/data/items/yew_shortbow_u.yaml
+++ b/data/items/equipment/yew_shortbow_u.yaml
diff --git a/data/items/guam_potion_unf.yaml b/data/items/guam_potion_unf.yaml
deleted file mode 100644
index 970a9d2..0000000
--- a/data/items/guam_potion_unf.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: guam_potion_unf
-name: "guam potion (unf)"
-color: "48"
-description: "An unfinished potion with guam floating in water."
-value: 5
-stackable: false
-made_from:
- - items: [guam]
- qty: 1
- - items: [vial_of_water]
- qty: 1
-ticks: 0
diff --git a/data/items/harralander_potion_unf.yaml b/data/items/harralander_potion_unf.yaml
deleted file mode 100644
index bd578c2..0000000
--- a/data/items/harralander_potion_unf.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: harralander_potion_unf
-name: "harralander potion (unf)"
-color: "48"
-description: "An unfinished potion with harralander floating in water."
-value: 18
-stackable: false
-made_from:
- - items: [harralander]
- qty: 1
- - items: [vial_of_water]
- qty: 1
-ticks: 0
diff --git a/data/items/irit_potion_unf.yaml b/data/items/irit_potion_unf.yaml
deleted file mode 100644
index d10a19a..0000000
--- a/data/items/irit_potion_unf.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: irit_potion_unf
-name: "irit potion (unf)"
-color: "48"
-description: "An unfinished potion with irit floating in water."
-value: 42
-stackable: false
-made_from:
- - items: [irit]
- qty: 1
- - items: [vial_of_water]
- qty: 1
-ticks: 0
diff --git a/data/items/kwuarm_potion_unf.yaml b/data/items/kwuarm_potion_unf.yaml
deleted file mode 100644
index 750a47a..0000000
--- a/data/items/kwuarm_potion_unf.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: kwuarm_potion_unf
-name: "kwuarm potion (unf)"
-color: "48"
-description: "An unfinished potion with kwuarm floating in water."
-value: 56
-stackable: false
-made_from:
- - items: [kwuarm]
- qty: 1
- - items: [vial_of_water]
- qty: 1
-ticks: 0
diff --git a/data/items/lantadyme_potion_unf.yaml b/data/items/lantadyme_potion_unf.yaml
deleted file mode 100644
index bc66c3c..0000000
--- a/data/items/lantadyme_potion_unf.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: lantadyme_potion_unf
-name: "lantadyme potion (unf)"
-color: "48"
-description: "An unfinished potion with lantadyme floating in water."
-value: 80
-stackable: false
-made_from:
- - items: [lantadyme]
- qty: 1
- - items: [vial_of_water]
- qty: 1
-ticks: 0
diff --git a/data/items/marrentill_potion_unf.yaml b/data/items/marrentill_potion_unf.yaml
deleted file mode 100644
index e7b3817..0000000
--- a/data/items/marrentill_potion_unf.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: marrentill_potion_unf
-name: "marrentill potion (unf)"
-color: "48"
-description: "An unfinished potion with marrentill floating in water."
-value: 8
-stackable: false
-made_from:
- - items: [marrentill]
- qty: 1
- - items: [vial_of_water]
- qty: 1
-ticks: 0
diff --git a/data/items/adamant_nails.yaml b/data/items/materials/adamant_nails.yaml
index c40d228..c40d228 100644
--- a/data/items/adamant_nails.yaml
+++ b/data/items/materials/adamant_nails.yaml
diff --git a/data/items/adamantite_bar.yaml b/data/items/materials/adamantite_bar.yaml
index 5a9b827..5a9b827 100644
--- a/data/items/adamantite_bar.yaml
+++ b/data/items/materials/adamantite_bar.yaml
diff --git a/data/items/adamantite_ore.yaml b/data/items/materials/adamantite_ore.yaml
index fabe0da..fabe0da 100644
--- a/data/items/adamantite_ore.yaml
+++ b/data/items/materials/adamantite_ore.yaml
diff --git a/data/items/arachnid_enzyme.yaml b/data/items/materials/arachnid_enzyme.yaml
index 4be2f65..4be2f65 100644
--- a/data/items/arachnid_enzyme.yaml
+++ b/data/items/materials/arachnid_enzyme.yaml
diff --git a/data/items/ashes.yaml b/data/items/materials/ashes.yaml
index 0b83f77..0b83f77 100644
--- a/data/items/ashes.yaml
+++ b/data/items/materials/ashes.yaml
diff --git a/data/items/avantoe.yaml b/data/items/materials/avantoe.yaml
index d596096..d596096 100644
--- a/data/items/avantoe.yaml
+++ b/data/items/materials/avantoe.yaml
diff --git a/data/items/ball_of_wool.yaml b/data/items/materials/ball_of_wool.yaml
index 1a680a4..1a680a4 100644
--- a/data/items/ball_of_wool.yaml
+++ b/data/items/materials/ball_of_wool.yaml
diff --git a/data/items/bones.yaml b/data/items/materials/bones.yaml
index 25097d3..25097d3 100644
--- a/data/items/bones.yaml
+++ b/data/items/materials/bones.yaml
diff --git a/data/items/bowstring.yaml b/data/items/materials/bowstring.yaml
index a180081..a180081 100644
--- a/data/items/bowstring.yaml
+++ b/data/items/materials/bowstring.yaml
diff --git a/data/items/bronze_bar.yaml b/data/items/materials/bronze_bar.yaml
index 053cfe8..053cfe8 100644
--- a/data/items/bronze_bar.yaml
+++ b/data/items/materials/bronze_bar.yaml
diff --git a/data/items/bronze_nails.yaml b/data/items/materials/bronze_nails.yaml
index 513e9c4..513e9c4 100644
--- a/data/items/bronze_nails.yaml
+++ b/data/items/materials/bronze_nails.yaml
diff --git a/data/items/cadantine.yaml b/data/items/materials/cadantine.yaml
index f684f0d..f684f0d 100644
--- a/data/items/cadantine.yaml
+++ b/data/items/materials/cadantine.yaml
diff --git a/data/items/chocolate_dust.yaml b/data/items/materials/chocolate_dust.yaml
index 56a6666..56a6666 100644
--- a/data/items/chocolate_dust.yaml
+++ b/data/items/materials/chocolate_dust.yaml
diff --git a/data/items/clay.yaml b/data/items/materials/clay.yaml
index 55c9b2b..55c9b2b 100644
--- a/data/items/clay.yaml
+++ b/data/items/materials/clay.yaml
diff --git a/data/items/coal.yaml b/data/items/materials/coal.yaml
index 7b79f08..7b79f08 100644
--- a/data/items/coal.yaml
+++ b/data/items/materials/coal.yaml
diff --git a/data/items/copper_ore.yaml b/data/items/materials/copper_ore.yaml
index 392879a..392879a 100644
--- a/data/items/copper_ore.yaml
+++ b/data/items/materials/copper_ore.yaml
diff --git a/data/items/cowhide.yaml b/data/items/materials/cowhide.yaml
index 2cb192e..2cb192e 100644
--- a/data/items/cowhide.yaml
+++ b/data/items/materials/cowhide.yaml
diff --git a/data/items/crushed_nest.yaml b/data/items/materials/crushed_nest.yaml
index 533d942..533d942 100644
--- a/data/items/crushed_nest.yaml
+++ b/data/items/materials/crushed_nest.yaml
diff --git a/data/items/diamond.yaml b/data/items/materials/diamond.yaml
index 2ec703f..2ec703f 100644
--- a/data/items/diamond.yaml
+++ b/data/items/materials/diamond.yaml
diff --git a/data/items/dwarf_weed.yaml b/data/items/materials/dwarf_weed.yaml
index 559edf3..559edf3 100644
--- a/data/items/dwarf_weed.yaml
+++ b/data/items/materials/dwarf_weed.yaml
diff --git a/data/items/ectoplasm.yaml b/data/items/materials/ectoplasm.yaml
index 8417e2d..8417e2d 100644
--- a/data/items/ectoplasm.yaml
+++ b/data/items/materials/ectoplasm.yaml
diff --git a/data/items/emerald.yaml b/data/items/materials/emerald.yaml
index 9a0c71d..9a0c71d 100644
--- a/data/items/emerald.yaml
+++ b/data/items/materials/emerald.yaml
diff --git a/data/items/eye_of_newt.yaml b/data/items/materials/eye_of_newt.yaml
index baf5ebe..baf5ebe 100644
--- a/data/items/eye_of_newt.yaml
+++ b/data/items/materials/eye_of_newt.yaml
diff --git a/data/items/feather.yaml b/data/items/materials/feather.yaml
index 25afc2a..25afc2a 100644
--- a/data/items/feather.yaml
+++ b/data/items/materials/feather.yaml
diff --git a/data/items/flax.yaml b/data/items/materials/flax.yaml
index 36f7082..36f7082 100644
--- a/data/items/flax.yaml
+++ b/data/items/materials/flax.yaml
diff --git a/data/items/gold_bar.yaml b/data/items/materials/gold_bar.yaml
index 3272b04..3272b04 100644
--- a/data/items/gold_bar.yaml
+++ b/data/items/materials/gold_bar.yaml
diff --git a/data/items/gold_ore.yaml b/data/items/materials/gold_ore.yaml
index e45ca03..e45ca03 100644
--- a/data/items/gold_ore.yaml
+++ b/data/items/materials/gold_ore.yaml
diff --git a/data/items/grimy_avantoe.yaml b/data/items/materials/grimy_avantoe.yaml
index 4f84679..4f84679 100644
--- a/data/items/grimy_avantoe.yaml
+++ b/data/items/materials/grimy_avantoe.yaml
diff --git a/data/items/grimy_cadantine.yaml b/data/items/materials/grimy_cadantine.yaml
index 0eb88f8..0eb88f8 100644
--- a/data/items/grimy_cadantine.yaml
+++ b/data/items/materials/grimy_cadantine.yaml
diff --git a/data/items/grimy_dwarf_weed.yaml b/data/items/materials/grimy_dwarf_weed.yaml
index 8f24595..8f24595 100644
--- a/data/items/grimy_dwarf_weed.yaml
+++ b/data/items/materials/grimy_dwarf_weed.yaml
diff --git a/data/items/grimy_guam.yaml b/data/items/materials/grimy_guam.yaml
index fb408af..fb408af 100644
--- a/data/items/grimy_guam.yaml
+++ b/data/items/materials/grimy_guam.yaml
diff --git a/data/items/grimy_harralander.yaml b/data/items/materials/grimy_harralander.yaml
index deea973..deea973 100644
--- a/data/items/grimy_harralander.yaml
+++ b/data/items/materials/grimy_harralander.yaml
diff --git a/data/items/grimy_irit.yaml b/data/items/materials/grimy_irit.yaml
index 54f66f8..54f66f8 100644
--- a/data/items/grimy_irit.yaml
+++ b/data/items/materials/grimy_irit.yaml
diff --git a/data/items/grimy_kwuarm.yaml b/data/items/materials/grimy_kwuarm.yaml
index 88630f8..88630f8 100644
--- a/data/items/grimy_kwuarm.yaml
+++ b/data/items/materials/grimy_kwuarm.yaml
diff --git a/data/items/grimy_lantadyme.yaml b/data/items/materials/grimy_lantadyme.yaml
index 8003931..8003931 100644
--- a/data/items/grimy_lantadyme.yaml
+++ b/data/items/materials/grimy_lantadyme.yaml
diff --git a/data/items/grimy_marrentill.yaml b/data/items/materials/grimy_marrentill.yaml
index 7548fe5..7548fe5 100644
--- a/data/items/grimy_marrentill.yaml
+++ b/data/items/materials/grimy_marrentill.yaml
diff --git a/data/items/grimy_ranarr.yaml b/data/items/materials/grimy_ranarr.yaml
index 1f82277..1f82277 100644
--- a/data/items/grimy_ranarr.yaml
+++ b/data/items/materials/grimy_ranarr.yaml
diff --git a/data/items/grimy_snapdragon.yaml b/data/items/materials/grimy_snapdragon.yaml
index f15c604..f15c604 100644
--- a/data/items/grimy_snapdragon.yaml
+++ b/data/items/materials/grimy_snapdragon.yaml
diff --git a/data/items/grimy_tarromin.yaml b/data/items/materials/grimy_tarromin.yaml
index 712061c..712061c 100644
--- a/data/items/grimy_tarromin.yaml
+++ b/data/items/materials/grimy_tarromin.yaml
diff --git a/data/items/grimy_toadflax.yaml b/data/items/materials/grimy_toadflax.yaml
index c489566..c489566 100644
--- a/data/items/grimy_toadflax.yaml
+++ b/data/items/materials/grimy_toadflax.yaml
diff --git a/data/items/grimy_torstol.yaml b/data/items/materials/grimy_torstol.yaml
index 47db8d7..47db8d7 100644
--- a/data/items/grimy_torstol.yaml
+++ b/data/items/materials/grimy_torstol.yaml
diff --git a/data/items/guam.yaml b/data/items/materials/guam.yaml
index 8fe7972..8fe7972 100644
--- a/data/items/guam.yaml
+++ b/data/items/materials/guam.yaml
diff --git a/data/items/hard_leather.yaml b/data/items/materials/hard_leather.yaml
index e04030c..e04030c 100644
--- a/data/items/hard_leather.yaml
+++ b/data/items/materials/hard_leather.yaml
diff --git a/data/items/harralander.yaml b/data/items/materials/harralander.yaml
index 08e66ed..08e66ed 100644
--- a/data/items/harralander.yaml
+++ b/data/items/materials/harralander.yaml
diff --git a/data/items/irit.yaml b/data/items/materials/irit.yaml
index 40f1956..40f1956 100644
--- a/data/items/irit.yaml
+++ b/data/items/materials/irit.yaml
diff --git a/data/items/iron_bar.yaml b/data/items/materials/iron_bar.yaml
index 26c8352..26c8352 100644
--- a/data/items/iron_bar.yaml
+++ b/data/items/materials/iron_bar.yaml
diff --git a/data/items/iron_nails.yaml b/data/items/materials/iron_nails.yaml
index 77323c9..77323c9 100644
--- a/data/items/iron_nails.yaml
+++ b/data/items/materials/iron_nails.yaml
diff --git a/data/items/iron_ore.yaml b/data/items/materials/iron_ore.yaml
index 78e82a5..78e82a5 100644
--- a/data/items/iron_ore.yaml
+++ b/data/items/materials/iron_ore.yaml
diff --git a/data/items/kwuarm.yaml b/data/items/materials/kwuarm.yaml
index ab0d7ff..ab0d7ff 100644
--- a/data/items/kwuarm.yaml
+++ b/data/items/materials/kwuarm.yaml
diff --git a/data/items/lantadyme.yaml b/data/items/materials/lantadyme.yaml
index d38b7da..d38b7da 100644
--- a/data/items/lantadyme.yaml
+++ b/data/items/materials/lantadyme.yaml
diff --git a/data/items/leather.yaml b/data/items/materials/leather.yaml
index 82d0696..82d0696 100644
--- a/data/items/leather.yaml
+++ b/data/items/materials/leather.yaml
diff --git a/data/items/limpwurt_root.yaml b/data/items/materials/limpwurt_root.yaml
index 49dd0dc..49dd0dc 100644
--- a/data/items/limpwurt_root.yaml
+++ b/data/items/materials/limpwurt_root.yaml
diff --git a/data/items/logs.yaml b/data/items/materials/logs.yaml
index 1395d94..1395d94 100644
--- a/data/items/logs.yaml
+++ b/data/items/materials/logs.yaml
diff --git a/data/items/magic_logs.yaml b/data/items/materials/magic_logs.yaml
index 01a804e..01a804e 100644
--- a/data/items/magic_logs.yaml
+++ b/data/items/materials/magic_logs.yaml
diff --git a/data/items/mahogany_logs.yaml b/data/items/materials/mahogany_logs.yaml
index 67b9a6c..67b9a6c 100644
--- a/data/items/mahogany_logs.yaml
+++ b/data/items/materials/mahogany_logs.yaml
diff --git a/data/items/mahogany_planks.yaml b/data/items/materials/mahogany_planks.yaml
index d551321..d551321 100644
--- a/data/items/mahogany_planks.yaml
+++ b/data/items/materials/mahogany_planks.yaml
diff --git a/data/items/maple_logs.yaml b/data/items/materials/maple_logs.yaml
index 6b081e1..6b081e1 100644
--- a/data/items/maple_logs.yaml
+++ b/data/items/materials/maple_logs.yaml
diff --git a/data/items/marrentill.yaml b/data/items/materials/marrentill.yaml
index 4bd492d..4bd492d 100644
--- a/data/items/marrentill.yaml
+++ b/data/items/materials/marrentill.yaml
diff --git a/data/items/mithril_bar.yaml b/data/items/materials/mithril_bar.yaml
index 00e97a4..00e97a4 100644
--- a/data/items/mithril_bar.yaml
+++ b/data/items/materials/mithril_bar.yaml
diff --git a/data/items/mithril_nails.yaml b/data/items/materials/mithril_nails.yaml
index aeb0544..aeb0544 100644
--- a/data/items/mithril_nails.yaml
+++ b/data/items/materials/mithril_nails.yaml
diff --git a/data/items/mithril_ore.yaml b/data/items/materials/mithril_ore.yaml
index 55d8d2c..55d8d2c 100644
--- a/data/items/mithril_ore.yaml
+++ b/data/items/materials/mithril_ore.yaml
diff --git a/data/items/oak_logs.yaml b/data/items/materials/oak_logs.yaml
index 840ebfd..840ebfd 100644
--- a/data/items/oak_logs.yaml
+++ b/data/items/materials/oak_logs.yaml
diff --git a/data/items/oak_planks.yaml b/data/items/materials/oak_planks.yaml
index 29376c6..29376c6 100644
--- a/data/items/oak_planks.yaml
+++ b/data/items/materials/oak_planks.yaml
diff --git a/data/items/planks.yaml b/data/items/materials/planks.yaml
index 67de72e..67de72e 100644
--- a/data/items/planks.yaml
+++ b/data/items/materials/planks.yaml
diff --git a/data/items/potato_cactus.yaml b/data/items/materials/potato_cactus.yaml
index 1178727..1178727 100644
--- a/data/items/potato_cactus.yaml
+++ b/data/items/materials/potato_cactus.yaml
diff --git a/data/items/ranarr.yaml b/data/items/materials/ranarr.yaml
index 683cce6..683cce6 100644
--- a/data/items/ranarr.yaml
+++ b/data/items/materials/ranarr.yaml
diff --git a/data/items/redwood_logs.yaml b/data/items/materials/redwood_logs.yaml
index 5ea1ab0..5ea1ab0 100644
--- a/data/items/redwood_logs.yaml
+++ b/data/items/materials/redwood_logs.yaml
diff --git a/data/items/ruby.yaml b/data/items/materials/ruby.yaml
index 644e1bf..644e1bf 100644
--- a/data/items/ruby.yaml
+++ b/data/items/materials/ruby.yaml
diff --git a/data/items/rune_nails.yaml b/data/items/materials/rune_nails.yaml
index 5d2dfc5..5d2dfc5 100644
--- a/data/items/rune_nails.yaml
+++ b/data/items/materials/rune_nails.yaml
diff --git a/data/items/runite_bar.yaml b/data/items/materials/runite_bar.yaml
index 1faff69..1faff69 100644
--- a/data/items/runite_bar.yaml
+++ b/data/items/materials/runite_bar.yaml
diff --git a/data/items/runite_ore.yaml b/data/items/materials/runite_ore.yaml
index 05d3248..05d3248 100644
--- a/data/items/runite_ore.yaml
+++ b/data/items/materials/runite_ore.yaml
diff --git a/data/items/sacred_eel.yaml b/data/items/materials/sacred_eel.yaml
index 9a03940..9a03940 100644
--- a/data/items/sacred_eel.yaml
+++ b/data/items/materials/sacred_eel.yaml
diff --git a/data/items/salt.yaml b/data/items/materials/salt.yaml
index d9c1b2a..d9c1b2a 100644
--- a/data/items/salt.yaml
+++ b/data/items/materials/salt.yaml
diff --git a/data/items/sapphire.yaml b/data/items/materials/sapphire.yaml
index 9710d45..9710d45 100644
--- a/data/items/sapphire.yaml
+++ b/data/items/materials/sapphire.yaml
diff --git a/data/items/silver_bar.yaml b/data/items/materials/silver_bar.yaml
index eccb25f..eccb25f 100644
--- a/data/items/silver_bar.yaml
+++ b/data/items/materials/silver_bar.yaml
diff --git a/data/items/silver_ore.yaml b/data/items/materials/silver_ore.yaml
index 5b40ff0..5b40ff0 100644
--- a/data/items/silver_ore.yaml
+++ b/data/items/materials/silver_ore.yaml
diff --git a/data/items/slug_mucus.yaml b/data/items/materials/slug_mucus.yaml
index 620038a..620038a 100644
--- a/data/items/slug_mucus.yaml
+++ b/data/items/materials/slug_mucus.yaml
diff --git a/data/items/snapdragon.yaml b/data/items/materials/snapdragon.yaml
index 0c3ba2f..0c3ba2f 100644
--- a/data/items/snapdragon.yaml
+++ b/data/items/materials/snapdragon.yaml
diff --git a/data/items/snape_grass.yaml b/data/items/materials/snape_grass.yaml
index 59a9b30..59a9b30 100644
--- a/data/items/snape_grass.yaml
+++ b/data/items/materials/snape_grass.yaml
diff --git a/data/items/materials/soft_clay.yaml b/data/items/materials/soft_clay.yaml
new file mode 100644
index 0000000..018e5ef
--- /dev/null
+++ b/data/items/materials/soft_clay.yaml
@@ -0,0 +1,13 @@
+id: soft_clay
+name: soft clay
+color: "173"
+description: "Soft, workable clay ready for shaping on a pottery wheel."
+value: 2
+stackable: false
+ticks: 2
+made_from:
+ - items: [clay]
+ quantity: 1
+ - items: [bucket_of_water, jug_of_water]
+ quantity: 1
+ byproducts: [empty_bucket, empty_jug]
diff --git a/data/items/steel_bar.yaml b/data/items/materials/steel_bar.yaml
index 450fd5c..450fd5c 100644
--- a/data/items/steel_bar.yaml
+++ b/data/items/materials/steel_bar.yaml
diff --git a/data/items/steel_nails.yaml b/data/items/materials/steel_nails.yaml
index 4890d18..4890d18 100644
--- a/data/items/steel_nails.yaml
+++ b/data/items/materials/steel_nails.yaml
diff --git a/data/items/stim_cell.yaml b/data/items/materials/stim_cell.yaml
index b431532..b431532 100644
--- a/data/items/stim_cell.yaml
+++ b/data/items/materials/stim_cell.yaml
diff --git a/data/items/tarromin.yaml b/data/items/materials/tarromin.yaml
index 5af2573..5af2573 100644
--- a/data/items/tarromin.yaml
+++ b/data/items/materials/tarromin.yaml
diff --git a/data/items/teak_logs.yaml b/data/items/materials/teak_logs.yaml
index c69f807..c69f807 100644
--- a/data/items/teak_logs.yaml
+++ b/data/items/materials/teak_logs.yaml
diff --git a/data/items/teak_planks.yaml b/data/items/materials/teak_planks.yaml
index f0a0642..f0a0642 100644
--- a/data/items/teak_planks.yaml
+++ b/data/items/materials/teak_planks.yaml
diff --git a/data/items/thread.yaml b/data/items/materials/thread.yaml
index 3a9a4f7..3a9a4f7 100644
--- a/data/items/thread.yaml
+++ b/data/items/materials/thread.yaml
diff --git a/data/items/tin_ore.yaml b/data/items/materials/tin_ore.yaml
index 0ae1cd0..0ae1cd0 100644
--- a/data/items/tin_ore.yaml
+++ b/data/items/materials/tin_ore.yaml
diff --git a/data/items/toadflax.yaml b/data/items/materials/toadflax.yaml
index 0de8f01..0de8f01 100644
--- a/data/items/toadflax.yaml
+++ b/data/items/materials/toadflax.yaml
diff --git a/data/items/torstol.yaml b/data/items/materials/torstol.yaml
index a8b8fbb..a8b8fbb 100644
--- a/data/items/torstol.yaml
+++ b/data/items/materials/torstol.yaml
diff --git a/data/items/uncut_diamond.yaml b/data/items/materials/uncut_diamond.yaml
index cf41d70..cf41d70 100644
--- a/data/items/uncut_diamond.yaml
+++ b/data/items/materials/uncut_diamond.yaml
diff --git a/data/items/uncut_emerald.yaml b/data/items/materials/uncut_emerald.yaml
index 1ea217a..1ea217a 100644
--- a/data/items/uncut_emerald.yaml
+++ b/data/items/materials/uncut_emerald.yaml
diff --git a/data/items/uncut_ruby.yaml b/data/items/materials/uncut_ruby.yaml
index dfa8188..dfa8188 100644
--- a/data/items/uncut_ruby.yaml
+++ b/data/items/materials/uncut_ruby.yaml
diff --git a/data/items/uncut_sapphire.yaml b/data/items/materials/uncut_sapphire.yaml
index bf073fa..bf073fa 100644
--- a/data/items/uncut_sapphire.yaml
+++ b/data/items/materials/uncut_sapphire.yaml
diff --git a/data/items/vial.yaml b/data/items/materials/vial.yaml
index 2a5260a..2a5260a 100644
--- a/data/items/vial.yaml
+++ b/data/items/materials/vial.yaml
diff --git a/data/items/white_berries.yaml b/data/items/materials/white_berries.yaml
index bdaa993..bdaa993 100644
--- a/data/items/white_berries.yaml
+++ b/data/items/materials/white_berries.yaml
diff --git a/data/items/willow_logs.yaml b/data/items/materials/willow_logs.yaml
index dbd315b..dbd315b 100644
--- a/data/items/willow_logs.yaml
+++ b/data/items/materials/willow_logs.yaml
diff --git a/data/items/wool.yaml b/data/items/materials/wool.yaml
index f557aaf..f557aaf 100644
--- a/data/items/wool.yaml
+++ b/data/items/materials/wool.yaml
diff --git a/data/items/yew_logs.yaml b/data/items/materials/yew_logs.yaml
index a5a86a2..a5a86a2 100644
--- a/data/items/yew_logs.yaml
+++ b/data/items/materials/yew_logs.yaml
diff --git a/data/items/birds_nest.yaml b/data/items/misc/birds_nest.yaml
index 1366b5d..1366b5d 100644
--- a/data/items/birds_nest.yaml
+++ b/data/items/misc/birds_nest.yaml
diff --git a/data/items/christmas_cracker.yaml b/data/items/misc/christmas_cracker.yaml
index c296443..c296443 100644
--- a/data/items/christmas_cracker.yaml
+++ b/data/items/misc/christmas_cracker.yaml
diff --git a/data/items/circuit_board.yaml b/data/items/misc/circuit_board.yaml
index 5b4db83..5b4db83 100644
--- a/data/items/circuit_board.yaml
+++ b/data/items/misc/circuit_board.yaml
diff --git a/data/items/credit_stick.yaml b/data/items/misc/credit_stick.yaml
index 46b3050..46b3050 100644
--- a/data/items/credit_stick.yaml
+++ b/data/items/misc/credit_stick.yaml
diff --git a/data/items/credits.yaml b/data/items/misc/credits.yaml
index 7e2de10..7e2de10 100644
--- a/data/items/credits.yaml
+++ b/data/items/misc/credits.yaml
diff --git a/data/items/mahogany_table.yaml b/data/items/misc/mahogany_table.yaml
index 245b929..245b929 100644
--- a/data/items/mahogany_table.yaml
+++ b/data/items/misc/mahogany_table.yaml
diff --git a/data/items/nutrient_bar.yaml b/data/items/misc/nutrient_bar.yaml
index d33d7de..d33d7de 100644
--- a/data/items/nutrient_bar.yaml
+++ b/data/items/misc/nutrient_bar.yaml
diff --git a/data/items/oak_shelf.yaml b/data/items/misc/oak_shelf.yaml
index 0f08fa5..0f08fa5 100644
--- a/data/items/oak_shelf.yaml
+++ b/data/items/misc/oak_shelf.yaml
diff --git a/data/items/oak_table.yaml b/data/items/misc/oak_table.yaml
index e94b372..e94b372 100644
--- a/data/items/oak_table.yaml
+++ b/data/items/misc/oak_table.yaml
diff --git a/data/items/pass_stub.yaml b/data/items/misc/pass_stub.yaml
index 1068190..1068190 100644
--- a/data/items/pass_stub.yaml
+++ b/data/items/misc/pass_stub.yaml
diff --git a/data/items/scrap_metal.yaml b/data/items/misc/scrap_metal.yaml
index d9b805e..d9b805e 100644
--- a/data/items/scrap_metal.yaml
+++ b/data/items/misc/scrap_metal.yaml
diff --git a/data/items/teak_table.yaml b/data/items/misc/teak_table.yaml
index 5c2c5c5..5c2c5c5 100644
--- a/data/items/teak_table.yaml
+++ b/data/items/misc/teak_table.yaml
diff --git a/data/items/wooden_chair.yaml b/data/items/misc/wooden_chair.yaml
index 16220ef..16220ef 100644
--- a/data/items/wooden_chair.yaml
+++ b/data/items/misc/wooden_chair.yaml
diff --git a/data/items/wooden_shelf.yaml b/data/items/misc/wooden_shelf.yaml
index 3b0d97d..3b0d97d 100644
--- a/data/items/wooden_shelf.yaml
+++ b/data/items/misc/wooden_shelf.yaml
diff --git a/data/items/wooden_table.yaml b/data/items/misc/wooden_table.yaml
index 171a9bb..171a9bb 100644
--- a/data/items/wooden_table.yaml
+++ b/data/items/misc/wooden_table.yaml
diff --git a/data/items/pastry_dough.yaml b/data/items/pastry_dough.yaml
deleted file mode 100644
index c08c0ae..0000000
--- a/data/items/pastry_dough.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: pastry_dough
-name: pastry dough
-color: "222"
-description: "A ball of uncooked pastry dough. It needs to be baked."
-value: 3
-stackable: false
-made_from:
- - items: [pot_of_flour]
- qty: 1
- - items: [bucket_of_water]
- qty: 1
diff --git a/data/items/pizza_dough.yaml b/data/items/pizza_dough.yaml
deleted file mode 100644
index 1105c60..0000000
--- a/data/items/pizza_dough.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: pizza_dough
-name: pizza dough
-color: "222"
-description: "A ball of uncooked pizza dough. It needs to be baked."
-value: 3
-stackable: false
-made_from:
- - items: [pot_of_flour]
- qty: 1
- - items: [bucket_of_water]
- qty: 1
diff --git a/data/items/ranarr_potion_unf.yaml b/data/items/ranarr_potion_unf.yaml
deleted file mode 100644
index 373abb6..0000000
--- a/data/items/ranarr_potion_unf.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: ranarr_potion_unf
-name: "ranarr potion (unf)"
-color: "48"
-description: "An unfinished potion with ranarr floating in water."
-value: 38
-stackable: false
-made_from:
- - items: [ranarr]
- qty: 1
- - items: [vial_of_water]
- qty: 1
-ticks: 0
diff --git a/data/items/avantoe_seed.yaml b/data/items/seeds/avantoe_seed.yaml
index fb7b427..fb7b427 100644
--- a/data/items/avantoe_seed.yaml
+++ b/data/items/seeds/avantoe_seed.yaml
diff --git a/data/items/cabbage_seed.yaml b/data/items/seeds/cabbage_seed.yaml
index 8a6db33..8a6db33 100644
--- a/data/items/cabbage_seed.yaml
+++ b/data/items/seeds/cabbage_seed.yaml
diff --git a/data/items/cadantine_seed.yaml b/data/items/seeds/cadantine_seed.yaml
index 8326033..8326033 100644
--- a/data/items/cadantine_seed.yaml
+++ b/data/items/seeds/cadantine_seed.yaml
diff --git a/data/items/dwarf_weed_seed.yaml b/data/items/seeds/dwarf_weed_seed.yaml
index c8ba6f2..c8ba6f2 100644
--- a/data/items/dwarf_weed_seed.yaml
+++ b/data/items/seeds/dwarf_weed_seed.yaml
diff --git a/data/items/guam_seed.yaml b/data/items/seeds/guam_seed.yaml
index b55e1a8..b55e1a8 100644
--- a/data/items/guam_seed.yaml
+++ b/data/items/seeds/guam_seed.yaml
diff --git a/data/items/harralander_seed.yaml b/data/items/seeds/harralander_seed.yaml
index 5be3571..5be3571 100644
--- a/data/items/harralander_seed.yaml
+++ b/data/items/seeds/harralander_seed.yaml
diff --git a/data/items/irit_seed.yaml b/data/items/seeds/irit_seed.yaml
index 4b8bad2..4b8bad2 100644
--- a/data/items/irit_seed.yaml
+++ b/data/items/seeds/irit_seed.yaml
diff --git a/data/items/kwuarm_seed.yaml b/data/items/seeds/kwuarm_seed.yaml
index 053c2f3..053c2f3 100644
--- a/data/items/kwuarm_seed.yaml
+++ b/data/items/seeds/kwuarm_seed.yaml
diff --git a/data/items/lantadyme_seed.yaml b/data/items/seeds/lantadyme_seed.yaml
index f4be89c..f4be89c 100644
--- a/data/items/lantadyme_seed.yaml
+++ b/data/items/seeds/lantadyme_seed.yaml
diff --git a/data/items/marrentill_seed.yaml b/data/items/seeds/marrentill_seed.yaml
index 4b42353..4b42353 100644
--- a/data/items/marrentill_seed.yaml
+++ b/data/items/seeds/marrentill_seed.yaml
diff --git a/data/items/onion_seed.yaml b/data/items/seeds/onion_seed.yaml
index a1231ad..a1231ad 100644
--- a/data/items/onion_seed.yaml
+++ b/data/items/seeds/onion_seed.yaml
diff --git a/data/items/potato_seed.yaml b/data/items/seeds/potato_seed.yaml
index 9daee8a..9daee8a 100644
--- a/data/items/potato_seed.yaml
+++ b/data/items/seeds/potato_seed.yaml
diff --git a/data/items/ranarr_seed.yaml b/data/items/seeds/ranarr_seed.yaml
index ef54c0d..ef54c0d 100644
--- a/data/items/ranarr_seed.yaml
+++ b/data/items/seeds/ranarr_seed.yaml
diff --git a/data/items/snapdragon_seed.yaml b/data/items/seeds/snapdragon_seed.yaml
index 06f2a9e..06f2a9e 100644
--- a/data/items/snapdragon_seed.yaml
+++ b/data/items/seeds/snapdragon_seed.yaml
diff --git a/data/items/strawberry_seed.yaml b/data/items/seeds/strawberry_seed.yaml
index 8b2f45c..8b2f45c 100644
--- a/data/items/strawberry_seed.yaml
+++ b/data/items/seeds/strawberry_seed.yaml
diff --git a/data/items/sweetcorn_seed.yaml b/data/items/seeds/sweetcorn_seed.yaml
index d85b35b..d85b35b 100644
--- a/data/items/sweetcorn_seed.yaml
+++ b/data/items/seeds/sweetcorn_seed.yaml
diff --git a/data/items/tarromin_seed.yaml b/data/items/seeds/tarromin_seed.yaml
index c10483a..c10483a 100644
--- a/data/items/tarromin_seed.yaml
+++ b/data/items/seeds/tarromin_seed.yaml
diff --git a/data/items/toadflax_seed.yaml b/data/items/seeds/toadflax_seed.yaml
index 1ef6cb2..1ef6cb2 100644
--- a/data/items/toadflax_seed.yaml
+++ b/data/items/seeds/toadflax_seed.yaml
diff --git a/data/items/tomato_seed.yaml b/data/items/seeds/tomato_seed.yaml
index c1db131..c1db131 100644
--- a/data/items/tomato_seed.yaml
+++ b/data/items/seeds/tomato_seed.yaml
diff --git a/data/items/torstol_seed.yaml b/data/items/seeds/torstol_seed.yaml
index 2dfd60c..2dfd60c 100644
--- a/data/items/torstol_seed.yaml
+++ b/data/items/seeds/torstol_seed.yaml
diff --git a/data/items/watermelon_seed.yaml b/data/items/seeds/watermelon_seed.yaml
index 7ea59c6..7ea59c6 100644
--- a/data/items/watermelon_seed.yaml
+++ b/data/items/seeds/watermelon_seed.yaml
diff --git a/data/items/snapdragon_potion_unf.yaml b/data/items/snapdragon_potion_unf.yaml
deleted file mode 100644
index ac3aba3..0000000
--- a/data/items/snapdragon_potion_unf.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: snapdragon_potion_unf
-name: "snapdragon potion (unf)"
-color: "48"
-description: "An unfinished potion with snapdragon floating in water."
-value: 68
-stackable: false
-made_from:
- - items: [snapdragon]
- qty: 1
- - items: [vial_of_water]
- qty: 1
-ticks: 0
diff --git a/data/items/soft_clay.yaml b/data/items/soft_clay.yaml
deleted file mode 100644
index 1ba07e3..0000000
--- a/data/items/soft_clay.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: soft_clay
-name: soft clay
-color: "173"
-description: "Soft, workable clay ready for shaping on a pottery wheel."
-value: 2
-stackable: false
-ticks: 2
-made_from:
- - items: [clay]
- qty: 1
- - items: [bucket_of_water, jug_of_water]
- qty: 1
- byproducts: [empty_bucket, empty_jug]
diff --git a/data/items/tarromin_potion_unf.yaml b/data/items/tarromin_potion_unf.yaml
deleted file mode 100644
index 6a3fab6..0000000
--- a/data/items/tarromin_potion_unf.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: tarromin_potion_unf
-name: "tarromin potion (unf)"
-color: "48"
-description: "An unfinished potion with tarromin floating in water."
-value: 12
-stackable: false
-made_from:
- - items: [tarromin]
- qty: 1
- - items: [vial_of_water]
- qty: 1
-ticks: 0
diff --git a/data/items/toadflax_potion_unf.yaml b/data/items/toadflax_potion_unf.yaml
deleted file mode 100644
index ca2a123..0000000
--- a/data/items/toadflax_potion_unf.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: toadflax_potion_unf
-name: "toadflax potion (unf)"
-color: "48"
-description: "An unfinished potion with toadflax floating in water."
-value: 28
-stackable: false
-made_from:
- - items: [toadflax]
- qty: 1
- - items: [vial_of_water]
- qty: 1
-ticks: 0
diff --git a/data/items/adamant_axe.yaml b/data/items/tools/adamant_axe.yaml
index 81232fe..81232fe 100644
--- a/data/items/adamant_axe.yaml
+++ b/data/items/tools/adamant_axe.yaml
diff --git a/data/items/adamant_pickaxe.yaml b/data/items/tools/adamant_pickaxe.yaml
index 4296b0d..4296b0d 100644
--- a/data/items/adamant_pickaxe.yaml
+++ b/data/items/tools/adamant_pickaxe.yaml
diff --git a/data/items/amulet_mould.yaml b/data/items/tools/amulet_mould.yaml
index a8153cb..a8153cb 100644
--- a/data/items/amulet_mould.yaml
+++ b/data/items/tools/amulet_mould.yaml
diff --git a/data/items/big_fishing_net.yaml b/data/items/tools/big_fishing_net.yaml
index a8b5352..a8b5352 100644
--- a/data/items/big_fishing_net.yaml
+++ b/data/items/tools/big_fishing_net.yaml
diff --git a/data/items/black_pickaxe.yaml b/data/items/tools/black_pickaxe.yaml
index c3df955..c3df955 100644
--- a/data/items/black_pickaxe.yaml
+++ b/data/items/tools/black_pickaxe.yaml
diff --git a/data/items/bracelet_mould.yaml b/data/items/tools/bracelet_mould.yaml
index 78bc099..78bc099 100644
--- a/data/items/bracelet_mould.yaml
+++ b/data/items/tools/bracelet_mould.yaml
diff --git a/data/items/bronze_axe.yaml b/data/items/tools/bronze_axe.yaml
index 84b2762..84b2762 100644
--- a/data/items/bronze_axe.yaml
+++ b/data/items/tools/bronze_axe.yaml
diff --git a/data/items/bronze_pickaxe.yaml b/data/items/tools/bronze_pickaxe.yaml
index b581fc0..b581fc0 100644
--- a/data/items/bronze_pickaxe.yaml
+++ b/data/items/tools/bronze_pickaxe.yaml
diff --git a/data/items/chisel.yaml b/data/items/tools/chisel.yaml
index a99c828..a99c828 100644
--- a/data/items/chisel.yaml
+++ b/data/items/tools/chisel.yaml
diff --git a/data/items/dragon_pickaxe.yaml b/data/items/tools/dragon_pickaxe.yaml
index 6f187b8..6f187b8 100644
--- a/data/items/dragon_pickaxe.yaml
+++ b/data/items/tools/dragon_pickaxe.yaml
diff --git a/data/items/empty_bucket.yaml b/data/items/tools/empty_bucket.yaml
index 6debb02..6debb02 100644
--- a/data/items/empty_bucket.yaml
+++ b/data/items/tools/empty_bucket.yaml
diff --git a/data/items/empty_jug.yaml b/data/items/tools/empty_jug.yaml
index c6774a5..c6774a5 100644
--- a/data/items/empty_jug.yaml
+++ b/data/items/tools/empty_jug.yaml
diff --git a/data/items/empty_pot.yaml b/data/items/tools/empty_pot.yaml
index a57b441..a57b441 100644
--- a/data/items/empty_pot.yaml
+++ b/data/items/tools/empty_pot.yaml
diff --git a/data/items/firesteel.yaml b/data/items/tools/firesteel.yaml
index b3f9302..b3f9302 100644
--- a/data/items/firesteel.yaml
+++ b/data/items/tools/firesteel.yaml
diff --git a/data/items/fishing_bait.yaml b/data/items/tools/fishing_bait.yaml
index 2336a7d..2336a7d 100644
--- a/data/items/fishing_bait.yaml
+++ b/data/items/tools/fishing_bait.yaml
diff --git a/data/items/fishing_rod.yaml b/data/items/tools/fishing_rod.yaml
index a48c0a1..a48c0a1 100644
--- a/data/items/fishing_rod.yaml
+++ b/data/items/tools/fishing_rod.yaml
diff --git a/data/items/fly_fishing_rod.yaml b/data/items/tools/fly_fishing_rod.yaml
index c0f3f78..c0f3f78 100644
--- a/data/items/fly_fishing_rod.yaml
+++ b/data/items/tools/fly_fishing_rod.yaml
diff --git a/data/items/hammer.yaml b/data/items/tools/hammer.yaml
index 74302a4..74302a4 100644
--- a/data/items/hammer.yaml
+++ b/data/items/tools/hammer.yaml
diff --git a/data/items/harpoon.yaml b/data/items/tools/harpoon.yaml
index 1412d5c..1412d5c 100644
--- a/data/items/harpoon.yaml
+++ b/data/items/tools/harpoon.yaml
diff --git a/data/items/iron_axe.yaml b/data/items/tools/iron_axe.yaml
index 3f989ca..3f989ca 100644
--- a/data/items/iron_axe.yaml
+++ b/data/items/tools/iron_axe.yaml
diff --git a/data/items/iron_pickaxe.yaml b/data/items/tools/iron_pickaxe.yaml
index d120fde..d120fde 100644
--- a/data/items/iron_pickaxe.yaml
+++ b/data/items/tools/iron_pickaxe.yaml
diff --git a/data/items/knife.yaml b/data/items/tools/knife.yaml
index fddf120..fddf120 100644
--- a/data/items/knife.yaml
+++ b/data/items/tools/knife.yaml
diff --git a/data/items/lighter.yaml b/data/items/tools/lighter.yaml
index 567f42e..567f42e 100644
--- a/data/items/lighter.yaml
+++ b/data/items/tools/lighter.yaml
diff --git a/data/items/lobster_pot.yaml b/data/items/tools/lobster_pot.yaml
index 79771c1..79771c1 100644
--- a/data/items/lobster_pot.yaml
+++ b/data/items/tools/lobster_pot.yaml
diff --git a/data/items/matches.yaml b/data/items/tools/matches.yaml
index d816cbc..d816cbc 100644
--- a/data/items/matches.yaml
+++ b/data/items/tools/matches.yaml
diff --git a/data/items/mithril_axe.yaml b/data/items/tools/mithril_axe.yaml
index ca18195..ca18195 100644
--- a/data/items/mithril_axe.yaml
+++ b/data/items/tools/mithril_axe.yaml
diff --git a/data/items/mithril_pickaxe.yaml b/data/items/tools/mithril_pickaxe.yaml
index 5da6937..5da6937 100644
--- a/data/items/mithril_pickaxe.yaml
+++ b/data/items/tools/mithril_pickaxe.yaml
diff --git a/data/items/necklace_mould.yaml b/data/items/tools/necklace_mould.yaml
index bc4df45..bc4df45 100644
--- a/data/items/necklace_mould.yaml
+++ b/data/items/tools/necklace_mould.yaml
diff --git a/data/items/needle.yaml b/data/items/tools/needle.yaml
index 5f53bf3..5f53bf3 100644
--- a/data/items/needle.yaml
+++ b/data/items/tools/needle.yaml
diff --git a/data/items/plant_pot.yaml b/data/items/tools/plant_pot.yaml
index 2f81e08..2f81e08 100644
--- a/data/items/plant_pot.yaml
+++ b/data/items/tools/plant_pot.yaml
diff --git a/data/items/rake.yaml b/data/items/tools/rake.yaml
index 5949626..5949626 100644
--- a/data/items/rake.yaml
+++ b/data/items/tools/rake.yaml
diff --git a/data/items/ring_mould.yaml b/data/items/tools/ring_mould.yaml
index d7846c8..d7846c8 100644
--- a/data/items/ring_mould.yaml
+++ b/data/items/tools/ring_mould.yaml
diff --git a/data/items/rune_axe.yaml b/data/items/tools/rune_axe.yaml
index a694bdf..a694bdf 100644
--- a/data/items/rune_axe.yaml
+++ b/data/items/tools/rune_axe.yaml
diff --git a/data/items/rune_pickaxe.yaml b/data/items/tools/rune_pickaxe.yaml
index 265231d..265231d 100644
--- a/data/items/rune_pickaxe.yaml
+++ b/data/items/tools/rune_pickaxe.yaml
diff --git a/data/items/saw.yaml b/data/items/tools/saw.yaml
index 4a7ad6a..4a7ad6a 100644
--- a/data/items/saw.yaml
+++ b/data/items/tools/saw.yaml
diff --git a/data/items/shears.yaml b/data/items/tools/shears.yaml
index 4166be3..4166be3 100644
--- a/data/items/shears.yaml
+++ b/data/items/tools/shears.yaml
diff --git a/data/items/small_fishing_net.yaml b/data/items/tools/small_fishing_net.yaml
index 9e91f8f..9e91f8f 100644
--- a/data/items/small_fishing_net.yaml
+++ b/data/items/tools/small_fishing_net.yaml
diff --git a/data/items/spade.yaml b/data/items/tools/spade.yaml
index 7d0b465..7d0b465 100644
--- a/data/items/spade.yaml
+++ b/data/items/tools/spade.yaml
diff --git a/data/items/steel_axe.yaml b/data/items/tools/steel_axe.yaml
index 6f2ac99..6f2ac99 100644
--- a/data/items/steel_axe.yaml
+++ b/data/items/tools/steel_axe.yaml
diff --git a/data/items/steel_pickaxe.yaml b/data/items/tools/steel_pickaxe.yaml
index cc048a1..cc048a1 100644
--- a/data/items/steel_pickaxe.yaml
+++ b/data/items/tools/steel_pickaxe.yaml
diff --git a/data/items/unfired_pie_dish.yaml b/data/items/tools/unfired_pie_dish.yaml
index fddc4d1..fddc4d1 100644
--- a/data/items/unfired_pie_dish.yaml
+++ b/data/items/tools/unfired_pie_dish.yaml
diff --git a/data/items/unfired_plant_pot.yaml b/data/items/tools/unfired_plant_pot.yaml
index c088242..c088242 100644
--- a/data/items/unfired_plant_pot.yaml
+++ b/data/items/tools/unfired_plant_pot.yaml
diff --git a/data/items/unfired_pot.yaml b/data/items/tools/unfired_pot.yaml
index 265a19f..265a19f 100644
--- a/data/items/unfired_pot.yaml
+++ b/data/items/tools/unfired_pot.yaml
diff --git a/data/items/watering_can.yaml b/data/items/tools/watering_can.yaml
index 4a75de9..4a75de9 100644
--- a/data/items/watering_can.yaml
+++ b/data/items/tools/watering_can.yaml
diff --git a/data/items/torstol_potion_unf.yaml b/data/items/torstol_potion_unf.yaml
deleted file mode 100644
index f5816d2..0000000
--- a/data/items/torstol_potion_unf.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: torstol_potion_unf
-name: "torstol potion (unf)"
-color: "48"
-description: "An unfinished potion with torstol floating in water."
-value: 102
-stackable: false
-made_from:
- - items: [torstol]
- qty: 1
- - items: [vial_of_water]
- qty: 1
-ticks: 0
diff --git a/data/mobs/estate_broker.yaml b/data/mobs/estate_broker.yaml
index 6698730..a8b3ed7 100644
--- a/data/mobs/estate_broker.yaml
+++ b/data/mobs/estate_broker.yaml
@@ -20,8 +20,8 @@ talk:
set_player_flags:
owns_house_local_neighborhood: local_neighborhood
condition:
- not:
- player_flag: owns_house_local_neighborhood
+ not: true
+ player_flag: owns_house_local_neighborhood
next: purchased
text: Sounds great! I'll buy one.
- next: goodbye
@@ -39,8 +39,8 @@ talk:
set_player_flags:
owns_house_local_neighborhood: local_neighborhood
condition:
- not:
- player_flag: owns_house_local_neighborhood
+ not: true
+ player_flag: owns_house_local_neighborhood
next: purchased
text: Yes, I'd like to buy a house.
- condition:
diff --git a/data/objects/anvil.yaml b/data/objects/anvil.yaml
index 22725c1..ab5a79f 100644
--- a/data/objects/anvil.yaml
+++ b/data/objects/anvil.yaml
@@ -3,7 +3,7 @@ name: anvil
color: "250"
description: "A heavy iron anvil for hammering metal into shape."
use_interactions:
- - item: silver_bar
+ - item_id: silver_bar
message: "You should use this with a mold at a furnace."
- - item: gold_bar
+ - item_id: gold_bar
message: "You should use this with a mold at a furnace."
diff --git a/data/recipes/clean/clean_avantoe.yaml b/data/recipes/clean/clean_avantoe.yaml
new file mode 100644
index 0000000..61f8f87
--- /dev/null
+++ b/data/recipes/clean/clean_avantoe.yaml
@@ -0,0 +1,11 @@
+id: clean_avantoe
+type: clean
+skill: pharmacy
+level: 48
+xp: 10
+wait: 2
+consume:
+ - items: [grimy_avantoe]
+ quantity: 1
+output: avantoe
+message: "You clean the grimy avantoe leaf."
diff --git a/data/recipes/clean/clean_cadantine.yaml b/data/recipes/clean/clean_cadantine.yaml
new file mode 100644
index 0000000..a013b5d
--- /dev/null
+++ b/data/recipes/clean/clean_cadantine.yaml
@@ -0,0 +1,11 @@
+id: clean_cadantine
+type: clean
+skill: pharmacy
+level: 65
+xp: 13
+wait: 2
+consume:
+ - items: [grimy_cadantine]
+ quantity: 1
+output: cadantine
+message: "You clean the grimy cadantine leaf."
diff --git a/data/recipes/clean/clean_dwarf_weed.yaml b/data/recipes/clean/clean_dwarf_weed.yaml
new file mode 100644
index 0000000..4ea9bb7
--- /dev/null
+++ b/data/recipes/clean/clean_dwarf_weed.yaml
@@ -0,0 +1,11 @@
+id: clean_dwarf_weed
+type: clean
+skill: pharmacy
+level: 70
+xp: 14
+wait: 2
+consume:
+ - items: [grimy_dwarf_weed]
+ quantity: 1
+output: dwarf_weed
+message: "You clean the grimy dwarf weed leaf."
diff --git a/data/recipes/clean/clean_guam.yaml b/data/recipes/clean/clean_guam.yaml
new file mode 100644
index 0000000..d8bb492
--- /dev/null
+++ b/data/recipes/clean/clean_guam.yaml
@@ -0,0 +1,11 @@
+id: clean_guam
+type: clean
+skill: pharmacy
+level: 3
+xp: 3
+wait: 2
+consume:
+ - items: [grimy_guam]
+ quantity: 1
+output: guam
+message: "You clean the grimy guam leaf."
diff --git a/data/recipes/clean/clean_harralander.yaml b/data/recipes/clean/clean_harralander.yaml
new file mode 100644
index 0000000..15d9d22
--- /dev/null
+++ b/data/recipes/clean/clean_harralander.yaml
@@ -0,0 +1,11 @@
+id: clean_harralander
+type: clean
+skill: pharmacy
+level: 20
+xp: 6
+wait: 2
+consume:
+ - items: [grimy_harralander]
+ quantity: 1
+output: harralander
+message: "You clean the grimy harralander leaf."
diff --git a/data/recipes/clean/clean_irit.yaml b/data/recipes/clean/clean_irit.yaml
new file mode 100644
index 0000000..f75603b
--- /dev/null
+++ b/data/recipes/clean/clean_irit.yaml
@@ -0,0 +1,11 @@
+id: clean_irit
+type: clean
+skill: pharmacy
+level: 40
+xp: 9
+wait: 2
+consume:
+ - items: [grimy_irit]
+ quantity: 1
+output: irit
+message: "You clean the grimy irit leaf."
diff --git a/data/recipes/clean/clean_kwuarm.yaml b/data/recipes/clean/clean_kwuarm.yaml
new file mode 100644
index 0000000..1a11a84
--- /dev/null
+++ b/data/recipes/clean/clean_kwuarm.yaml
@@ -0,0 +1,11 @@
+id: clean_kwuarm
+type: clean
+skill: pharmacy
+level: 54
+xp: 11
+wait: 2
+consume:
+ - items: [grimy_kwuarm]
+ quantity: 1
+output: kwuarm
+message: "You clean the grimy kwuarm leaf."
diff --git a/data/recipes/clean/clean_lantadyme.yaml b/data/recipes/clean/clean_lantadyme.yaml
new file mode 100644
index 0000000..076f20d
--- /dev/null
+++ b/data/recipes/clean/clean_lantadyme.yaml
@@ -0,0 +1,11 @@
+id: clean_lantadyme
+type: clean
+skill: pharmacy
+level: 67
+xp: 13
+wait: 2
+consume:
+ - items: [grimy_lantadyme]
+ quantity: 1
+output: lantadyme
+message: "You clean the grimy lantadyme leaf."
diff --git a/data/recipes/clean/clean_marrentill.yaml b/data/recipes/clean/clean_marrentill.yaml
new file mode 100644
index 0000000..f1565dd
--- /dev/null
+++ b/data/recipes/clean/clean_marrentill.yaml
@@ -0,0 +1,11 @@
+id: clean_marrentill
+type: clean
+skill: pharmacy
+level: 5
+xp: 4
+wait: 2
+consume:
+ - items: [grimy_marrentill]
+ quantity: 1
+output: marrentill
+message: "You clean the grimy marrentill leaf."
diff --git a/data/recipes/clean/clean_ranarr.yaml b/data/recipes/clean/clean_ranarr.yaml
new file mode 100644
index 0000000..2e639d8
--- /dev/null
+++ b/data/recipes/clean/clean_ranarr.yaml
@@ -0,0 +1,11 @@
+id: clean_ranarr
+type: clean
+skill: pharmacy
+level: 25
+xp: 8
+wait: 2
+consume:
+ - items: [grimy_ranarr]
+ quantity: 1
+output: ranarr
+message: "You clean the grimy ranarr leaf."
diff --git a/data/recipes/clean/clean_snapdragon.yaml b/data/recipes/clean/clean_snapdragon.yaml
new file mode 100644
index 0000000..995ac8f
--- /dev/null
+++ b/data/recipes/clean/clean_snapdragon.yaml
@@ -0,0 +1,11 @@
+id: clean_snapdragon
+type: clean
+skill: pharmacy
+level: 59
+xp: 12
+wait: 2
+consume:
+ - items: [grimy_snapdragon]
+ quantity: 1
+output: snapdragon
+message: "You clean the grimy snapdragon leaf."
diff --git a/data/recipes/clean/clean_tarromin.yaml b/data/recipes/clean/clean_tarromin.yaml
new file mode 100644
index 0000000..1ad55e1
--- /dev/null
+++ b/data/recipes/clean/clean_tarromin.yaml
@@ -0,0 +1,11 @@
+id: clean_tarromin
+type: clean
+skill: pharmacy
+level: 11
+xp: 5
+wait: 2
+consume:
+ - items: [grimy_tarromin]
+ quantity: 1
+output: tarromin
+message: "You clean the grimy tarromin leaf."
diff --git a/data/recipes/clean/clean_toadflax.yaml b/data/recipes/clean/clean_toadflax.yaml
new file mode 100644
index 0000000..11aafca
--- /dev/null
+++ b/data/recipes/clean/clean_toadflax.yaml
@@ -0,0 +1,11 @@
+id: clean_toadflax
+type: clean
+skill: pharmacy
+level: 30
+xp: 8
+wait: 2
+consume:
+ - items: [grimy_toadflax]
+ quantity: 1
+output: toadflax
+message: "You clean the grimy toadflax leaf."
diff --git a/data/recipes/clean/clean_torstol.yaml b/data/recipes/clean/clean_torstol.yaml
new file mode 100644
index 0000000..c9bac28
--- /dev/null
+++ b/data/recipes/clean/clean_torstol.yaml
@@ -0,0 +1,11 @@
+id: clean_torstol
+type: clean
+skill: pharmacy
+level: 75
+xp: 15
+wait: 2
+consume:
+ - items: [grimy_torstol]
+ quantity: 1
+output: torstol
+message: "You clean the grimy torstol leaf."
diff --git a/data/recipes/clean_avantoe.yaml b/data/recipes/clean_avantoe.yaml
deleted file mode 100644
index 6903718..0000000
--- a/data/recipes/clean_avantoe.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: clean_avantoe
-type: clean
-skill: pharmacy
-level: 48
-xp: 10
-wait: 2
-consume:
- - items: [grimy_avantoe]
- qty: 1
-output: avantoe
-message: "You clean the grimy avantoe leaf."
diff --git a/data/recipes/clean_cadantine.yaml b/data/recipes/clean_cadantine.yaml
deleted file mode 100644
index 0a84f1f..0000000
--- a/data/recipes/clean_cadantine.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: clean_cadantine
-type: clean
-skill: pharmacy
-level: 65
-xp: 13
-wait: 2
-consume:
- - items: [grimy_cadantine]
- qty: 1
-output: cadantine
-message: "You clean the grimy cadantine leaf."
diff --git a/data/recipes/clean_dwarf_weed.yaml b/data/recipes/clean_dwarf_weed.yaml
deleted file mode 100644
index 4843036..0000000
--- a/data/recipes/clean_dwarf_weed.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: clean_dwarf_weed
-type: clean
-skill: pharmacy
-level: 70
-xp: 14
-wait: 2
-consume:
- - items: [grimy_dwarf_weed]
- qty: 1
-output: dwarf_weed
-message: "You clean the grimy dwarf weed leaf."
diff --git a/data/recipes/clean_guam.yaml b/data/recipes/clean_guam.yaml
deleted file mode 100644
index faa1e0f..0000000
--- a/data/recipes/clean_guam.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: clean_guam
-type: clean
-skill: pharmacy
-level: 3
-xp: 3
-wait: 2
-consume:
- - items: [grimy_guam]
- qty: 1
-output: guam
-message: "You clean the grimy guam leaf."
diff --git a/data/recipes/clean_harralander.yaml b/data/recipes/clean_harralander.yaml
deleted file mode 100644
index 42aca32..0000000
--- a/data/recipes/clean_harralander.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: clean_harralander
-type: clean
-skill: pharmacy
-level: 20
-xp: 6
-wait: 2
-consume:
- - items: [grimy_harralander]
- qty: 1
-output: harralander
-message: "You clean the grimy harralander leaf."
diff --git a/data/recipes/clean_irit.yaml b/data/recipes/clean_irit.yaml
deleted file mode 100644
index 1524c20..0000000
--- a/data/recipes/clean_irit.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: clean_irit
-type: clean
-skill: pharmacy
-level: 40
-xp: 9
-wait: 2
-consume:
- - items: [grimy_irit]
- qty: 1
-output: irit
-message: "You clean the grimy irit leaf."
diff --git a/data/recipes/clean_kwuarm.yaml b/data/recipes/clean_kwuarm.yaml
deleted file mode 100644
index 178dec4..0000000
--- a/data/recipes/clean_kwuarm.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: clean_kwuarm
-type: clean
-skill: pharmacy
-level: 54
-xp: 11
-wait: 2
-consume:
- - items: [grimy_kwuarm]
- qty: 1
-output: kwuarm
-message: "You clean the grimy kwuarm leaf."
diff --git a/data/recipes/clean_lantadyme.yaml b/data/recipes/clean_lantadyme.yaml
deleted file mode 100644
index 05c4f98..0000000
--- a/data/recipes/clean_lantadyme.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: clean_lantadyme
-type: clean
-skill: pharmacy
-level: 67
-xp: 13
-wait: 2
-consume:
- - items: [grimy_lantadyme]
- qty: 1
-output: lantadyme
-message: "You clean the grimy lantadyme leaf."
diff --git a/data/recipes/clean_marrentill.yaml b/data/recipes/clean_marrentill.yaml
deleted file mode 100644
index 55959c5..0000000
--- a/data/recipes/clean_marrentill.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: clean_marrentill
-type: clean
-skill: pharmacy
-level: 5
-xp: 4
-wait: 2
-consume:
- - items: [grimy_marrentill]
- qty: 1
-output: marrentill
-message: "You clean the grimy marrentill leaf."
diff --git a/data/recipes/clean_ranarr.yaml b/data/recipes/clean_ranarr.yaml
deleted file mode 100644
index 712c88b..0000000
--- a/data/recipes/clean_ranarr.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: clean_ranarr
-type: clean
-skill: pharmacy
-level: 25
-xp: 8
-wait: 2
-consume:
- - items: [grimy_ranarr]
- qty: 1
-output: ranarr
-message: "You clean the grimy ranarr leaf."
diff --git a/data/recipes/clean_snapdragon.yaml b/data/recipes/clean_snapdragon.yaml
deleted file mode 100644
index d90c775..0000000
--- a/data/recipes/clean_snapdragon.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: clean_snapdragon
-type: clean
-skill: pharmacy
-level: 59
-xp: 12
-wait: 2
-consume:
- - items: [grimy_snapdragon]
- qty: 1
-output: snapdragon
-message: "You clean the grimy snapdragon leaf."
diff --git a/data/recipes/clean_tarromin.yaml b/data/recipes/clean_tarromin.yaml
deleted file mode 100644
index 1752b7c..0000000
--- a/data/recipes/clean_tarromin.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: clean_tarromin
-type: clean
-skill: pharmacy
-level: 11
-xp: 5
-wait: 2
-consume:
- - items: [grimy_tarromin]
- qty: 1
-output: tarromin
-message: "You clean the grimy tarromin leaf."
diff --git a/data/recipes/clean_toadflax.yaml b/data/recipes/clean_toadflax.yaml
deleted file mode 100644
index 4124576..0000000
--- a/data/recipes/clean_toadflax.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: clean_toadflax
-type: clean
-skill: pharmacy
-level: 30
-xp: 8
-wait: 2
-consume:
- - items: [grimy_toadflax]
- qty: 1
-output: toadflax
-message: "You clean the grimy toadflax leaf."
diff --git a/data/recipes/clean_torstol.yaml b/data/recipes/clean_torstol.yaml
deleted file mode 100644
index 7ce0017..0000000
--- a/data/recipes/clean_torstol.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: clean_torstol
-type: clean
-skill: pharmacy
-level: 75
-xp: 15
-wait: 2
-consume:
- - items: [grimy_torstol]
- qty: 1
-output: torstol
-message: "You clean the grimy torstol leaf."
diff --git a/data/recipes/construct_mahogany_planks.yaml b/data/recipes/construct_mahogany_planks.yaml
deleted file mode 100644
index 07e0f40..0000000
--- a/data/recipes/construct_mahogany_planks.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: construct_mahogany_planks
-type: construction
-level: 50
-xp: 30
-wait: 20
-station: [workbench]
-tool: saw
-consume:
- - items: [mahogany_logs]
- qty: 1
-output: mahogany_planks
-message: "You saw the mahogany logs into luxurious mahogany planks."
diff --git a/data/recipes/construct_mahogany_table.yaml b/data/recipes/construct_mahogany_table.yaml
deleted file mode 100644
index 252736f..0000000
--- a/data/recipes/construct_mahogany_table.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: construct_mahogany_table
-type: construction
-level: 52
-xp: 120
-wait: 6
-station: [workbench]
-consume:
- - items: [mahogany_planks]
- qty: 3
-output: mahogany_table
-message: "You build a luxurious mahogany table."
diff --git a/data/recipes/construct_oak_planks.yaml b/data/recipes/construct_oak_planks.yaml
deleted file mode 100644
index 5858208..0000000
--- a/data/recipes/construct_oak_planks.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: construct_oak_planks
-type: construction
-level: 15
-xp: 10
-wait: 20
-station: [workbench]
-tool: saw
-consume:
- - items: [oak_logs]
- qty: 1
-output: oak_planks
-message: "You saw the oak logs into sturdy oak planks."
diff --git a/data/recipes/construct_oak_shelf.yaml b/data/recipes/construct_oak_shelf.yaml
deleted file mode 100644
index 8661679..0000000
--- a/data/recipes/construct_oak_shelf.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: construct_oak_shelf
-type: construction
-level: 18
-xp: 40
-wait: 6
-station: [workbench]
-consume:
- - items: [oak_planks]
- qty: 2
-output: oak_shelf
-message: "You assemble the oak planks into a sturdy shelf."
diff --git a/data/recipes/construct_oak_table.yaml b/data/recipes/construct_oak_table.yaml
deleted file mode 100644
index e8eff24..0000000
--- a/data/recipes/construct_oak_table.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: construct_oak_table
-type: construction
-level: 20
-xp: 50
-wait: 6
-station: [workbench]
-consume:
- - items: [oak_planks]
- qty: 3
-output: oak_table
-message: "You build a fine oak table."
diff --git a/data/recipes/construct_planks.yaml b/data/recipes/construct_planks.yaml
deleted file mode 100644
index 4a2a363..0000000
--- a/data/recipes/construct_planks.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: construct_planks
-type: construction
-level: 1
-xp: 5
-wait: 20
-station: [workbench]
-tool: saw
-consume:
- - items: [logs]
- qty: 1
-output: planks
-message: "You saw the logs into some regular planks."
diff --git a/data/recipes/construct_teak_planks.yaml b/data/recipes/construct_teak_planks.yaml
deleted file mode 100644
index 022ff7f..0000000
--- a/data/recipes/construct_teak_planks.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: construct_teak_planks
-type: construction
-level: 35
-xp: 20
-wait: 20
-station: [workbench]
-tool: saw
-consume:
- - items: [teak_logs]
- qty: 1
-output: teak_planks
-message: "You saw the teak logs into fine teak planks."
diff --git a/data/recipes/construct_teak_table.yaml b/data/recipes/construct_teak_table.yaml
deleted file mode 100644
index db926b8..0000000
--- a/data/recipes/construct_teak_table.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: construct_teak_table
-type: construction
-level: 38
-xp: 80
-wait: 6
-station: [workbench]
-consume:
- - items: [teak_planks]
- qty: 3
-output: teak_table
-message: "You build an elegant teak table."
diff --git a/data/recipes/construct_wooden_chair.yaml b/data/recipes/construct_wooden_chair.yaml
deleted file mode 100644
index cad6200..0000000
--- a/data/recipes/construct_wooden_chair.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: construct_wooden_chair
-type: construction
-level: 8
-xp: 30
-wait: 6
-station: [workbench]
-consume:
- - items: [planks]
- qty: 2
-output: wooden_chair
-message: "You craft a comfortable wooden chair."
diff --git a/data/recipes/construct_wooden_shelf.yaml b/data/recipes/construct_wooden_shelf.yaml
deleted file mode 100644
index f09a199..0000000
--- a/data/recipes/construct_wooden_shelf.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: construct_wooden_shelf
-type: construction
-level: 4
-xp: 20
-wait: 6
-station: [workbench]
-consume:
- - items: [planks]
- qty: 2
-output: wooden_shelf
-message: "You assemble the planks into a wooden shelf."
diff --git a/data/recipes/construct_wooden_table.yaml b/data/recipes/construct_wooden_table.yaml
deleted file mode 100644
index 8451f31..0000000
--- a/data/recipes/construct_wooden_table.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: construct_wooden_table
-type: construction
-level: 6
-xp: 25
-wait: 6
-station: [workbench]
-consume:
- - items: [planks]
- qty: 3
-output: wooden_table
-message: "You build a sturdy wooden table."
diff --git a/data/recipes/construction/construct_mahogany_planks.yaml b/data/recipes/construction/construct_mahogany_planks.yaml
new file mode 100644
index 0000000..6634f78
--- /dev/null
+++ b/data/recipes/construction/construct_mahogany_planks.yaml
@@ -0,0 +1,12 @@
+id: construct_mahogany_planks
+type: construction
+level: 50
+xp: 30
+wait: 20
+station: [workbench]
+tool: saw
+consume:
+ - items: [mahogany_logs]
+ quantity: 1
+output: mahogany_planks
+message: "You saw the mahogany logs into luxurious mahogany planks."
diff --git a/data/recipes/construction/construct_mahogany_table.yaml b/data/recipes/construction/construct_mahogany_table.yaml
new file mode 100644
index 0000000..89d08e4
--- /dev/null
+++ b/data/recipes/construction/construct_mahogany_table.yaml
@@ -0,0 +1,11 @@
+id: construct_mahogany_table
+type: construction
+level: 52
+xp: 120
+wait: 6
+station: [workbench]
+consume:
+ - items: [mahogany_planks]
+ quantity: 3
+output: mahogany_table
+message: "You build a luxurious mahogany table."
diff --git a/data/recipes/construction/construct_oak_planks.yaml b/data/recipes/construction/construct_oak_planks.yaml
new file mode 100644
index 0000000..77c2fcb
--- /dev/null
+++ b/data/recipes/construction/construct_oak_planks.yaml
@@ -0,0 +1,12 @@
+id: construct_oak_planks
+type: construction
+level: 15
+xp: 10
+wait: 20
+station: [workbench]
+tool: saw
+consume:
+ - items: [oak_logs]
+ quantity: 1
+output: oak_planks
+message: "You saw the oak logs into sturdy oak planks."
diff --git a/data/recipes/construction/construct_oak_shelf.yaml b/data/recipes/construction/construct_oak_shelf.yaml
new file mode 100644
index 0000000..042e64f
--- /dev/null
+++ b/data/recipes/construction/construct_oak_shelf.yaml
@@ -0,0 +1,11 @@
+id: construct_oak_shelf
+type: construction
+level: 18
+xp: 40
+wait: 6
+station: [workbench]
+consume:
+ - items: [oak_planks]
+ quantity: 2
+output: oak_shelf
+message: "You assemble the oak planks into a sturdy shelf."
diff --git a/data/recipes/construction/construct_oak_table.yaml b/data/recipes/construction/construct_oak_table.yaml
new file mode 100644
index 0000000..8eafe15
--- /dev/null
+++ b/data/recipes/construction/construct_oak_table.yaml
@@ -0,0 +1,11 @@
+id: construct_oak_table
+type: construction
+level: 20
+xp: 50
+wait: 6
+station: [workbench]
+consume:
+ - items: [oak_planks]
+ quantity: 3
+output: oak_table
+message: "You build a fine oak table."
diff --git a/data/recipes/construction/construct_planks.yaml b/data/recipes/construction/construct_planks.yaml
new file mode 100644
index 0000000..3ed8058
--- /dev/null
+++ b/data/recipes/construction/construct_planks.yaml
@@ -0,0 +1,12 @@
+id: construct_planks
+type: construction
+level: 1
+xp: 5
+wait: 20
+station: [workbench]
+tool: saw
+consume:
+ - items: [logs]
+ quantity: 1
+output: planks
+message: "You saw the logs into some regular planks."
diff --git a/data/recipes/construction/construct_teak_planks.yaml b/data/recipes/construction/construct_teak_planks.yaml
new file mode 100644
index 0000000..aec0888
--- /dev/null
+++ b/data/recipes/construction/construct_teak_planks.yaml
@@ -0,0 +1,12 @@
+id: construct_teak_planks
+type: construction
+level: 35
+xp: 20
+wait: 20
+station: [workbench]
+tool: saw
+consume:
+ - items: [teak_logs]
+ quantity: 1
+output: teak_planks
+message: "You saw the teak logs into fine teak planks."
diff --git a/data/recipes/construction/construct_teak_table.yaml b/data/recipes/construction/construct_teak_table.yaml
new file mode 100644
index 0000000..88d4a2a
--- /dev/null
+++ b/data/recipes/construction/construct_teak_table.yaml
@@ -0,0 +1,11 @@
+id: construct_teak_table
+type: construction
+level: 38
+xp: 80
+wait: 6
+station: [workbench]
+consume:
+ - items: [teak_planks]
+ quantity: 3
+output: teak_table
+message: "You build an elegant teak table."
diff --git a/data/recipes/construction/construct_wooden_chair.yaml b/data/recipes/construction/construct_wooden_chair.yaml
new file mode 100644
index 0000000..3a9120d
--- /dev/null
+++ b/data/recipes/construction/construct_wooden_chair.yaml
@@ -0,0 +1,11 @@
+id: construct_wooden_chair
+type: construction
+level: 8
+xp: 30
+wait: 6
+station: [workbench]
+consume:
+ - items: [planks]
+ quantity: 2
+output: wooden_chair
+message: "You craft a comfortable wooden chair."
diff --git a/data/recipes/construction/construct_wooden_shelf.yaml b/data/recipes/construction/construct_wooden_shelf.yaml
new file mode 100644
index 0000000..c8ea836
--- /dev/null
+++ b/data/recipes/construction/construct_wooden_shelf.yaml
@@ -0,0 +1,11 @@
+id: construct_wooden_shelf
+type: construction
+level: 4
+xp: 20
+wait: 6
+station: [workbench]
+consume:
+ - items: [planks]
+ quantity: 2
+output: wooden_shelf
+message: "You assemble the planks into a wooden shelf."
diff --git a/data/recipes/construction/construct_wooden_table.yaml b/data/recipes/construction/construct_wooden_table.yaml
new file mode 100644
index 0000000..21d6c53
--- /dev/null
+++ b/data/recipes/construction/construct_wooden_table.yaml
@@ -0,0 +1,11 @@
+id: construct_wooden_table
+type: construction
+level: 6
+xp: 25
+wait: 6
+station: [workbench]
+consume:
+ - items: [planks]
+ quantity: 3
+output: wooden_table
+message: "You build a sturdy wooden table."
diff --git a/data/recipes/cook_anchovies.yaml b/data/recipes/cook_anchovies.yaml
deleted file mode 100644
index 07d3f62..0000000
--- a/data/recipes/cook_anchovies.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: cook_anchovies
-type: cooking
-level: 1
-xp: 30
-wait: 6
-station: [fire, cooking_range]
-consume:
- - items: [raw_anchovies]
- qty: 1
-output: anchovies
-fail: burnt_fish
-message: "Cooked to perfection. It looks great!"
-fail_message: "You accidentally burn the anchovies."
diff --git a/data/recipes/cook_bread.yaml b/data/recipes/cook_bread.yaml
deleted file mode 100644
index c76cedc..0000000
--- a/data/recipes/cook_bread.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: cook_bread
-type: cooking
-level: 1
-xp: 40
-wait: 6
-station: [cooking_range]
-consume:
- - items: [bread_dough]
- qty: 1
-output: bread
-fail: burnt_meat
-message: "You bake the dough into a fresh loaf of bread."
-fail_message: "You burn the bread to a crisp."
diff --git a/data/recipes/cook_herring.yaml b/data/recipes/cook_herring.yaml
deleted file mode 100644
index 05d10eb..0000000
--- a/data/recipes/cook_herring.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: cook_herring
-type: cooking
-level: 5
-xp: 50
-wait: 6
-station: [fire, cooking_range]
-consume:
- - items: [raw_herring]
- qty: 1
-output: herring
-fail: burnt_fish
-message: "Cooked to perfection. It looks great!"
-fail_message: "You accidentally burn the herring."
diff --git a/data/recipes/cook_salmon.yaml b/data/recipes/cook_salmon.yaml
deleted file mode 100644
index ab086fb..0000000
--- a/data/recipes/cook_salmon.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: cook_salmon
-type: cooking
-level: 25
-xp: 90
-wait: 6
-station: [fire, cooking_range]
-consume:
- - items: [raw_salmon]
- qty: 1
-output: salmon
-fail: burnt_fish
-message: "Cooked to perfection. It looks great!"
-fail_message: "You accidentally burn the salmon."
diff --git a/data/recipes/cook_sardine.yaml b/data/recipes/cook_sardine.yaml
deleted file mode 100644
index f7e8389..0000000
--- a/data/recipes/cook_sardine.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: cook_sardine
-type: cooking
-level: 1
-xp: 40
-wait: 6
-station: [fire, cooking_range]
-consume:
- - items: [raw_sardine]
- qty: 1
-output: sardine
-fail: burnt_fish
-message: "Cooked to perfection. It looks great!"
-fail_message: "You accidentally burn the sardine."
diff --git a/data/recipes/cook_shrimps.yaml b/data/recipes/cook_shrimps.yaml
deleted file mode 100644
index 8755b68..0000000
--- a/data/recipes/cook_shrimps.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: cook_shrimps
-type: cooking
-level: 1
-xp: 30
-wait: 6
-station: [fire, cooking_range]
-consume:
- - items: [raw_shrimps]
- qty: 1
-output: shrimps
-fail: burnt_fish
-message: "Cooked to perfection. It looks great!"
-fail_message: "You accidentally burn the shrimps."
diff --git a/data/recipes/cook_trout.yaml b/data/recipes/cook_trout.yaml
deleted file mode 100644
index a392e3d..0000000
--- a/data/recipes/cook_trout.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: cook_trout
-type: cooking
-level: 15
-xp: 70
-wait: 6
-station: [fire, cooking_range]
-consume:
- - items: [raw_trout]
- qty: 1
-output: trout
-fail: burnt_fish
-message: "Cooked to perfection. It looks great!"
-fail_message: "You accidentally burn the trout."
diff --git a/data/recipes/cooking/cook_anchovies.yaml b/data/recipes/cooking/cook_anchovies.yaml
new file mode 100644
index 0000000..bd4ea41
--- /dev/null
+++ b/data/recipes/cooking/cook_anchovies.yaml
@@ -0,0 +1,13 @@
+id: cook_anchovies
+type: cooking
+level: 1
+xp: 30
+wait: 6
+station: [fire, cooking_range]
+consume:
+ - items: [raw_anchovies]
+ quantity: 1
+output: anchovies
+fail: burnt_fish
+message: "Cooked to perfection. It looks great!"
+fail_message: "You accidentally burn the anchovies."
diff --git a/data/recipes/cooking/cook_bread.yaml b/data/recipes/cooking/cook_bread.yaml
new file mode 100644
index 0000000..d1c68bf
--- /dev/null
+++ b/data/recipes/cooking/cook_bread.yaml
@@ -0,0 +1,13 @@
+id: cook_bread
+type: cooking
+level: 1
+xp: 40
+wait: 6
+station: [cooking_range]
+consume:
+ - items: [bread_dough]
+ quantity: 1
+output: bread
+fail: burnt_meat
+message: "You bake the dough into a fresh loaf of bread."
+fail_message: "You burn the bread to a crisp."
diff --git a/data/recipes/cooking/cook_herring.yaml b/data/recipes/cooking/cook_herring.yaml
new file mode 100644
index 0000000..3113092
--- /dev/null
+++ b/data/recipes/cooking/cook_herring.yaml
@@ -0,0 +1,13 @@
+id: cook_herring
+type: cooking
+level: 5
+xp: 50
+wait: 6
+station: [fire, cooking_range]
+consume:
+ - items: [raw_herring]
+ quantity: 1
+output: herring
+fail: burnt_fish
+message: "Cooked to perfection. It looks great!"
+fail_message: "You accidentally burn the herring."
diff --git a/data/recipes/cooking/cook_salmon.yaml b/data/recipes/cooking/cook_salmon.yaml
new file mode 100644
index 0000000..32c0fb0
--- /dev/null
+++ b/data/recipes/cooking/cook_salmon.yaml
@@ -0,0 +1,13 @@
+id: cook_salmon
+type: cooking
+level: 25
+xp: 90
+wait: 6
+station: [fire, cooking_range]
+consume:
+ - items: [raw_salmon]
+ quantity: 1
+output: salmon
+fail: burnt_fish
+message: "Cooked to perfection. It looks great!"
+fail_message: "You accidentally burn the salmon."
diff --git a/data/recipes/cooking/cook_sardine.yaml b/data/recipes/cooking/cook_sardine.yaml
new file mode 100644
index 0000000..fcda9b5
--- /dev/null
+++ b/data/recipes/cooking/cook_sardine.yaml
@@ -0,0 +1,13 @@
+id: cook_sardine
+type: cooking
+level: 1
+xp: 40
+wait: 6
+station: [fire, cooking_range]
+consume:
+ - items: [raw_sardine]
+ quantity: 1
+output: sardine
+fail: burnt_fish
+message: "Cooked to perfection. It looks great!"
+fail_message: "You accidentally burn the sardine."
diff --git a/data/recipes/cooking/cook_shrimps.yaml b/data/recipes/cooking/cook_shrimps.yaml
new file mode 100644
index 0000000..feb4506
--- /dev/null
+++ b/data/recipes/cooking/cook_shrimps.yaml
@@ -0,0 +1,13 @@
+id: cook_shrimps
+type: cooking
+level: 1
+xp: 30
+wait: 6
+station: [fire, cooking_range]
+consume:
+ - items: [raw_shrimps]
+ quantity: 1
+output: shrimps
+fail: burnt_fish
+message: "Cooked to perfection. It looks great!"
+fail_message: "You accidentally burn the shrimps."
diff --git a/data/recipes/cooking/cook_trout.yaml b/data/recipes/cooking/cook_trout.yaml
new file mode 100644
index 0000000..cec6cd4
--- /dev/null
+++ b/data/recipes/cooking/cook_trout.yaml
@@ -0,0 +1,13 @@
+id: cook_trout
+type: cooking
+level: 15
+xp: 70
+wait: 6
+station: [fire, cooking_range]
+consume:
+ - items: [raw_trout]
+ quantity: 1
+output: trout
+fail: burnt_fish
+message: "Cooked to perfection. It looks great!"
+fail_message: "You accidentally burn the trout."
diff --git a/data/recipes/craft_ball_of_wool.yaml b/data/recipes/craft_ball_of_wool.yaml
deleted file mode 100644
index 2f8f4f9..0000000
--- a/data/recipes/craft_ball_of_wool.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: craft_ball_of_wool
-type: crafting
-level: 1
-xp: 3
-wait: 3
-station: [spinning_wheel]
-consume:
- - items: [wool]
- qty: 1
-output: ball_of_wool
-message: "You spin the wool into a ball."
diff --git a/data/recipes/craft_bowstring.yaml b/data/recipes/craft_bowstring.yaml
deleted file mode 100644
index 1df6aba..0000000
--- a/data/recipes/craft_bowstring.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: craft_bowstring
-type: crafting
-level: 1
-xp: 15
-wait: 3
-station: [spinning_wheel]
-consume:
- - items: [flax]
- qty: 1
-output: bowstring
-message: "You spin the flax into a bowstring."
diff --git a/data/recipes/craft_coif.yaml b/data/recipes/craft_coif.yaml
deleted file mode 100644
index 49fc4c3..0000000
--- a/data/recipes/craft_coif.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: craft_coif
-type: crafting
-level: 38
-xp: 37
-wait: 4
-tool: needle
-consume:
- - items: [hard_leather]
- qty: 1
- - items: [thread]
- qty: 1
-output: coif
-message: "You craft a coif."
diff --git a/data/recipes/craft_cut_diamond.yaml b/data/recipes/craft_cut_diamond.yaml
deleted file mode 100644
index b0187a8..0000000
--- a/data/recipes/craft_cut_diamond.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: craft_cut_diamond
-type: crafting
-level: 43
-xp: 108
-wait: 3
-tool: chisel
-consume:
- - items: [uncut_diamond]
- qty: 1
-output: diamond
-message: "You cut the diamond into a beautiful gem."
diff --git a/data/recipes/craft_cut_emerald.yaml b/data/recipes/craft_cut_emerald.yaml
deleted file mode 100644
index 69fdd19..0000000
--- a/data/recipes/craft_cut_emerald.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: craft_cut_emerald
-type: crafting
-level: 27
-xp: 68
-wait: 3
-tool: chisel
-consume:
- - items: [uncut_emerald]
- qty: 1
-output: emerald
-message: "You cut the emerald into a beautiful gem."
diff --git a/data/recipes/craft_cut_ruby.yaml b/data/recipes/craft_cut_ruby.yaml
deleted file mode 100644
index 6cc402c..0000000
--- a/data/recipes/craft_cut_ruby.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: craft_cut_ruby
-type: crafting
-level: 63
-xp: 85
-wait: 3
-tool: chisel
-consume:
- - items: [uncut_ruby]
- qty: 1
-output: ruby
-message: "You cut the ruby into a beautiful gem."
diff --git a/data/recipes/craft_cut_sapphire.yaml b/data/recipes/craft_cut_sapphire.yaml
deleted file mode 100644
index 6f3bb65..0000000
--- a/data/recipes/craft_cut_sapphire.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: craft_cut_sapphire
-type: crafting
-level: 20
-xp: 50
-wait: 3
-tool: chisel
-consume:
- - items: [uncut_sapphire]
- qty: 1
-output: sapphire
-message: "You cut the sapphire into a beautiful gem."
diff --git a/data/recipes/craft_diamond_amulet_u.yaml b/data/recipes/craft_diamond_amulet_u.yaml
deleted file mode 100644
index 053c409..0000000
--- a/data/recipes/craft_diamond_amulet_u.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-id: craft_diamond_amulet_u
-type: crafting
-level: 70
-xp: 100
-wait: 4
-station: [furnace]
-consume:
- - items: [gold_bar]
- qty: 1
- - items: [diamond]
- qty: 1
- - items: [amulet_mould]
- qty: 1
- byproducts: [amulet_mould]
-output: diamond_amulet_u
-message: "You cast a diamond amulet."
diff --git a/data/recipes/craft_diamond_bracelet.yaml b/data/recipes/craft_diamond_bracelet.yaml
deleted file mode 100644
index 17ac23e..0000000
--- a/data/recipes/craft_diamond_bracelet.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-id: craft_diamond_bracelet
-type: crafting
-level: 58
-xp: 95
-wait: 4
-station: [furnace]
-consume:
- - items: [gold_bar]
- qty: 1
- - items: [diamond]
- qty: 1
- - items: [bracelet_mould]
- qty: 1
- byproducts: [bracelet_mould]
-output: diamond_bracelet
-message: "You cast a diamond bracelet."
diff --git a/data/recipes/craft_diamond_necklace.yaml b/data/recipes/craft_diamond_necklace.yaml
deleted file mode 100644
index cebbec1..0000000
--- a/data/recipes/craft_diamond_necklace.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-id: craft_diamond_necklace
-type: crafting
-level: 56
-xp: 90
-wait: 4
-station: [furnace]
-consume:
- - items: [gold_bar]
- qty: 1
- - items: [diamond]
- qty: 1
- - items: [necklace_mould]
- qty: 1
- byproducts: [necklace_mould]
-output: diamond_necklace
-message: "You cast a diamond necklace."
diff --git a/data/recipes/craft_diamond_ring.yaml b/data/recipes/craft_diamond_ring.yaml
deleted file mode 100644
index aacce0e..0000000
--- a/data/recipes/craft_diamond_ring.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-id: craft_diamond_ring
-type: crafting
-level: 43
-xp: 85
-wait: 4
-station: [furnace]
-consume:
- - items: [gold_bar]
- qty: 1
- - items: [diamond]
- qty: 1
- - items: [ring_mould]
- qty: 1
- byproducts: [ring_mould]
-output: diamond_ring
-message: "You cast a diamond ring."
diff --git a/data/recipes/craft_emerald_amulet_u.yaml b/data/recipes/craft_emerald_amulet_u.yaml
deleted file mode 100644
index dbf8b8a..0000000
--- a/data/recipes/craft_emerald_amulet_u.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-id: craft_emerald_amulet_u
-type: crafting
-level: 31
-xp: 70
-wait: 4
-station: [furnace]
-consume:
- - items: [gold_bar]
- qty: 1
- - items: [emerald]
- qty: 1
- - items: [amulet_mould]
- qty: 1
- byproducts: [amulet_mould]
-output: emerald_amulet_u
-message: "You cast a emerald amulet."
diff --git a/data/recipes/craft_emerald_bracelet.yaml b/data/recipes/craft_emerald_bracelet.yaml
deleted file mode 100644
index b3dd83e..0000000
--- a/data/recipes/craft_emerald_bracelet.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-id: craft_emerald_bracelet
-type: crafting
-level: 30
-xp: 65
-wait: 4
-station: [furnace]
-consume:
- - items: [gold_bar]
- qty: 1
- - items: [emerald]
- qty: 1
- - items: [bracelet_mould]
- qty: 1
- byproducts: [bracelet_mould]
-output: emerald_bracelet
-message: "You cast a emerald bracelet."
diff --git a/data/recipes/craft_emerald_necklace.yaml b/data/recipes/craft_emerald_necklace.yaml
deleted file mode 100644
index cd0a94b..0000000
--- a/data/recipes/craft_emerald_necklace.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-id: craft_emerald_necklace
-type: crafting
-level: 29
-xp: 60
-wait: 4
-station: [furnace]
-consume:
- - items: [gold_bar]
- qty: 1
- - items: [emerald]
- qty: 1
- - items: [necklace_mould]
- qty: 1
- byproducts: [necklace_mould]
-output: emerald_necklace
-message: "You cast a emerald necklace."
diff --git a/data/recipes/craft_emerald_ring.yaml b/data/recipes/craft_emerald_ring.yaml
deleted file mode 100644
index ebbe473..0000000
--- a/data/recipes/craft_emerald_ring.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-id: craft_emerald_ring
-type: crafting
-level: 27
-xp: 55
-wait: 4
-station: [furnace]
-consume:
- - items: [gold_bar]
- qty: 1
- - items: [emerald]
- qty: 1
- - items: [ring_mould]
- qty: 1
- byproducts: [ring_mould]
-output: emerald_ring
-message: "You cast a emerald ring."
diff --git a/data/recipes/craft_gold_amulet_u.yaml b/data/recipes/craft_gold_amulet_u.yaml
deleted file mode 100644
index 3701320..0000000
--- a/data/recipes/craft_gold_amulet_u.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
-id: craft_gold_amulet_u
-type: crafting
-level: 8
-xp: 30
-wait: 4
-station: [furnace]
-consume:
- - items: [gold_bar]
- qty: 1
- - items: [amulet_mould]
- qty: 1
- byproducts: [amulet_mould]
-output: gold_amulet_u
-message: "You cast a gold amulet."
diff --git a/data/recipes/craft_gold_bracelet.yaml b/data/recipes/craft_gold_bracelet.yaml
deleted file mode 100644
index 630b8de..0000000
--- a/data/recipes/craft_gold_bracelet.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
-id: craft_gold_bracelet
-type: crafting
-level: 7
-xp: 25
-wait: 4
-station: [furnace]
-consume:
- - items: [gold_bar]
- qty: 1
- - items: [bracelet_mould]
- qty: 1
- byproducts: [bracelet_mould]
-output: gold_bracelet
-message: "You cast a gold bracelet."
diff --git a/data/recipes/craft_gold_necklace.yaml b/data/recipes/craft_gold_necklace.yaml
deleted file mode 100644
index 6879338..0000000
--- a/data/recipes/craft_gold_necklace.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
-id: craft_gold_necklace
-type: crafting
-level: 6
-xp: 20
-wait: 4
-station: [furnace]
-consume:
- - items: [gold_bar]
- qty: 1
- - items: [necklace_mould]
- qty: 1
- byproducts: [necklace_mould]
-output: gold_necklace
-message: "You cast a gold necklace."
diff --git a/data/recipes/craft_gold_ring.yaml b/data/recipes/craft_gold_ring.yaml
deleted file mode 100644
index 4e2c13f..0000000
--- a/data/recipes/craft_gold_ring.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
-id: craft_gold_ring
-type: crafting
-level: 5
-xp: 15
-wait: 4
-station: [furnace]
-consume:
- - items: [gold_bar]
- qty: 1
- - items: [ring_mould]
- qty: 1
- byproducts: [ring_mould]
-output: gold_ring
-message: "You cast a gold ring."
diff --git a/data/recipes/craft_hard_leather_body.yaml b/data/recipes/craft_hard_leather_body.yaml
deleted file mode 100644
index 088553e..0000000
--- a/data/recipes/craft_hard_leather_body.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: craft_hard_leather_body
-type: crafting
-level: 28
-xp: 35
-wait: 4
-tool: needle
-consume:
- - items: [hard_leather]
- qty: 1
- - items: [thread]
- qty: 1
-output: hard_leather_body
-message: "You craft a hard leather body."
diff --git a/data/recipes/craft_hard_leather_shield.yaml b/data/recipes/craft_hard_leather_shield.yaml
deleted file mode 100644
index b41e5b4..0000000
--- a/data/recipes/craft_hard_leather_shield.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: craft_hard_leather_shield
-type: crafting
-level: 41
-xp: 40
-wait: 4
-tool: needle
-consume:
- - items: [hard_leather]
- qty: 1
- - items: [thread]
- qty: 1
-output: hard_leather_shield
-message: "You craft a hard leather shield."
diff --git a/data/recipes/craft_leather_body.yaml b/data/recipes/craft_leather_body.yaml
deleted file mode 100644
index b01eefa..0000000
--- a/data/recipes/craft_leather_body.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: craft_leather_body
-type: crafting
-level: 14
-xp: 25
-wait: 4
-tool: needle
-consume:
- - items: [leather]
- qty: 1
- - items: [thread]
- qty: 1
-output: leather_body
-message: "You craft a leather body."
diff --git a/data/recipes/craft_leather_boots.yaml b/data/recipes/craft_leather_boots.yaml
deleted file mode 100644
index df86ae9..0000000
--- a/data/recipes/craft_leather_boots.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: craft_leather_boots
-type: crafting
-level: 7
-xp: 16
-wait: 4
-tool: needle
-consume:
- - items: [leather]
- qty: 1
- - items: [thread]
- qty: 1
-output: leather_boots
-message: "You craft a pair of leather boots."
diff --git a/data/recipes/craft_leather_chaps.yaml b/data/recipes/craft_leather_chaps.yaml
deleted file mode 100644
index bfbb832..0000000
--- a/data/recipes/craft_leather_chaps.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: craft_leather_chaps
-type: crafting
-level: 18
-xp: 27
-wait: 4
-tool: needle
-consume:
- - items: [leather]
- qty: 1
- - items: [thread]
- qty: 1
-output: leather_chaps
-message: "You craft a pair of leather chaps."
diff --git a/data/recipes/craft_leather_cowl.yaml b/data/recipes/craft_leather_cowl.yaml
deleted file mode 100644
index 7f018bf..0000000
--- a/data/recipes/craft_leather_cowl.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: craft_leather_cowl
-type: crafting
-level: 9
-xp: 19
-wait: 4
-tool: needle
-consume:
- - items: [leather]
- qty: 1
- - items: [thread]
- qty: 1
-output: leather_cowl
-message: "You craft a leather cowl."
diff --git a/data/recipes/craft_leather_gloves.yaml b/data/recipes/craft_leather_gloves.yaml
deleted file mode 100644
index 4f6b27c..0000000
--- a/data/recipes/craft_leather_gloves.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: craft_leather_gloves
-type: crafting
-level: 1
-xp: 14
-wait: 4
-tool: needle
-consume:
- - items: [leather]
- qty: 1
- - items: [thread]
- qty: 1
-output: leather_gloves
-message: "You craft a pair of leather gloves."
diff --git a/data/recipes/craft_leather_vambraces.yaml b/data/recipes/craft_leather_vambraces.yaml
deleted file mode 100644
index 44624c9..0000000
--- a/data/recipes/craft_leather_vambraces.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: craft_leather_vambraces
-type: crafting
-level: 11
-xp: 22
-wait: 4
-tool: needle
-consume:
- - items: [leather]
- qty: 1
- - items: [thread]
- qty: 1
-output: leather_vambraces
-message: "You craft a pair of leather vambraces."
diff --git a/data/recipes/craft_pie_dish.yaml b/data/recipes/craft_pie_dish.yaml
deleted file mode 100644
index 0648dd5..0000000
--- a/data/recipes/craft_pie_dish.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-id: craft_pie_dish
-type: crafting
-level: 7
-xp: 10
-wait: 4
-station: [pottery_oven]
-consume:
- - items: [unfired_pie_dish]
- qty: 1
-output: pie_dish
-message: "You fire the pie dish in the oven successfully."
-fail_message: "The pie dish cracks in the oven and is ruined."
-success:
- base: 0.40
- per_level: 0.02
- cap: 0.95
diff --git a/data/recipes/craft_plant_pot.yaml b/data/recipes/craft_plant_pot.yaml
deleted file mode 100644
index efa353c..0000000
--- a/data/recipes/craft_plant_pot.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-id: craft_plant_pot
-type: crafting
-level: 19
-xp: 18
-wait: 4
-station: [pottery_oven]
-consume:
- - items: [unfired_plant_pot]
- qty: 1
-output: plant_pot
-message: "You fire the plant pot in the oven successfully."
-fail_message: "The plant pot cracks in the oven and is ruined."
-success:
- base: 0.30
- per_level: 0.02
- cap: 0.95
diff --git a/data/recipes/craft_pot.yaml b/data/recipes/craft_pot.yaml
deleted file mode 100644
index 4e7a5ce..0000000
--- a/data/recipes/craft_pot.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-id: craft_pot
-type: crafting
-level: 1
-xp: 6
-wait: 4
-station: [pottery_oven]
-consume:
- - items: [unfired_pot]
- qty: 1
-output: empty_pot
-message: "You fire the pot in the oven successfully."
-fail_message: "The pot cracks in the oven and is ruined."
-success:
- base: 0.50
- per_level: 0.02
- cap: 0.95
diff --git a/data/recipes/craft_ruby_amulet_u.yaml b/data/recipes/craft_ruby_amulet_u.yaml
deleted file mode 100644
index 4f1fbea..0000000
--- a/data/recipes/craft_ruby_amulet_u.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-id: craft_ruby_amulet_u
-type: crafting
-level: 50
-xp: 85
-wait: 4
-station: [furnace]
-consume:
- - items: [gold_bar]
- qty: 1
- - items: [ruby]
- qty: 1
- - items: [amulet_mould]
- qty: 1
- byproducts: [amulet_mould]
-output: ruby_amulet_u
-message: "You cast a ruby amulet."
diff --git a/data/recipes/craft_ruby_bracelet.yaml b/data/recipes/craft_ruby_bracelet.yaml
deleted file mode 100644
index c5e0027..0000000
--- a/data/recipes/craft_ruby_bracelet.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-id: craft_ruby_bracelet
-type: crafting
-level: 42
-xp: 80
-wait: 4
-station: [furnace]
-consume:
- - items: [gold_bar]
- qty: 1
- - items: [ruby]
- qty: 1
- - items: [bracelet_mould]
- qty: 1
- byproducts: [bracelet_mould]
-output: ruby_bracelet
-message: "You cast a ruby bracelet."
diff --git a/data/recipes/craft_ruby_necklace.yaml b/data/recipes/craft_ruby_necklace.yaml
deleted file mode 100644
index 4504f7b..0000000
--- a/data/recipes/craft_ruby_necklace.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-id: craft_ruby_necklace
-type: crafting
-level: 40
-xp: 75
-wait: 4
-station: [furnace]
-consume:
- - items: [gold_bar]
- qty: 1
- - items: [ruby]
- qty: 1
- - items: [necklace_mould]
- qty: 1
- byproducts: [necklace_mould]
-output: ruby_necklace
-message: "You cast a ruby necklace."
diff --git a/data/recipes/craft_ruby_ring.yaml b/data/recipes/craft_ruby_ring.yaml
deleted file mode 100644
index e38a490..0000000
--- a/data/recipes/craft_ruby_ring.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-id: craft_ruby_ring
-type: crafting
-level: 63
-xp: 70
-wait: 4
-station: [furnace]
-consume:
- - items: [gold_bar]
- qty: 1
- - items: [ruby]
- qty: 1
- - items: [ring_mould]
- qty: 1
- byproducts: [ring_mould]
-output: ruby_ring
-message: "You cast a ruby ring."
diff --git a/data/recipes/craft_sapphire_amulet_u.yaml b/data/recipes/craft_sapphire_amulet_u.yaml
deleted file mode 100644
index a15a484..0000000
--- a/data/recipes/craft_sapphire_amulet_u.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-id: craft_sapphire_amulet_u
-type: crafting
-level: 24
-xp: 65
-wait: 4
-station: [furnace]
-consume:
- - items: [gold_bar]
- qty: 1
- - items: [sapphire]
- qty: 1
- - items: [amulet_mould]
- qty: 1
- byproducts: [amulet_mould]
-output: sapphire_amulet_u
-message: "You cast a sapphire amulet."
diff --git a/data/recipes/craft_sapphire_bracelet.yaml b/data/recipes/craft_sapphire_bracelet.yaml
deleted file mode 100644
index ee5d1a7..0000000
--- a/data/recipes/craft_sapphire_bracelet.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-id: craft_sapphire_bracelet
-type: crafting
-level: 23
-xp: 60
-wait: 4
-station: [furnace]
-consume:
- - items: [gold_bar]
- qty: 1
- - items: [sapphire]
- qty: 1
- - items: [bracelet_mould]
- qty: 1
- byproducts: [bracelet_mould]
-output: sapphire_bracelet
-message: "You cast a sapphire bracelet."
diff --git a/data/recipes/craft_sapphire_necklace.yaml b/data/recipes/craft_sapphire_necklace.yaml
deleted file mode 100644
index 1dfd112..0000000
--- a/data/recipes/craft_sapphire_necklace.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-id: craft_sapphire_necklace
-type: crafting
-level: 22
-xp: 55
-wait: 4
-station: [furnace]
-consume:
- - items: [gold_bar]
- qty: 1
- - items: [sapphire]
- qty: 1
- - items: [necklace_mould]
- qty: 1
- byproducts: [necklace_mould]
-output: sapphire_necklace
-message: "You cast a sapphire necklace."
diff --git a/data/recipes/craft_sapphire_ring.yaml b/data/recipes/craft_sapphire_ring.yaml
deleted file mode 100644
index dd793ac..0000000
--- a/data/recipes/craft_sapphire_ring.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-id: craft_sapphire_ring
-type: crafting
-level: 20
-xp: 40
-wait: 4
-station: [furnace]
-consume:
- - items: [gold_bar]
- qty: 1
- - items: [sapphire]
- qty: 1
- - items: [ring_mould]
- qty: 1
- byproducts: [ring_mould]
-output: sapphire_ring
-message: "You cast a sapphire ring."
diff --git a/data/recipes/craft_unfired_pie_dish.yaml b/data/recipes/craft_unfired_pie_dish.yaml
deleted file mode 100644
index e55d47e..0000000
--- a/data/recipes/craft_unfired_pie_dish.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: craft_unfired_pie_dish
-type: crafting
-level: 7
-xp: 15
-wait: 4
-station: [pottery_wheel]
-consume:
- - items: [soft_clay]
- qty: 1
-output: unfired_pie_dish
-message: "You shape the clay into a pie dish."
diff --git a/data/recipes/craft_unfired_plant_pot.yaml b/data/recipes/craft_unfired_plant_pot.yaml
deleted file mode 100644
index 6dbd8ef..0000000
--- a/data/recipes/craft_unfired_plant_pot.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: craft_unfired_plant_pot
-type: crafting
-level: 19
-xp: 18
-wait: 4
-station: [pottery_wheel]
-consume:
- - items: [soft_clay]
- qty: 1
-output: unfired_plant_pot
-message: "You shape the clay into a plant pot."
diff --git a/data/recipes/craft_unfired_pot.yaml b/data/recipes/craft_unfired_pot.yaml
deleted file mode 100644
index 8d72be0..0000000
--- a/data/recipes/craft_unfired_pot.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-id: craft_unfired_pot
-type: crafting
-level: 1
-xp: 6
-wait: 4
-station: [pottery_wheel]
-consume:
- - items: [soft_clay]
- qty: 1
-output: unfired_pot
-message: "You shape the clay into a pot."
diff --git a/data/recipes/crafting/craft_ball_of_wool.yaml b/data/recipes/crafting/craft_ball_of_wool.yaml
new file mode 100644
index 0000000..ef25972
--- /dev/null
+++ b/data/recipes/crafting/craft_ball_of_wool.yaml
@@ -0,0 +1,11 @@
+id: craft_ball_of_wool
+type: crafting
+level: 1
+xp: 3
+wait: 3
+station: [spinning_wheel]
+consume:
+ - items: [wool]
+ quantity: 1
+output: ball_of_wool
+message: "You spin the wool into a ball."
diff --git a/data/recipes/crafting/craft_bowstring.yaml b/data/recipes/crafting/craft_bowstring.yaml
new file mode 100644
index 0000000..caca1a4
--- /dev/null
+++ b/data/recipes/crafting/craft_bowstring.yaml
@@ -0,0 +1,11 @@
+id: craft_bowstring
+type: crafting
+level: 1
+xp: 15
+wait: 3
+station: [spinning_wheel]
+consume:
+ - items: [flax]
+ quantity: 1
+output: bowstring
+message: "You spin the flax into a bowstring."
diff --git a/data/recipes/crafting/craft_coif.yaml b/data/recipes/crafting/craft_coif.yaml
new file mode 100644
index 0000000..4aa914a
--- /dev/null
+++ b/data/recipes/crafting/craft_coif.yaml
@@ -0,0 +1,13 @@
+id: craft_coif
+type: crafting
+level: 38
+xp: 37
+wait: 4
+tool: needle
+consume:
+ - items: [hard_leather]
+ quantity: 1
+ - items: [thread]
+ quantity: 1
+output: coif
+message: "You craft a coif."
diff --git a/data/recipes/crafting/craft_cut_diamond.yaml b/data/recipes/crafting/craft_cut_diamond.yaml
new file mode 100644
index 0000000..175a518
--- /dev/null
+++ b/data/recipes/crafting/craft_cut_diamond.yaml
@@ -0,0 +1,11 @@
+id: craft_cut_diamond
+type: crafting
+level: 43
+xp: 108
+wait: 3
+tool: chisel
+consume:
+ - items: [uncut_diamond]
+ quantity: 1
+output: diamond
+message: "You cut the diamond into a beautiful gem."
diff --git a/data/recipes/crafting/craft_cut_emerald.yaml b/data/recipes/crafting/craft_cut_emerald.yaml
new file mode 100644
index 0000000..f725103
--- /dev/null
+++ b/data/recipes/crafting/craft_cut_emerald.yaml
@@ -0,0 +1,11 @@
+id: craft_cut_emerald
+type: crafting
+level: 27
+xp: 68
+wait: 3
+tool: chisel
+consume:
+ - items: [uncut_emerald]
+ quantity: 1
+output: emerald
+message: "You cut the emerald into a beautiful gem."
diff --git a/data/recipes/crafting/craft_cut_ruby.yaml b/data/recipes/crafting/craft_cut_ruby.yaml
new file mode 100644
index 0000000..bfc5e88
--- /dev/null
+++ b/data/recipes/crafting/craft_cut_ruby.yaml
@@ -0,0 +1,11 @@
+id: craft_cut_ruby
+type: crafting
+level: 63
+xp: 85
+wait: 3
+tool: chisel
+consume:
+ - items: [uncut_ruby]
+ quantity: 1
+output: ruby
+message: "You cut the ruby into a beautiful gem."
diff --git a/data/recipes/crafting/craft_cut_sapphire.yaml b/data/recipes/crafting/craft_cut_sapphire.yaml
new file mode 100644
index 0000000..bfd9a00
--- /dev/null
+++ b/data/recipes/crafting/craft_cut_sapphire.yaml
@@ -0,0 +1,11 @@
+id: craft_cut_sapphire
+type: crafting
+level: 20
+xp: 50
+wait: 3
+tool: chisel
+consume:
+ - items: [uncut_sapphire]
+ quantity: 1
+output: sapphire
+message: "You cut the sapphire into a beautiful gem."
diff --git a/data/recipes/crafting/craft_diamond_amulet_u.yaml b/data/recipes/crafting/craft_diamond_amulet_u.yaml
new file mode 100644
index 0000000..533ce2e
--- /dev/null
+++ b/data/recipes/crafting/craft_diamond_amulet_u.yaml
@@ -0,0 +1,16 @@
+id: craft_diamond_amulet_u
+type: crafting
+level: 70
+xp: 100
+wait: 4
+station: [furnace]
+consume:
+ - items: [gold_bar]
+ quantity: 1
+ - items: [diamond]
+ quantity: 1
+ - items: [amulet_mould]
+ quantity: 1
+ byproducts: [amulet_mould]
+output: diamond_amulet_u
+message: "You cast a diamond amulet."
diff --git a/data/recipes/crafting/craft_diamond_bracelet.yaml b/data/recipes/crafting/craft_diamond_bracelet.yaml
new file mode 100644
index 0000000..f9486d3
--- /dev/null
+++ b/data/recipes/crafting/craft_diamond_bracelet.yaml
@@ -0,0 +1,16 @@
+id: craft_diamond_bracelet
+type: crafting
+level: 58
+xp: 95
+wait: 4
+station: [furnace]
+consume:
+ - items: [gold_bar]
+ quantity: 1
+ - items: [diamond]
+ quantity: 1
+ - items: [bracelet_mould]
+ quantity: 1
+ byproducts: [bracelet_mould]
+output: diamond_bracelet
+message: "You cast a diamond bracelet."
diff --git a/data/recipes/crafting/craft_diamond_necklace.yaml b/data/recipes/crafting/craft_diamond_necklace.yaml
new file mode 100644
index 0000000..bdd4425
--- /dev/null
+++ b/data/recipes/crafting/craft_diamond_necklace.yaml
@@ -0,0 +1,16 @@
+id: craft_diamond_necklace
+type: crafting
+level: 56
+xp: 90
+wait: 4
+station: [furnace]
+consume:
+ - items: [gold_bar]
+ quantity: 1
+ - items: [diamond]
+ quantity: 1
+ - items: [necklace_mould]
+ quantity: 1
+ byproducts: [necklace_mould]
+output: diamond_necklace
+message: "You cast a diamond necklace."
diff --git a/data/recipes/crafting/craft_diamond_ring.yaml b/data/recipes/crafting/craft_diamond_ring.yaml
new file mode 100644
index 0000000..eaf3a59
--- /dev/null
+++ b/data/recipes/crafting/craft_diamond_ring.yaml
@@ -0,0 +1,16 @@
+id: craft_diamond_ring
+type: crafting
+level: 43
+xp: 85
+wait: 4
+station: [furnace]
+consume:
+ - items: [gold_bar]
+ quantity: 1
+ - items: [diamond]
+ quantity: 1
+ - items: [ring_mould]
+ quantity: 1
+ byproducts: [ring_mould]
+output: diamond_ring
+message: "You cast a diamond ring."
diff --git a/data/recipes/crafting/craft_emerald_amulet_u.yaml b/data/recipes/crafting/craft_emerald_amulet_u.yaml
new file mode 100644
index 0000000..82773fb
--- /dev/null
+++ b/data/recipes/crafting/craft_emerald_amulet_u.yaml
@@ -0,0 +1,16 @@
+id: craft_emerald_amulet_u
+type: crafting
+level: 31
+xp: 70
+wait: 4
+station: [furnace]
+consume:
+ - items: [gold_bar]
+ quantity: 1
+ - items: [emerald]
+ quantity: 1
+ - items: [amulet_mould]
+ quantity: 1
+ byproducts: [amulet_mould]
+output: emerald_amulet_u
+message: "You cast a emerald amulet."
diff --git a/data/recipes/crafting/craft_emerald_bracelet.yaml b/data/recipes/crafting/craft_emerald_bracelet.yaml
new file mode 100644
index 0000000..3c2f7b4
--- /dev/null
+++ b/data/recipes/crafting/craft_emerald_bracelet.yaml
@@ -0,0 +1,16 @@
+id: craft_emerald_bracelet
+type: crafting
+level: 30
+xp: 65
+wait: 4
+station: [furnace]
+consume:
+ - items: [gold_bar]
+ quantity: 1
+ - items: [emerald]
+ quantity: 1
+ - items: [bracelet_mould]
+ quantity: 1
+ byproducts: [bracelet_mould]
+output: emerald_bracelet
+message: "You cast a emerald bracelet."
diff --git a/data/recipes/crafting/craft_emerald_necklace.yaml b/data/recipes/crafting/craft_emerald_necklace.yaml
new file mode 100644
index 0000000..7815c16
--- /dev/null
+++ b/data/recipes/crafting/craft_emerald_necklace.yaml
@@ -0,0 +1,16 @@
+id: craft_emerald_necklace
+type: crafting
+level: 29
+xp: 60
+wait: 4
+station: [furnace]
+consume:
+ - items: [gold_bar]
+ quantity: 1
+ - items: [emerald]
+ quantity: 1
+ - items: [necklace_mould]
+ quantity: 1
+ byproducts: [necklace_mould]
+output: emerald_necklace
+message: "You cast a emerald necklace."
diff --git a/data/recipes/crafting/craft_emerald_ring.yaml b/data/recipes/crafting/craft_emerald_ring.yaml
new file mode 100644
index 0000000..2590490
--- /dev/null
+++ b/data/recipes/crafting/craft_emerald_ring.yaml
@@ -0,0 +1,16 @@
+id: craft_emerald_ring
+type: crafting
+level: 27
+xp: 55
+wait: 4
+station: [furnace]
+consume:
+ - items: [gold_bar]
+ quantity: 1
+ - items: [emerald]
+ quantity: 1
+ - items: [ring_mould]
+ quantity: 1
+ byproducts: [ring_mould]
+output: emerald_ring
+message: "You cast a emerald ring."
diff --git a/data/recipes/crafting/craft_gold_amulet_u.yaml b/data/recipes/crafting/craft_gold_amulet_u.yaml
new file mode 100644
index 0000000..69daa89
--- /dev/null
+++ b/data/recipes/crafting/craft_gold_amulet_u.yaml
@@ -0,0 +1,14 @@
+id: craft_gold_amulet_u
+type: crafting
+level: 8
+xp: 30
+wait: 4
+station: [furnace]
+consume:
+ - items: [gold_bar]
+ quantity: 1
+ - items: [amulet_mould]
+ quantity: 1
+ byproducts: [amulet_mould]
+output: gold_amulet_u
+message: "You cast a gold amulet."
diff --git a/data/recipes/crafting/craft_gold_bracelet.yaml b/data/recipes/crafting/craft_gold_bracelet.yaml
new file mode 100644
index 0000000..e3de6d0
--- /dev/null
+++ b/data/recipes/crafting/craft_gold_bracelet.yaml
@@ -0,0 +1,14 @@
+id: craft_gold_bracelet
+type: crafting
+level: 7
+xp: 25
+wait: 4
+station: [furnace]
+consume:
+ - items: [gold_bar]
+ quantity: 1
+ - items: [bracelet_mould]
+ quantity: 1
+ byproducts: [bracelet_mould]
+output: gold_bracelet
+message: "You cast a gold bracelet."
diff --git a/data/recipes/crafting/craft_gold_necklace.yaml b/data/recipes/crafting/craft_gold_necklace.yaml
new file mode 100644
index 0000000..ab52bef
--- /dev/null
+++ b/data/recipes/crafting/craft_gold_necklace.yaml
@@ -0,0 +1,14 @@
+id: craft_gold_necklace
+type: crafting
+level: 6
+xp: 20
+wait: 4
+station: [furnace]
+consume:
+ - items: [gold_bar]
+ quantity: 1
+ - items: [necklace_mould]
+ quantity: 1
+ byproducts: [necklace_mould]
+output: gold_necklace
+message: "You cast a gold necklace."
diff --git a/data/recipes/crafting/craft_gold_ring.yaml b/data/recipes/crafting/craft_gold_ring.yaml
new file mode 100644
index 0000000..943c31d
--- /dev/null
+++ b/data/recipes/crafting/craft_gold_ring.yaml
@@ -0,0 +1,14 @@
+id: craft_gold_ring
+type: crafting
+level: 5
+xp: 15
+wait: 4
+station: [furnace]
+consume:
+ - items: [gold_bar]
+ quantity: 1
+ - items: [ring_mould]
+ quantity: 1
+ byproducts: [ring_mould]
+output: gold_ring
+message: "You cast a gold ring."
diff --git a/data/recipes/crafting/craft_hard_leather_body.yaml b/data/recipes/crafting/craft_hard_leather_body.yaml
new file mode 100644
index 0000000..d1287df
--- /dev/null
+++ b/data/recipes/crafting/craft_hard_leather_body.yaml
@@ -0,0 +1,13 @@
+id: craft_hard_leather_body
+type: crafting
+level: 28
+xp: 35
+wait: 4
+tool: needle
+consume:
+ - items: [hard_leather]
+ quantity: 1
+ - items: [thread]
+ quantity: 1
+output: hard_leather_body
+message: "You craft a hard leather body."
diff --git a/data/recipes/crafting/craft_hard_leather_shield.yaml b/data/recipes/crafting/craft_hard_leather_shield.yaml
new file mode 100644
index 0000000..7dbd66a
--- /dev/null
+++ b/data/recipes/crafting/craft_hard_leather_shield.yaml
@@ -0,0 +1,13 @@
+id: craft_hard_leather_shield
+type: crafting
+level: 41
+xp: 40
+wait: 4
+tool: needle
+consume:
+ - items: [hard_leather]
+ quantity: 1
+ - items: [thread]
+ quantity: 1
+output: hard_leather_shield
+message: "You craft a hard leather shield."
diff --git a/data/recipes/crafting/craft_leather_body.yaml b/data/recipes/crafting/craft_leather_body.yaml
new file mode 100644
index 0000000..0f9774e
--- /dev/null
+++ b/data/recipes/crafting/craft_leather_body.yaml
@@ -0,0 +1,13 @@
+id: craft_leather_body
+type: crafting
+level: 14
+xp: 25
+wait: 4
+tool: needle
+consume:
+ - items: [leather]
+ quantity: 1
+ - items: [thread]
+ quantity: 1
+output: leather_body
+message: "You craft a leather body."
diff --git a/data/recipes/crafting/craft_leather_boots.yaml b/data/recipes/crafting/craft_leather_boots.yaml
new file mode 100644
index 0000000..b935a08
--- /dev/null
+++ b/data/recipes/crafting/craft_leather_boots.yaml
@@ -0,0 +1,13 @@
+id: craft_leather_boots
+type: crafting
+level: 7
+xp: 16
+wait: 4
+tool: needle
+consume:
+ - items: [leather]
+ quantity: 1
+ - items: [thread]
+ quantity: 1
+output: leather_boots
+message: "You craft a pair of leather boots."
diff --git a/data/recipes/crafting/craft_leather_chaps.yaml b/data/recipes/crafting/craft_leather_chaps.yaml
new file mode 100644
index 0000000..fac2a9d
--- /dev/null
+++ b/data/recipes/crafting/craft_leather_chaps.yaml
@@ -0,0 +1,13 @@
+id: craft_leather_chaps
+type: crafting
+level: 18
+xp: 27
+wait: 4
+tool: needle
+consume:
+ - items: [leather]
+ quantity: 1
+ - items: [thread]
+ quantity: 1
+output: leather_chaps
+message: "You craft a pair of leather chaps."
diff --git a/data/recipes/crafting/craft_leather_cowl.yaml b/data/recipes/crafting/craft_leather_cowl.yaml
new file mode 100644
index 0000000..9e5de25
--- /dev/null
+++ b/data/recipes/crafting/craft_leather_cowl.yaml
@@ -0,0 +1,13 @@
+id: craft_leather_cowl
+type: crafting
+level: 9
+xp: 19
+wait: 4
+tool: needle
+consume:
+ - items: [leather]
+ quantity: 1
+ - items: [thread]
+ quantity: 1
+output: leather_cowl
+message: "You craft a leather cowl."
diff --git a/data/recipes/crafting/craft_leather_gloves.yaml b/data/recipes/crafting/craft_leather_gloves.yaml
new file mode 100644
index 0000000..4b4fdfc
--- /dev/null
+++ b/data/recipes/crafting/craft_leather_gloves.yaml
@@ -0,0 +1,13 @@
+id: craft_leather_gloves
+type: crafting
+level: 1
+xp: 14
+wait: 4
+tool: needle
+consume:
+ - items: [leather]
+ quantity: 1
+ - items: [thread]
+ quantity: 1
+output: leather_gloves
+message: "You craft a pair of leather gloves."
diff --git a/data/recipes/crafting/craft_leather_vambraces.yaml b/data/recipes/crafting/craft_leather_vambraces.yaml
new file mode 100644
index 0000000..1227271
--- /dev/null
+++ b/data/recipes/crafting/craft_leather_vambraces.yaml
@@ -0,0 +1,13 @@
+id: craft_leather_vambraces
+type: crafting
+level: 11
+xp: 22
+wait: 4
+tool: needle
+consume:
+ - items: [leather]
+ quantity: 1
+ - items: [thread]
+ quantity: 1
+output: leather_vambraces
+message: "You craft a pair of leather vambraces."
diff --git a/data/recipes/crafting/craft_pie_dish.yaml b/data/recipes/crafting/craft_pie_dish.yaml
new file mode 100644
index 0000000..2b31d17
--- /dev/null
+++ b/data/recipes/crafting/craft_pie_dish.yaml
@@ -0,0 +1,16 @@
+id: craft_pie_dish
+type: crafting
+level: 7
+xp: 10
+wait: 4
+station: [pottery_oven]
+consume:
+ - items: [unfired_pie_dish]
+ quantity: 1
+output: pie_dish
+message: "You fire the pie dish in the oven successfully."
+fail_message: "The pie dish cracks in the oven and is ruined."
+success:
+ base: 0.40
+ per_level: 0.02
+ cap: 0.95
diff --git a/data/recipes/crafting/craft_plant_pot.yaml b/data/recipes/crafting/craft_plant_pot.yaml
new file mode 100644
index 0000000..ddfcefa
--- /dev/null
+++ b/data/recipes/crafting/craft_plant_pot.yaml
@@ -0,0 +1,16 @@
+id: craft_plant_pot
+type: crafting
+level: 19
+xp: 18
+wait: 4
+station: [pottery_oven]
+consume:
+ - items: [unfired_plant_pot]
+ quantity: 1
+output: plant_pot
+message: "You fire the plant pot in the oven successfully."
+fail_message: "The plant pot cracks in the oven and is ruined."
+success:
+ base: 0.30
+ per_level: 0.02
+ cap: 0.95
diff --git a/data/recipes/crafting/craft_pot.yaml b/data/recipes/crafting/craft_pot.yaml
new file mode 100644
index 0000000..1b80eef
--- /dev/null
+++ b/data/recipes/crafting/craft_pot.yaml
@@ -0,0 +1,16 @@
+id: craft_pot
+type: crafting
+level: 1
+xp: 6
+wait: 4
+station: [pottery_oven]
+consume:
+ - items: [unfired_pot]
+ quantity: 1
+output: empty_pot
+message: "You fire the pot in the oven successfully."
+fail_message: "The pot cracks in the oven and is ruined."
+success:
+ base: 0.50
+ per_level: 0.02
+ cap: 0.95
diff --git a/data/recipes/crafting/craft_ruby_amulet_u.yaml b/data/recipes/crafting/craft_ruby_amulet_u.yaml
new file mode 100644
index 0000000..38a864e
--- /dev/null
+++ b/data/recipes/crafting/craft_ruby_amulet_u.yaml
@@ -0,0 +1,16 @@
+id: craft_ruby_amulet_u
+type: crafting
+level: 50
+xp: 85
+wait: 4
+station: [furnace]
+consume:
+ - items: [gold_bar]
+ quantity: 1
+ - items: [ruby]
+ quantity: 1
+ - items: [amulet_mould]
+ quantity: 1
+ byproducts: [amulet_mould]
+output: ruby_amulet_u
+message: "You cast a ruby amulet."
diff --git a/data/recipes/crafting/craft_ruby_bracelet.yaml b/data/recipes/crafting/craft_ruby_bracelet.yaml
new file mode 100644
index 0000000..bcc963e
--- /dev/null
+++ b/data/recipes/crafting/craft_ruby_bracelet.yaml
@@ -0,0 +1,16 @@
+id: craft_ruby_bracelet
+type: crafting
+level: 42
+xp: 80
+wait: 4
+station: [furnace]
+consume:
+ - items: [gold_bar]
+ quantity: 1
+ - items: [ruby]
+ quantity: 1
+ - items: [bracelet_mould]
+ quantity: 1
+ byproducts: [bracelet_mould]
+output: ruby_bracelet
+message: "You cast a ruby bracelet."
diff --git a/data/recipes/crafting/craft_ruby_necklace.yaml b/data/recipes/crafting/craft_ruby_necklace.yaml
new file mode 100644
index 0000000..60e47a3
--- /dev/null
+++ b/data/recipes/crafting/craft_ruby_necklace.yaml
@@ -0,0 +1,16 @@
+id: craft_ruby_necklace
+type: crafting
+level: 40
+xp: 75
+wait: 4
+station: [furnace]
+consume:
+ - items: [gold_bar]
+ quantity: 1
+ - items: [ruby]
+ quantity: 1
+ - items: [necklace_mould]
+ quantity: 1
+ byproducts: [necklace_mould]
+output: ruby_necklace
+message: "You cast a ruby necklace."
diff --git a/data/recipes/crafting/craft_ruby_ring.yaml b/data/recipes/crafting/craft_ruby_ring.yaml
new file mode 100644
index 0000000..61d2b46
--- /dev/null
+++ b/data/recipes/crafting/craft_ruby_ring.yaml
@@ -0,0 +1,16 @@
+id: craft_ruby_ring
+type: crafting
+level: 63
+xp: 70
+wait: 4
+station: [furnace]
+consume:
+ - items: [gold_bar]
+ quantity: 1
+ - items: [ruby]
+ quantity: 1
+ - items: [ring_mould]
+ quantity: 1
+ byproducts: [ring_mould]
+output: ruby_ring
+message: "You cast a ruby ring."
diff --git a/data/recipes/crafting/craft_sapphire_amulet_u.yaml b/data/recipes/crafting/craft_sapphire_amulet_u.yaml
new file mode 100644
index 0000000..07ef6bf
--- /dev/null
+++ b/data/recipes/crafting/craft_sapphire_amulet_u.yaml
@@ -0,0 +1,16 @@
+id: craft_sapphire_amulet_u
+type: crafting
+level: 24
+xp: 65
+wait: 4
+station: [furnace]
+consume:
+ - items: [gold_bar]
+ quantity: 1
+ - items: [sapphire]
+ quantity: 1
+ - items: [amulet_mould]
+ quantity: 1
+ byproducts: [amulet_mould]
+output: sapphire_amulet_u
+message: "You cast a sapphire amulet."
diff --git a/data/recipes/crafting/craft_sapphire_bracelet.yaml b/data/recipes/crafting/craft_sapphire_bracelet.yaml
new file mode 100644
index 0000000..3bc1067
--- /dev/null
+++ b/data/recipes/crafting/craft_sapphire_bracelet.yaml
@@ -0,0 +1,16 @@
+id: craft_sapphire_bracelet
+type: crafting
+level: 23
+xp: 60
+wait: 4
+station: [furnace]
+consume:
+ - items: [gold_bar]
+ quantity: 1
+ - items: [sapphire]
+ quantity: 1
+ - items: [bracelet_mould]
+ quantity: 1
+ byproducts: [bracelet_mould]
+output: sapphire_bracelet
+message: "You cast a sapphire bracelet."
diff --git a/data/recipes/crafting/craft_sapphire_necklace.yaml b/data/recipes/crafting/craft_sapphire_necklace.yaml
new file mode 100644
index 0000000..8006bf7
--- /dev/null
+++ b/data/recipes/crafting/craft_sapphire_necklace.yaml
@@ -0,0 +1,16 @@
+id: craft_sapphire_necklace
+type: crafting
+level: 22
+xp: 55
+wait: 4
+station: [furnace]
+consume:
+ - items: [gold_bar]
+ quantity: 1
+ - items: [sapphire]
+ quantity: 1
+ - items: [necklace_mould]
+ quantity: 1
+ byproducts: [necklace_mould]
+output: sapphire_necklace
+message: "You cast a sapphire necklace."
diff --git a/data/recipes/crafting/craft_sapphire_ring.yaml b/data/recipes/crafting/craft_sapphire_ring.yaml
new file mode 100644
index 0000000..cc67b73
--- /dev/null
+++ b/data/recipes/crafting/craft_sapphire_ring.yaml
@@ -0,0 +1,16 @@
+id: craft_sapphire_ring
+type: crafting
+level: 20
+xp: 40
+wait: 4
+station: [furnace]
+consume:
+ - items: [gold_bar]
+ quantity: 1
+ - items: [sapphire]
+ quantity: 1
+ - items: [ring_mould]
+ quantity: 1
+ byproducts: [ring_mould]
+output: sapphire_ring
+message: "You cast a sapphire ring."
diff --git a/data/recipes/crafting/craft_unfired_pie_dish.yaml b/data/recipes/crafting/craft_unfired_pie_dish.yaml
new file mode 100644
index 0000000..7ba5032
--- /dev/null
+++ b/data/recipes/crafting/craft_unfired_pie_dish.yaml
@@ -0,0 +1,11 @@
+id: craft_unfired_pie_dish
+type: crafting
+level: 7
+xp: 15
+wait: 4
+station: [pottery_wheel]
+consume:
+ - items: [soft_clay]
+ quantity: 1
+output: unfired_pie_dish
+message: "You shape the clay into a pie dish."
diff --git a/data/recipes/crafting/craft_unfired_plant_pot.yaml b/data/recipes/crafting/craft_unfired_plant_pot.yaml
new file mode 100644
index 0000000..db7a38e
--- /dev/null
+++ b/data/recipes/crafting/craft_unfired_plant_pot.yaml
@@ -0,0 +1,11 @@
+id: craft_unfired_plant_pot
+type: crafting
+level: 19
+xp: 18
+wait: 4
+station: [pottery_wheel]
+consume:
+ - items: [soft_clay]
+ quantity: 1
+output: unfired_plant_pot
+message: "You shape the clay into a plant pot."
diff --git a/data/recipes/crafting/craft_unfired_pot.yaml b/data/recipes/crafting/craft_unfired_pot.yaml
new file mode 100644
index 0000000..5b2762d
--- /dev/null
+++ b/data/recipes/crafting/craft_unfired_pot.yaml
@@ -0,0 +1,11 @@
+id: craft_unfired_pot
+type: crafting
+level: 1
+xp: 6
+wait: 4
+station: [pottery_wheel]
+consume:
+ - items: [soft_clay]
+ quantity: 1
+output: unfired_pot
+message: "You shape the clay into a pot."
diff --git a/data/recipes/fletch_adamant_arrow.yaml b/data/recipes/fletch_adamant_arrow.yaml
deleted file mode 100644
index 0166c99..0000000
--- a/data/recipes/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]
- qty: 15
- - items: [adamant_arrowtips]
- qty: 15
-output: adamant_arrow
-output_qty: 15
-message: "You attach the adamant arrowtips to the headless arrows."
diff --git a/data/recipes/fletch_adamant_bolts.yaml b/data/recipes/fletch_adamant_bolts.yaml
deleted file mode 100644
index 46aa57f..0000000
--- a/data/recipes/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]
- qty: 10
- - items: [feather]
- qty: 10
-output: adamant_bolts
-output_qty: 10
-message: "You attach feathers to the adamant bolts."
diff --git a/data/recipes/fletch_arrow_shaft.yaml b/data/recipes/fletch_arrow_shaft.yaml
deleted file mode 100644
index b2d3c81..0000000
--- a/data/recipes/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]
- qty: 1
-output: arrow_shaft
-output_qty: 15
-message: "You carefully cut the logs into arrow shafts."
diff --git a/data/recipes/fletch_arrow_shaft_magic.yaml b/data/recipes/fletch_arrow_shaft_magic.yaml
deleted file mode 100644
index b72c4b8..0000000
--- a/data/recipes/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]
- qty: 1
-output: arrow_shaft
-output_qty: 40
-message: "You carefully cut the magic logs into arrow shafts."
diff --git a/data/recipes/fletch_arrow_shaft_maple.yaml b/data/recipes/fletch_arrow_shaft_maple.yaml
deleted file mode 100644
index aa9fea6..0000000
--- a/data/recipes/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]
- qty: 1
-output: arrow_shaft
-output_qty: 30
-message: "You carefully cut the maple logs into arrow shafts."
diff --git a/data/recipes/fletch_arrow_shaft_oak.yaml b/data/recipes/fletch_arrow_shaft_oak.yaml
deleted file mode 100644
index 1940e87..0000000
--- a/data/recipes/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]
- qty: 1
-output: arrow_shaft
-output_qty: 20
-message: "You carefully cut the oak logs into arrow shafts."
diff --git a/data/recipes/fletch_arrow_shaft_willow.yaml b/data/recipes/fletch_arrow_shaft_willow.yaml
deleted file mode 100644
index 986ffae..0000000
--- a/data/recipes/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]
- qty: 1
-output: arrow_shaft
-output_qty: 25
-message: "You carefully cut the willow logs into arrow shafts."
diff --git a/data/recipes/fletch_arrow_shaft_yew.yaml b/data/recipes/fletch_arrow_shaft_yew.yaml
deleted file mode 100644
index bc7775e..0000000
--- a/data/recipes/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]
- qty: 1
-output: arrow_shaft
-output_qty: 35
-message: "You carefully cut the yew logs into arrow shafts."
diff --git a/data/recipes/fletch_bronze_arrow.yaml b/data/recipes/fletch_bronze_arrow.yaml
deleted file mode 100644
index 4cc7aca..0000000
--- a/data/recipes/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]
- qty: 15
- - items: [bronze_arrowtips]
- qty: 15
-output: bronze_arrow
-output_qty: 15
-message: "You attach the bronze arrowtips to the headless arrows."
diff --git a/data/recipes/fletch_bronze_bolts.yaml b/data/recipes/fletch_bronze_bolts.yaml
deleted file mode 100644
index f10595a..0000000
--- a/data/recipes/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]
- qty: 10
- - items: [feather]
- qty: 10
-output: bronze_bolts
-output_qty: 10
-message: "You attach feathers to the bronze bolts."
diff --git a/data/recipes/fletch_diamond_bolt_tips.yaml b/data/recipes/fletch_diamond_bolt_tips.yaml
deleted file mode 100644
index dc57964..0000000
--- a/data/recipes/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]
- qty: 1
-output: diamond_bolt_tips
-output_qty: 12
-message: "You cut the diamond into bolt tips."
diff --git a/data/recipes/fletch_diamond_bolts.yaml b/data/recipes/fletch_diamond_bolts.yaml
deleted file mode 100644
index 7b0a460..0000000
--- a/data/recipes/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]
- qty: 10
- - items: [diamond_bolt_tips]
- qty: 10
-output: diamond_bolts
-output_qty: 10
-message: "You tip the adamant bolts with diamond."
diff --git a/data/recipes/fletch_emerald_bolt_tips.yaml b/data/recipes/fletch_emerald_bolt_tips.yaml
deleted file mode 100644
index b7d78a5..0000000
--- a/data/recipes/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]
- qty: 1
-output: emerald_bolt_tips
-output_qty: 12
-message: "You cut the emerald into bolt tips."
diff --git a/data/recipes/fletch_emerald_bolts.yaml b/data/recipes/fletch_emerald_bolts.yaml
deleted file mode 100644
index 353432a..0000000
--- a/data/recipes/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]
- qty: 10
- - items: [emerald_bolt_tips]
- qty: 10
-output: emerald_bolts
-output_qty: 10
-message: "You tip the mithril bolts with emerald."
diff --git a/data/recipes/fletch_headless_arrow.yaml b/data/recipes/fletch_headless_arrow.yaml
deleted file mode 100644
index 3016430..0000000
--- a/data/recipes/fletch_headless_arrow.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: fletch_headless_arrow
-type: fletching
-level: 1
-xp: 1
-wait: 2
-consume:
- - items: [arrow_shaft]
- qty: 15
- - items: [feather]
- qty: 15
-output: headless_arrow
-output_qty: 15
-message: "You attach feathers to the arrow shafts."
diff --git a/data/recipes/fletch_iron_arrow.yaml b/data/recipes/fletch_iron_arrow.yaml
deleted file mode 100644
index 2727ca1..0000000
--- a/data/recipes/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]
- qty: 15
- - items: [iron_arrowtips]
- qty: 15
-output: iron_arrow
-output_qty: 15
-message: "You attach the iron arrowtips to the headless arrows."
diff --git a/data/recipes/fletch_iron_bolts.yaml b/data/recipes/fletch_iron_bolts.yaml
deleted file mode 100644
index 44a623f..0000000
--- a/data/recipes/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]
- qty: 10
- - items: [feather]
- qty: 10
-output: iron_bolts
-output_qty: 10
-message: "You attach feathers to the iron bolts."
diff --git a/data/recipes/fletch_longbow.yaml b/data/recipes/fletch_longbow.yaml
deleted file mode 100644
index 392ccfb..0000000
--- a/data/recipes/fletch_longbow.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: fletch_longbow
-type: fletching
-level: 10
-xp: 10
-wait: 2
-consume:
- - items: [longbow_u]
- qty: 1
- - items: [bowstring]
- qty: 1
-output: longbow
-message: "You string the longbow."
diff --git a/data/recipes/fletch_longbow_u.yaml b/data/recipes/fletch_longbow_u.yaml
deleted file mode 100644
index e836a9e..0000000
--- a/data/recipes/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]
- qty: 1
-output: longbow_u
-message: "You carefully carve the log into a longbow."
diff --git a/data/recipes/fletch_magic_longbow.yaml b/data/recipes/fletch_magic_longbow.yaml
deleted file mode 100644
index c8cc0d4..0000000
--- a/data/recipes/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]
- qty: 1
- - items: [bowstring]
- qty: 1
-output: magic_longbow
-message: "You string the magic longbow."
diff --git a/data/recipes/fletch_magic_longbow_u.yaml b/data/recipes/fletch_magic_longbow_u.yaml
deleted file mode 100644
index 19eddc8..0000000
--- a/data/recipes/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]
- qty: 1
-output: magic_longbow_u
-message: "You carefully carve the magic log into a longbow."
diff --git a/data/recipes/fletch_magic_shortbow.yaml b/data/recipes/fletch_magic_shortbow.yaml
deleted file mode 100644
index b9b3bb2..0000000
--- a/data/recipes/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]
- qty: 1
- - items: [bowstring]
- qty: 1
-output: magic_shortbow
-message: "You string the magic shortbow."
diff --git a/data/recipes/fletch_magic_shortbow_u.yaml b/data/recipes/fletch_magic_shortbow_u.yaml
deleted file mode 100644
index e9f2e38..0000000
--- a/data/recipes/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]
- qty: 1
-output: magic_shortbow_u
-message: "You carefully carve the magic log into a shortbow."
diff --git a/data/recipes/fletch_maple_longbow.yaml b/data/recipes/fletch_maple_longbow.yaml
deleted file mode 100644
index 1ecfb74..0000000
--- a/data/recipes/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]
- qty: 1
- - items: [bowstring]
- qty: 1
-output: maple_longbow
-message: "You string the maple longbow."
diff --git a/data/recipes/fletch_maple_longbow_u.yaml b/data/recipes/fletch_maple_longbow_u.yaml
deleted file mode 100644
index 981c6e0..0000000
--- a/data/recipes/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]
- qty: 1
-output: maple_longbow_u
-message: "You carefully carve the maple log into a longbow."
diff --git a/data/recipes/fletch_maple_shortbow.yaml b/data/recipes/fletch_maple_shortbow.yaml
deleted file mode 100644
index 844a080..0000000
--- a/data/recipes/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]
- qty: 1
- - items: [bowstring]
- qty: 1
-output: maple_shortbow
-message: "You string the maple shortbow."
diff --git a/data/recipes/fletch_maple_shortbow_u.yaml b/data/recipes/fletch_maple_shortbow_u.yaml
deleted file mode 100644
index 56dc15c..0000000
--- a/data/recipes/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]
- qty: 1
-output: maple_shortbow_u
-message: "You carefully carve the maple log into a shortbow."
diff --git a/data/recipes/fletch_mithril_arrow.yaml b/data/recipes/fletch_mithril_arrow.yaml
deleted file mode 100644
index 1d03823..0000000
--- a/data/recipes/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]
- qty: 15
- - items: [mithril_arrowtips]
- qty: 15
-output: mithril_arrow
-output_qty: 15
-message: "You attach the mithril arrowtips to the headless arrows."
diff --git a/data/recipes/fletch_mithril_bolts.yaml b/data/recipes/fletch_mithril_bolts.yaml
deleted file mode 100644
index 90e85a8..0000000
--- a/data/recipes/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]
- qty: 10
- - items: [feather]
- qty: 10
-output: mithril_bolts
-output_qty: 10
-message: "You attach feathers to the mithril bolts."
diff --git a/data/recipes/fletch_oak_longbow.yaml b/data/recipes/fletch_oak_longbow.yaml
deleted file mode 100644
index 39b72d8..0000000
--- a/data/recipes/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]
- qty: 1
- - items: [bowstring]
- qty: 1
-output: oak_longbow
-message: "You string the oak longbow."
diff --git a/data/recipes/fletch_oak_longbow_u.yaml b/data/recipes/fletch_oak_longbow_u.yaml
deleted file mode 100644
index 733c227..0000000
--- a/data/recipes/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]
- qty: 1
-output: oak_longbow_u
-message: "You carefully carve the oak log into a longbow."
diff --git a/data/recipes/fletch_oak_shortbow.yaml b/data/recipes/fletch_oak_shortbow.yaml
deleted file mode 100644
index ade20ff..0000000
--- a/data/recipes/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]
- qty: 1
- - items: [bowstring]
- qty: 1
-output: oak_shortbow
-message: "You string the oak shortbow."
diff --git a/data/recipes/fletch_oak_shortbow_u.yaml b/data/recipes/fletch_oak_shortbow_u.yaml
deleted file mode 100644
index d994a77..0000000
--- a/data/recipes/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]
- qty: 1
-output: oak_shortbow_u
-message: "You carefully carve the oak log into a shortbow."
diff --git a/data/recipes/fletch_ruby_bolt_tips.yaml b/data/recipes/fletch_ruby_bolt_tips.yaml
deleted file mode 100644
index 293a840..0000000
--- a/data/recipes/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]
- qty: 1
-output: ruby_bolt_tips
-output_qty: 12
-message: "You cut the ruby into bolt tips."
diff --git a/data/recipes/fletch_ruby_bolts.yaml b/data/recipes/fletch_ruby_bolts.yaml
deleted file mode 100644
index 1fdac70..0000000
--- a/data/recipes/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]
- qty: 10
- - items: [ruby_bolt_tips]
- qty: 10
-output: ruby_bolts
-output_qty: 10
-message: "You tip the adamant bolts with ruby."
diff --git a/data/recipes/fletch_rune_arrow.yaml b/data/recipes/fletch_rune_arrow.yaml
deleted file mode 100644
index a208bce..0000000
--- a/data/recipes/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]
- qty: 15
- - items: [rune_arrowtips]
- qty: 15
-output: rune_arrow
-output_qty: 15
-message: "You attach the rune arrowtips to the headless arrows."
diff --git a/data/recipes/fletch_rune_bolts.yaml b/data/recipes/fletch_rune_bolts.yaml
deleted file mode 100644
index 6073d7f..0000000
--- a/data/recipes/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]
- qty: 10
- - items: [feather]
- qty: 10
-output: rune_bolts
-output_qty: 10
-message: "You attach feathers to the rune bolts."
diff --git a/data/recipes/fletch_sapphire_bolt_tips.yaml b/data/recipes/fletch_sapphire_bolt_tips.yaml
deleted file mode 100644
index 353d1de..0000000
--- a/data/recipes/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]
- qty: 1
-output: sapphire_bolt_tips
-output_qty: 12
-message: "You cut the sapphire into bolt tips."
diff --git a/data/recipes/fletch_sapphire_bolts.yaml b/data/recipes/fletch_sapphire_bolts.yaml
deleted file mode 100644
index 503809a..0000000
--- a/data/recipes/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]
- qty: 10
- - items: [sapphire_bolt_tips]
- qty: 10
-output: sapphire_bolts
-output_qty: 10
-message: "You tip the mithril bolts with sapphire."
diff --git a/data/recipes/fletch_shortbow.yaml b/data/recipes/fletch_shortbow.yaml
deleted file mode 100644
index 0911aa2..0000000
--- a/data/recipes/fletch_shortbow.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: fletch_shortbow
-type: fletching
-level: 5
-xp: 5
-wait: 2
-consume:
- - items: [shortbow_u]
- qty: 1
- - items: [bowstring]
- qty: 1
-output: shortbow
-message: "You string the shortbow."
diff --git a/data/recipes/fletch_shortbow_u.yaml b/data/recipes/fletch_shortbow_u.yaml
deleted file mode 100644
index bda2a93..0000000
--- a/data/recipes/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]
- qty: 1
-output: shortbow_u
-message: "You carefully carve the log into a shortbow."
diff --git a/data/recipes/fletch_steel_arrow.yaml b/data/recipes/fletch_steel_arrow.yaml
deleted file mode 100644
index fc28d21..0000000
--- a/data/recipes/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]
- qty: 15
- - items: [steel_arrowtips]
- qty: 15
-output: steel_arrow
-output_qty: 15
-message: "You attach the steel arrowtips to the headless arrows."
diff --git a/data/recipes/fletch_steel_bolts.yaml b/data/recipes/fletch_steel_bolts.yaml
deleted file mode 100644
index 156b6e0..0000000
--- a/data/recipes/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]
- qty: 10
- - items: [feather]
- qty: 10
-output: steel_bolts
-output_qty: 10
-message: "You attach feathers to the steel bolts."
diff --git a/data/recipes/fletch_willow_longbow.yaml b/data/recipes/fletch_willow_longbow.yaml
deleted file mode 100644
index 6f4a9c0..0000000
--- a/data/recipes/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]
- qty: 1
- - items: [bowstring]
- qty: 1
-output: willow_longbow
-message: "You string the willow longbow."
diff --git a/data/recipes/fletch_willow_longbow_u.yaml b/data/recipes/fletch_willow_longbow_u.yaml
deleted file mode 100644
index 5afbe89..0000000
--- a/data/recipes/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]
- qty: 1
-output: willow_longbow_u
-message: "You carefully carve the willow log into a longbow."
diff --git a/data/recipes/fletch_willow_shortbow.yaml b/data/recipes/fletch_willow_shortbow.yaml
deleted file mode 100644
index e6d044f..0000000
--- a/data/recipes/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]
- qty: 1
- - items: [bowstring]
- qty: 1
-output: willow_shortbow
-message: "You string the willow shortbow."
diff --git a/data/recipes/fletch_willow_shortbow_u.yaml b/data/recipes/fletch_willow_shortbow_u.yaml
deleted file mode 100644
index b0fb490..0000000
--- a/data/recipes/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]
- qty: 1
-output: willow_shortbow_u
-message: "You carefully carve the willow log into a shortbow."
diff --git a/data/recipes/fletch_yew_longbow.yaml b/data/recipes/fletch_yew_longbow.yaml
deleted file mode 100644
index 34b6a79..0000000
--- a/data/recipes/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]
- qty: 1
- - items: [bowstring]
- qty: 1
-output: yew_longbow
-message: "You string the yew longbow."
diff --git a/data/recipes/fletch_yew_longbow_u.yaml b/data/recipes/fletch_yew_longbow_u.yaml
deleted file mode 100644
index c5abe34..0000000
--- a/data/recipes/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]
- qty: 1
-output: yew_longbow_u
-message: "You carefully carve the yew log into a longbow."
diff --git a/data/recipes/fletch_yew_shortbow.yaml b/data/recipes/fletch_yew_shortbow.yaml
deleted file mode 100644
index 2dac064..0000000
--- a/data/recipes/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]
- qty: 1
- - items: [bowstring]
- qty: 1
-output: yew_shortbow
-message: "You string the yew shortbow."
diff --git a/data/recipes/fletch_yew_shortbow_u.yaml b/data/recipes/fletch_yew_shortbow_u.yaml
deleted file mode 100644
index 4b61a38..0000000
--- a/data/recipes/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]
- qty: 1
-output: yew_shortbow_u
-message: "You carefully carve the yew log into a shortbow."
diff --git a/data/recipes/fletching/fletch_adamant_arrow.yaml b/data/recipes/fletching/fletch_adamant_arrow.yaml
new file mode 100644
index 0000000..57f899b
--- /dev/null
+++ b/data/recipes/fletching/fletch_adamant_arrow.yaml
@@ -0,0 +1,13 @@
+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
new file mode 100644
index 0000000..d96c811
--- /dev/null
+++ b/data/recipes/fletching/fletch_adamant_bolts.yaml
@@ -0,0 +1,13 @@
+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
new file mode 100644
index 0000000..ebf3861
--- /dev/null
+++ b/data/recipes/fletching/fletch_arrow_shaft.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..0621859
--- /dev/null
+++ b/data/recipes/fletching/fletch_arrow_shaft_magic.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..b6671da
--- /dev/null
+++ b/data/recipes/fletching/fletch_arrow_shaft_maple.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..cf38335
--- /dev/null
+++ b/data/recipes/fletching/fletch_arrow_shaft_oak.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..0c39a13
--- /dev/null
+++ b/data/recipes/fletching/fletch_arrow_shaft_willow.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..785325e
--- /dev/null
+++ b/data/recipes/fletching/fletch_arrow_shaft_yew.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..4b9e924
--- /dev/null
+++ b/data/recipes/fletching/fletch_bronze_arrow.yaml
@@ -0,0 +1,13 @@
+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
new file mode 100644
index 0000000..cdcf6db
--- /dev/null
+++ b/data/recipes/fletching/fletch_bronze_bolts.yaml
@@ -0,0 +1,13 @@
+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
new file mode 100644
index 0000000..0ccabe2
--- /dev/null
+++ b/data/recipes/fletching/fletch_diamond_bolt_tips.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..8c98fc2
--- /dev/null
+++ b/data/recipes/fletching/fletch_diamond_bolts.yaml
@@ -0,0 +1,13 @@
+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
new file mode 100644
index 0000000..d48cf73
--- /dev/null
+++ b/data/recipes/fletching/fletch_emerald_bolt_tips.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..8e7e446
--- /dev/null
+++ b/data/recipes/fletching/fletch_emerald_bolts.yaml
@@ -0,0 +1,13 @@
+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_headless_arrow.yaml b/data/recipes/fletching/fletch_headless_arrow.yaml
new file mode 100644
index 0000000..84d88f5
--- /dev/null
+++ b/data/recipes/fletching/fletch_headless_arrow.yaml
@@ -0,0 +1,13 @@
+id: fletch_headless_arrow
+type: fletching
+level: 1
+xp: 1
+wait: 2
+consume:
+ - items: [arrow_shaft]
+ quantity: 15
+ - items: [feather]
+ quantity: 15
+output: headless_arrow
+output_qty: 15
+message: "You attach feathers to the arrow shafts."
diff --git a/data/recipes/fletching/fletch_iron_arrow.yaml b/data/recipes/fletching/fletch_iron_arrow.yaml
new file mode 100644
index 0000000..fbb1c2c
--- /dev/null
+++ b/data/recipes/fletching/fletch_iron_arrow.yaml
@@ -0,0 +1,13 @@
+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
new file mode 100644
index 0000000..bb90403
--- /dev/null
+++ b/data/recipes/fletching/fletch_iron_bolts.yaml
@@ -0,0 +1,13 @@
+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
new file mode 100644
index 0000000..fa46921
--- /dev/null
+++ b/data/recipes/fletching/fletch_longbow.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..a893b49
--- /dev/null
+++ b/data/recipes/fletching/fletch_longbow_u.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..8e16b9c
--- /dev/null
+++ b/data/recipes/fletching/fletch_magic_longbow.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..ce2e247
--- /dev/null
+++ b/data/recipes/fletching/fletch_magic_longbow_u.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..58abce9
--- /dev/null
+++ b/data/recipes/fletching/fletch_magic_shortbow.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..3fbf6cd
--- /dev/null
+++ b/data/recipes/fletching/fletch_magic_shortbow_u.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..123d531
--- /dev/null
+++ b/data/recipes/fletching/fletch_maple_longbow.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..4c61993
--- /dev/null
+++ b/data/recipes/fletching/fletch_maple_longbow_u.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..aebe50a
--- /dev/null
+++ b/data/recipes/fletching/fletch_maple_shortbow.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..36270dd
--- /dev/null
+++ b/data/recipes/fletching/fletch_maple_shortbow_u.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..87a46e2
--- /dev/null
+++ b/data/recipes/fletching/fletch_mithril_arrow.yaml
@@ -0,0 +1,13 @@
+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
new file mode 100644
index 0000000..a2afcb8
--- /dev/null
+++ b/data/recipes/fletching/fletch_mithril_bolts.yaml
@@ -0,0 +1,13 @@
+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
new file mode 100644
index 0000000..f1c96ba
--- /dev/null
+++ b/data/recipes/fletching/fletch_oak_longbow.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..8f0b234
--- /dev/null
+++ b/data/recipes/fletching/fletch_oak_longbow_u.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..198657f
--- /dev/null
+++ b/data/recipes/fletching/fletch_oak_shortbow.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..4f96a09
--- /dev/null
+++ b/data/recipes/fletching/fletch_oak_shortbow_u.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..bc9f695
--- /dev/null
+++ b/data/recipes/fletching/fletch_ruby_bolt_tips.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..ac1d150
--- /dev/null
+++ b/data/recipes/fletching/fletch_ruby_bolts.yaml
@@ -0,0 +1,13 @@
+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
new file mode 100644
index 0000000..aa2795e
--- /dev/null
+++ b/data/recipes/fletching/fletch_rune_arrow.yaml
@@ -0,0 +1,13 @@
+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
new file mode 100644
index 0000000..20bb7b9
--- /dev/null
+++ b/data/recipes/fletching/fletch_rune_bolts.yaml
@@ -0,0 +1,13 @@
+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
new file mode 100644
index 0000000..dca6fef
--- /dev/null
+++ b/data/recipes/fletching/fletch_sapphire_bolt_tips.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..712578f
--- /dev/null
+++ b/data/recipes/fletching/fletch_sapphire_bolts.yaml
@@ -0,0 +1,13 @@
+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
new file mode 100644
index 0000000..e96317d
--- /dev/null
+++ b/data/recipes/fletching/fletch_shortbow.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..3ab4931
--- /dev/null
+++ b/data/recipes/fletching/fletch_shortbow_u.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..9bd229a
--- /dev/null
+++ b/data/recipes/fletching/fletch_steel_arrow.yaml
@@ -0,0 +1,13 @@
+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
new file mode 100644
index 0000000..9bfdafe
--- /dev/null
+++ b/data/recipes/fletching/fletch_steel_bolts.yaml
@@ -0,0 +1,13 @@
+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
new file mode 100644
index 0000000..28071ac
--- /dev/null
+++ b/data/recipes/fletching/fletch_willow_longbow.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..ef808c0
--- /dev/null
+++ b/data/recipes/fletching/fletch_willow_longbow_u.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..a2b2c2b
--- /dev/null
+++ b/data/recipes/fletching/fletch_willow_shortbow.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..95609b3
--- /dev/null
+++ b/data/recipes/fletching/fletch_willow_shortbow_u.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..208c623
--- /dev/null
+++ b/data/recipes/fletching/fletch_yew_longbow.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..bdce4f8
--- /dev/null
+++ b/data/recipes/fletching/fletch_yew_longbow_u.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..fbe5f62
--- /dev/null
+++ b/data/recipes/fletching/fletch_yew_shortbow.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..774e3a5
--- /dev/null
+++ b/data/recipes/fletching/fletch_yew_shortbow_u.yaml
@@ -0,0 +1,11 @@
+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/mix_amp_potion.yaml b/data/recipes/mix_amp_potion.yaml
deleted file mode 100644
index 1a50af9..0000000
--- a/data/recipes/mix_amp_potion.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: mix_amp_potion
-type: pharmacy
-skill: pharmacy
-level: 12
-xp: 50
-wait: 4
-consume:
- - items: [tarromin_potion_unf]
- qty: 1
- - items: [limpwurt_root]
- qty: 1
-output: amp_potion
-message: "You mix an amp potion."
diff --git a/data/recipes/mix_bio_serum.yaml b/data/recipes/mix_bio_serum.yaml
deleted file mode 100644
index 7f1931a..0000000
--- a/data/recipes/mix_bio_serum.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: mix_bio_serum
-type: pharmacy
-skill: pharmacy
-level: 5
-xp: 38
-wait: 4
-consume:
- - items: [marrentill_potion_unf]
- qty: 1
- - items: [antitoxin_powder]
- qty: 1
-output: bio_serum
-message: "You mix a bio serum."
diff --git a/data/recipes/mix_full_restore.yaml b/data/recipes/mix_full_restore.yaml
deleted file mode 100644
index 76312cf..0000000
--- a/data/recipes/mix_full_restore.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: mix_full_restore
-type: pharmacy
-skill: pharmacy
-level: 63
-xp: 143
-wait: 4
-consume:
- - items: [snapdragon_potion_unf]
- qty: 1
- - items: [arachnid_enzyme]
- qty: 1
-output: full_restore
-message: "You mix a full restore."
diff --git a/data/recipes/mix_nano_restore.yaml b/data/recipes/mix_nano_restore.yaml
deleted file mode 100644
index 36bb251..0000000
--- a/data/recipes/mix_nano_restore.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: mix_nano_restore
-type: pharmacy
-skill: pharmacy
-level: 22
-xp: 63
-wait: 4
-consume:
- - items: [harralander_potion_unf]
- qty: 1
- - items: [arachnid_enzyme]
- qty: 1
-output: nano_restore
-message: "You mix a nano restore."
diff --git a/data/recipes/mix_restoration_compound.yaml b/data/recipes/mix_restoration_compound.yaml
deleted file mode 100644
index 6639784..0000000
--- a/data/recipes/mix_restoration_compound.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: mix_restoration_compound
-type: pharmacy
-skill: pharmacy
-level: 81
-xp: 180
-wait: 4
-consume:
- - items: [toadflax_potion_unf]
- qty: 1
- - items: [crushed_nest]
- qty: 1
-output: restoration_compound
-message: "You mix a restoration compound."
diff --git a/data/recipes/mix_science_serum.yaml b/data/recipes/mix_science_serum.yaml
deleted file mode 100644
index 5f60945..0000000
--- a/data/recipes/mix_science_serum.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: mix_science_serum
-type: pharmacy
-skill: pharmacy
-level: 76
-xp: 173
-wait: 4
-consume:
- - items: [lantadyme_potion_unf]
- qty: 1
- - items: [potato_cactus]
- qty: 1
-output: science_serum
-message: "You mix a science serum."
diff --git a/data/recipes/mix_shield_potion.yaml b/data/recipes/mix_shield_potion.yaml
deleted file mode 100644
index 875e870..0000000
--- a/data/recipes/mix_shield_potion.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: mix_shield_potion
-type: pharmacy
-skill: pharmacy
-level: 30
-xp: 75
-wait: 4
-consume:
- - items: [ranarr_potion_unf]
- qty: 1
- - items: [white_berries]
- qty: 1
-output: shield_potion
-message: "You mix a shield potion."
diff --git a/data/recipes/mix_stim_cell.yaml b/data/recipes/mix_stim_cell.yaml
deleted file mode 100644
index 8915f39..0000000
--- a/data/recipes/mix_stim_cell.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: mix_stim_cell
-type: pharmacy
-skill: pharmacy
-level: 26
-xp: 68
-wait: 4
-consume:
- - items: [harralander_potion_unf]
- qty: 1
- - items: [chocolate_dust]
- qty: 1
-output: stim_cell
-message: "You mix a stim cell."
diff --git a/data/recipes/mix_stim_potion.yaml b/data/recipes/mix_stim_potion.yaml
deleted file mode 100644
index 67db442..0000000
--- a/data/recipes/mix_stim_potion.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: mix_stim_potion
-type: pharmacy
-skill: pharmacy
-level: 3
-xp: 25
-wait: 4
-consume:
- - items: [guam_potion_unf]
- qty: 1
- - items: [eye_of_newt]
- qty: 1
-output: stim_potion
-message: "You mix a stim potion."
diff --git a/data/recipes/mix_super_amp.yaml b/data/recipes/mix_super_amp.yaml
deleted file mode 100644
index ea30e35..0000000
--- a/data/recipes/mix_super_amp.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: mix_super_amp
-type: pharmacy
-skill: pharmacy
-level: 55
-xp: 125
-wait: 4
-consume:
- - items: [kwuarm_potion_unf]
- qty: 1
- - items: [limpwurt_root]
- qty: 1
-output: super_amp
-message: "You mix a super amp."
diff --git a/data/recipes/mix_super_bio_serum.yaml b/data/recipes/mix_super_bio_serum.yaml
deleted file mode 100644
index 808dd31..0000000
--- a/data/recipes/mix_super_bio_serum.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: mix_super_bio_serum
-type: pharmacy
-skill: pharmacy
-level: 48
-xp: 106
-wait: 4
-consume:
- - items: [irit_potion_unf]
- qty: 1
- - items: [antitoxin_powder]
- qty: 1
-output: super_bio_serum
-message: "You mix a super bio serum."
diff --git a/data/recipes/mix_super_shield.yaml b/data/recipes/mix_super_shield.yaml
deleted file mode 100644
index 07d849b..0000000
--- a/data/recipes/mix_super_shield.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: mix_super_shield
-type: pharmacy
-skill: pharmacy
-level: 66
-xp: 150
-wait: 4
-consume:
- - items: [cadantine_potion_unf]
- qty: 1
- - items: [white_berries]
- qty: 1
-output: super_shield
-message: "You mix a super shield."
diff --git a/data/recipes/mix_super_stim.yaml b/data/recipes/mix_super_stim.yaml
deleted file mode 100644
index 2382960..0000000
--- a/data/recipes/mix_super_stim.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: mix_super_stim
-type: pharmacy
-skill: pharmacy
-level: 45
-xp: 100
-wait: 4
-consume:
- - items: [irit_potion_unf]
- qty: 1
- - items: [eye_of_newt]
- qty: 1
-output: super_stim
-message: "You mix a super stim."
diff --git a/data/recipes/mix_super_stim_cell.yaml b/data/recipes/mix_super_stim_cell.yaml
deleted file mode 100644
index 5ed6bd3..0000000
--- a/data/recipes/mix_super_stim_cell.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: mix_super_stim_cell
-type: pharmacy
-skill: pharmacy
-level: 52
-xp: 118
-wait: 4
-consume:
- - items: [avantoe_potion_unf]
- qty: 1
- - items: [blight_spore]
- qty: 1
-output: super_stim_cell
-message: "You mix a super stim cell."
diff --git a/data/recipes/mix_targeting_serum.yaml b/data/recipes/mix_targeting_serum.yaml
deleted file mode 100644
index 54bd338..0000000
--- a/data/recipes/mix_targeting_serum.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: mix_targeting_serum
-type: pharmacy
-skill: pharmacy
-level: 72
-xp: 163
-wait: 4
-consume:
- - items: [dwarf_weed_potion_unf]
- qty: 1
- - items: [catalyst_wine]
- qty: 1
-output: targeting_serum
-message: "You mix a targeting serum."
diff --git a/data/recipes/mix_tech_serum.yaml b/data/recipes/mix_tech_serum.yaml
deleted file mode 100644
index ea41efd..0000000
--- a/data/recipes/mix_tech_serum.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: mix_tech_serum
-type: pharmacy
-skill: pharmacy
-level: 38
-xp: 88
-wait: 4
-consume:
- - items: [ranarr_potion_unf]
- qty: 1
- - items: [snape_grass]
- qty: 1
-output: tech_serum
-message: "You mix a tech serum."
diff --git a/data/recipes/mixing/mix_amp_potion.yaml b/data/recipes/mixing/mix_amp_potion.yaml
new file mode 100644
index 0000000..6145296
--- /dev/null
+++ b/data/recipes/mixing/mix_amp_potion.yaml
@@ -0,0 +1,13 @@
+id: mix_amp_potion
+type: pharmacy
+skill: pharmacy
+level: 12
+xp: 50
+wait: 4
+consume:
+ - items: [tarromin_potion_unf]
+ quantity: 1
+ - items: [limpwurt_root]
+ quantity: 1
+output: amp_potion
+message: "You mix an amp potion."
diff --git a/data/recipes/mixing/mix_bio_serum.yaml b/data/recipes/mixing/mix_bio_serum.yaml
new file mode 100644
index 0000000..6f5a3ea
--- /dev/null
+++ b/data/recipes/mixing/mix_bio_serum.yaml
@@ -0,0 +1,13 @@
+id: mix_bio_serum
+type: pharmacy
+skill: pharmacy
+level: 5
+xp: 38
+wait: 4
+consume:
+ - items: [marrentill_potion_unf]
+ quantity: 1
+ - items: [antitoxin_powder]
+ quantity: 1
+output: bio_serum
+message: "You mix a bio serum."
diff --git a/data/recipes/mixing/mix_full_restore.yaml b/data/recipes/mixing/mix_full_restore.yaml
new file mode 100644
index 0000000..b9e320e
--- /dev/null
+++ b/data/recipes/mixing/mix_full_restore.yaml
@@ -0,0 +1,13 @@
+id: mix_full_restore
+type: pharmacy
+skill: pharmacy
+level: 63
+xp: 143
+wait: 4
+consume:
+ - items: [snapdragon_potion_unf]
+ quantity: 1
+ - items: [arachnid_enzyme]
+ quantity: 1
+output: full_restore
+message: "You mix a full restore."
diff --git a/data/recipes/mixing/mix_nano_restore.yaml b/data/recipes/mixing/mix_nano_restore.yaml
new file mode 100644
index 0000000..4a5ebb0
--- /dev/null
+++ b/data/recipes/mixing/mix_nano_restore.yaml
@@ -0,0 +1,13 @@
+id: mix_nano_restore
+type: pharmacy
+skill: pharmacy
+level: 22
+xp: 63
+wait: 4
+consume:
+ - items: [harralander_potion_unf]
+ quantity: 1
+ - items: [arachnid_enzyme]
+ quantity: 1
+output: nano_restore
+message: "You mix a nano restore."
diff --git a/data/recipes/mixing/mix_restoration_compound.yaml b/data/recipes/mixing/mix_restoration_compound.yaml
new file mode 100644
index 0000000..c945cac
--- /dev/null
+++ b/data/recipes/mixing/mix_restoration_compound.yaml
@@ -0,0 +1,13 @@
+id: mix_restoration_compound
+type: pharmacy
+skill: pharmacy
+level: 81
+xp: 180
+wait: 4
+consume:
+ - items: [toadflax_potion_unf]
+ quantity: 1
+ - items: [crushed_nest]
+ quantity: 1
+output: restoration_compound
+message: "You mix a restoration compound."
diff --git a/data/recipes/mixing/mix_science_serum.yaml b/data/recipes/mixing/mix_science_serum.yaml
new file mode 100644
index 0000000..b65f2d2
--- /dev/null
+++ b/data/recipes/mixing/mix_science_serum.yaml
@@ -0,0 +1,13 @@
+id: mix_science_serum
+type: pharmacy
+skill: pharmacy
+level: 76
+xp: 173
+wait: 4
+consume:
+ - items: [lantadyme_potion_unf]
+ quantity: 1
+ - items: [potato_cactus]
+ quantity: 1
+output: science_serum
+message: "You mix a science serum."
diff --git a/data/recipes/mixing/mix_shield_potion.yaml b/data/recipes/mixing/mix_shield_potion.yaml
new file mode 100644
index 0000000..fc44abf
--- /dev/null
+++ b/data/recipes/mixing/mix_shield_potion.yaml
@@ -0,0 +1,13 @@
+id: mix_shield_potion
+type: pharmacy
+skill: pharmacy
+level: 30
+xp: 75
+wait: 4
+consume:
+ - items: [ranarr_potion_unf]
+ quantity: 1
+ - items: [white_berries]
+ quantity: 1
+output: shield_potion
+message: "You mix a shield potion."
diff --git a/data/recipes/mixing/mix_stim_cell.yaml b/data/recipes/mixing/mix_stim_cell.yaml
new file mode 100644
index 0000000..ccca53a
--- /dev/null
+++ b/data/recipes/mixing/mix_stim_cell.yaml
@@ -0,0 +1,13 @@
+id: mix_stim_cell
+type: pharmacy
+skill: pharmacy
+level: 26
+xp: 68
+wait: 4
+consume:
+ - items: [harralander_potion_unf]
+ quantity: 1
+ - items: [chocolate_dust]
+ quantity: 1
+output: stim_cell
+message: "You mix a stim cell."
diff --git a/data/recipes/mixing/mix_stim_potion.yaml b/data/recipes/mixing/mix_stim_potion.yaml
new file mode 100644
index 0000000..437b985
--- /dev/null
+++ b/data/recipes/mixing/mix_stim_potion.yaml
@@ -0,0 +1,13 @@
+id: mix_stim_potion
+type: pharmacy
+skill: pharmacy
+level: 3
+xp: 25
+wait: 4
+consume:
+ - items: [guam_potion_unf]
+ quantity: 1
+ - items: [eye_of_newt]
+ quantity: 1
+output: stim_potion
+message: "You mix a stim potion."
diff --git a/data/recipes/mixing/mix_super_amp.yaml b/data/recipes/mixing/mix_super_amp.yaml
new file mode 100644
index 0000000..5e05019
--- /dev/null
+++ b/data/recipes/mixing/mix_super_amp.yaml
@@ -0,0 +1,13 @@
+id: mix_super_amp
+type: pharmacy
+skill: pharmacy
+level: 55
+xp: 125
+wait: 4
+consume:
+ - items: [kwuarm_potion_unf]
+ quantity: 1
+ - items: [limpwurt_root]
+ quantity: 1
+output: super_amp
+message: "You mix a super amp."
diff --git a/data/recipes/mixing/mix_super_bio_serum.yaml b/data/recipes/mixing/mix_super_bio_serum.yaml
new file mode 100644
index 0000000..96e6632
--- /dev/null
+++ b/data/recipes/mixing/mix_super_bio_serum.yaml
@@ -0,0 +1,13 @@
+id: mix_super_bio_serum
+type: pharmacy
+skill: pharmacy
+level: 48
+xp: 106
+wait: 4
+consume:
+ - items: [irit_potion_unf]
+ quantity: 1
+ - items: [antitoxin_powder]
+ quantity: 1
+output: super_bio_serum
+message: "You mix a super bio serum."
diff --git a/data/recipes/mixing/mix_super_shield.yaml b/data/recipes/mixing/mix_super_shield.yaml
new file mode 100644
index 0000000..61b3686
--- /dev/null
+++ b/data/recipes/mixing/mix_super_shield.yaml
@@ -0,0 +1,13 @@
+id: mix_super_shield
+type: pharmacy
+skill: pharmacy
+level: 66
+xp: 150
+wait: 4
+consume:
+ - items: [cadantine_potion_unf]
+ quantity: 1
+ - items: [white_berries]
+ quantity: 1
+output: super_shield
+message: "You mix a super shield."
diff --git a/data/recipes/mixing/mix_super_stim.yaml b/data/recipes/mixing/mix_super_stim.yaml
new file mode 100644
index 0000000..bac9f8f
--- /dev/null
+++ b/data/recipes/mixing/mix_super_stim.yaml
@@ -0,0 +1,13 @@
+id: mix_super_stim
+type: pharmacy
+skill: pharmacy
+level: 45
+xp: 100
+wait: 4
+consume:
+ - items: [irit_potion_unf]
+ quantity: 1
+ - items: [eye_of_newt]
+ quantity: 1
+output: super_stim
+message: "You mix a super stim."
diff --git a/data/recipes/mixing/mix_super_stim_cell.yaml b/data/recipes/mixing/mix_super_stim_cell.yaml
new file mode 100644
index 0000000..26640ea
--- /dev/null
+++ b/data/recipes/mixing/mix_super_stim_cell.yaml
@@ -0,0 +1,13 @@
+id: mix_super_stim_cell
+type: pharmacy
+skill: pharmacy
+level: 52
+xp: 118
+wait: 4
+consume:
+ - items: [avantoe_potion_unf]
+ quantity: 1
+ - items: [blight_spore]
+ quantity: 1
+output: super_stim_cell
+message: "You mix a super stim cell."
diff --git a/data/recipes/mixing/mix_targeting_serum.yaml b/data/recipes/mixing/mix_targeting_serum.yaml
new file mode 100644
index 0000000..06f8421
--- /dev/null
+++ b/data/recipes/mixing/mix_targeting_serum.yaml
@@ -0,0 +1,13 @@
+id: mix_targeting_serum
+type: pharmacy
+skill: pharmacy
+level: 72
+xp: 163
+wait: 4
+consume:
+ - items: [dwarf_weed_potion_unf]
+ quantity: 1
+ - items: [catalyst_wine]
+ quantity: 1
+output: targeting_serum
+message: "You mix a targeting serum."
diff --git a/data/recipes/mixing/mix_tech_serum.yaml b/data/recipes/mixing/mix_tech_serum.yaml
new file mode 100644
index 0000000..a57d2f9
--- /dev/null
+++ b/data/recipes/mixing/mix_tech_serum.yaml
@@ -0,0 +1,13 @@
+id: mix_tech_serum
+type: pharmacy
+skill: pharmacy
+level: 38
+xp: 88
+wait: 4
+consume:
+ - items: [ranarr_potion_unf]
+ quantity: 1
+ - items: [snape_grass]
+ quantity: 1
+output: tech_serum
+message: "You mix a tech serum."
diff --git a/data/recipes/smelt_adamantite.yaml b/data/recipes/smelt_adamantite.yaml
deleted file mode 100644
index c5a34f7..0000000
--- a/data/recipes/smelt_adamantite.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-id: smelt_adamantite
-type: smelting
-skill: smithing
-level: 65
-xp: 37
-wait: 4
-station: [furnace]
-consume:
- - items: [adamantite_ore]
- qty: 1
- - items: [coal]
- qty: 6
-output: adamantite_bar
-message: "You remove a white hot adamantite bar!"
-fail_message: "You fail to smelt a usable bar."
diff --git a/data/recipes/smelt_bronze.yaml b/data/recipes/smelt_bronze.yaml
deleted file mode 100644
index bb46da5..0000000
--- a/data/recipes/smelt_bronze.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-id: smelt_bronze
-type: smelting
-skill: smithing
-level: 1
-xp: 6
-wait: 4
-station: [furnace]
-consume:
- - items: [copper_ore]
- qty: 1
- - items: [tin_ore]
- qty: 1
-output: bronze_bar
-message: "You remove a white hot bronze bar!"
-fail_message: "You fail to smelt a usable bar."
diff --git a/data/recipes/smelt_gold.yaml b/data/recipes/smelt_gold.yaml
deleted file mode 100644
index 6f30147..0000000
--- a/data/recipes/smelt_gold.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: smelt_gold
-type: smelting
-skill: smithing
-level: 45
-xp: 22
-wait: 4
-station: [furnace]
-consume:
- - items: [gold_ore]
- qty: 1
-output: gold_bar
-message: "You remove a white hot gold bar!"
-fail_message: "You fail to smelt a usable bar."
diff --git a/data/recipes/smelt_iron.yaml b/data/recipes/smelt_iron.yaml
deleted file mode 100644
index 2631b28..0000000
--- a/data/recipes/smelt_iron.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-id: smelt_iron
-type: smelting
-skill: smithing
-level: 20
-xp: 12
-wait: 4
-station: [furnace]
-consume:
- - items: [iron_ore]
- qty: 1
-output: iron_bar
-message: "You remove a white hot iron bar!"
-fail_message: "The iron is too impure to make a workable bar."
-success:
- base: 0.5
- per_level: 0
- cap: 0.5
diff --git a/data/recipes/smelt_mithril.yaml b/data/recipes/smelt_mithril.yaml
deleted file mode 100644
index 0e2856a..0000000
--- a/data/recipes/smelt_mithril.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-id: smelt_mithril
-type: smelting
-skill: smithing
-level: 50
-xp: 30
-wait: 4
-station: [furnace]
-consume:
- - items: [mithril_ore]
- qty: 1
- - items: [coal]
- qty: 4
-output: mithril_bar
-message: "You remove a white hot mithril bar!"
-fail_message: "You fail to smelt a usable bar."
diff --git a/data/recipes/smelt_runite.yaml b/data/recipes/smelt_runite.yaml
deleted file mode 100644
index 1c2935e..0000000
--- a/data/recipes/smelt_runite.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-id: smelt_runite
-type: smelting
-skill: smithing
-level: 80
-xp: 50
-wait: 4
-station: [furnace]
-consume:
- - items: [runite_ore]
- qty: 1
- - items: [coal]
- qty: 8
-output: runite_bar
-message: "You remove a white hot runite bar!"
-fail_message: "You fail to smelt a usable bar."
diff --git a/data/recipes/smelt_silver.yaml b/data/recipes/smelt_silver.yaml
deleted file mode 100644
index 1630452..0000000
--- a/data/recipes/smelt_silver.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-id: smelt_silver
-type: smelting
-skill: smithing
-level: 25
-xp: 14
-wait: 4
-station: [furnace]
-consume:
- - items: [silver_ore]
- qty: 1
-output: silver_bar
-message: "You remove a white hot silver bar!"
-fail_message: "You fail to smelt a usable bar."
diff --git a/data/recipes/smelt_steel.yaml b/data/recipes/smelt_steel.yaml
deleted file mode 100644
index e3df4b6..0000000
--- a/data/recipes/smelt_steel.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-id: smelt_steel
-type: smelting
-skill: smithing
-level: 40
-xp: 17
-wait: 4
-station: [furnace]
-consume:
- - items: [iron_ore]
- qty: 1
- - items: [coal]
- qty: 2
-output: steel_bar
-message: "You remove a white hot steel bar!"
-fail_message: "You fail to smelt a usable bar."
diff --git a/data/recipes/smelting/smelt_adamantite.yaml b/data/recipes/smelting/smelt_adamantite.yaml
new file mode 100644
index 0000000..5ff675b
--- /dev/null
+++ b/data/recipes/smelting/smelt_adamantite.yaml
@@ -0,0 +1,15 @@
+id: smelt_adamantite
+type: smelting
+skill: smithing
+level: 65
+xp: 37
+wait: 4
+station: [furnace]
+consume:
+ - items: [adamantite_ore]
+ quantity: 1
+ - items: [coal]
+ quantity: 6
+output: adamantite_bar
+message: "You remove a white hot adamantite bar!"
+fail_message: "You fail to smelt a usable bar."
diff --git a/data/recipes/smelting/smelt_bronze.yaml b/data/recipes/smelting/smelt_bronze.yaml
new file mode 100644
index 0000000..bdb33af
--- /dev/null
+++ b/data/recipes/smelting/smelt_bronze.yaml
@@ -0,0 +1,15 @@
+id: smelt_bronze
+type: smelting
+skill: smithing
+level: 1
+xp: 6
+wait: 4
+station: [furnace]
+consume:
+ - items: [copper_ore]
+ quantity: 1
+ - items: [tin_ore]
+ quantity: 1
+output: bronze_bar
+message: "You remove a white hot bronze bar!"
+fail_message: "You fail to smelt a usable bar."
diff --git a/data/recipes/smelting/smelt_gold.yaml b/data/recipes/smelting/smelt_gold.yaml
new file mode 100644
index 0000000..91229e8
--- /dev/null
+++ b/data/recipes/smelting/smelt_gold.yaml
@@ -0,0 +1,13 @@
+id: smelt_gold
+type: smelting
+skill: smithing
+level: 45
+xp: 22
+wait: 4
+station: [furnace]
+consume:
+ - items: [gold_ore]
+ quantity: 1
+output: gold_bar
+message: "You remove a white hot gold bar!"
+fail_message: "You fail to smelt a usable bar."
diff --git a/data/recipes/smelting/smelt_iron.yaml b/data/recipes/smelting/smelt_iron.yaml
new file mode 100644
index 0000000..ce0ca11
--- /dev/null
+++ b/data/recipes/smelting/smelt_iron.yaml
@@ -0,0 +1,17 @@
+id: smelt_iron
+type: smelting
+skill: smithing
+level: 20
+xp: 12
+wait: 4
+station: [furnace]
+consume:
+ - items: [iron_ore]
+ quantity: 1
+output: iron_bar
+message: "You remove a white hot iron bar!"
+fail_message: "The iron is too impure to make a workable bar."
+success:
+ base: 0.5
+ per_level: 0
+ cap: 0.5
diff --git a/data/recipes/smelting/smelt_mithril.yaml b/data/recipes/smelting/smelt_mithril.yaml
new file mode 100644
index 0000000..6285c1b
--- /dev/null
+++ b/data/recipes/smelting/smelt_mithril.yaml
@@ -0,0 +1,15 @@
+id: smelt_mithril
+type: smelting
+skill: smithing
+level: 50
+xp: 30
+wait: 4
+station: [furnace]
+consume:
+ - items: [mithril_ore]
+ quantity: 1
+ - items: [coal]
+ quantity: 4
+output: mithril_bar
+message: "You remove a white hot mithril bar!"
+fail_message: "You fail to smelt a usable bar."
diff --git a/data/recipes/smelting/smelt_runite.yaml b/data/recipes/smelting/smelt_runite.yaml
new file mode 100644
index 0000000..b683221
--- /dev/null
+++ b/data/recipes/smelting/smelt_runite.yaml
@@ -0,0 +1,15 @@
+id: smelt_runite
+type: smelting
+skill: smithing
+level: 80
+xp: 50
+wait: 4
+station: [furnace]
+consume:
+ - items: [runite_ore]
+ quantity: 1
+ - items: [coal]
+ quantity: 8
+output: runite_bar
+message: "You remove a white hot runite bar!"
+fail_message: "You fail to smelt a usable bar."
diff --git a/data/recipes/smelting/smelt_silver.yaml b/data/recipes/smelting/smelt_silver.yaml
new file mode 100644
index 0000000..f381bd0
--- /dev/null
+++ b/data/recipes/smelting/smelt_silver.yaml
@@ -0,0 +1,13 @@
+id: smelt_silver
+type: smelting
+skill: smithing
+level: 25
+xp: 14
+wait: 4
+station: [furnace]
+consume:
+ - items: [silver_ore]
+ quantity: 1
+output: silver_bar
+message: "You remove a white hot silver bar!"
+fail_message: "You fail to smelt a usable bar."
diff --git a/data/recipes/smelting/smelt_steel.yaml b/data/recipes/smelting/smelt_steel.yaml
new file mode 100644
index 0000000..ec1c2df
--- /dev/null
+++ b/data/recipes/smelting/smelt_steel.yaml
@@ -0,0 +1,15 @@
+id: smelt_steel
+type: smelting
+skill: smithing
+level: 40
+xp: 17
+wait: 4
+station: [furnace]
+consume:
+ - items: [iron_ore]
+ quantity: 1
+ - items: [coal]
+ quantity: 2
+output: steel_bar
+message: "You remove a white hot steel bar!"
+fail_message: "You fail to smelt a usable bar."
diff --git a/data/recipes/smith_adamant_arrowtips.yaml b/data/recipes/smith_adamant_arrowtips.yaml
deleted file mode 100644
index 6ad49d7..0000000
--- a/data/recipes/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]
- qty: 1
-output: adamant_arrowtips
-output_qty: 15
-message: "You hammer out some adamant arrowtips."
diff --git a/data/recipes/smith_adamant_bolts_unf.yaml b/data/recipes/smith_adamant_bolts_unf.yaml
deleted file mode 100644
index 9e71a5c..0000000
--- a/data/recipes/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]
- qty: 1
-output: adamant_bolts_unf
-output_qty: 10
-message: "You hammer out some adamant bolts."
diff --git a/data/recipes/smith_adamant_dagger.yaml b/data/recipes/smith_adamant_dagger.yaml
deleted file mode 100644
index 8d5f6d2..0000000
--- a/data/recipes/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]
- qty: 1
-output: adamant_dagger
-message: "You hammer out an adamant dagger."
diff --git a/data/recipes/smith_adamant_full_helm.yaml b/data/recipes/smith_adamant_full_helm.yaml
deleted file mode 100644
index 61a79b0..0000000
--- a/data/recipes/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]
- qty: 2
-output: adamant_full_helm
-message: "You hammer out an adamant full helm."
diff --git a/data/recipes/smith_adamant_med_helm.yaml b/data/recipes/smith_adamant_med_helm.yaml
deleted file mode 100644
index 275adaf..0000000
--- a/data/recipes/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]
- qty: 1
-output: adamant_med_helm
-message: "You hammer out an adamant med helm."
diff --git a/data/recipes/smith_adamant_nails.yaml b/data/recipes/smith_adamant_nails.yaml
deleted file mode 100644
index 195cbb1..0000000
--- a/data/recipes/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]
- qty: 1
-output: adamant_nails
-output_qty: 15
-message: "You hammer out some adamant nails."
diff --git a/data/recipes/smith_adamant_platebody.yaml b/data/recipes/smith_adamant_platebody.yaml
deleted file mode 100644
index abd5284..0000000
--- a/data/recipes/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]
- qty: 5
-output: adamant_platebody
-message: "You hammer out an adamant platebody."
diff --git a/data/recipes/smith_adamant_sword.yaml b/data/recipes/smith_adamant_sword.yaml
deleted file mode 100644
index c973310..0000000
--- a/data/recipes/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]
- qty: 1
-output: adamant_sword
-message: "You hammer out an adamant sword."
diff --git a/data/recipes/smith_bronze_arrowtips.yaml b/data/recipes/smith_bronze_arrowtips.yaml
deleted file mode 100644
index 4b74869..0000000
--- a/data/recipes/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]
- qty: 1
-output: bronze_arrowtips
-output_qty: 15
-message: "You hammer out some bronze arrowtips."
diff --git a/data/recipes/smith_bronze_bolts_unf.yaml b/data/recipes/smith_bronze_bolts_unf.yaml
deleted file mode 100644
index 349be84..0000000
--- a/data/recipes/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]
- qty: 1
-output: bronze_bolts_unf
-output_qty: 10
-message: "You hammer out some bronze bolts."
diff --git a/data/recipes/smith_bronze_dagger.yaml b/data/recipes/smith_bronze_dagger.yaml
deleted file mode 100644
index fc458c8..0000000
--- a/data/recipes/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]
- qty: 1
-output: bronze_dagger
-message: "You hammer out a bronze dagger."
diff --git a/data/recipes/smith_bronze_full_helm.yaml b/data/recipes/smith_bronze_full_helm.yaml
deleted file mode 100644
index cd689fd..0000000
--- a/data/recipes/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]
- qty: 2
-output: bronze_full_helm
-message: "You hammer out a bronze full helm."
diff --git a/data/recipes/smith_bronze_med_helm.yaml b/data/recipes/smith_bronze_med_helm.yaml
deleted file mode 100644
index 60e1353..0000000
--- a/data/recipes/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]
- qty: 1
-output: bronze_med_helm
-message: "You hammer out a bronze med helm."
diff --git a/data/recipes/smith_bronze_nails.yaml b/data/recipes/smith_bronze_nails.yaml
deleted file mode 100644
index fa3286b..0000000
--- a/data/recipes/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]
- qty: 1
-output: bronze_nails
-output_qty: 15
-message: "You hammer out some bronze nails."
diff --git a/data/recipes/smith_bronze_platebody.yaml b/data/recipes/smith_bronze_platebody.yaml
deleted file mode 100644
index 5e49649..0000000
--- a/data/recipes/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]
- qty: 5
-output: bronze_platebody
-message: "You hammer out a bronze platebody."
diff --git a/data/recipes/smith_bronze_sword.yaml b/data/recipes/smith_bronze_sword.yaml
deleted file mode 100644
index 23d3237..0000000
--- a/data/recipes/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]
- qty: 1
-output: bronze_sword
-message: "You hammer out a bronze sword."
diff --git a/data/recipes/smith_iron_arrowtips.yaml b/data/recipes/smith_iron_arrowtips.yaml
deleted file mode 100644
index 9b773ee..0000000
--- a/data/recipes/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]
- qty: 1
-output: iron_arrowtips
-output_qty: 15
-message: "You hammer out some iron arrowtips."
diff --git a/data/recipes/smith_iron_bolts_unf.yaml b/data/recipes/smith_iron_bolts_unf.yaml
deleted file mode 100644
index 564daab..0000000
--- a/data/recipes/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]
- qty: 1
-output: iron_bolts_unf
-output_qty: 10
-message: "You hammer out some iron bolts."
diff --git a/data/recipes/smith_iron_dagger.yaml b/data/recipes/smith_iron_dagger.yaml
deleted file mode 100644
index d885717..0000000
--- a/data/recipes/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]
- qty: 1
-output: iron_dagger
-message: "You hammer out an iron dagger."
diff --git a/data/recipes/smith_iron_full_helm.yaml b/data/recipes/smith_iron_full_helm.yaml
deleted file mode 100644
index afcd56a..0000000
--- a/data/recipes/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]
- qty: 2
-output: iron_full_helm
-message: "You hammer out an iron full helm."
diff --git a/data/recipes/smith_iron_med_helm.yaml b/data/recipes/smith_iron_med_helm.yaml
deleted file mode 100644
index 5aabb9e..0000000
--- a/data/recipes/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]
- qty: 1
-output: iron_med_helm
-message: "You hammer out an iron med helm."
diff --git a/data/recipes/smith_iron_nails.yaml b/data/recipes/smith_iron_nails.yaml
deleted file mode 100644
index 2e6e59f..0000000
--- a/data/recipes/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]
- qty: 1
-output: iron_nails
-output_qty: 15
-message: "You hammer out some iron nails."
diff --git a/data/recipes/smith_iron_platebody.yaml b/data/recipes/smith_iron_platebody.yaml
deleted file mode 100644
index c1fd795..0000000
--- a/data/recipes/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]
- qty: 5
-output: iron_platebody
-message: "You hammer out an iron platebody."
diff --git a/data/recipes/smith_iron_sword.yaml b/data/recipes/smith_iron_sword.yaml
deleted file mode 100644
index ab87fdc..0000000
--- a/data/recipes/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]
- qty: 1
-output: iron_sword
-message: "You hammer out an iron sword."
diff --git a/data/recipes/smith_mithril_arrowtips.yaml b/data/recipes/smith_mithril_arrowtips.yaml
deleted file mode 100644
index a61e5f1..0000000
--- a/data/recipes/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]
- qty: 1
-output: mithril_arrowtips
-output_qty: 15
-message: "You hammer out some mithril arrowtips."
diff --git a/data/recipes/smith_mithril_bolts_unf.yaml b/data/recipes/smith_mithril_bolts_unf.yaml
deleted file mode 100644
index eb6da91..0000000
--- a/data/recipes/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]
- qty: 1
-output: mithril_bolts_unf
-output_qty: 10
-message: "You hammer out some mithril bolts."
diff --git a/data/recipes/smith_mithril_dagger.yaml b/data/recipes/smith_mithril_dagger.yaml
deleted file mode 100644
index f8aea86..0000000
--- a/data/recipes/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]
- qty: 1
-output: mithril_dagger
-message: "You hammer out a mithril dagger."
diff --git a/data/recipes/smith_mithril_full_helm.yaml b/data/recipes/smith_mithril_full_helm.yaml
deleted file mode 100644
index 64b78c4..0000000
--- a/data/recipes/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]
- qty: 2
-output: mithril_full_helm
-message: "You hammer out a mithril full helm."
diff --git a/data/recipes/smith_mithril_med_helm.yaml b/data/recipes/smith_mithril_med_helm.yaml
deleted file mode 100644
index d8cc14e..0000000
--- a/data/recipes/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]
- qty: 1
-output: mithril_med_helm
-message: "You hammer out a mithril med helm."
diff --git a/data/recipes/smith_mithril_nails.yaml b/data/recipes/smith_mithril_nails.yaml
deleted file mode 100644
index 5f33c62..0000000
--- a/data/recipes/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]
- qty: 1
-output: mithril_nails
-output_qty: 15
-message: "You hammer out some mithril nails."
diff --git a/data/recipes/smith_mithril_platebody.yaml b/data/recipes/smith_mithril_platebody.yaml
deleted file mode 100644
index 43efb1d..0000000
--- a/data/recipes/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]
- qty: 5
-output: mithril_platebody
-message: "You hammer out a mithril platebody."
diff --git a/data/recipes/smith_mithril_sword.yaml b/data/recipes/smith_mithril_sword.yaml
deleted file mode 100644
index 98cf6dc..0000000
--- a/data/recipes/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]
- qty: 1
-output: mithril_sword
-message: "You hammer out a mithril sword."
diff --git a/data/recipes/smith_rune_arrowtips.yaml b/data/recipes/smith_rune_arrowtips.yaml
deleted file mode 100644
index 4d2731e..0000000
--- a/data/recipes/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]
- qty: 1
-output: rune_arrowtips
-output_qty: 15
-message: "You hammer out some rune arrowtips."
diff --git a/data/recipes/smith_rune_bolts_unf.yaml b/data/recipes/smith_rune_bolts_unf.yaml
deleted file mode 100644
index 088871b..0000000
--- a/data/recipes/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]
- qty: 1
-output: rune_bolts_unf
-output_qty: 10
-message: "You hammer out some rune bolts."
diff --git a/data/recipes/smith_rune_dagger.yaml b/data/recipes/smith_rune_dagger.yaml
deleted file mode 100644
index 3b76cae..0000000
--- a/data/recipes/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]
- qty: 1
-output: rune_dagger
-message: "You hammer out a rune dagger."
diff --git a/data/recipes/smith_rune_full_helm.yaml b/data/recipes/smith_rune_full_helm.yaml
deleted file mode 100644
index 1714afa..0000000
--- a/data/recipes/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]
- qty: 2
-output: rune_full_helm
-message: "You hammer out a rune full helm."
diff --git a/data/recipes/smith_rune_med_helm.yaml b/data/recipes/smith_rune_med_helm.yaml
deleted file mode 100644
index 0c58be5..0000000
--- a/data/recipes/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]
- qty: 1
-output: rune_med_helm
-message: "You hammer out a rune med helm."
diff --git a/data/recipes/smith_rune_nails.yaml b/data/recipes/smith_rune_nails.yaml
deleted file mode 100644
index ad99d41..0000000
--- a/data/recipes/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]
- qty: 1
-output: rune_nails
-output_qty: 15
-message: "You hammer out some rune nails."
diff --git a/data/recipes/smith_rune_platebody.yaml b/data/recipes/smith_rune_platebody.yaml
deleted file mode 100644
index 7f27292..0000000
--- a/data/recipes/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]
- qty: 5
-output: rune_platebody
-message: "You hammer out a rune platebody."
diff --git a/data/recipes/smith_rune_sword.yaml b/data/recipes/smith_rune_sword.yaml
deleted file mode 100644
index c329102..0000000
--- a/data/recipes/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]
- qty: 1
-output: rune_sword
-message: "You hammer out a rune sword."
diff --git a/data/recipes/smith_steel_arrowtips.yaml b/data/recipes/smith_steel_arrowtips.yaml
deleted file mode 100644
index 0f97327..0000000
--- a/data/recipes/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]
- qty: 1
-output: steel_arrowtips
-output_qty: 15
-message: "You hammer out some steel arrowtips."
diff --git a/data/recipes/smith_steel_bolts_unf.yaml b/data/recipes/smith_steel_bolts_unf.yaml
deleted file mode 100644
index 550dd5e..0000000
--- a/data/recipes/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]
- qty: 1
-output: steel_bolts_unf
-output_qty: 10
-message: "You hammer out some steel bolts."
diff --git a/data/recipes/smith_steel_dagger.yaml b/data/recipes/smith_steel_dagger.yaml
deleted file mode 100644
index e569065..0000000
--- a/data/recipes/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]
- qty: 1
-output: steel_dagger
-message: "You hammer out a steel dagger."
diff --git a/data/recipes/smith_steel_full_helm.yaml b/data/recipes/smith_steel_full_helm.yaml
deleted file mode 100644
index a23b110..0000000
--- a/data/recipes/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]
- qty: 2
-output: steel_full_helm
-message: "You hammer out a steel full helm."
diff --git a/data/recipes/smith_steel_med_helm.yaml b/data/recipes/smith_steel_med_helm.yaml
deleted file mode 100644
index 264b487..0000000
--- a/data/recipes/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]
- qty: 1
-output: steel_med_helm
-message: "You hammer out a steel med helm."
diff --git a/data/recipes/smith_steel_nails.yaml b/data/recipes/smith_steel_nails.yaml
deleted file mode 100644
index 63a5881..0000000
--- a/data/recipes/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]
- qty: 1
-output: steel_nails
-output_qty: 15
-message: "You hammer out some steel nails."
diff --git a/data/recipes/smith_steel_platebody.yaml b/data/recipes/smith_steel_platebody.yaml
deleted file mode 100644
index 5d001b0..0000000
--- a/data/recipes/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]
- qty: 5
-output: steel_platebody
-message: "You hammer out a steel platebody."
diff --git a/data/recipes/smith_steel_sword.yaml b/data/recipes/smith_steel_sword.yaml
deleted file mode 100644
index b8c18bd..0000000
--- a/data/recipes/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]
- qty: 1
-output: steel_sword
-message: "You hammer out a steel sword."
diff --git a/data/recipes/smithing/smith_adamant_arrowtips.yaml b/data/recipes/smithing/smith_adamant_arrowtips.yaml
new file mode 100644
index 0000000..466cfa2
--- /dev/null
+++ b/data/recipes/smithing/smith_adamant_arrowtips.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..2fe1730
--- /dev/null
+++ b/data/recipes/smithing/smith_adamant_bolts_unf.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..fb68dbf
--- /dev/null
+++ b/data/recipes/smithing/smith_adamant_dagger.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..2d849e5
--- /dev/null
+++ b/data/recipes/smithing/smith_adamant_full_helm.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..505691c
--- /dev/null
+++ b/data/recipes/smithing/smith_adamant_med_helm.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..2273b67
--- /dev/null
+++ b/data/recipes/smithing/smith_adamant_nails.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..33dc400
--- /dev/null
+++ b/data/recipes/smithing/smith_adamant_platebody.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..e853ea6
--- /dev/null
+++ b/data/recipes/smithing/smith_adamant_sword.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..77d724f
--- /dev/null
+++ b/data/recipes/smithing/smith_bronze_arrowtips.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..340d14f
--- /dev/null
+++ b/data/recipes/smithing/smith_bronze_bolts_unf.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..4842927
--- /dev/null
+++ b/data/recipes/smithing/smith_bronze_dagger.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..6ef58ed
--- /dev/null
+++ b/data/recipes/smithing/smith_bronze_full_helm.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..6abbc60
--- /dev/null
+++ b/data/recipes/smithing/smith_bronze_med_helm.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..c75a957
--- /dev/null
+++ b/data/recipes/smithing/smith_bronze_nails.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..c9d7d39
--- /dev/null
+++ b/data/recipes/smithing/smith_bronze_platebody.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..322ca9f
--- /dev/null
+++ b/data/recipes/smithing/smith_bronze_sword.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..d11286e
--- /dev/null
+++ b/data/recipes/smithing/smith_iron_arrowtips.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..9529cab
--- /dev/null
+++ b/data/recipes/smithing/smith_iron_bolts_unf.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..3694ca5
--- /dev/null
+++ b/data/recipes/smithing/smith_iron_dagger.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..cfa27f5
--- /dev/null
+++ b/data/recipes/smithing/smith_iron_full_helm.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..54b6654
--- /dev/null
+++ b/data/recipes/smithing/smith_iron_med_helm.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..cb6ad89
--- /dev/null
+++ b/data/recipes/smithing/smith_iron_nails.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..dd8a7d6
--- /dev/null
+++ b/data/recipes/smithing/smith_iron_platebody.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..c2d4bc5
--- /dev/null
+++ b/data/recipes/smithing/smith_iron_sword.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..2d27604
--- /dev/null
+++ b/data/recipes/smithing/smith_mithril_arrowtips.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..03afe7f
--- /dev/null
+++ b/data/recipes/smithing/smith_mithril_bolts_unf.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..a437969
--- /dev/null
+++ b/data/recipes/smithing/smith_mithril_dagger.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..f9d6b74
--- /dev/null
+++ b/data/recipes/smithing/smith_mithril_full_helm.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..c4ebc30
--- /dev/null
+++ b/data/recipes/smithing/smith_mithril_med_helm.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..6c9f225
--- /dev/null
+++ b/data/recipes/smithing/smith_mithril_nails.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..751a7ea
--- /dev/null
+++ b/data/recipes/smithing/smith_mithril_platebody.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..fdf96ff
--- /dev/null
+++ b/data/recipes/smithing/smith_mithril_sword.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..93554a9
--- /dev/null
+++ b/data/recipes/smithing/smith_rune_arrowtips.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..93047ce
--- /dev/null
+++ b/data/recipes/smithing/smith_rune_bolts_unf.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..a87b356
--- /dev/null
+++ b/data/recipes/smithing/smith_rune_dagger.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..79ae3b0
--- /dev/null
+++ b/data/recipes/smithing/smith_rune_full_helm.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..74e639a
--- /dev/null
+++ b/data/recipes/smithing/smith_rune_med_helm.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..2681a57
--- /dev/null
+++ b/data/recipes/smithing/smith_rune_nails.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..4a36082
--- /dev/null
+++ b/data/recipes/smithing/smith_rune_platebody.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..20f94f6
--- /dev/null
+++ b/data/recipes/smithing/smith_rune_sword.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..1ec292f
--- /dev/null
+++ b/data/recipes/smithing/smith_steel_arrowtips.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..cc071ef
--- /dev/null
+++ b/data/recipes/smithing/smith_steel_bolts_unf.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..6e2f1c4
--- /dev/null
+++ b/data/recipes/smithing/smith_steel_dagger.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..c95d0d0
--- /dev/null
+++ b/data/recipes/smithing/smith_steel_full_helm.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..3415ec8
--- /dev/null
+++ b/data/recipes/smithing/smith_steel_med_helm.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..2da69c8
--- /dev/null
+++ b/data/recipes/smithing/smith_steel_nails.yaml
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000..da11d11
--- /dev/null
+++ b/data/recipes/smithing/smith_steel_platebody.yaml
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..63270bc
--- /dev/null
+++ b/data/recipes/smithing/smith_steel_sword.yaml
@@ -0,0 +1,11 @@
+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/rooms/1.yaml b/data/rooms/1.yaml
deleted file mode 100644
index c4ec261..0000000
--- a/data/rooms/1.yaml
+++ /dev/null
@@ -1,38 +0,0 @@
-id: 1
-name: "Town Square"
-description: "The central square of Lumbridge. A fountain gurgles peacefully in the center. Paths lead west toward the gathering grounds and east toward the production district."
-map_symbol: "S"
-exits:
- east: 2
- west: 22
- up: 100
- down: 31
-objects:
- - id: lumby_fountain
- - id: charging_station
- - id: bank_booth
-mobs:
- - "newbie_trainer"
- - "general_store_clerk"
-spawns:
- - item_id: cape_of_agility
- quantity: 1
- respawn_ticks: 60
- - item_id: graceful_hat
- quantity: 1
- respawn_ticks: 60
- - item_id: graceful_torso
- quantity: 1
- respawn_ticks: 60
- - item_id: graceful_legs
- quantity: 1
- respawn_ticks: 60
- - item_id: graceful_gloves
- quantity: 1
- respawn_ticks: 60
- - item_id: graceful_boots
- quantity: 1
- respawn_ticks: 60
- - item_id: graceful_cape
- quantity: 1
- respawn_ticks: 60
diff --git a/data/rooms/11.yaml b/data/rooms/11.yaml
deleted file mode 100644
index a7b3247..0000000
--- a/data/rooms/11.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-id: 11
-name: "Kitchen"
-description: "A warm kitchen with a stone hearth and wooden counters. A large iron cooking range dominates one wall, and shelves are stocked with ingredients."
-exits:
- east: 12
- west: 10
-objects:
- - id: cooking_range
-mobs:
- - id: zombie
-spawns:
- - item_id: bucket_of_water
- quantity: 1
- respawn_ticks: 200
- - item_id: pot_of_flour
- quantity: 1
- respawn_ticks: 200
diff --git a/data/rooms/12.yaml b/data/rooms/12.yaml
deleted file mode 100644
index 80380b8..0000000
--- a/data/rooms/12.yaml
+++ /dev/null
@@ -1,28 +0,0 @@
-id: 12
-name: "Forge"
-description: "A blazing forge and anvil dominate this smoky workshop. The heat from the furnace is intense."
-exits:
- east: 13
- west: 11
-mobs:
- - "weapons_smith"
-objects:
- - id: furnace
- - id: anvil
- - id: bank_booth
-spawns:
- - item_id: copper_ore
- quantity: 3
- respawn_ticks: 30
- - item_id: tin_ore
- quantity: 3
- respawn_ticks: 30
- - item_id: iron_ore
- quantity: 3
- respawn_ticks: 30
- - item_id: coal
- quantity: 5
- respawn_ticks: 30
- - item_id: hammer
- quantity: 1
- respawn_ticks: 30
diff --git a/data/rooms/13.yaml b/data/rooms/13.yaml
deleted file mode 100644
index 939e751..0000000
--- a/data/rooms/13.yaml
+++ /dev/null
@@ -1,46 +0,0 @@
-id: 13
-name: "Crafting Workshop"
-description: "A bustling workshop filled with crafting stations. A {130}spinning wheel{/} sits in one corner, a {173}pottery wheel{/} and {196}pottery oven{/} in another. Racks of tools and raw materials line the walls. The forge is just to the west."
-exits:
- east: 14
- west: 12
-objects:
- - id: spinning_wheel
- - id: pottery_wheel
- - id: pottery_oven
- - id: sheep
- - id: clay_rock
- - id: clay_rock
-mobs:
- - id: tanner
- - id: cow
- - id: cow
- - id: cow
-spawns:
- - item_id: flax
- quantity: 5
- respawn_ticks: 60
- - item_id: thread
- quantity: 10
- respawn_ticks: 60
- - item_id: shears
- quantity: 1
- respawn_ticks: 60
- - item_id: needle
- quantity: 1
- respawn_ticks: 60
- - item_id: chisel
- quantity: 1
- respawn_ticks: 60
- - item_id: ring_mould
- quantity: 1
- respawn_ticks: 60
- - item_id: necklace_mould
- quantity: 1
- respawn_ticks: 60
- - item_id: amulet_mould
- quantity: 1
- respawn_ticks: 60
- - item_id: bracelet_mould
- quantity: 1
- respawn_ticks: 60
diff --git a/data/rooms/14.yaml b/data/rooms/14.yaml
deleted file mode 100644
index 63db344..0000000
--- a/data/rooms/14.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-id: 14
-name: "Fletching Bench"
-description: "A workbench covered in wood shavings, feathers, and arrowheads. Racks of unfinished bows line the walls, and a pile of bowstrings sits in the corner."
-exits:
- east: 15
- west: 13
-mobs:
- - id: moss_giant
-spawns:
- - item_id: bowstring
- quantity: 5
- respawn_ticks: 50
- - item_id: knife
- quantity: 1
- respawn_ticks: 50
- - item_id: chisel
- quantity: 1
- respawn_ticks: 50
diff --git a/data/rooms/150.yaml b/data/rooms/150.yaml
deleted file mode 100644
index 462a30c..0000000
--- a/data/rooms/150.yaml
+++ /dev/null
@@ -1,32 +0,0 @@
-id: 150
-name: "Hydroponics Bay"
-description: "A large enclosed area with climate-controlled growing stations. {34}Lush green patches{/} of soil are laid out in neat rows under artificial UV lamps. A {94}tool shed{/} stands near the entrance. Exits lead to specialized growing areas."
-map_symbol: "H"
-exits:
- north: 15
- east: 151
- south: 152
-objects:
- - id: tool_shed
-spawns:
- - item_id: rake
- quantity: 1
- respawn_ticks: 120
- - item_id: spade
- quantity: 1
- respawn_ticks: 120
- - item_id: watering_can
- quantity: 1
- respawn_ticks: 120
- - item_id: plant_cure
- quantity: 3
- respawn_ticks: 200
- - item_id: guam_seed
- quantity: 5
- respawn_ticks: 300
- - item_id: potato_seed
- quantity: 10
- respawn_ticks: 300
- - item_id: onion_seed
- quantity: 10
- respawn_ticks: 300
diff --git a/data/rooms/2.yaml b/data/rooms/2.yaml
deleted file mode 100644
index 39fcb9a..0000000
--- a/data/rooms/2.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-id: 2
-name: "Fishing Spot"
-description: "A quiet spot along the riverbank. The water is clear and fish can be seen swimming beneath the surface. You can cast a line here with a fishing rod and bait."
-map_symbol: "F"
-exits:
- east: 4
- north: 3
- west: 1
-objects:
- - id: bait_fishing_spot
-spawns:
- - item_id: fishing_rod
- quantity: 1
- respawn_ticks: 30
- - item_id: fishing_bait
- quantity: 20
- respawn_ticks: 60
diff --git a/data/rooms/3.yaml b/data/rooms/3.yaml
deleted file mode 100644
index 8e4d1ed..0000000
--- a/data/rooms/3.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-id: 3
-name: "Fly Fishing Pool"
-description: "A calm pool where insects hover over the water. Perfect for fly fishing."
-map_symbol: "X"
-exits:
- south: 2
-objects:
- - id: fly_fishing_spot
-spawns:
- - item_id: fly_fishing_rod
- quantity: 1
- respawn_ticks: 30
- - item_id: feather
- quantity: 15
- respawn_ticks: 60
diff --git a/data/rooms/4.yaml b/data/rooms/4.yaml
deleted file mode 100644
index 6a60783..0000000
--- a/data/rooms/4.yaml
+++ /dev/null
@@ -1,29 +0,0 @@
-id: 4
-name: "Woodcutting Glade"
-description: "A sunny glade at the edge of the forest. A sturdy tree stands here, perfect for practicing woodcutting."
-map_symbol: "W"
-exits:
- east: 6
- north: 5
- west: 2
-objects:
- - id: tree
- - id: tree
- - id: tree
- - id: tree
-mobs:
- - id: rat
- - id: rat
-spawns:
- - item_id: bronze_axe
- quantity: 1
- respawn_ticks: 60
- - item_id: matches
- quantity: 100
- respawn_ticks: 120
- - item_id: firesteel
- quantity: 1
- respawn_ticks: 120
- - item_id: lighter
- quantity: 1
- respawn_ticks: 120
diff --git a/data/rooms/5.yaml b/data/rooms/5.yaml
deleted file mode 100644
index 195c098..0000000
--- a/data/rooms/5.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: 5
-name: "Oak Grove"
-description: "A stand of young oak trees. The wood here is tougher but more valuable."
-map_symbol: "O"
-exits:
- south: 4
-objects:
- - id: oak_tree
-spawns:
- - item_id: iron_axe
- quantity: 1
- respawn_ticks: 60
diff --git a/data/rooms/6.yaml b/data/rooms/6.yaml
deleted file mode 100644
index 66d8d85..0000000
--- a/data/rooms/6.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-id: 6
-name: "Mining Pit"
-description: "A small mining pit. The rocky walls glint with veins of copper ore."
-map_symbol: "M"
-exits:
- east: 8
- north: 7
- west: 4
-objects:
- - id: copper_rock
- - id: copper_rock
- - id: copper_rock
-spawns:
- - item_id: bronze_pickaxe
- quantity: 1
- respawn_ticks: 30
diff --git a/data/rooms/9.yaml b/data/rooms/9.yaml
deleted file mode 100644
index fe15233..0000000
--- a/data/rooms/9.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-id: 9
-name: "Scavenging Post"
-description: "A cluttered yard full of {243}scrap metal{/} and discarded technology. Piles of salvageable debris are scattered across the ground. The air smells of ozone and rust."
-exits:
- east: 10
- west: 8
- north: 150
- south: 151
- northeast: 152
- southeast: 153
- northwest: 154
- southwest: 155
-objects:
- - id: scrap_pile
- - id: scrap_pile
- - id: scrap_pile
- - id: solar_altar
diff --git a/data/rooms/200.yaml b/data/rooms/agility/200.yaml
index 469e214..469e214 100644
--- a/data/rooms/200.yaml
+++ b/data/rooms/agility/200.yaml
diff --git a/data/rooms/201.yaml b/data/rooms/agility/201.yaml
index 2beb2ec..2beb2ec 100644
--- a/data/rooms/201.yaml
+++ b/data/rooms/agility/201.yaml
diff --git a/data/rooms/202.yaml b/data/rooms/agility/202.yaml
index b9c0292..b9c0292 100644
--- a/data/rooms/202.yaml
+++ b/data/rooms/agility/202.yaml
diff --git a/data/rooms/203.yaml b/data/rooms/agility/203.yaml
index 4f5d883..4f5d883 100644
--- a/data/rooms/203.yaml
+++ b/data/rooms/agility/203.yaml
diff --git a/data/rooms/204.yaml b/data/rooms/agility/204.yaml
index 2d82040..2d82040 100644
--- a/data/rooms/204.yaml
+++ b/data/rooms/agility/204.yaml
diff --git a/data/rooms/205.yaml b/data/rooms/agility/205.yaml
index d814f7d..d814f7d 100644
--- a/data/rooms/205.yaml
+++ b/data/rooms/agility/205.yaml
diff --git a/data/rooms/210.yaml b/data/rooms/agility/210.yaml
index 2b86500..2b86500 100644
--- a/data/rooms/210.yaml
+++ b/data/rooms/agility/210.yaml
diff --git a/data/rooms/211.yaml b/data/rooms/agility/211.yaml
index 8b1f069..8b1f069 100644
--- a/data/rooms/211.yaml
+++ b/data/rooms/agility/211.yaml
diff --git a/data/rooms/212.yaml b/data/rooms/agility/212.yaml
index 0393a7e..0393a7e 100644
--- a/data/rooms/212.yaml
+++ b/data/rooms/agility/212.yaml
diff --git a/data/rooms/213.yaml b/data/rooms/agility/213.yaml
index be3b736..be3b736 100644
--- a/data/rooms/213.yaml
+++ b/data/rooms/agility/213.yaml
diff --git a/data/rooms/214.yaml b/data/rooms/agility/214.yaml
index 9f9e081..9f9e081 100644
--- a/data/rooms/214.yaml
+++ b/data/rooms/agility/214.yaml
diff --git a/data/rooms/215.yaml b/data/rooms/agility/215.yaml
index 7ea39f9..7ea39f9 100644
--- a/data/rooms/215.yaml
+++ b/data/rooms/agility/215.yaml
diff --git a/data/rooms/220.yaml b/data/rooms/agility/220.yaml
index d6826a3..d6826a3 100644
--- a/data/rooms/220.yaml
+++ b/data/rooms/agility/220.yaml
diff --git a/data/rooms/221.yaml b/data/rooms/agility/221.yaml
index 936ee65..936ee65 100644
--- a/data/rooms/221.yaml
+++ b/data/rooms/agility/221.yaml
diff --git a/data/rooms/222.yaml b/data/rooms/agility/222.yaml
index 119db01..119db01 100644
--- a/data/rooms/222.yaml
+++ b/data/rooms/agility/222.yaml
diff --git a/data/rooms/223.yaml b/data/rooms/agility/223.yaml
index 0e557f6..0e557f6 100644
--- a/data/rooms/223.yaml
+++ b/data/rooms/agility/223.yaml
diff --git a/data/rooms/224.yaml b/data/rooms/agility/224.yaml
index 4e70f7a..4e70f7a 100644
--- a/data/rooms/224.yaml
+++ b/data/rooms/agility/224.yaml
diff --git a/data/rooms/225.yaml b/data/rooms/agility/225.yaml
index 3413792..3413792 100644
--- a/data/rooms/225.yaml
+++ b/data/rooms/agility/225.yaml
diff --git a/data/rooms/226.yaml b/data/rooms/agility/226.yaml
index 6faa2c1..6faa2c1 100644
--- a/data/rooms/226.yaml
+++ b/data/rooms/agility/226.yaml
diff --git a/data/rooms/player_housing/200.yaml b/data/rooms/player_housing/200.yaml
deleted file mode 100644
index 1781818..0000000
--- a/data/rooms/player_housing/200.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-id: 200
-name: "Player House: Entrance Hall"
-description: "A cozy entrance hall with polished floors and warm lighting. A welcome mat greets visitors at the door."
-exits:
- south: 170
- north: 201
-objects:
- - id: charging_station
diff --git a/data/rooms/player_housing/201.yaml b/data/rooms/player_housing/201.yaml
deleted file mode 100644
index dbc1fd9..0000000
--- a/data/rooms/player_housing/201.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-id: 201
-name: "Player House: Workshop"
-description: "A well-lit workshop with a sturdy workbench covered in tools and blueprints. Wood shavings litter the floor around the bench."
-exits:
- south: 200
-objects:
- - id: workbench
- - id: bank_booth
diff --git a/data/rooms/player_housing/300.yaml b/data/rooms/player_housing/300.yaml
new file mode 100644
index 0000000..06f1a07
--- /dev/null
+++ b/data/rooms/player_housing/300.yaml
@@ -0,0 +1,8 @@
+id: 300
+name: "Player House: Entrance Hall"
+description: "A cozy entrance hall with polished floors and warm lighting. A welcome mat greets visitors at the door."
+exits:
+ south: 170
+ north: 301
+objects:
+ - id: charging_station
diff --git a/data/rooms/player_housing/301.yaml b/data/rooms/player_housing/301.yaml
new file mode 100644
index 0000000..c2f3e65
--- /dev/null
+++ b/data/rooms/player_housing/301.yaml
@@ -0,0 +1,8 @@
+id: 301
+name: "Player House: Workshop"
+description: "A well-lit workshop with a sturdy workbench covered in tools and blueprints. Wood shavings litter the floor around the bench."
+exits:
+ south: 300
+objects:
+ - id: workbench
+ - id: bank_booth
diff --git a/data/rooms/town/1.yaml b/data/rooms/town/1.yaml
new file mode 100644
index 0000000..ec97af2
--- /dev/null
+++ b/data/rooms/town/1.yaml
@@ -0,0 +1,38 @@
+id: 1
+name: "Town Square"
+description: "The central square of Lumbridge. A fountain gurgles peacefully in the center. Paths lead west toward the gathering grounds and east toward the production district."
+map_symbol: "S"
+exits:
+ east: 2
+ west: 22
+ up: 30
+ down: 31
+objects:
+ - id: lumby_fountain
+ - id: charging_station
+ - id: bank_booth
+mobs:
+ - "newbie_trainer"
+ - "general_store_clerk"
+item_spawns:
+ - id: cape_of_agility
+ quantity: 1
+ respawn_ticks: 60
+ - id: graceful_hat
+ quantity: 1
+ respawn_ticks: 60
+ - id: graceful_torso
+ quantity: 1
+ respawn_ticks: 60
+ - id: graceful_legs
+ quantity: 1
+ respawn_ticks: 60
+ - id: graceful_gloves
+ quantity: 1
+ respawn_ticks: 60
+ - id: graceful_boots
+ quantity: 1
+ respawn_ticks: 60
+ - id: graceful_cape
+ quantity: 1
+ respawn_ticks: 60
diff --git a/data/rooms/10.yaml b/data/rooms/town/10.yaml
index edeac4d..edeac4d 100644
--- a/data/rooms/10.yaml
+++ b/data/rooms/town/10.yaml
diff --git a/data/rooms/town/11.yaml b/data/rooms/town/11.yaml
new file mode 100644
index 0000000..9bc45a7
--- /dev/null
+++ b/data/rooms/town/11.yaml
@@ -0,0 +1,17 @@
+id: 11
+name: "Kitchen"
+description: "A warm kitchen with a stone hearth and wooden counters. A large iron cooking range dominates one wall, and shelves are stocked with ingredients."
+exits:
+ east: 12
+ west: 10
+objects:
+ - id: cooking_range
+mobs:
+ - id: zombie
+item_spawns:
+ - id: bucket_of_water
+ quantity: 1
+ respawn_ticks: 200
+ - id: pot_of_flour
+ quantity: 1
+ respawn_ticks: 200
diff --git a/data/rooms/town/12.yaml b/data/rooms/town/12.yaml
new file mode 100644
index 0000000..e44ae3a
--- /dev/null
+++ b/data/rooms/town/12.yaml
@@ -0,0 +1,28 @@
+id: 12
+name: "Forge"
+description: "A blazing forge and anvil dominate this smoky workshop. The heat from the furnace is intense."
+exits:
+ east: 13
+ west: 11
+mobs:
+ - "weapons_smith"
+objects:
+ - id: furnace
+ - id: anvil
+ - id: bank_booth
+item_spawns:
+ - id: copper_ore
+ quantity: 3
+ respawn_ticks: 30
+ - id: tin_ore
+ quantity: 3
+ respawn_ticks: 30
+ - id: iron_ore
+ quantity: 3
+ respawn_ticks: 30
+ - id: coal
+ quantity: 5
+ respawn_ticks: 30
+ - id: hammer
+ quantity: 1
+ respawn_ticks: 30
diff --git a/data/rooms/town/13.yaml b/data/rooms/town/13.yaml
new file mode 100644
index 0000000..dc07dc1
--- /dev/null
+++ b/data/rooms/town/13.yaml
@@ -0,0 +1,46 @@
+id: 13
+name: "Crafting Workshop"
+description: "A bustling workshop filled with crafting stations. A {130}spinning wheel{/} sits in one corner, a {173}pottery wheel{/} and {196}pottery oven{/} in another. Racks of tools and raw materials line the walls. The forge is just to the west."
+exits:
+ east: 14
+ west: 12
+objects:
+ - id: spinning_wheel
+ - id: pottery_wheel
+ - id: pottery_oven
+ - id: sheep
+ - id: clay_rock
+ - id: clay_rock
+mobs:
+ - id: tanner
+ - id: cow
+ - id: cow
+ - id: cow
+item_spawns:
+ - id: flax
+ quantity: 5
+ respawn_ticks: 60
+ - id: thread
+ quantity: 10
+ respawn_ticks: 60
+ - id: shears
+ quantity: 1
+ respawn_ticks: 60
+ - id: needle
+ quantity: 1
+ respawn_ticks: 60
+ - id: chisel
+ quantity: 1
+ respawn_ticks: 60
+ - id: ring_mould
+ quantity: 1
+ respawn_ticks: 60
+ - id: necklace_mould
+ quantity: 1
+ respawn_ticks: 60
+ - id: amulet_mould
+ quantity: 1
+ respawn_ticks: 60
+ - id: bracelet_mould
+ quantity: 1
+ respawn_ticks: 60
diff --git a/data/rooms/town/14.yaml b/data/rooms/town/14.yaml
new file mode 100644
index 0000000..ad515de
--- /dev/null
+++ b/data/rooms/town/14.yaml
@@ -0,0 +1,18 @@
+id: 14
+name: "Fletching Bench"
+description: "A workbench covered in wood shavings, feathers, and arrowheads. Racks of unfinished bows line the walls, and a pile of bowstrings sits in the corner."
+exits:
+ east: 15
+ west: 13
+mobs:
+ - id: moss_giant
+item_spawns:
+ - id: bowstring
+ quantity: 5
+ respawn_ticks: 50
+ - id: knife
+ quantity: 1
+ respawn_ticks: 50
+ - id: chisel
+ quantity: 1
+ respawn_ticks: 50
diff --git a/data/rooms/15.yaml b/data/rooms/town/15.yaml
index a67624d..a67624d 100644
--- a/data/rooms/15.yaml
+++ b/data/rooms/town/15.yaml
diff --git a/data/rooms/16.yaml b/data/rooms/town/16.yaml
index 3776f4f..3776f4f 100644
--- a/data/rooms/16.yaml
+++ b/data/rooms/town/16.yaml
diff --git a/data/rooms/17.yaml b/data/rooms/town/17.yaml
index 512ec9e..512ec9e 100644
--- a/data/rooms/17.yaml
+++ b/data/rooms/town/17.yaml
diff --git a/data/rooms/18.yaml b/data/rooms/town/18.yaml
index 9dfc446..9dfc446 100644
--- a/data/rooms/18.yaml
+++ b/data/rooms/town/18.yaml
diff --git a/data/rooms/19.yaml b/data/rooms/town/19.yaml
index 35800a0..35800a0 100644
--- a/data/rooms/19.yaml
+++ b/data/rooms/town/19.yaml
diff --git a/data/rooms/town/2.yaml b/data/rooms/town/2.yaml
new file mode 100644
index 0000000..686dc65
--- /dev/null
+++ b/data/rooms/town/2.yaml
@@ -0,0 +1,17 @@
+id: 2
+name: "Fishing Spot"
+description: "A quiet spot along the riverbank. The water is clear and fish can be seen swimming beneath the surface. You can cast a line here with a fishing rod and bait."
+map_symbol: "F"
+exits:
+ east: 4
+ north: 3
+ west: 1
+objects:
+ - id: bait_fishing_spot
+item_spawns:
+ - id: fishing_rod
+ quantity: 1
+ respawn_ticks: 30
+ - id: fishing_bait
+ quantity: 20
+ respawn_ticks: 60
diff --git a/data/rooms/20.yaml b/data/rooms/town/20.yaml
index 379322b..379322b 100644
--- a/data/rooms/20.yaml
+++ b/data/rooms/town/20.yaml
diff --git a/data/rooms/21.yaml b/data/rooms/town/21.yaml
index 987f5ea..987f5ea 100644
--- a/data/rooms/21.yaml
+++ b/data/rooms/town/21.yaml
diff --git a/data/rooms/22.yaml b/data/rooms/town/22.yaml
index 478995b..478995b 100644
--- a/data/rooms/22.yaml
+++ b/data/rooms/town/22.yaml
diff --git a/data/rooms/23.yaml b/data/rooms/town/23.yaml
index f7301f6..f7301f6 100644
--- a/data/rooms/23.yaml
+++ b/data/rooms/town/23.yaml
diff --git a/data/rooms/24.yaml b/data/rooms/town/24.yaml
index 60bf0d8..60bf0d8 100644
--- a/data/rooms/24.yaml
+++ b/data/rooms/town/24.yaml
diff --git a/data/rooms/25.yaml b/data/rooms/town/25.yaml
index a3dd843..a3dd843 100644
--- a/data/rooms/25.yaml
+++ b/data/rooms/town/25.yaml
diff --git a/data/rooms/26.yaml b/data/rooms/town/26.yaml
index a2899e7..a2899e7 100644
--- a/data/rooms/26.yaml
+++ b/data/rooms/town/26.yaml
diff --git a/data/rooms/27.yaml b/data/rooms/town/27.yaml
index d435424..d435424 100644
--- a/data/rooms/27.yaml
+++ b/data/rooms/town/27.yaml
diff --git a/data/rooms/28.yaml b/data/rooms/town/28.yaml
index 03913d5..03913d5 100644
--- a/data/rooms/28.yaml
+++ b/data/rooms/town/28.yaml
diff --git a/data/rooms/town/3.yaml b/data/rooms/town/3.yaml
new file mode 100644
index 0000000..16ce1c8
--- /dev/null
+++ b/data/rooms/town/3.yaml
@@ -0,0 +1,15 @@
+id: 3
+name: "Fly Fishing Pool"
+description: "A calm pool where insects hover over the water. Perfect for fly fishing."
+map_symbol: "X"
+exits:
+ south: 2
+objects:
+ - id: fly_fishing_spot
+item_spawns:
+ - id: fly_fishing_rod
+ quantity: 1
+ respawn_ticks: 30
+ - id: feather
+ quantity: 15
+ respawn_ticks: 60
diff --git a/data/rooms/30.yaml b/data/rooms/town/30.yaml
index 8c167eb..8c167eb 100644
--- a/data/rooms/30.yaml
+++ b/data/rooms/town/30.yaml
diff --git a/data/rooms/31.yaml b/data/rooms/town/31.yaml
index 3d010df..3d010df 100644
--- a/data/rooms/31.yaml
+++ b/data/rooms/town/31.yaml
diff --git a/data/rooms/town/4.yaml b/data/rooms/town/4.yaml
new file mode 100644
index 0000000..2c9f27f
--- /dev/null
+++ b/data/rooms/town/4.yaml
@@ -0,0 +1,29 @@
+id: 4
+name: "Woodcutting Glade"
+description: "A sunny glade at the edge of the forest. A sturdy tree stands here, perfect for practicing woodcutting."
+map_symbol: "W"
+exits:
+ east: 6
+ north: 5
+ west: 2
+objects:
+ - id: tree
+ - id: tree
+ - id: tree
+ - id: tree
+mobs:
+ - id: rat
+ - id: rat
+item_spawns:
+ - id: bronze_axe
+ quantity: 1
+ respawn_ticks: 60
+ - id: matches
+ quantity: 100
+ respawn_ticks: 120
+ - id: firesteel
+ quantity: 1
+ respawn_ticks: 120
+ - id: lighter
+ quantity: 1
+ respawn_ticks: 120
diff --git a/data/rooms/town/5.yaml b/data/rooms/town/5.yaml
new file mode 100644
index 0000000..bd760b1
--- /dev/null
+++ b/data/rooms/town/5.yaml
@@ -0,0 +1,12 @@
+id: 5
+name: "Oak Grove"
+description: "A stand of young oak trees. The wood here is tougher but more valuable."
+map_symbol: "O"
+exits:
+ south: 4
+objects:
+ - id: oak_tree
+item_spawns:
+ - id: iron_axe
+ quantity: 1
+ respawn_ticks: 60
diff --git a/data/rooms/town/6.yaml b/data/rooms/town/6.yaml
new file mode 100644
index 0000000..0fb9c59
--- /dev/null
+++ b/data/rooms/town/6.yaml
@@ -0,0 +1,16 @@
+id: 6
+name: "Mining Pit"
+description: "A small mining pit. The rocky walls glint with veins of copper ore."
+map_symbol: "M"
+exits:
+ east: 8
+ north: 7
+ west: 4
+objects:
+ - id: copper_rock
+ - id: copper_rock
+ - id: copper_rock
+item_spawns:
+ - id: bronze_pickaxe
+ quantity: 1
+ respawn_ticks: 30
diff --git a/data/rooms/7.yaml b/data/rooms/town/7.yaml
index 37dfed9..37dfed9 100644
--- a/data/rooms/7.yaml
+++ b/data/rooms/town/7.yaml
diff --git a/data/rooms/8.yaml b/data/rooms/town/8.yaml
index b82c1bb..b82c1bb 100644
--- a/data/rooms/8.yaml
+++ b/data/rooms/town/8.yaml
diff --git a/data/rooms/town/9.yaml b/data/rooms/town/9.yaml
new file mode 100644
index 0000000..9a19943
--- /dev/null
+++ b/data/rooms/town/9.yaml
@@ -0,0 +1,14 @@
+id: 9
+name: "Scavenging Post"
+description: "A cluttered yard full of {243}scrap metal{/} and discarded technology. Piles of salvageable debris are scattered across the ground. The air smells of ozone and rust."
+exits:
+ east: 10
+ west: 8
+ north: 150
+ south: 151
+ northeast: 152
+objects:
+ - id: scrap_pile
+ - id: scrap_pile
+ - id: scrap_pile
+ - id: solar_altar
diff --git a/data/rooms/50.yaml b/data/rooms/underground/50.yaml
index 0c9ebf2..0c9ebf2 100644
--- a/data/rooms/50.yaml
+++ b/data/rooms/underground/50.yaml
diff --git a/data/rooms/51.yaml b/data/rooms/underground/51.yaml
index 74a97ef..74a97ef 100644
--- a/data/rooms/51.yaml
+++ b/data/rooms/underground/51.yaml
diff --git a/data/rooms/52.yaml b/data/rooms/underground/52.yaml
index 2f90abd..2f90abd 100644
--- a/data/rooms/52.yaml
+++ b/data/rooms/underground/52.yaml
diff --git a/data/rooms/53.yaml b/data/rooms/underground/53.yaml
index 126719b..126719b 100644
--- a/data/rooms/53.yaml
+++ b/data/rooms/underground/53.yaml
diff --git a/data/rooms/54.yaml b/data/rooms/underground/54.yaml
index e86e5b7..e86e5b7 100644
--- a/data/rooms/54.yaml
+++ b/data/rooms/underground/54.yaml
diff --git a/data/rooms/55.yaml b/data/rooms/underground/55.yaml
index 8000170..8000170 100644
--- a/data/rooms/55.yaml
+++ b/data/rooms/underground/55.yaml
diff --git a/data/rooms/56.yaml b/data/rooms/underground/56.yaml
index 375696d..375696d 100644
--- a/data/rooms/56.yaml
+++ b/data/rooms/underground/56.yaml
diff --git a/data/rooms/57.yaml b/data/rooms/underground/57.yaml
index 714197d..714197d 100644
--- a/data/rooms/57.yaml
+++ b/data/rooms/underground/57.yaml
diff --git a/data/rooms/58.yaml b/data/rooms/underground/58.yaml
index f4d1829..f4d1829 100644
--- a/data/rooms/58.yaml
+++ b/data/rooms/underground/58.yaml
diff --git a/data/rooms/59.yaml b/data/rooms/underground/59.yaml
index a1cc594..a1cc594 100644
--- a/data/rooms/59.yaml
+++ b/data/rooms/underground/59.yaml
diff --git a/data/rooms/60.yaml b/data/rooms/underground/60.yaml
index e9b7114..e9b7114 100644
--- a/data/rooms/60.yaml
+++ b/data/rooms/underground/60.yaml
diff --git a/data/rooms/61.yaml b/data/rooms/underground/61.yaml
index a85c7ba..a85c7ba 100644
--- a/data/rooms/61.yaml
+++ b/data/rooms/underground/61.yaml
diff --git a/data/rooms/62.yaml b/data/rooms/underground/62.yaml
index 9ed1fad..9ed1fad 100644
--- a/data/rooms/62.yaml
+++ b/data/rooms/underground/62.yaml
diff --git a/data/rooms/63.yaml b/data/rooms/underground/63.yaml
index b6bf53e..b6bf53e 100644
--- a/data/rooms/63.yaml
+++ b/data/rooms/underground/63.yaml
diff --git a/data/rooms/64.yaml b/data/rooms/underground/64.yaml
index b412992..b412992 100644
--- a/data/rooms/64.yaml
+++ b/data/rooms/underground/64.yaml
diff --git a/data/rooms/65.yaml b/data/rooms/underground/65.yaml
index 47aed6b..47aed6b 100644
--- a/data/rooms/65.yaml
+++ b/data/rooms/underground/65.yaml
diff --git a/data/rooms/66.yaml b/data/rooms/underground/66.yaml
index 755d5e2..755d5e2 100644
--- a/data/rooms/66.yaml
+++ b/data/rooms/underground/66.yaml
diff --git a/data/rooms/67.yaml b/data/rooms/underground/67.yaml
index 6b8d663..6b8d663 100644
--- a/data/rooms/67.yaml
+++ b/data/rooms/underground/67.yaml
diff --git a/data/rooms/68.yaml b/data/rooms/underground/68.yaml
index f631981..f631981 100644
--- a/data/rooms/68.yaml
+++ b/data/rooms/underground/68.yaml
diff --git a/data/rooms/wilderness/150.yaml b/data/rooms/wilderness/150.yaml
new file mode 100644
index 0000000..36b86ea
--- /dev/null
+++ b/data/rooms/wilderness/150.yaml
@@ -0,0 +1,32 @@
+id: 150
+name: "Hydroponics Bay"
+description: "A large enclosed area with climate-controlled growing stations. {34}Lush green patches{/} of soil are laid out in neat rows under artificial UV lamps. A {94}tool shed{/} stands near the entrance. Exits lead to specialized growing areas."
+map_symbol: "H"
+exits:
+ north: 15
+ east: 151
+ south: 152
+objects:
+ - id: tool_shed
+item_spawns:
+ - id: rake
+ quantity: 1
+ respawn_ticks: 120
+ - id: spade
+ quantity: 1
+ respawn_ticks: 120
+ - id: watering_can
+ quantity: 1
+ respawn_ticks: 120
+ - id: plant_cure
+ quantity: 3
+ respawn_ticks: 200
+ - id: guam_seed
+ quantity: 5
+ respawn_ticks: 300
+ - id: potato_seed
+ quantity: 10
+ respawn_ticks: 300
+ - id: onion_seed
+ quantity: 10
+ respawn_ticks: 300
diff --git a/data/rooms/151.yaml b/data/rooms/wilderness/151.yaml
index 1bb25bf..1bb25bf 100644
--- a/data/rooms/151.yaml
+++ b/data/rooms/wilderness/151.yaml
diff --git a/data/rooms/152.yaml b/data/rooms/wilderness/152.yaml
index 5d2f62d..5d2f62d 100644
--- a/data/rooms/152.yaml
+++ b/data/rooms/wilderness/152.yaml
diff --git a/data/rooms/160.yaml b/data/rooms/wilderness/160.yaml
index dea9d00..dea9d00 100644
--- a/data/rooms/160.yaml
+++ b/data/rooms/wilderness/160.yaml
diff --git a/data/rooms/161.yaml b/data/rooms/wilderness/161.yaml
index c887347..c887347 100644
--- a/data/rooms/161.yaml
+++ b/data/rooms/wilderness/161.yaml
diff --git a/data/rooms/162.yaml b/data/rooms/wilderness/162.yaml
index a8333d8..a8333d8 100644
--- a/data/rooms/162.yaml
+++ b/data/rooms/wilderness/162.yaml
diff --git a/data/rooms/170.yaml b/data/rooms/wilderness/170.yaml
index 35d539f..35d539f 100644
--- a/data/rooms/170.yaml
+++ b/data/rooms/wilderness/170.yaml
diff --git a/data/rooms/171.yaml b/data/rooms/wilderness/171.yaml
index 552ccd4..552ccd4 100644
--- a/data/rooms/171.yaml
+++ b/data/rooms/wilderness/171.yaml
diff --git a/internal/action/recipe.go b/internal/action/recipe.go
index c1f30fb..5a69195 100644
--- a/internal/action/recipe.go
+++ b/internal/action/recipe.go
@@ -1,7 +1,6 @@
package action
import (
- "os"
"path/filepath"
"gopkg.in/yaml.v3"
@@ -9,7 +8,7 @@ import (
type ConsumeEntry struct {
Items []string `yaml:"items"`
- Qty int `yaml:"qty"`
+ Quantity int `yaml:"quantity"`
Byproducts []string `yaml:"byproducts"`
}
@@ -80,25 +79,15 @@ func (s *RecipeStore) LoadByType(recipeType string) ([]RecipeDef, error) {
func (s *RecipeStore) loadAllRaw() ([]RecipeDef, error) {
dir := filepath.Join(s.dataDir, "recipes")
- entries, err := os.ReadDir(dir)
- if err != nil {
- return nil, err
- }
var recipes []RecipeDef
- for _, e := range entries {
- if e.IsDir() || filepath.Ext(e.Name()) != ".yaml" {
- continue
- }
- data, err := os.ReadFile(filepath.Join(dir, e.Name()))
- if err != nil {
- continue
- }
+ WalkYAMLDir(dir, func(path, id string, data []byte) error {
var r RecipeDef
if err := yaml.Unmarshal(data, &r); err != nil {
- continue
+ return nil
}
recipes = append(recipes, r)
- }
+ return nil
+ })
return recipes, nil
}
@@ -190,23 +179,23 @@ func (r *RecipeDef) ConsumeAll(hasItem func(string) bool, removeItem func(string
found := false
for _, id := range e.Items {
if hasItem(id) {
- removeItem(id, e.Qty)
- found = true
- break
- }
- }
- if !found {
- return false
+ removeItem(id, e.Quantity)
+ found = true
+ break
}
}
- return true
+ if !found {
+ return false
+ }
+}
+return true
}
func (r *RecipeDef) HasAllItemsQty(countItem func(string) int) bool {
for _, e := range r.Consume {
found := false
for _, id := range e.Items {
- qty := e.Qty
+ qty := e.Quantity
if qty <= 0 {
qty = 1
}
diff --git a/internal/action/store.go b/internal/action/store.go
index a8db9df..6cb2e39 100644
--- a/internal/action/store.go
+++ b/internal/action/store.go
@@ -5,10 +5,79 @@ import (
"math/rand"
"os"
"path/filepath"
+ "strings"
+ "sync"
"gopkg.in/yaml.v3"
)
+func BuildPathIndex(dir string) map[string]string {
+ index := make(map[string]string)
+ filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
+ if err != nil || d.IsDir() || !strings.HasSuffix(d.Name(), ".yaml") {
+ return nil
+ }
+ id := strings.TrimSuffix(d.Name(), ".yaml")
+ index[id] = path
+ return nil
+ })
+ return index
+}
+
+func BuildRoomIndex(dir string) map[int]string {
+ index := make(map[int]string)
+ filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
+ if err != nil || d.IsDir() || !strings.HasSuffix(d.Name(), ".yaml") {
+ return nil
+ }
+ id := strings.TrimSuffix(d.Name(), ".yaml")
+ var n int
+ if _, scanErr := fmt.Sscanf(id, "%d", &n); scanErr == nil {
+ index[n] = path
+ }
+ return nil
+ })
+ return index
+}
+
+func WalkYAMLDir(dir string, fn func(path, id string, data []byte) error) error {
+ return filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
+ if err != nil || d.IsDir() || !strings.HasSuffix(d.Name(), ".yaml") {
+ return nil
+ }
+ data, readErr := os.ReadFile(path)
+ if readErr != nil {
+ return nil
+ }
+ id := strings.TrimSuffix(d.Name(), ".yaml")
+ return fn(path, id, data)
+ })
+}
+
+type Duplicate struct {
+ ID string
+ Paths []string
+}
+
+func CheckDuplicateIDs(dir string) []Duplicate {
+ seen := make(map[string][]string)
+ filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
+ if err != nil || d.IsDir() || !strings.HasSuffix(d.Name(), ".yaml") {
+ return nil
+ }
+ id := strings.TrimSuffix(d.Name(), ".yaml")
+ seen[id] = append(seen[id], path)
+ return nil
+ })
+ var dups []Duplicate
+ for id, paths := range seen {
+ if len(paths) > 1 {
+ dups = append(dups, Duplicate{ID: id, Paths: paths})
+ }
+ }
+ return dups
+}
+
func SuccessChance(cfg SuccessFormula, level int, requiredLevel int) float64 {
chance := cfg.Base + float64(level-requiredLevel)*cfg.PerLevel
if chance > cfg.Cap {
@@ -20,8 +89,26 @@ func SuccessChance(cfg SuccessFormula, level int, requiredLevel int) float64 {
return chance
}
+var (
+ dropIndex map[string]string
+ dropIndexMu sync.Mutex
+)
+
+func loadDropIndex(dataDir string) map[string]string {
+ dropIndexMu.Lock()
+ defer dropIndexMu.Unlock()
+ if dropIndex == nil {
+ dropIndex = BuildPathIndex(filepath.Join(dataDir, "drops"))
+ }
+ return dropIndex
+}
+
func LoadDropTable(dataDir, id string) (*DropTableDef, error) {
- path := filepath.Join(dataDir, "drops", id+".yaml")
+ index := loadDropIndex(dataDir)
+ path, ok := index[id]
+ if !ok {
+ path = filepath.Join(dataDir, "drops", id+".yaml")
+ }
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read drop table %s: %w", id, err)
diff --git a/internal/config/config.go b/internal/config/config.go
index 122c311..124d45d 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -60,7 +60,13 @@ func DefaultColors() ColorsConfig {
}
type GameConfig struct {
- TickLength int `yaml:"tick_length"`
+ TickLength int `yaml:"tick_length"`
+ StartupValidation StartupValidationConfig `yaml:"startup_validation"`
+}
+
+type StartupValidationConfig struct {
+ CheckSources []int `yaml:"check_sources"`
+ IgnoreUnreachable []int `yaml:"ignore_unreachable"`
}
type TelnetConfig struct {
@@ -91,6 +97,10 @@ func Default() *Config {
return &Config{
Game: GameConfig{
TickLength: 600,
+ StartupValidation: StartupValidationConfig{
+ CheckSources: []int{1},
+ IgnoreUnreachable: []int{},
+ },
},
Colors: DefaultColors(),
Telnet: TelnetConfig{
diff --git a/internal/game/action_burn.go b/internal/game/action_burn.go
index 4d8b65e..c31d7d5 100644
--- a/internal/game/action_burn.go
+++ b/internal/game/action_burn.go
@@ -3,6 +3,7 @@ package game
import (
"fmt"
"math/rand"
+ "strings"
"thehouseoficarus/internal/action"
"thehouseoficarus/internal/engine"
@@ -11,6 +12,18 @@ import (
"thehouseoficarus/internal/player"
)
+func (g *Game) executeBurn(sess *net.Session, args []string, rawInput string) {
+ p := sess.Player
+ g.cancelAction(p)
+ g.doBurn(sess, p, strings.Join(args, " "))
+}
+
+func (g *Game) executeStoke(sess *net.Session, args []string, rawInput string) {
+ p := sess.Player
+ g.cancelAction(p)
+ g.doStoke(sess, p, strings.Join(args, " "))
+}
+
func (g *Game) doBurn(sess *net.Session, p *player.Player, input string) {
if p.Action != nil {
g.cancelAction(p)
diff --git a/internal/game/action_steal.go b/internal/game/action_steal.go
index ee5456c..3510ce7 100644
--- a/internal/game/action_steal.go
+++ b/internal/game/action_steal.go
@@ -16,6 +16,16 @@ import (
"thehouseoficarus/internal/world"
)
+func (g *Game) executeSteal(sess *net.Session, args []string, rawInput string) {
+ p := sess.Player
+ g.cancelAction(p)
+ if len(args) == 0 {
+ g.doSteal(sess, "")
+ } else {
+ g.doSteal(sess, strings.Join(args, " "))
+ }
+}
+
var stallGuardTalk = &action.TalkConfig{
Nodes: map[string]action.TalkNode{
"start": {
diff --git a/internal/game/aggro.go b/internal/game/aggro.go
deleted file mode 100644
index 2a9fff4..0000000
--- a/internal/game/aggro.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package game
-
-import (
- "fmt"
-
- "thehouseoficarus/internal/combat"
- "thehouseoficarus/internal/engine"
- "thehouseoficarus/internal/net"
-)
-
-func (g *Game) checkAggro(sess *net.Session) {
- p := sess.Player
-
- if combat.GetCombat(p.Name) != nil {
- return
- }
-
- playerLevel := p.CombatLevel()
- mobs := g.MobStore.MobsInRoom(p.RoomID)
-
- for _, mob := range mobs {
- if !mob.Aggressive || mob.HP <= 0 || mob.Protected {
- continue
- }
- if combat.IsMobInCombat(mob.InstanceID) {
- continue
- }
- mobLevel := mobCombatLevel(mob)
- if playerLevel > mobLevel*2 {
- continue
- }
- aggMob := mob
- g.Ticks.Subscribe(engine.ToTicks(1), func() bool {
- if combat.GetCombat(p.Name) != nil {
- return false
- }
- if aggMob.HP <= 0 || aggMob.RoomID != p.RoomID {
- return false
- }
- if combat.IsMobInCombat(aggMob.InstanceID) {
- return false
- }
- attacker := aggMob.Name
- if !aggMob.Unique {
- attacker = "The " + aggMob.Name
- }
- sess.WriteLine(fmt.Sprintf("\n%s attacks you!", g.colorize(sess, "mob_name", attacker)))
- g.startCombat(sess, p, aggMob)
- return false
- })
- break
- }
-}
diff --git a/internal/game/cmd_alias.go b/internal/game/cmd_alias.go
index e7e4e12..890b904 100644
--- a/internal/game/cmd_alias.go
+++ b/internal/game/cmd_alias.go
@@ -8,6 +8,14 @@ import (
"thehouseoficarus/internal/net"
)
+func (g *Game) executeAlias(sess *net.Session, args []string, rawInput string) {
+ g.doAlias(sess, args)
+}
+
+func (g *Game) executeUnalias(sess *net.Session, args []string, rawInput string) {
+ g.doUnalias(sess, args)
+}
+
func (g *Game) doAlias(sess *net.Session, args []string) {
if sess.Account == nil {
sess.WriteLine("No account loaded.")
diff --git a/internal/game/cmd_attack.go b/internal/game/cmd_attack.go
index b0ce214..222e3c3 100644
--- a/internal/game/cmd_attack.go
+++ b/internal/game/cmd_attack.go
@@ -608,6 +608,20 @@ func (g *Game) stopCombat(playerName string) {
}
}
+func (g *Game) executeAttack(sess *net.Session, args []string, rawInput string) {
+ if len(args) == 0 {
+ p := sess.Player
+ target := g.resolveDefaultMob(p.RoomID)
+ if target == "" {
+ sess.WriteLine("Attack what?")
+ return
+ }
+ g.doAttack(sess, target)
+ } else {
+ g.doAttack(sess, strings.Join(args, " "))
+ }
+}
+
func (g *Game) respawnMob(instanceID string) {
inst := g.MobStore.GetInstance(instanceID)
if inst == nil {
diff --git a/internal/game/cmd_autotrigger.go b/internal/game/cmd_autotrigger.go
index 3c41735..e2ecd1a 100644
--- a/internal/game/cmd_autotrigger.go
+++ b/internal/game/cmd_autotrigger.go
@@ -52,3 +52,7 @@ func (g *Game) doAutotrigger(sess *net.Session, input string) {
p.AutotriggerMod = mod.ID
sess.WriteLine(fmt.Sprintf("Autotrigger set to: %s", mod.Name))
}
+
+func (g *Game) executeAutotrigger(sess *net.Session, args []string, rawInput string) {
+ g.doAutotrigger(sess, strings.Join(args, " "))
+}
diff --git a/internal/game/cmd_bank.go b/internal/game/cmd_bank.go
index 0785b81..d91e115 100644
--- a/internal/game/cmd_bank.go
+++ b/internal/game/cmd_bank.go
@@ -10,6 +10,10 @@ import (
"thehouseoficarus/internal/player"
)
+func (g *Game) executeBank(sess *net.Session, args []string, rawInput string) {
+ g.doBank(sess)
+}
+
func (g *Game) handleBankInput(sess *net.Session, input string) {
if sess.Player == nil {
sess.State = net.StateGame
diff --git a/internal/game/cmd_clean.go b/internal/game/cmd_clean.go
index 1f00d75..47896bb 100644
--- a/internal/game/cmd_clean.go
+++ b/internal/game/cmd_clean.go
@@ -11,6 +11,10 @@ import (
"thehouseoficarus/internal/player"
)
+func (g *Game) executeClean(sess *net.Session, args []string, rawInput string) {
+ g.doClean(sess, strings.Join(args, " "))
+}
+
func (g *Game) doClean(sess *net.Session, input string) {
p := sess.Player
diff --git a/internal/game/cmd_color.go b/internal/game/cmd_color.go
index 3fb7716..257c965 100644
--- a/internal/game/cmd_color.go
+++ b/internal/game/cmd_color.go
@@ -128,6 +128,10 @@ func (g *Game) colorSource(sess *net.Session, category string) string {
return "builtin"
}
+func (g *Game) executeColor(sess *net.Session, args []string, rawInput string) {
+ g.doColor(sess, strings.Join(args, " "))
+}
+
var colorCategoryOrder = []string{
"room_name",
"room_number",
diff --git a/internal/game/cmd_colortable.go b/internal/game/cmd_colortable.go
index 5aa36d9..86299dc 100644
--- a/internal/game/cmd_colortable.go
+++ b/internal/game/cmd_colortable.go
@@ -37,6 +37,10 @@ func (g *Game) doColortable(sess *net.Session) {
writeANSITable(sess, mode)
}
+func (g *Game) executeColortable(sess *net.Session, args []string, rawInput string) {
+ g.doColortable(sess)
+}
+
func writeSectionHeader(sess *net.Session, title string, unicode bool) {
if unicode {
sess.WriteLine(fmt.Sprintf("── %s ──", title))
diff --git a/internal/game/cmd_consume.go b/internal/game/cmd_consume.go
new file mode 100644
index 0000000..0a7d335
--- /dev/null
+++ b/internal/game/cmd_consume.go
@@ -0,0 +1,293 @@
+package game
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "thehouseoficarus/internal/engine"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
+ "thehouseoficarus/internal/player"
+)
+
+func (g *Game) executeEat(sess *net.Session, args []string, rawInput string) {
+ g.doEat(sess, strings.Join(args, " "))
+}
+
+func (g *Game) executeDrink(sess *net.Session, args []string, rawInput string) {
+ g.doDrink(sess, args)
+}
+
+func (g *Game) doEat(sess *net.Session, input string) {
+ p := sess.Player
+
+ if input == "" {
+ sess.WriteLine("Eat what?")
+ return
+ }
+
+ qty, itemName := parseQty(input)
+ _ = qty
+
+ matches := g.findInventoryMatches(itemName, p)
+ if len(matches) == 0 {
+ sess.WriteLine(fmt.Sprintf("You don't have any '%s'.", itemName))
+ return
+ }
+
+ unique := uniqueItemNames(matches)
+ if len(unique) > 1 {
+ g.showWhichOne(sess, matches)
+ return
+ }
+
+ itemID := matches[0].ID
+ def, _ := g.ItemStore.Load(itemID)
+
+ if def == nil || def.EatMessage == "" {
+ sess.WriteLine("You can't eat that.")
+ return
+ }
+
+ if p.ConsumeCooldown > 0 {
+ sess.WriteLine("You're still digesting your last meal.")
+ return
+ }
+
+ g.cancelRest(p.Name)
+
+ if p.Action == nil && len(p.WalkSequence) == 0 {
+ g.doEatNow(sess, p, itemID, def)
+ return
+ }
+
+ g.cancelAction(p)
+
+ g.consumeQueue[p.Name] = &QueuedCommand{
+ Session: sess,
+ Command: "eat",
+ Args: itemID,
+ Timestamp: time.Now(),
+ }
+
+ if !p.OptionBool("queue_silently") {
+ sess.WriteLine(fmt.Sprintf("\nYou prepare to eat %s.", g.itemColorize(sess, def, def.Name)))
+ }
+}
+
+func (g *Game) doEatNow(sess *net.Session, p *player.Player, itemID string, def *object.ItemDef) {
+ if def == nil {
+ var err error
+ def, err = g.ItemStore.Load(itemID)
+ if err != nil || def.EatMessage == "" {
+ return
+ }
+ }
+
+ p.RemoveItem(itemID, 1)
+
+ p.HP += def.HealValue
+ maxHP := p.MaxHP()
+ if p.HP > maxHP {
+ p.HP = maxHP
+ }
+ if p.HP < 0 {
+ p.HP = 0
+ }
+
+ if def.HealValue != 0 {
+ p.StartRegen()
+ }
+
+ p.ConsumeCooldown = 3
+ g.AccountStore.SaveCharacter(p)
+
+ p.ActionState = &ActionState{Type: ActionEating, TargetName: def.Name}
+
+ sess.WriteLine(g.colorize(sess, "eat_food", def.EatMessage))
+
+ if p.HP <= 0 {
+ g.endCombat(sess, p, nil)
+ }
+}
+
+func (g *Game) processConsumeQueue(p *player.Player, sess *net.Session) bool {
+ qc, ok := g.consumeQueue[p.Name]
+ if !ok {
+ return false
+ }
+ delete(g.consumeQueue, p.Name)
+
+ itemID := qc.Args
+ def, err := g.ItemStore.Load(itemID)
+
+ if qc.Command == "drink" {
+ if err != nil || (def.PotionEffect == "" && def.HealValue <= 0) {
+ return false
+ }
+ if p.ConsumeCooldown > 0 {
+ return false
+ }
+ if !p.HasItem(itemID) {
+ sess.WriteLine("You no longer have that to drink.")
+ return false
+ }
+ g.applyPotion(sess, p, itemID, def)
+ return true
+ }
+
+ if err != nil || def.EatMessage == "" {
+ return false
+ }
+
+ if p.ConsumeCooldown > 0 {
+ return false
+ }
+
+ if !p.HasItem(itemID) {
+ sess.WriteLine("You no longer have that to eat.")
+ return false
+ }
+
+ g.doEatNow(sess, p, itemID, def)
+ return true
+}
+
+func (g *Game) doDrink(sess *net.Session, args []string) {
+ p := sess.Player
+
+ if len(args) == 0 {
+ sess.WriteLine("Drink what?")
+ return
+ }
+
+ input := strings.Join(args, " ")
+ matches := g.findInventoryMatches(input, p)
+ if len(matches) == 0 {
+ sess.WriteLine(fmt.Sprintf("You don't have any '%s'.", input))
+ return
+ }
+
+ unique := uniqueItemNames(matches)
+ if len(unique) > 1 {
+ g.showWhichOne(sess, matches)
+ return
+ }
+
+ itemID := matches[0].ID
+ def, _ := g.ItemStore.Load(itemID)
+
+ if def == nil {
+ sess.WriteLine("Something went wrong.")
+ return
+ }
+
+ if def.PotionEffect == "" && def.HealValue <= 0 {
+ sess.WriteLine(fmt.Sprintf("You can't drink the %s.", g.itemColorize(sess, def, def.Name)))
+ return
+ }
+
+ if p.ConsumeCooldown > 0 {
+ sess.WriteLine("You're still recovering from your last drink.")
+ return
+ }
+
+ g.cancelRest(p.Name)
+
+ if p.Action == nil && len(p.WalkSequence) == 0 {
+ g.applyPotion(sess, p, itemID, def)
+ return
+ }
+
+ g.cancelAction(p)
+
+ g.consumeQueue[p.Name] = &QueuedCommand{
+ Session: sess,
+ Command: "drink",
+ Args: itemID,
+ Timestamp: time.Now(),
+ }
+
+ if !p.OptionBool("queue_silently") {
+ sess.WriteLine(fmt.Sprintf("\nYou prepare to drink the %s.", g.itemColorize(sess, def, def.Name)))
+ }
+}
+
+func (g *Game) applyPotion(sess *net.Session, p *player.Player, itemID string, def *object.ItemDef) {
+ if def == nil {
+ var err error
+ def, err = g.ItemStore.Load(itemID)
+ if err != nil || (def.PotionEffect == "" && def.HealValue <= 0) {
+ return
+ }
+ }
+
+ p.RemoveItem(itemID, 1)
+
+ effect := strings.ToLower(def.PotionEffect)
+
+ switch effect {
+ case "battery":
+ batteryRestore := def.PotionBonus
+ p.Battery += float64(batteryRestore)
+ if p.Battery > 100 {
+ p.Battery = 100
+ }
+ sess.WriteLine(fmt.Sprintf("\nYou drink the %s. Your battery is restored by %d%%.",
+ g.itemColorize(sess, def, def.Name), batteryRestore))
+
+ case "heal", "":
+ healAmount := def.PotionBonus
+ if healAmount <= 0 {
+ healAmount = def.HealValue
+ }
+ if healAmount <= 0 {
+ healAmount = 50
+ }
+ before := p.HP
+ maxHP := p.Level(player.Hitpoints)
+ if p.HP+healAmount > maxHP {
+ p.HP = maxHP
+ } else {
+ p.HP += healAmount
+ }
+ actualHeal := p.HP - before
+ sess.WriteLine(fmt.Sprintf("\nYou drink the %s. You restore %d hitpoints.",
+ g.itemColorize(sess, def, def.Name), actualHeal))
+
+ case "all_combat":
+ duration := engine.ToTicks(def.PotionDuration)
+ buffs := []string{"attack", "strength", "defense"}
+ for _, stat := range buffs {
+ p.ActiveBuffs = append(p.ActiveBuffs, player.PotionBuff{
+ Stat: stat,
+ BonusPercent: def.PotionBonus,
+ TicksLeft: duration,
+ })
+ }
+ sess.WriteLine(fmt.Sprintf("\nYou drink the %s. Your combat stats are boosted by %d%% for %d ticks.",
+ g.itemColorize(sess, def, def.Name), def.PotionBonus, duration))
+
+ default:
+ duration := engine.ToTicks(def.PotionDuration)
+ p.ActiveBuffs = append(p.ActiveBuffs, player.PotionBuff{
+ Stat: effect,
+ BonusPercent: def.PotionBonus,
+ TicksLeft: duration,
+ })
+ sess.WriteLine(fmt.Sprintf("\nYou drink the %s. Your %s is boosted by %d%% for %d ticks.",
+ g.itemColorize(sess, def, def.Name), effect, def.PotionBonus, duration))
+ }
+
+ 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)))
+ }
+ }
+ }
+}
diff --git a/internal/game/cmd_cook.go b/internal/game/cmd_cook.go
index 5b78745..288afb0 100644
--- a/internal/game/cmd_cook.go
+++ b/internal/game/cmd_cook.go
@@ -2,12 +2,17 @@ package game
import (
"fmt"
+ "strings"
"thehouseoficarus/internal/action"
"thehouseoficarus/internal/net"
"thehouseoficarus/internal/player"
)
+func (g *Game) executeCook(sess *net.Session, args []string, rawInput string) {
+ g.doCook(sess, strings.Join(args, " "))
+}
+
func (g *Game) doCook(sess *net.Session, input string) {
p := sess.Player
g.cancelAction(p)
diff --git a/internal/game/cmd_craft.go b/internal/game/cmd_craft.go
index 7f8808b..447d980 100644
--- a/internal/game/cmd_craft.go
+++ b/internal/game/cmd_craft.go
@@ -8,6 +8,14 @@ import (
"thehouseoficarus/internal/player"
)
+func (g *Game) executeCraft(sess *net.Session, args []string, rawInput string) {
+ g.doCraft(sess, strings.Join(args, " "))
+}
+
+func (g *Game) executeConstruct(sess *net.Session, args []string, rawInput string) {
+ g.doConstruct(sess, strings.Join(args, " "))
+}
+
func (g *Game) doCraft(sess *net.Session, input string) {
g.doGenericStationSkill(sess, input, "crafting", player.Crafting, "last_craft", "Crafting", "craft", "Craft")
}
diff --git a/internal/game/cmd_description.go b/internal/game/cmd_description.go
index ced5dcc..5d0c5c1 100644
--- a/internal/game/cmd_description.go
+++ b/internal/game/cmd_description.go
@@ -6,6 +6,10 @@ import (
"thehouseoficarus/internal/net"
)
+func (g *Game) executeDescription(sess *net.Session, args []string, rawInput string) {
+ g.doDescription(sess)
+}
+
func (g *Game) doDescription(sess *net.Session) {
p := sess.Player
sess.WriteLine("")
diff --git a/internal/game/cmd_drink.go b/internal/game/cmd_drink.go
deleted file mode 100644
index 44ef991..0000000
--- a/internal/game/cmd_drink.go
+++ /dev/null
@@ -1,150 +0,0 @@
-package game
-
-import (
- "fmt"
- "strings"
- "time"
-
- "thehouseoficarus/internal/engine"
- "thehouseoficarus/internal/net"
- "thehouseoficarus/internal/object"
- "thehouseoficarus/internal/player"
-)
-
-func (g *Game) doDrink(sess *net.Session, args []string) {
- p := sess.Player
-
- if len(args) == 0 {
- sess.WriteLine("Drink what?")
- return
- }
-
- input := strings.Join(args, " ")
- matches := g.findInventoryMatches(input, p)
- if len(matches) == 0 {
- sess.WriteLine(fmt.Sprintf("You don't have any '%s'.", input))
- return
- }
-
- unique := uniqueItemNames(matches)
- if len(unique) > 1 {
- g.showWhichOne(sess, matches)
- return
- }
-
- itemID := matches[0].ID
- def, _ := g.ItemStore.Load(itemID)
-
- if def == nil {
- sess.WriteLine("Something went wrong.")
- return
- }
-
- if def.PotionEffect == "" && def.HealValue <= 0 {
- sess.WriteLine(fmt.Sprintf("You can't drink the %s.", g.itemColorize(sess, def, def.Name)))
- return
- }
-
- if p.ConsumeCooldown > 0 {
- sess.WriteLine("You're still recovering from your last drink.")
- return
- }
-
- g.cancelRest(p.Name)
-
- if p.Action == nil && len(p.WalkSequence) == 0 {
- g.applyPotion(sess, p, itemID, def)
- return
- }
-
- g.cancelAction(p)
-
- g.consumeQueue[p.Name] = &QueuedCommand{
- Session: sess,
- Command: "drink",
- Args: itemID,
- Timestamp: time.Now(),
- }
-
- if !p.OptionBool("queue_silently") {
- sess.WriteLine(fmt.Sprintf("\nYou prepare to drink the %s.", g.itemColorize(sess, def, def.Name)))
- }
-}
-
-func (g *Game) applyPotion(sess *net.Session, p *player.Player, itemID string, def *object.ItemDef) {
- if def == nil {
- var err error
- def, err = g.ItemStore.Load(itemID)
- if err != nil || (def.PotionEffect == "" && def.HealValue <= 0) {
- return
- }
- }
-
- p.RemoveItem(itemID, 1)
-
- effect := strings.ToLower(def.PotionEffect)
-
- switch effect {
- case "battery":
- batteryRestore := def.PotionBonus
- p.Battery += float64(batteryRestore)
- if p.Battery > 100 {
- p.Battery = 100
- }
- sess.WriteLine(fmt.Sprintf("\nYou drink the %s. Your battery is restored by %d%%.",
- g.itemColorize(sess, def, def.Name), batteryRestore))
-
- case "heal", "":
- healAmount := def.PotionBonus
- if healAmount <= 0 {
- healAmount = def.HealValue
- }
- if healAmount <= 0 {
- healAmount = 50
- }
- before := p.HP
- maxHP := p.Level(player.Hitpoints)
- if p.HP+healAmount > maxHP {
- p.HP = maxHP
- } else {
- p.HP += healAmount
- }
- actualHeal := p.HP - before
- sess.WriteLine(fmt.Sprintf("\nYou drink the %s. You restore %d hitpoints.",
- g.itemColorize(sess, def, def.Name), actualHeal))
-
- case "all_combat":
- duration := engine.ToTicks(def.PotionDuration)
- buffs := []string{"attack", "strength", "defense"}
- for _, stat := range buffs {
- p.ActiveBuffs = append(p.ActiveBuffs, player.PotionBuff{
- Stat: stat,
- BonusPercent: def.PotionBonus,
- TicksLeft: duration,
- })
- }
- sess.WriteLine(fmt.Sprintf("\nYou drink the %s. Your combat stats are boosted by %d%% for %d ticks.",
- g.itemColorize(sess, def, def.Name), def.PotionBonus, duration))
-
- default:
- duration := engine.ToTicks(def.PotionDuration)
- p.ActiveBuffs = append(p.ActiveBuffs, player.PotionBuff{
- Stat: effect,
- BonusPercent: def.PotionBonus,
- TicksLeft: duration,
- })
- sess.WriteLine(fmt.Sprintf("\nYou drink the %s. Your %s is boosted by %d%% for %d ticks.",
- g.itemColorize(sess, def, def.Name), effect, def.PotionBonus, duration))
- }
-
- 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)))
- }
- }
- }
-}
diff --git a/internal/game/cmd_drop.go b/internal/game/cmd_drop.go
index d3141e3..551b014 100644
--- a/internal/game/cmd_drop.go
+++ b/internal/game/cmd_drop.go
@@ -180,6 +180,20 @@ func (g *Game) doDrop(sess *net.Session, input string) {
}
}
+func (g *Game) executeDrop(sess *net.Session, args []string, rawInput string) {
+ if len(args) == 0 {
+ sess.WriteLine("Drop what?")
+ } else if args[0] == "all" {
+ if len(args) == 1 {
+ g.doDropAll(sess)
+ } else {
+ g.doDropAllNamed(sess, strings.Join(args[1:], " "))
+ }
+ } else {
+ g.doDrop(sess, strings.Join(args, " "))
+ }
+}
+
func (g *Game) handleDropAllConfirm(sess *net.Session, input string) {
p := sess.Player
if p == nil {
diff --git a/internal/game/cmd_eat.go b/internal/game/cmd_eat.go
deleted file mode 100644
index f94ade3..0000000
--- a/internal/game/cmd_eat.go
+++ /dev/null
@@ -1,145 +0,0 @@
-package game
-
-import (
- "fmt"
- "time"
-
- "thehouseoficarus/internal/net"
- "thehouseoficarus/internal/object"
- "thehouseoficarus/internal/player"
-)
-
-func (g *Game) doEat(sess *net.Session, input string) {
- p := sess.Player
-
- if input == "" {
- sess.WriteLine("Eat what?")
- return
- }
-
- qty, itemName := parseQty(input)
- _ = qty
-
- matches := g.findInventoryMatches(itemName, p)
- if len(matches) == 0 {
- sess.WriteLine(fmt.Sprintf("You don't have any '%s'.", itemName))
- return
- }
-
- unique := uniqueItemNames(matches)
- if len(unique) > 1 {
- g.showWhichOne(sess, matches)
- return
- }
-
- itemID := matches[0].ID
- def, _ := g.ItemStore.Load(itemID)
-
- if def == nil || def.EatMessage == "" {
- sess.WriteLine("You can't eat that.")
- return
- }
-
- if p.ConsumeCooldown > 0 {
- sess.WriteLine("You're still digesting your last meal.")
- return
- }
-
- g.cancelRest(p.Name)
-
- if p.Action == nil && len(p.WalkSequence) == 0 {
- g.doEatNow(sess, p, itemID, def)
- return
- }
-
- g.cancelAction(p)
-
- g.consumeQueue[p.Name] = &QueuedCommand{
- Session: sess,
- Command: "eat",
- Args: itemID,
- Timestamp: time.Now(),
- }
-
- if !p.OptionBool("queue_silently") {
- sess.WriteLine(fmt.Sprintf("\nYou prepare to eat %s.", g.itemColorize(sess, def, def.Name)))
- }
-}
-
-func (g *Game) doEatNow(sess *net.Session, p *player.Player, itemID string, def *object.ItemDef) {
- if def == nil {
- var err error
- def, err = g.ItemStore.Load(itemID)
- if err != nil || def.EatMessage == "" {
- return
- }
- }
-
- p.RemoveItem(itemID, 1)
-
- p.HP += def.HealValue
- maxHP := p.MaxHP()
- if p.HP > maxHP {
- p.HP = maxHP
- }
- if p.HP < 0 {
- p.HP = 0
- }
-
- if def.HealValue != 0 {
- p.StartRegen()
- }
-
- p.ConsumeCooldown = 3
- g.AccountStore.SaveCharacter(p)
-
- p.ActionState = &ActionState{Type: ActionEating, TargetName: def.Name}
-
- sess.WriteLine(g.colorize(sess, "eat_food", def.EatMessage))
-
- if p.HP <= 0 {
- g.endCombat(sess, p, nil)
- }
-}
-
-func (g *Game) processConsumeQueue(p *player.Player, sess *net.Session) bool {
- qc, ok := g.consumeQueue[p.Name]
- if !ok {
- return false
- }
- delete(g.consumeQueue, p.Name)
-
- itemID := qc.Args
- def, err := g.ItemStore.Load(itemID)
-
- if qc.Command == "drink" {
- if err != nil || (def.PotionEffect == "" && def.HealValue <= 0) {
- return false
- }
- if p.ConsumeCooldown > 0 {
- return false
- }
- if !p.HasItem(itemID) {
- sess.WriteLine("You no longer have that to drink.")
- return false
- }
- g.applyPotion(sess, p, itemID, def)
- return true
- }
-
- if err != nil || def.EatMessage == "" {
- return false
- }
-
- if p.ConsumeCooldown > 0 {
- return false
- }
-
- if !p.HasItem(itemID) {
- sess.WriteLine("You no longer have that to eat.")
- return false
- }
-
- g.doEatNow(sess, p, itemID, def)
- return true
-}
diff --git a/internal/game/cmd_estate_directory.go b/internal/game/cmd_estate_directory.go
index c79135f..dbc79f6 100644
--- a/internal/game/cmd_estate_directory.go
+++ b/internal/game/cmd_estate_directory.go
@@ -186,7 +186,7 @@ func (g *Game) getOwnedHouseRoom(p *player.Player, flagKey string) int {
}
houseMap := map[string]int{
- "local_neighborhood": 200,
+ "local_neighborhood": 300,
}
return houseMap[v]
}
diff --git a/internal/game/cmd_farm.go b/internal/game/cmd_farm.go
index 5ef36d9..791cb6d 100644
--- a/internal/game/cmd_farm.go
+++ b/internal/game/cmd_farm.go
@@ -9,6 +9,64 @@ import (
"thehouseoficarus/internal/player"
)
+func (g *Game) executePlant(sess *net.Session, args []string, rawInput string) {
+ p := sess.Player
+ g.cancelAction(p)
+ if len(args) == 0 {
+ sess.WriteLine("Plant what?")
+ } else {
+ g.doPlant(sess, strings.Join(args, " "))
+ }
+}
+
+func (g *Game) executeHarvest(sess *net.Session, args []string, rawInput string) {
+ p := sess.Player
+ g.cancelAction(p)
+ if len(args) == 0 {
+ g.doHarvest(sess, "")
+ } else {
+ g.doHarvest(sess, strings.Join(args, " "))
+ }
+}
+
+func (g *Game) executeRake(sess *net.Session, args []string, rawInput string) {
+ p := sess.Player
+ g.cancelAction(p)
+ if len(args) == 0 {
+ g.doRake(sess, "")
+ } else {
+ g.doRake(sess, strings.Join(args, " "))
+ }
+}
+
+func (g *Game) executeWater(sess *net.Session, args []string, rawInput string) {
+ p := sess.Player
+ g.cancelAction(p)
+ if len(args) == 0 {
+ g.doWater(sess, "")
+ } else {
+ g.doWater(sess, strings.Join(args, " "))
+ }
+}
+
+func (g *Game) executeCure(sess *net.Session, args []string, rawInput string) {
+ p := sess.Player
+ g.cancelAction(p)
+ if len(args) == 0 {
+ g.doCure(sess, "")
+ } else {
+ g.doCure(sess, strings.Join(args, " "))
+ }
+}
+
+func (g *Game) executeInspect(sess *net.Session, args []string, rawInput string) {
+ if len(args) == 0 {
+ g.doInspect(sess, "")
+ } else {
+ g.doInspect(sess, strings.Join(args, " "))
+ }
+}
+
func (g *Game) doPlant(sess *net.Session, input string) {
p := sess.Player
diff --git a/internal/game/cmd_fletch.go b/internal/game/cmd_fletch.go
index 1f6a075..e218dca 100644
--- a/internal/game/cmd_fletch.go
+++ b/internal/game/cmd_fletch.go
@@ -9,6 +9,10 @@ import (
"thehouseoficarus/internal/player"
)
+func (g *Game) executeFletch(sess *net.Session, args []string, rawInput string) {
+ g.doFletch(sess, strings.Join(args, " "))
+}
+
var fletchLogItems = map[string]bool{
"logs": true, "oak_logs": true, "willow_logs": true,
"maple_logs": true, "yew_logs": true, "magic_logs": true,
diff --git a/internal/game/cmd_get.go b/internal/game/cmd_get.go
index bae721b..f3485fe 100644
--- a/internal/game/cmd_get.go
+++ b/internal/game/cmd_get.go
@@ -2,6 +2,7 @@ package game
import (
"fmt"
+ "strings"
"thehouseoficarus/internal/net"
"thehouseoficarus/internal/player"
@@ -419,3 +420,17 @@ func (g *Game) findInventoryMatches(input string, p *player.Player) []itemMatch
}
})
}
+
+func (g *Game) executeGet(sess *net.Session, args []string, rawInput string) {
+ if len(args) == 0 {
+ sess.WriteLine("Get what?")
+ } else if args[0] == "all" {
+ if len(args) == 1 {
+ g.doGetAll(sess)
+ } else {
+ g.doGetAllNamed(sess, strings.Join(args[1:], " "))
+ }
+ } else {
+ g.doGet(sess, strings.Join(args, " "))
+ }
+}
diff --git a/internal/game/cmd_handlers.go b/internal/game/cmd_handlers.go
deleted file mode 100644
index b8db359..0000000
--- a/internal/game/cmd_handlers.go
+++ /dev/null
@@ -1,370 +0,0 @@
-package game
-
-import (
- "fmt"
- "strings"
-
- "thehouseoficarus/internal/net"
-)
-
-func (g *Game) executeGet(sess *net.Session, args []string, rawInput string) {
- if len(args) == 0 {
- sess.WriteLine("Get what?")
- } else if args[0] == "all" {
- if len(args) == 1 {
- g.doGetAll(sess)
- } else {
- g.doGetAllNamed(sess, strings.Join(args[1:], " "))
- }
- } else {
- g.doGet(sess, strings.Join(args, " "))
- }
-}
-
-func (g *Game) executeDrop(sess *net.Session, args []string, rawInput string) {
- if len(args) == 0 {
- sess.WriteLine("Drop what?")
- } else if args[0] == "all" {
- if len(args) == 1 {
- g.doDropAll(sess)
- } else {
- g.doDropAllNamed(sess, strings.Join(args[1:], " "))
- }
- } else {
- g.doDrop(sess, strings.Join(args, " "))
- }
-}
-
-func (g *Game) executeAttack(sess *net.Session, args []string, rawInput string) {
- if len(args) == 0 {
- p := sess.Player
- target := g.resolveDefaultMob(p.RoomID)
- if target == "" {
- sess.WriteLine("Attack what?")
- return
- }
- g.doAttack(sess, target)
- } else {
- g.doAttack(sess, strings.Join(args, " "))
- }
-}
-
-func (g *Game) executeStyle(sess *net.Session, args []string, rawInput string) {
- if len(args) == 0 {
- g.doStyle(sess, "")
- } else {
- g.doStyle(sess, args[0])
- }
-}
-
-func (g *Game) executeLook(sess *net.Session, args []string, rawInput string) {
- if len(args) == 0 {
- g.doLook(sess)
- } else {
- g.doLookTarget(sess, strings.Join(args, " "))
- }
-}
-
-func (g *Game) executeMove(sess *net.Session, args []string, rawInput string) {
- g.doMove(sess, strings.TrimSpace(rawInput), 1.0)
-}
-
-func (g *Game) executeSay(sess *net.Session, args []string, rawInput string) {
- if len(args) == 0 {
- sess.WriteLine("Say what?")
- } else {
- msgStart := strings.Index(strings.ToLower(rawInput), "say ") + 4
- if msgStart >= 4 && msgStart < len(rawInput) {
- g.doSay(sess, rawInput[msgStart:])
- } else {
- g.doSay(sess, strings.Join(args, " "))
- }
- }
-}
-
-func (g *Game) executeScore(sess *net.Session, args []string, rawInput string) {
- g.doScore(sess)
-}
-
-func (g *Game) executeTask(sess *net.Session, args []string, rawInput string) {
- g.doTask(sess)
-}
-
-func (g *Game) executeStats(sess *net.Session, args []string, rawInput string) {
- g.doStats(sess)
-}
-
-func (g *Game) executeTech(sess *net.Session, args []string, rawInput string) {
- g.doTech(sess, strings.Join(args, " "))
-}
-
-func (g *Game) executeInventory(sess *net.Session, args []string, rawInput string) {
- g.doInventory(sess)
-}
-
-func (g *Game) executeQuit(sess *net.Session, args []string, rawInput string) {
- g.doQuit(sess)
-}
-
-func (g *Game) executeDescription(sess *net.Session, args []string, rawInput string) {
- g.doDescription(sess)
-}
-
-func (g *Game) executeOption(sess *net.Session, args []string, rawInput string) {
- g.doOption(sess, strings.Join(args, " "))
-}
-
-func (g *Game) executeColor(sess *net.Session, args []string, rawInput string) {
- g.doColor(sess, strings.Join(args, " "))
-}
-
-func (g *Game) executeColortable(sess *net.Session, args []string, rawInput string) {
- g.doColortable(sess)
-}
-
-func (g *Game) executePrompt(sess *net.Session, args []string, rawInput string) {
- if len(args) == 0 {
- g.doPrompt(sess, "")
- } else {
- msgStart := strings.Index(strings.ToLower(rawInput), "prompt ") + 7
- if msgStart >= 7 && msgStart < len(rawInput) {
- g.doPrompt(sess, rawInput[msgStart:])
- } else {
- g.doPrompt(sess, strings.Join(args, " "))
- }
- }
-}
-
-func (g *Game) executeExits(sess *net.Session, args []string, rawInput string) {
- g.doExits(sess)
-}
-
-func (g *Game) executeMap(sess *net.Session, args []string, rawInput string) {
- g.doMap(sess)
-}
-
-func (g *Game) executeQueued(sess *net.Session, args []string, rawInput string) {
- g.doQueued(sess)
-}
-
-func (g *Game) executeHelp(sess *net.Session, args []string, rawInput string) {
- if len(args) == 0 {
- g.doHelp(sess, "")
- } else {
- g.doHelp(sess, strings.Join(args, " "))
- }
-}
-
-func (g *Game) executeGather(sess *net.Session, args []string, rawInput string) {
- verb := strings.TrimSpace(rawInput)
- p := sess.Player
- g.cancelAction(p)
- if len(args) == 0 {
- target := g.resolveDefaultTarget(p.RoomID, verb)
- if target == "" {
- sess.WriteLine(fmt.Sprintf("%s what?", verb))
- return
- }
- g.startAction(sess, verb, target)
- } else {
- g.startAction(sess, verb, strings.Join(args, " "))
- }
-}
-
-func (g *Game) executeUse(sess *net.Session, args []string, rawInput string) {
- if g.tryFinishingBlow(sess, strings.Join(args, " ")) {
- return
- }
- g.doUse(sess, strings.Join(args, " "))
-}
-
-func (g *Game) executeCook(sess *net.Session, args []string, rawInput string) {
- g.doCook(sess, strings.Join(args, " "))
-}
-
-func (g *Game) executeSmelt(sess *net.Session, args []string, rawInput string) {
- g.doSmelt(sess, strings.Join(args, " "))
-}
-
-func (g *Game) executeSmith(sess *net.Session, args []string, rawInput string) {
- g.doSmith(sess, strings.Join(args, " "))
-}
-
-func (g *Game) executeCraft(sess *net.Session, args []string, rawInput string) {
- g.doCraft(sess, strings.Join(args, " "))
-}
-
-func (g *Game) executeEat(sess *net.Session, args []string, rawInput string) {
- g.doEat(sess, strings.Join(args, " "))
-}
-
-func (g *Game) executeDrink(sess *net.Session, args []string, rawInput string) {
- g.doDrink(sess, args)
-}
-
-func (g *Game) executeFletch(sess *net.Session, args []string, rawInput string) {
- g.doFletch(sess, strings.Join(args, " "))
-}
-
-func (g *Game) executeClean(sess *net.Session, args []string, rawInput string) {
- g.doClean(sess, strings.Join(args, " "))
-}
-
-func (g *Game) executeMix(sess *net.Session, args []string, rawInput string) {
- g.doMix(sess, strings.Join(args, " "))
-}
-
-func (g *Game) executeIdentify(sess *net.Session, args []string, rawInput string) {
- g.doIdentify(sess, strings.Join(args, " "))
-}
-
-func (g *Game) executeAutotrigger(sess *net.Session, args []string, rawInput string) {
- g.doAutotrigger(sess, strings.Join(args, " "))
-}
-
-func (g *Game) executeMods(sess *net.Session, args []string, rawInput string) {
- showAll := len(args) > 0 && args[0] == "all"
- g.doMods(sess, showAll)
-}
-
-func (g *Game) executeSneak(sess *net.Session, args []string, rawInput string) {
- g.doSneak(sess)
-}
-
-func (g *Game) executeTrigger(sess *net.Session, args []string, rawInput string) {
- g.doTrigger(sess, strings.Join(args, " "))
-}
-
-func (g *Game) executeTalk(sess *net.Session, args []string, rawInput string) {
- p := sess.Player
- g.cancelAction(p)
- if len(args) == 0 {
- sess.WriteLine("Talk to whom?")
- } else {
- g.startAction(sess, "talk", strings.Join(args, " "))
- }
-}
-
-func (g *Game) executeBurn(sess *net.Session, args []string, rawInput string) {
- p := sess.Player
- g.cancelAction(p)
- g.doBurn(sess, p, strings.Join(args, " "))
-}
-
-func (g *Game) executeStoke(sess *net.Session, args []string, rawInput string) {
- p := sess.Player
- g.cancelAction(p)
- g.doStoke(sess, p, strings.Join(args, " "))
-}
-
-func (g *Game) executeAlias(sess *net.Session, args []string, rawInput string) {
- g.doAlias(sess, args)
-}
-
-func (g *Game) executeUnalias(sess *net.Session, args []string, rawInput string) {
- g.doUnalias(sess, args)
-}
-
-func (g *Game) executeSearch(sess *net.Session, args []string, rawInput string) {
- p := sess.Player
- g.cancelAction(p)
- if len(args) == 0 {
- sess.WriteLine("Search what?")
- } else {
- g.doSearch(sess, strings.Join(args, " "))
- }
-}
-
-func (g *Game) executeSteal(sess *net.Session, args []string, rawInput string) {
- p := sess.Player
- g.cancelAction(p)
- if len(args) == 0 {
- g.doSteal(sess, "")
- } else {
- g.doSteal(sess, strings.Join(args, " "))
- }
-}
-
-func (g *Game) executePlant(sess *net.Session, args []string, rawInput string) {
- p := sess.Player
- g.cancelAction(p)
- if len(args) == 0 {
- sess.WriteLine("Plant what?")
- } else {
- g.doPlant(sess, strings.Join(args, " "))
- }
-}
-
-func (g *Game) executeHarvest(sess *net.Session, args []string, rawInput string) {
- p := sess.Player
- g.cancelAction(p)
- if len(args) == 0 {
- g.doHarvest(sess, "")
- } else {
- g.doHarvest(sess, strings.Join(args, " "))
- }
-}
-
-func (g *Game) executeRake(sess *net.Session, args []string, rawInput string) {
- p := sess.Player
- g.cancelAction(p)
- if len(args) == 0 {
- g.doRake(sess, "")
- } else {
- g.doRake(sess, strings.Join(args, " "))
- }
-}
-
-func (g *Game) executeWater(sess *net.Session, args []string, rawInput string) {
- p := sess.Player
- g.cancelAction(p)
- if len(args) == 0 {
- g.doWater(sess, "")
- } else {
- g.doWater(sess, strings.Join(args, " "))
- }
-}
-
-func (g *Game) executeCure(sess *net.Session, args []string, rawInput string) {
- p := sess.Player
- g.cancelAction(p)
- if len(args) == 0 {
- g.doCure(sess, "")
- } else {
- g.doCure(sess, strings.Join(args, " "))
- }
-}
-
-func (g *Game) executeInspect(sess *net.Session, args []string, rawInput string) {
- if len(args) == 0 {
- g.doInspect(sess, "")
- } else {
- g.doInspect(sess, strings.Join(args, " "))
- }
-}
-
-func (g *Game) executeWalk(sess *net.Session, args []string, rawInput string) {
- g.doWalk(sess, args)
-}
-
-func (g *Game) executeBank(sess *net.Session, args []string, rawInput string) {
- g.doBank(sess)
-}
-
-func (g *Game) executeConstruct(sess *net.Session, args []string, rawInput string) {
- g.doConstruct(sess, strings.Join(args, " "))
-}
-
-func (g *Game) executeJack(sess *net.Session, args []string, rawInput string) {
- p := sess.Player
- g.cancelAction(p)
- g.doJack(sess, strings.Join(args, " "))
-}
-
-func (g *Game) executeWear(sess *net.Session, args []string, rawInput string) {
- g.doWear(sess, strings.Join(args, " "))
-}
-
-func (g *Game) executeRemove(sess *net.Session, args []string, rawInput string) {
- g.doRemove(sess, strings.Join(args, " "))
-}
diff --git a/internal/game/cmd_id.go b/internal/game/cmd_id.go
index f7158e9..cb886dc 100644
--- a/internal/game/cmd_id.go
+++ b/internal/game/cmd_id.go
@@ -2,6 +2,7 @@ package game
import (
"fmt"
+ "strings"
"thehouseoficarus/internal/action"
"thehouseoficarus/internal/net"
@@ -81,6 +82,10 @@ func junkMultiplier(junkID string, level int) int {
return mult
}
+func (g *Game) executeIdentify(sess *net.Session, args []string, rawInput string) {
+ g.doIdentify(sess, strings.Join(args, " "))
+}
+
func (g *Game) doIdentify(sess *net.Session, input string) {
p := sess.Player
g.cancelAction(p)
diff --git a/internal/game/cmd_inventory.go b/internal/game/cmd_inventory.go
index ae7b328..5024642 100644
--- a/internal/game/cmd_inventory.go
+++ b/internal/game/cmd_inventory.go
@@ -6,6 +6,10 @@ import (
"thehouseoficarus/internal/net"
)
+func (g *Game) executeInventory(sess *net.Session, args []string, rawInput string) {
+ g.doInventory(sess)
+}
+
func (g *Game) doInventory(sess *net.Session) {
p := sess.Player
sess.WriteLine("")
diff --git a/internal/game/cmd_jack.go b/internal/game/cmd_jack.go
index 8cb5062..16f8b2a 100644
--- a/internal/game/cmd_jack.go
+++ b/internal/game/cmd_jack.go
@@ -10,6 +10,12 @@ import (
"thehouseoficarus/internal/player"
)
+func (g *Game) executeJack(sess *net.Session, args []string, rawInput string) {
+ p := sess.Player
+ g.cancelAction(p)
+ g.doJack(sess, strings.Join(args, " "))
+}
+
func (g *Game) doJack(sess *net.Session, target string) {
p := sess.Player
diff --git a/internal/game/cmd_look.go b/internal/game/cmd_look.go
index 5fa2cf4..6913333 100644
--- a/internal/game/cmd_look.go
+++ b/internal/game/cmd_look.go
@@ -768,6 +768,18 @@ func (g *Game) writeLookSideBySide(sess *net.Session, p *player.Player, descLine
}
}
+func (g *Game) executeLook(sess *net.Session, args []string, rawInput string) {
+ if len(args) == 0 {
+ g.doLook(sess)
+ } else {
+ g.doLookTarget(sess, strings.Join(args, " "))
+ }
+}
+
+func (g *Game) executeExits(sess *net.Session, args []string, rawInput string) {
+ g.doExits(sess)
+}
+
func (g *Game) showItemStats(sess *net.Session, def *object.ItemDef) {
s := def.Stats
hasAttack := s.StabAttack != 0 || s.SlashAttack != 0 || s.CrushAttack != 0 ||
diff --git a/internal/game/cmd_map.go b/internal/game/cmd_map.go
index 7cad008..be35a66 100644
--- a/internal/game/cmd_map.go
+++ b/internal/game/cmd_map.go
@@ -4,6 +4,10 @@ import (
"thehouseoficarus/internal/net"
)
+func (g *Game) executeMap(sess *net.Session, args []string, rawInput string) {
+ g.doMap(sess)
+}
+
func (g *Game) doMap(sess *net.Session) {
p := sess.Player
mapWidth := p.OptionInt("mapwidth")
diff --git a/internal/game/cmd_mix.go b/internal/game/cmd_mix.go
index 8fc600b..7bf1f7b 100644
--- a/internal/game/cmd_mix.go
+++ b/internal/game/cmd_mix.go
@@ -1,11 +1,17 @@
package game
import (
+ "strings"
+
"thehouseoficarus/internal/action"
"thehouseoficarus/internal/net"
"thehouseoficarus/internal/player"
)
+func (g *Game) executeMix(sess *net.Session, args []string, rawInput string) {
+ g.doMix(sess, strings.Join(args, " "))
+}
+
func (g *Game) doMix(sess *net.Session, input string) {
p := sess.Player
g.cancelAction(p)
diff --git a/internal/game/cmd_mods.go b/internal/game/cmd_mods.go
index 65d7ad7..438e11a 100644
--- a/internal/game/cmd_mods.go
+++ b/internal/game/cmd_mods.go
@@ -9,6 +9,11 @@ import (
"thehouseoficarus/internal/player"
)
+func (g *Game) executeMods(sess *net.Session, args []string, rawInput string) {
+ showAll := len(args) > 0 && args[0] == "all"
+ g.doMods(sess, showAll)
+}
+
func (g *Game) doMods(sess *net.Session, showAll bool) {
p := sess.Player
sciLevel := p.Level(player.Science)
diff --git a/internal/game/cmd_move.go b/internal/game/cmd_move.go
index 5d6ea66..e93b14b 100644
--- a/internal/game/cmd_move.go
+++ b/internal/game/cmd_move.go
@@ -2,6 +2,7 @@ package game
import (
"fmt"
+ "strings"
"thehouseoficarus/internal/combat"
"thehouseoficarus/internal/engine"
@@ -165,6 +166,10 @@ func (g *Game) seedRoomMobs(roomID int) {
g.MobStore.SeedMobs(roomID, room.Mobs)
}
+func (g *Game) executeMove(sess *net.Session, args []string, rawInput string) {
+ g.doMove(sess, strings.TrimSpace(rawInput), 1.0)
+}
+
func (g *Game) seedRoomObjects(roomID int) {
room, err := g.World.LoadRoom(roomID)
if err != nil {
diff --git a/internal/game/cmd_option.go b/internal/game/cmd_option.go
index fdf5371..19256ba 100644
--- a/internal/game/cmd_option.go
+++ b/internal/game/cmd_option.go
@@ -85,6 +85,10 @@ func (g *Game) doOption(sess *net.Session, input string) {
sess.WriteLine(fmt.Sprintf("\n%s set to %s.", def.Name, formatOptionValue(p, def)))
}
+func (g *Game) executeOption(sess *net.Session, args []string, rawInput string) {
+ g.doOption(sess, strings.Join(args, " "))
+}
+
func formatOptionValue(p *player.Player, def *player.OptionDef) string {
switch def.Type {
case player.OptBool:
diff --git a/internal/game/cmd_prompt.go b/internal/game/cmd_prompt.go
index 8850ea1..d91556a 100644
--- a/internal/game/cmd_prompt.go
+++ b/internal/game/cmd_prompt.go
@@ -2,10 +2,24 @@ package game
import (
"fmt"
+ "strings"
"thehouseoficarus/internal/net"
)
+func (g *Game) executePrompt(sess *net.Session, args []string, rawInput string) {
+ if len(args) == 0 {
+ g.doPrompt(sess, "")
+ } else {
+ msgStart := strings.Index(strings.ToLower(rawInput), "prompt ") + 7
+ if msgStart >= 7 && msgStart < len(rawInput) {
+ g.doPrompt(sess, rawInput[msgStart:])
+ } else {
+ g.doPrompt(sess, strings.Join(args, " "))
+ }
+ }
+}
+
func (g *Game) doPrompt(sess *net.Session, input string) {
p := sess.Player
diff --git a/internal/game/cmd_queued.go b/internal/game/cmd_queued.go
index fdb1643..58ac74b 100644
--- a/internal/game/cmd_queued.go
+++ b/internal/game/cmd_queued.go
@@ -7,6 +7,10 @@ import (
"thehouseoficarus/internal/net"
)
+func (g *Game) executeQueued(sess *net.Session, args []string, rawInput string) {
+ g.doQueued(sess)
+}
+
func (g *Game) doQueued(sess *net.Session) {
p := sess.Player
diff --git a/internal/game/cmd_quit.go b/internal/game/cmd_quit.go
index d52c9ae..46403f4 100644
--- a/internal/game/cmd_quit.go
+++ b/internal/game/cmd_quit.go
@@ -8,6 +8,10 @@ import (
"thehouseoficarus/internal/net"
)
+func (g *Game) executeQuit(sess *net.Session, args []string, rawInput string) {
+ g.doQuit(sess)
+}
+
func (g *Game) doQuit(sess *net.Session) {
p := sess.Player
diff --git a/internal/game/cmd_registry.go b/internal/game/cmd_registry.go
index e5bf30e..0439cc2 100644
--- a/internal/game/cmd_registry.go
+++ b/internal/game/cmd_registry.go
@@ -168,3 +168,29 @@ func (g *Game) executeCommand(sess *net.Session, cmd string, args []string, rawI
sess.WriteLine("Unknown command.")
}
+
+func (g *Game) executeGather(sess *net.Session, args []string, rawInput string) {
+ verb := strings.TrimSpace(rawInput)
+ p := sess.Player
+ g.cancelAction(p)
+ if len(args) == 0 {
+ target := g.resolveDefaultTarget(p.RoomID, verb)
+ if target == "" {
+ sess.WriteLine(fmt.Sprintf("%s what?", verb))
+ return
+ }
+ g.startAction(sess, verb, target)
+ } else {
+ g.startAction(sess, verb, strings.Join(args, " "))
+ }
+}
+
+func (g *Game) executeTalk(sess *net.Session, args []string, rawInput string) {
+ p := sess.Player
+ g.cancelAction(p)
+ if len(args) == 0 {
+ sess.WriteLine("Talk to whom?")
+ } else {
+ g.startAction(sess, "talk", strings.Join(args, " "))
+ }
+}
diff --git a/internal/game/cmd_remove.go b/internal/game/cmd_remove.go
index e168fd9..c046537 100644
--- a/internal/game/cmd_remove.go
+++ b/internal/game/cmd_remove.go
@@ -9,6 +9,10 @@ import (
"thehouseoficarus/internal/player"
)
+func (g *Game) executeRemove(sess *net.Session, args []string, rawInput string) {
+ g.doRemove(sess, strings.Join(args, " "))
+}
+
func (g *Game) doRemove(sess *net.Session, input string) {
p := sess.Player
diff --git a/internal/game/cmd_say.go b/internal/game/cmd_say.go
index 3d619a0..f21ed88 100644
--- a/internal/game/cmd_say.go
+++ b/internal/game/cmd_say.go
@@ -2,10 +2,24 @@ package game
import (
"fmt"
+ "strings"
"thehouseoficarus/internal/net"
)
+func (g *Game) executeSay(sess *net.Session, args []string, rawInput string) {
+ if len(args) == 0 {
+ sess.WriteLine("Say what?")
+ } else {
+ msgStart := strings.Index(strings.ToLower(rawInput), "say ") + 4
+ if msgStart >= 4 && msgStart < len(rawInput) {
+ g.doSay(sess, rawInput[msgStart:])
+ } else {
+ g.doSay(sess, strings.Join(args, " "))
+ }
+ }
+}
+
func (g *Game) doSay(sess *net.Session, msg string) {
p := sess.Player
roomID := p.RoomID
diff --git a/internal/game/cmd_score.go b/internal/game/cmd_score.go
index 0b0caf2..b49fd7f 100644
--- a/internal/game/cmd_score.go
+++ b/internal/game/cmd_score.go
@@ -9,6 +9,10 @@ import (
"thehouseoficarus/internal/player"
)
+func (g *Game) executeScore(sess *net.Session, args []string, rawInput string) {
+ g.doScore(sess)
+}
+
func (g *Game) doScore(sess *net.Session) {
p := sess.Player
mode := g.colorMode(sess)
diff --git a/internal/game/cmd_search.go b/internal/game/cmd_search.go
index cc22d49..4122e0a 100644
--- a/internal/game/cmd_search.go
+++ b/internal/game/cmd_search.go
@@ -7,6 +7,16 @@ import (
"thehouseoficarus/internal/net"
)
+func (g *Game) executeSearch(sess *net.Session, args []string, rawInput string) {
+ p := sess.Player
+ g.cancelAction(p)
+ if len(args) == 0 {
+ sess.WriteLine("Search what?")
+ } else {
+ g.doSearch(sess, strings.Join(args, " "))
+ }
+}
+
func (g *Game) doSearch(sess *net.Session, input string) {
p := sess.Player
g.cancelAction(p)
diff --git a/internal/game/cmd_smelt.go b/internal/game/cmd_smelt.go
index 53ff426..a72cf53 100644
--- a/internal/game/cmd_smelt.go
+++ b/internal/game/cmd_smelt.go
@@ -2,12 +2,17 @@ package game
import (
"fmt"
+ "strings"
"thehouseoficarus/internal/action"
"thehouseoficarus/internal/net"
"thehouseoficarus/internal/player"
)
+func (g *Game) executeSmelt(sess *net.Session, args []string, rawInput string) {
+ g.doSmelt(sess, strings.Join(args, " "))
+}
+
func (g *Game) doSmelt(sess *net.Session, input string) {
p := sess.Player
g.cancelAction(p)
diff --git a/internal/game/cmd_smith.go b/internal/game/cmd_smith.go
index 067d251..a2eaad7 100644
--- a/internal/game/cmd_smith.go
+++ b/internal/game/cmd_smith.go
@@ -10,6 +10,10 @@ import (
"thehouseoficarus/internal/player"
)
+func (g *Game) executeSmith(sess *net.Session, args []string, rawInput string) {
+ g.doSmith(sess, strings.Join(args, " "))
+}
+
func (g *Game) doSmith(sess *net.Session, input string) {
p := sess.Player
g.cancelAction(p)
diff --git a/internal/game/cmd_sneak.go b/internal/game/cmd_sneak.go
index b37862d..325a36b 100644
--- a/internal/game/cmd_sneak.go
+++ b/internal/game/cmd_sneak.go
@@ -6,6 +6,10 @@ import (
"thehouseoficarus/internal/net"
)
+func (g *Game) executeSneak(sess *net.Session, args []string, rawInput string) {
+ g.doSneak(sess)
+}
+
func (g *Game) doSneak(sess *net.Session) {
p := sess.Player
p.Sneaking = !p.Sneaking
diff --git a/internal/game/cmd_stats.go b/internal/game/cmd_stats.go
index 51bacf1..906abf2 100644
--- a/internal/game/cmd_stats.go
+++ b/internal/game/cmd_stats.go
@@ -9,6 +9,10 @@ import (
"thehouseoficarus/internal/player"
)
+func (g *Game) executeStats(sess *net.Session, args []string, rawInput string) {
+ g.doStats(sess)
+}
+
func (g *Game) doStats(sess *net.Session) {
p := sess.Player
totals := g.playerEquipBonuses(p)
diff --git a/internal/game/cmd_style.go b/internal/game/cmd_style.go
index 269fa3f..94dd234 100644
--- a/internal/game/cmd_style.go
+++ b/internal/game/cmd_style.go
@@ -43,3 +43,11 @@ func (g *Game) doStyle(sess *net.Session, input string) {
}
sess.WriteLine(fmt.Sprintf("\nUnknown style: %s. Choices: accurate, aggressive, defensive, balanced", input))
}
+
+func (g *Game) executeStyle(sess *net.Session, args []string, rawInput string) {
+ if len(args) == 0 {
+ g.doStyle(sess, "")
+ } else {
+ g.doStyle(sess, args[0])
+ }
+}
diff --git a/internal/game/cmd_task.go b/internal/game/cmd_task.go
index 5b67421..e54db15 100644
--- a/internal/game/cmd_task.go
+++ b/internal/game/cmd_task.go
@@ -6,6 +6,10 @@ import (
"thehouseoficarus/internal/net"
)
+func (g *Game) executeTask(sess *net.Session, args []string, rawInput string) {
+ g.doTask(sess)
+}
+
func (g *Game) doTask(sess *net.Session) {
p := sess.Player
diff --git a/internal/game/cmd_tech.go b/internal/game/cmd_tech.go
index 89d5c2a..cb4c309 100644
--- a/internal/game/cmd_tech.go
+++ b/internal/game/cmd_tech.go
@@ -194,3 +194,7 @@ func (g *Game) doTechQuick(sess *net.Session, p *player.Player, input string) {
g.AccountStore.SaveCharacter(p)
sess.WriteLine(fmt.Sprintf("\nQuick tech set to %s.", matches[0].Name))
}
+
+func (g *Game) executeTech(sess *net.Session, args []string, rawInput string) {
+ g.doTech(sess, strings.Join(args, " "))
+}
diff --git a/internal/game/cmd_trigger.go b/internal/game/cmd_trigger.go
index 89ed397..7d637ca 100644
--- a/internal/game/cmd_trigger.go
+++ b/internal/game/cmd_trigger.go
@@ -7,9 +7,12 @@ import (
"thehouseoficarus/internal/combat"
"thehouseoficarus/internal/net"
"thehouseoficarus/internal/player"
- "thehouseoficarus/internal/world"
)
+func (g *Game) executeTrigger(sess *net.Session, args []string, rawInput string) {
+ g.doTrigger(sess, strings.Join(args, " "))
+}
+
func (g *Game) doTrigger(sess *net.Session, input string) {
p := sess.Player
input = strings.TrimSpace(input)
@@ -106,635 +109,3 @@ func (g *Game) triggerCombatMod(sess *net.Session, p *player.Player, mod *ModDef
p.AutotriggerMod = mod.ID
g.startCombat(sess, p, mob)
}
-
-func (g *Game) triggerTransport(sess *net.Session, p *player.Player, mod *ModDef) {
- if combat.GetCombat(p.Name) != nil {
- sess.WriteLine("You can't teleport during combat!")
- return
- }
-
- if p.Action != nil {
- 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)
-
- if g.Hub != nil {
- for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
- if other != sess {
- other.WriteLine(fmt.Sprintf("\n%s teleports away.", p.Name))
- }
- }
- }
-
- sess.WriteLine(fmt.Sprintf("\nYou activate %s...", mod.Name))
-
- p.RoomID = mod.Destination
- if g.Hub != nil {
- g.Hub.LeaveRoom(sess)
- g.Hub.EnterRoom(sess, p.RoomID)
- }
-
- room, _ := g.World.LoadRoom(p.RoomID)
- destName := fmt.Sprintf("room %d", p.RoomID)
- if room != nil {
- destName = room.Name
- }
- sess.WriteLine(fmt.Sprintf("You materialize at %s.", destName))
- g.AccountStore.SaveCharacter(p)
- g.doLook(sess)
-}
-
-func (g *Game) triggerProcessing(sess *net.Session, p *player.Player, mod *ModDef, targetArg string) {
- if combat.GetCombat(p.Name) != nil {
- sess.WriteLine("You can't do that during combat!")
- return
- }
-
- if targetArg == "" {
- sess.WriteLine(fmt.Sprintf("Usage: trigger %s <item>", strings.ReplaceAll(mod.ID, "_", " ")))
- return
- }
-
- slot, inv := g.findInventoryItem(p, targetArg)
- if slot < 0 {
- sess.WriteLine("You don't have that item.")
- return
- }
-
- itemDef, err := g.ItemStore.Load(inv.ItemID)
- if err != nil || itemDef.Value <= 0 {
- sess.WriteLine("That item has no value.")
- return
- }
-
- if p.Action != nil {
- g.cancelAction(p)
- }
-
- g.consumeJunkCost(p, mod)
-
- creditValue := itemDef.Value
- if mod.ID == "low_process" {
- creditValue = itemDef.Value / 2
- if creditValue < 1 {
- creditValue = 1
- }
- }
-
- if inv.Quantity > 1 {
- inv.Quantity--
- } else {
- p.SetInvSlot(slot, nil)
- }
-
- p.Credits += creditValue
-
- sciXP := mod.BaseXP
- g.awardSkillXP(sess, p, player.Science, sciXP)
- g.AccountStore.SaveCharacter(p)
-
- 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) triggerUtility(sess *net.Session, p *player.Player, mod *ModDef, targetArg string) {
- switch mod.ID {
- case "low_process", "high_process":
- g.triggerProcessing(sess, p, mod, targetArg)
- case "bones_to_nutrients":
- g.triggerBonesToNutrients(sess, p, mod)
- case "em_grab":
- g.triggerEmGrab(sess, p, mod, targetArg)
- case "superheat":
- g.triggerSuperheat(sess, p, mod, targetArg)
- case "plank_make":
- g.triggerPlankMake(sess, p, mod, targetArg)
- }
-}
-
-func (g *Game) triggerBonesToNutrients(sess *net.Session, p *player.Player, mod *ModDef) {
- if combat.GetCombat(p.Name) != nil {
- sess.WriteLine("You can't do that during combat!")
- return
- }
-
- boneCount := p.CountItem("bones")
- if boneCount == 0 {
- sess.WriteLine("You don't have any bones.")
- return
- }
-
- if p.Action != nil {
- g.cancelAction(p)
- }
-
- g.consumeJunkCost(p, mod)
-
- for i := 0; i < 28; i++ {
- slot := p.InvSlot(i)
- if slot != nil && slot.ItemID == "bones" {
- slot.ItemID = "nutrient_bar"
- }
- }
-
- sciXP := mod.BaseXP * boneCount
- g.awardSkillXP(sess, p, player.Science, sciXP)
- g.AccountStore.SaveCharacter(p)
-
- 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) {
- if targetArg == "" {
- sess.WriteLine("Grab what? Usage: trigger em grab <item>")
- return
- }
-
- if p.FirstFreeSlot() < 0 {
- sess.WriteLine("Your inventory is full.")
- return
- }
-
- groundItems := g.World.GroundItems(p.RoomID)
- var matchedID string
- for itemID := range groundItems {
- def, _ := g.ItemStore.Load(itemID)
- if def != nil && def.MatchesName(targetArg) {
- matchedID = itemID
- break
- }
- if strings.HasPrefix(itemID, strings.ToLower(targetArg)) {
- matchedID = itemID
- break
- }
- }
-
- if matchedID == "" {
- sess.WriteLine("You don't see that here.")
- return
- }
-
- if p.Action != nil {
- g.cancelAction(p)
- }
-
- g.consumeJunkCost(p, mod)
-
- qty := groundItems[matchedID]
- g.World.RemoveGroundItem(p.RoomID, matchedID, qty)
-
- def, _ := g.ItemStore.Load(matchedID)
- name := matchedID
- if def != nil {
- name = def.Name
- }
-
- 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)
-
- 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) {
- if combat.GetCombat(p.Name) != nil {
- sess.WriteLine("You can't do that during combat!")
- return
- }
-
- if targetArg == "" {
- sess.WriteLine("Superheat what? Usage: trigger superheat <ore>")
- return
- }
-
- slot, inv := g.findInventoryItem(p, targetArg)
- if slot < 0 {
- sess.WriteLine("You don't have that item.")
- return
- }
-
- recipe := g.RecipeStore.FindByInput("smelt", inv.ItemID)
- if recipe == nil {
- sess.WriteLine("You can't superheat that.")
- return
- }
-
- if recipe.Level > 0 && p.Level(player.SkillName(recipe.Skill)) < recipe.Level {
- sess.WriteLine(fmt.Sprintf("You need level %d %s to smelt that.", recipe.Level, recipe.Skill))
- return
- }
-
- for _, e := range recipe.Consume {
- for _, itemID := range e.Items {
- qty := e.Qty
- if qty <= 0 {
- qty = 1
- }
- if p.CountItem(itemID) < qty {
- def, _ := g.ItemStore.Load(itemID)
- name := itemID
- if def != nil {
- name = def.Name
- }
- sess.WriteLine(fmt.Sprintf("You need %d %s.", qty, name))
- return
- }
- }
- }
-
- if p.Action != nil {
- g.cancelAction(p)
- }
-
- g.consumeJunkCost(p, mod)
-
- for _, e := range recipe.Consume {
- for _, itemID := range e.Items {
- qty := e.Qty
- if qty <= 0 {
- qty = 1
- }
- p.RemoveItem(itemID, qty)
- }
- }
-
- outputQty := recipe.OutputQty
- if outputQty <= 0 {
- outputQty = 1
- }
- placed := false
- for i := 0; i < 28; i++ {
- slot := p.InvSlot(i)
- if slot != nil && slot.ItemID == recipe.Output {
- slot.Quantity += outputQty
- placed = true
- break
- }
- }
- if !placed {
- freeSlot := p.FirstFreeSlot()
- p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: recipe.Output, Quantity: outputQty})
- }
-
- sciXP := mod.BaseXP
- g.awardSkillXP(sess, p, player.Science, sciXP)
- if recipe.XP > 0 {
- g.awardSkillXP(sess, p, player.SkillName(recipe.Skill), recipe.XP)
- }
- g.AccountStore.SaveCharacter(p)
-
- outputDef, _ := g.ItemStore.Load(recipe.Output)
- outputName := recipe.Output
- if outputDef != nil {
- outputName = outputDef.Name
- }
- inputDef, _ := g.ItemStore.Load(inv.ItemID)
- inputName := inv.ItemID
- if inputDef != nil {
- inputName = inputDef.Name
- }
-
- 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) {
- if combat.GetCombat(p.Name) != nil {
- sess.WriteLine("You can't do that during combat!")
- return
- }
-
- logTypes := map[string]struct {
- plankID string
- name string
- cost int
- }{
- "logs": {"planks", "planks", 3},
- "oak_logs": {"oak_planks", "oak planks", 7},
- "teak_logs": {"teak_planks", "teak planks", 14},
- "mahogany_logs": {"mahogany_planks", "mahogany planks", 28},
- }
-
- if targetArg != "" {
- slot, inv := g.findInventoryItem(p, targetArg)
- if slot < 0 {
- sess.WriteLine("You don't have that item.")
- return
- }
- info, ok := logTypes[inv.ItemID]
- if !ok {
- sess.WriteLine("You can't turn that into planks.")
- return
- }
-
- if p.Credits < info.cost {
- sess.WriteLine(fmt.Sprintf("You need %d credits for Plank Make (70%% of sawmill cost).", info.cost))
- return
- }
-
- if p.Action != nil {
- g.cancelAction(p)
- }
-
- g.consumeJunkCost(p, mod)
-
- p.RemoveItem(inv.ItemID, 1)
- p.Credits -= info.cost
-
- freeSlot := p.FirstFreeSlot()
- if freeSlot == -1 {
- sess.WriteLine("Your inventory is full.")
- return
- }
- 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)
-
- 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
- }
-
- for logID, info := range logTypes {
- qty := p.CountItem(logID)
- if qty == 0 {
- continue
- }
-
- totalCost := info.cost * qty
- if p.Credits < totalCost {
- continue
- }
-
- if p.Action != nil {
- g.cancelAction(p)
- }
-
- g.consumeJunkCost(p, mod)
-
- p.RemoveItem(logID, qty)
- p.Credits -= totalCost
-
- processed := 0
- for i := 0; i < qty; i++ {
- freeSlot := p.FirstFreeSlot()
- if freeSlot == -1 {
- g.World.AddGroundItem(p.RoomID, info.plankID, qty-i)
- break
- }
- p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: info.plankID, Quantity: 1})
- 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)
-
- 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
- }
-
- sess.WriteLine("You don't have any logs to convert.")
-}
-
-func (g *Game) triggerEnchant(sess *net.Session, p *player.Player, mod *ModDef, targetArg string) {
- if chipInfo, ok := chipMap[mod.ID]; ok {
- g.triggerChipBolts(sess, p, mod, chipInfo)
- return
- }
-
- enchants, ok := enchantMap[mod.ID]
- if !ok {
- sess.WriteLine("That enchantment has no known recipes.")
- return
- }
-
- if targetArg == "" {
- sess.WriteLine(fmt.Sprintf("Enchant what? Use: trigger %s <jewelry item>", strings.ReplaceAll(mod.ID, "_", " ")))
- return
- }
-
- slot, inv := g.findInventoryItem(p, targetArg)
- if slot < 0 {
- sess.WriteLine("You don't have that item.")
- return
- }
-
- outputID, ok := enchants[inv.ItemID]
- if !ok {
- sess.WriteLine("You can't enchant that with this mod.")
- return
- }
-
- if p.Action != nil {
- g.cancelAction(p)
- }
-
- g.consumeJunkCost(p, mod)
-
- inv.ItemID = outputID
-
- sciXP := mod.BaseXP
- g.awardSkillXP(sess, p, player.Science, sciXP)
- g.AccountStore.SaveCharacter(p)
-
- outputDef, _ := g.ItemStore.Load(outputID)
- outputName := outputID
- if outputDef != nil {
- outputName = outputDef.Name
- }
- inputDef, _ := g.ItemStore.Load(inv.ItemID)
- inputName := inv.ItemID
- if inputDef != nil {
- inputName = inputDef.Name
- }
-
- 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) {
- count := p.CountItem(chip.Input)
- if count < chip.Qty {
- inputDef, _ := g.ItemStore.Load(chip.Input)
- name := chip.Input
- if inputDef != nil {
- name = inputDef.Name
- }
- sess.WriteLine(fmt.Sprintf("You need at least %d %s.", chip.Qty, name))
- return
- }
-
- if p.Action != nil {
- g.cancelAction(p)
- }
-
- g.consumeJunkCost(p, mod)
-
- p.RemoveItem(chip.Input, chip.Qty)
- placed := false
- for i := 0; i < 28; i++ {
- slot := p.InvSlot(i)
- if slot != nil && slot.ItemID == chip.Output {
- slot.Quantity += chip.Qty
- placed = true
- break
- }
- }
- if !placed {
- freeSlot := p.FirstFreeSlot()
- 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)
-
- inputDef, _ := g.ItemStore.Load(chip.Input)
- name := chip.Input
- if inputDef != nil {
- name = inputDef.Name
- }
-
- 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)))
- }
-}
-
-func (g *Game) scienceAttack(sess *net.Session, p *player.Player, mob *world.MobInstance, mod *ModDef) bool {
- if p.Level(player.Science) < mod.Level {
- sess.WriteLine(fmt.Sprintf("You need level %d science to trigger %s.", mod.Level, mod.Name))
- return false
- }
- if !g.hasJunkCost(p, mod) {
- return false
- }
-
- if g.processConsumeQueue(p, sess) {
- p.ActionState = &ActionState{Type: ActionEating, TargetName: "food"}
- return true
- }
-
- g.consumeJunkCost(p, mod)
-
- equipSciBonus := g.totalEquipScienceAttack(p)
- attRoll := (p.Level(player.Science) + g.buffLevelBonus(p, "science") + 8) * (equipSciBonus + 64)
-
- mobSciDef := mob.ScienceDefense
- defRoll := (mob.Defense + 9) * (mobSciDef + 64)
-
- if mob.Weakness == mod.Element {
- attRoll = attRoll * 13 / 10
- }
-
- if combat.HitCheck(attRoll, defRoll) {
- dmg := combat.RollDamage(mod.MaxHit)
- if dmg < 0 {
- dmg = 0
- }
- mob.HP -= dmg
- if mob.HP < 0 {
- mob.HP = 0
- }
- if mob.HP < mob.MaxHP && mob.HP > 0 {
- mob.StartRegen()
- }
-
- sciXP := mod.BaseXP
- hpXP := mod.BaseXP / 3
- if hpXP < 1 {
- hpXP = 1
- }
- var gains []xpGain
-
- g.awardSkillXP(sess, p, player.Science, sciXP)
- gains = append(gains, xpGain{string(player.Science), sciXP})
-
- g.awardSkillXP(sess, p, player.Hitpoints, hpXP)
- gains = append(gains, xpGain{string(player.Hitpoints), hpXP})
-
- g.AccountStore.SaveCharacter(p)
-
- mobName := mobDisplayName(mob, true)
- prefix := fmt.Sprintf(" %s hits %s for %s damage.",
- g.colorize(sess, "science_mod", mod.Name),
- g.colorize(sess, "mob_name", mobName),
- g.colorize(sess, "damage", fmt.Sprint(dmg)))
- hpPart := fmt.Sprintf("[%s/%dhp]", g.colorize(sess, "enemy_hp", fmt.Sprint(mob.HP)), mob.MaxHP)
- line := prefix + " " + hpPart
- if p.OptionBool("xp_drops") && len(gains) > 0 {
- var parts []string
- for _, gain := range gains {
- parts = append(parts, fmt.Sprintf("+%dxp %s", gain.XP, player.SkillAbbr[player.SkillName(gain.Skill)]))
- }
- line += g.colorize(sess, "xp", " ("+strings.Join(parts, ", ")+")")
- }
- sess.WriteLine(line)
- } else {
- sess.WriteLine(g.colorize(sess, "miss", fmt.Sprintf(" %s fails to connect.", mod.Name)))
- }
- return true
-}
-
-func (g *Game) findInventoryItem(p *player.Player, input string) (int, *player.InventorySlot) {
- lower := strings.ToLower(strings.TrimSpace(input))
- 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
- }
- if def.MatchesName(lower) {
- return i, slot
- }
- }
- return -1, nil
-}
diff --git a/internal/game/cmd_trigger_combat.go b/internal/game/cmd_trigger_combat.go
new file mode 100644
index 0000000..545a117
--- /dev/null
+++ b/internal/game/cmd_trigger_combat.go
@@ -0,0 +1,104 @@
+package game
+
+import (
+ "fmt"
+ "strings"
+
+ "thehouseoficarus/internal/combat"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+ "thehouseoficarus/internal/world"
+)
+
+func (g *Game) scienceAttack(sess *net.Session, p *player.Player, mob *world.MobInstance, mod *ModDef) bool {
+ if p.Level(player.Science) < mod.Level {
+ sess.WriteLine(fmt.Sprintf("You need level %d science to trigger %s.", mod.Level, mod.Name))
+ return false
+ }
+ if !g.hasJunkCost(p, mod) {
+ return false
+ }
+
+ if g.processConsumeQueue(p, sess) {
+ p.ActionState = &ActionState{Type: ActionEating, TargetName: "food"}
+ return true
+ }
+
+ g.consumeJunkCost(p, mod)
+
+ equipSciBonus := g.totalEquipScienceAttack(p)
+ attRoll := (p.Level(player.Science) + g.buffLevelBonus(p, "science") + 8) * (equipSciBonus + 64)
+
+ mobSciDef := mob.ScienceDefense
+ defRoll := (mob.Defense + 9) * (mobSciDef + 64)
+
+ if mob.Weakness == mod.Element {
+ attRoll = attRoll * 13 / 10
+ }
+
+ if combat.HitCheck(attRoll, defRoll) {
+ dmg := combat.RollDamage(mod.MaxHit)
+ if dmg < 0 {
+ dmg = 0
+ }
+ mob.HP -= dmg
+ if mob.HP < 0 {
+ mob.HP = 0
+ }
+ if mob.HP < mob.MaxHP && mob.HP > 0 {
+ mob.StartRegen()
+ }
+
+ sciXP := mod.BaseXP
+ hpXP := mod.BaseXP / 3
+ if hpXP < 1 {
+ hpXP = 1
+ }
+ var gains []xpGain
+
+ g.awardSkillXP(sess, p, player.Science, sciXP)
+ gains = append(gains, xpGain{string(player.Science), sciXP})
+
+ g.awardSkillXP(sess, p, player.Hitpoints, hpXP)
+ gains = append(gains, xpGain{string(player.Hitpoints), hpXP})
+
+ g.AccountStore.SaveCharacter(p)
+
+ mobName := mobDisplayName(mob, true)
+ prefix := fmt.Sprintf(" %s hits %s for %s damage.",
+ g.colorize(sess, "science_mod", mod.Name),
+ g.colorize(sess, "mob_name", mobName),
+ g.colorize(sess, "damage", fmt.Sprint(dmg)))
+ hpPart := fmt.Sprintf("[%s/%dhp]", g.colorize(sess, "enemy_hp", fmt.Sprint(mob.HP)), mob.MaxHP)
+ line := prefix + " " + hpPart
+ if p.OptionBool("xp_drops") && len(gains) > 0 {
+ var parts []string
+ for _, gain := range gains {
+ parts = append(parts, fmt.Sprintf("+%dxp %s", gain.XP, player.SkillAbbr[player.SkillName(gain.Skill)]))
+ }
+ line += g.colorize(sess, "xp", " ("+strings.Join(parts, ", ")+")")
+ }
+ sess.WriteLine(line)
+ } else {
+ sess.WriteLine(g.colorize(sess, "miss", fmt.Sprintf(" %s fails to connect.", mod.Name)))
+ }
+ return true
+}
+
+func (g *Game) findInventoryItem(p *player.Player, input string) (int, *player.InventorySlot) {
+ lower := strings.ToLower(strings.TrimSpace(input))
+ 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
+ }
+ if def.MatchesName(lower) {
+ return i, slot
+ }
+ }
+ return -1, nil
+}
diff --git a/internal/game/cmd_trigger_enchant.go b/internal/game/cmd_trigger_enchant.go
new file mode 100644
index 0000000..535616b
--- /dev/null
+++ b/internal/game/cmd_trigger_enchant.go
@@ -0,0 +1,116 @@
+package game
+
+import (
+ "fmt"
+ "strings"
+
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+)
+
+func (g *Game) triggerEnchant(sess *net.Session, p *player.Player, mod *ModDef, targetArg string) {
+ if chipInfo, ok := chipMap[mod.ID]; ok {
+ g.triggerChipBolts(sess, p, mod, chipInfo)
+ return
+ }
+
+ enchants, ok := enchantMap[mod.ID]
+ if !ok {
+ sess.WriteLine("That enchantment has no known recipes.")
+ return
+ }
+
+ if targetArg == "" {
+ sess.WriteLine(fmt.Sprintf("Enchant what? Use: trigger %s <jewelry item>", strings.ReplaceAll(mod.ID, "_", " ")))
+ return
+ }
+
+ slot, inv := g.findInventoryItem(p, targetArg)
+ if slot < 0 {
+ sess.WriteLine("You don't have that item.")
+ return
+ }
+
+ outputID, ok := enchants[inv.ItemID]
+ if !ok {
+ sess.WriteLine("You can't enchant that with this mod.")
+ return
+ }
+
+ if p.Action != nil {
+ g.cancelAction(p)
+ }
+
+ g.consumeJunkCost(p, mod)
+
+ inv.ItemID = outputID
+
+ sciXP := mod.BaseXP
+ g.awardSkillXP(sess, p, player.Science, sciXP)
+ g.AccountStore.SaveCharacter(p)
+
+ outputDef, _ := g.ItemStore.Load(outputID)
+ outputName := outputID
+ if outputDef != nil {
+ outputName = outputDef.Name
+ }
+ inputDef, _ := g.ItemStore.Load(inv.ItemID)
+ inputName := inv.ItemID
+ if inputDef != nil {
+ inputName = inputDef.Name
+ }
+
+ 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) {
+ count := p.CountItem(chip.Input)
+ if count < chip.Qty {
+ inputDef, _ := g.ItemStore.Load(chip.Input)
+ name := chip.Input
+ if inputDef != nil {
+ name = inputDef.Name
+ }
+ sess.WriteLine(fmt.Sprintf("You need at least %d %s.", chip.Qty, name))
+ return
+ }
+
+ if p.Action != nil {
+ g.cancelAction(p)
+ }
+
+ g.consumeJunkCost(p, mod)
+
+ p.RemoveItem(chip.Input, chip.Qty)
+ placed := false
+ for i := 0; i < 28; i++ {
+ slot := p.InvSlot(i)
+ if slot != nil && slot.ItemID == chip.Output {
+ slot.Quantity += chip.Qty
+ placed = true
+ break
+ }
+ }
+ if !placed {
+ freeSlot := p.FirstFreeSlot()
+ 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)
+
+ inputDef, _ := g.ItemStore.Load(chip.Input)
+ name := chip.Input
+ if inputDef != nil {
+ name = inputDef.Name
+ }
+
+ 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
new file mode 100644
index 0000000..2ca8db2
--- /dev/null
+++ b/internal/game/cmd_trigger_transport.go
@@ -0,0 +1,54 @@
+package game
+
+import (
+ "fmt"
+
+ "thehouseoficarus/internal/combat"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+)
+
+func (g *Game) triggerTransport(sess *net.Session, p *player.Player, mod *ModDef) {
+ if combat.GetCombat(p.Name) != nil {
+ sess.WriteLine("You can't teleport during combat!")
+ return
+ }
+
+ if p.Action != nil {
+ 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)
+
+ if g.Hub != nil {
+ for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
+ if other != sess {
+ other.WriteLine(fmt.Sprintf("\n%s teleports away.", p.Name))
+ }
+ }
+ }
+
+ sess.WriteLine(fmt.Sprintf("\nYou activate %s...", mod.Name))
+
+ p.RoomID = mod.Destination
+ if g.Hub != nil {
+ g.Hub.LeaveRoom(sess)
+ g.Hub.EnterRoom(sess, p.RoomID)
+ }
+
+ room, _ := g.World.LoadRoom(p.RoomID)
+ destName := fmt.Sprintf("room %d", p.RoomID)
+ if room != nil {
+ destName = room.Name
+ }
+ sess.WriteLine(fmt.Sprintf("You materialize at %s.", destName))
+ g.AccountStore.SaveCharacter(p)
+ g.doLook(sess)
+}
diff --git a/internal/game/cmd_trigger_utility.go b/internal/game/cmd_trigger_utility.go
new file mode 100644
index 0000000..efb23e7
--- /dev/null
+++ b/internal/game/cmd_trigger_utility.go
@@ -0,0 +1,397 @@
+package game
+
+import (
+ "fmt"
+ "strings"
+
+ "thehouseoficarus/internal/combat"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+)
+
+func (g *Game) triggerUtility(sess *net.Session, p *player.Player, mod *ModDef, targetArg string) {
+ switch mod.ID {
+ case "low_process", "high_process":
+ g.triggerProcessing(sess, p, mod, targetArg)
+ case "bones_to_nutrients":
+ g.triggerBonesToNutrients(sess, p, mod)
+ case "em_grab":
+ g.triggerEmGrab(sess, p, mod, targetArg)
+ case "superheat":
+ g.triggerSuperheat(sess, p, mod, targetArg)
+ case "plank_make":
+ g.triggerPlankMake(sess, p, mod, targetArg)
+ }
+}
+
+func (g *Game) triggerProcessing(sess *net.Session, p *player.Player, mod *ModDef, targetArg string) {
+ if combat.GetCombat(p.Name) != nil {
+ sess.WriteLine("You can't do that during combat!")
+ return
+ }
+
+ if targetArg == "" {
+ sess.WriteLine(fmt.Sprintf("Usage: trigger %s <item>", strings.ReplaceAll(mod.ID, "_", " ")))
+ return
+ }
+
+ slot, inv := g.findInventoryItem(p, targetArg)
+ if slot < 0 {
+ sess.WriteLine("You don't have that item.")
+ return
+ }
+
+ itemDef, err := g.ItemStore.Load(inv.ItemID)
+ if err != nil || itemDef.Value <= 0 {
+ sess.WriteLine("That item has no value.")
+ return
+ }
+
+ if p.Action != nil {
+ g.cancelAction(p)
+ }
+
+ g.consumeJunkCost(p, mod)
+
+ creditValue := itemDef.Value
+ if mod.ID == "low_process" {
+ creditValue = itemDef.Value / 2
+ if creditValue < 1 {
+ creditValue = 1
+ }
+ }
+
+ if inv.Quantity > 1 {
+ inv.Quantity--
+ } else {
+ p.SetInvSlot(slot, nil)
+ }
+
+ p.Credits += creditValue
+
+ sciXP := mod.BaseXP
+ g.awardSkillXP(sess, p, player.Science, sciXP)
+ g.AccountStore.SaveCharacter(p)
+
+ 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) {
+ if combat.GetCombat(p.Name) != nil {
+ sess.WriteLine("You can't do that during combat!")
+ return
+ }
+
+ boneCount := p.CountItem("bones")
+ if boneCount == 0 {
+ sess.WriteLine("You don't have any bones.")
+ return
+ }
+
+ if p.Action != nil {
+ g.cancelAction(p)
+ }
+
+ g.consumeJunkCost(p, mod)
+
+ for i := 0; i < 28; i++ {
+ slot := p.InvSlot(i)
+ if slot != nil && slot.ItemID == "bones" {
+ slot.ItemID = "nutrient_bar"
+ }
+ }
+
+ sciXP := mod.BaseXP * boneCount
+ g.awardSkillXP(sess, p, player.Science, sciXP)
+ g.AccountStore.SaveCharacter(p)
+
+ 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) {
+ if targetArg == "" {
+ sess.WriteLine("Grab what? Usage: trigger em grab <item>")
+ return
+ }
+
+ if p.FirstFreeSlot() < 0 {
+ sess.WriteLine("Your inventory is full.")
+ return
+ }
+
+ groundItems := g.World.GroundItems(p.RoomID)
+ var matchedID string
+ for itemID := range groundItems {
+ def, _ := g.ItemStore.Load(itemID)
+ if def != nil && def.MatchesName(targetArg) {
+ matchedID = itemID
+ break
+ }
+ if strings.HasPrefix(itemID, strings.ToLower(targetArg)) {
+ matchedID = itemID
+ break
+ }
+ }
+
+ if matchedID == "" {
+ sess.WriteLine("You don't see that here.")
+ return
+ }
+
+ if p.Action != nil {
+ g.cancelAction(p)
+ }
+
+ g.consumeJunkCost(p, mod)
+
+ qty := groundItems[matchedID]
+ g.World.RemoveGroundItem(p.RoomID, matchedID, qty)
+
+ def, _ := g.ItemStore.Load(matchedID)
+ name := matchedID
+ if def != nil {
+ name = def.Name
+ }
+
+ 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)
+
+ 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) {
+ if combat.GetCombat(p.Name) != nil {
+ sess.WriteLine("You can't do that during combat!")
+ return
+ }
+
+ if targetArg == "" {
+ sess.WriteLine("Superheat what? Usage: trigger superheat <ore>")
+ return
+ }
+
+ slot, inv := g.findInventoryItem(p, targetArg)
+ if slot < 0 {
+ sess.WriteLine("You don't have that item.")
+ return
+ }
+
+ recipe := g.RecipeStore.FindByInput("smelt", inv.ItemID)
+ if recipe == nil {
+ sess.WriteLine("You can't superheat that.")
+ return
+ }
+
+ if recipe.Level > 0 && p.Level(player.SkillName(recipe.Skill)) < recipe.Level {
+ sess.WriteLine(fmt.Sprintf("You need level %d %s to smelt that.", recipe.Level, recipe.Skill))
+ return
+ }
+
+ for _, e := range recipe.Consume {
+ for _, itemID := range e.Items {
+ qty := e.Quantity
+ if qty <= 0 {
+ qty = 1
+ }
+ if p.CountItem(itemID) < qty {
+ def, _ := g.ItemStore.Load(itemID)
+ name := itemID
+ if def != nil {
+ name = def.Name
+ }
+ sess.WriteLine(fmt.Sprintf("You need %d %s.", qty, name))
+ return
+ }
+ }
+ }
+
+ if p.Action != nil {
+ g.cancelAction(p)
+ }
+
+ g.consumeJunkCost(p, mod)
+
+ for _, e := range recipe.Consume {
+ for _, itemID := range e.Items {
+ qty := e.Quantity
+ if qty <= 0 {
+ qty = 1
+ }
+ p.RemoveItem(itemID, qty)
+ }
+ }
+
+ outputQty := recipe.OutputQty
+ if outputQty <= 0 {
+ outputQty = 1
+ }
+ placed := false
+ for i := 0; i < 28; i++ {
+ slot := p.InvSlot(i)
+ if slot != nil && slot.ItemID == recipe.Output {
+ slot.Quantity += outputQty
+ placed = true
+ break
+ }
+ }
+ if !placed {
+ freeSlot := p.FirstFreeSlot()
+ p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: recipe.Output, Quantity: outputQty})
+ }
+
+ sciXP := mod.BaseXP
+ g.awardSkillXP(sess, p, player.Science, sciXP)
+ if recipe.XP > 0 {
+ g.awardSkillXP(sess, p, player.SkillName(recipe.Skill), recipe.XP)
+ }
+ g.AccountStore.SaveCharacter(p)
+
+ outputDef, _ := g.ItemStore.Load(recipe.Output)
+ outputName := recipe.Output
+ if outputDef != nil {
+ outputName = outputDef.Name
+ }
+ inputDef, _ := g.ItemStore.Load(inv.ItemID)
+ inputName := inv.ItemID
+ if inputDef != nil {
+ inputName = inputDef.Name
+ }
+
+ 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) {
+ if combat.GetCombat(p.Name) != nil {
+ sess.WriteLine("You can't do that during combat!")
+ return
+ }
+
+ logTypes := map[string]struct {
+ plankID string
+ name string
+ cost int
+ }{
+ "logs": {"planks", "planks", 3},
+ "oak_logs": {"oak_planks", "oak planks", 7},
+ "teak_logs": {"teak_planks", "teak planks", 14},
+ "mahogany_logs": {"mahogany_planks", "mahogany planks", 28},
+ }
+
+ if targetArg != "" {
+ slot, inv := g.findInventoryItem(p, targetArg)
+ if slot < 0 {
+ sess.WriteLine("You don't have that item.")
+ return
+ }
+ info, ok := logTypes[inv.ItemID]
+ if !ok {
+ sess.WriteLine("You can't turn that into planks.")
+ return
+ }
+
+ if p.Credits < info.cost {
+ sess.WriteLine(fmt.Sprintf("You need %d credits for Plank Make (70%% of sawmill cost).", info.cost))
+ return
+ }
+
+ if p.Action != nil {
+ g.cancelAction(p)
+ }
+
+ g.consumeJunkCost(p, mod)
+
+ p.RemoveItem(inv.ItemID, 1)
+ p.Credits -= info.cost
+
+ freeSlot := p.FirstFreeSlot()
+ if freeSlot == -1 {
+ sess.WriteLine("Your inventory is full.")
+ return
+ }
+ 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)
+
+ 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
+ }
+
+ for logID, info := range logTypes {
+ qty := p.CountItem(logID)
+ if qty == 0 {
+ continue
+ }
+
+ totalCost := info.cost * qty
+ if p.Credits < totalCost {
+ continue
+ }
+
+ if p.Action != nil {
+ g.cancelAction(p)
+ }
+
+ g.consumeJunkCost(p, mod)
+
+ p.RemoveItem(logID, qty)
+ p.Credits -= totalCost
+
+ processed := 0
+ for i := 0; i < qty; i++ {
+ freeSlot := p.FirstFreeSlot()
+ if freeSlot == -1 {
+ g.World.AddGroundItem(p.RoomID, info.plankID, qty-i)
+ break
+ }
+ p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: info.plankID, Quantity: 1})
+ 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)
+
+ 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
+ }
+
+ sess.WriteLine("You don't have any logs to convert.")
+}
diff --git a/internal/game/cmd_use.go b/internal/game/cmd_use.go
index fbe5945..4f830a8 100644
--- a/internal/game/cmd_use.go
+++ b/internal/game/cmd_use.go
@@ -10,6 +10,13 @@ import (
"thehouseoficarus/internal/player"
)
+func (g *Game) executeUse(sess *net.Session, args []string, rawInput string) {
+ if g.tryFinishingBlow(sess, strings.Join(args, " ")) {
+ return
+ }
+ g.doUse(sess, strings.Join(args, " "))
+}
+
func (g *Game) doUse(sess *net.Session, input string) {
if strings.TrimSpace(input) == "" {
g.doHelp(sess, "use")
diff --git a/internal/game/cmd_walk.go b/internal/game/cmd_walk.go
index eca608f..9485af5 100644
--- a/internal/game/cmd_walk.go
+++ b/internal/game/cmd_walk.go
@@ -10,6 +10,10 @@ import (
"thehouseoficarus/internal/world"
)
+func (g *Game) executeWalk(sess *net.Session, args []string, rawInput string) {
+ g.doWalk(sess, args)
+}
+
func (g *Game) doWalk(sess *net.Session, args []string) {
p := sess.Player
if len(args) == 0 {
diff --git a/internal/game/cmd_wear.go b/internal/game/cmd_wear.go
index 36765e8..f2fd84c 100644
--- a/internal/game/cmd_wear.go
+++ b/internal/game/cmd_wear.go
@@ -9,6 +9,10 @@ import (
"thehouseoficarus/internal/player"
)
+func (g *Game) executeWear(sess *net.Session, args []string, rawInput string) {
+ g.doWear(sess, strings.Join(args, " "))
+}
+
func (g *Game) checkRequirements(p *player.Player, def *object.ItemDef) string {
if len(def.Requirements) == 0 {
return ""
diff --git a/internal/game/core_course.go b/internal/game/core_course.go
new file mode 100644
index 0000000..908466d
--- /dev/null
+++ b/internal/game/core_course.go
@@ -0,0 +1,135 @@
+package game
+
+import (
+ "path/filepath"
+ "sync"
+
+ "thehouseoficarus/internal/action"
+ "gopkg.in/yaml.v3"
+)
+
+type ObstacleDef struct {
+ RoomID int `yaml:"room_id"`
+ Verb string `yaml:"verb"`
+ TicksPerPhase float64 `yaml:"ticks_per_phase"`
+ XP int `yaml:"xp"`
+ FailDamage [2]int `yaml:"fail_damage"`
+ Messages []string `yaml:"messages"`
+}
+
+type CourseConfig struct {
+ ID string `yaml:"id"`
+ Name string `yaml:"name"`
+ RequiredLevel int `yaml:"required_level"`
+ StartRoom int `yaml:"start_room"`
+ CompletionXP int `yaml:"completion_xp"`
+ Obstacles []ObstacleDef `yaml:"obstacles"`
+}
+
+type ObstacleInfo struct {
+ CourseID string
+ CourseName string
+ ObstacleIndex int
+ TotalObstacles int
+ Verb string
+ Messages []string
+ TicksPerPhase float64
+ ObstacleXP int
+ CompletionXP int
+ FailDamage [2]int
+ NextRoom int
+ StartRoom int
+ RequiredLevel int
+}
+
+var obstacleVerbs = map[string]bool{}
+
+var verbGerund = map[string]string{
+ "scramble": "scrambling",
+ "jump": "jumping",
+ "swing": "swinging",
+ "balance": "balancing",
+ "climb": "climbing",
+ "crawl": "crawling",
+ "vault": "vaulting",
+ "leap": "leaping",
+ "slide": "sliding",
+}
+
+type CourseStore struct {
+ dataDir string
+ mu sync.Mutex
+ courses map[string]*CourseConfig
+ roomToObstacle map[int]*ObstacleInfo
+ loaded bool
+}
+
+func NewCourseStore(dataDir string) *CourseStore {
+ return &CourseStore{
+ dataDir: dataDir,
+ courses: make(map[string]*CourseConfig),
+ }
+}
+
+func (cs *CourseStore) LoadAll() {
+ cs.mu.Lock()
+ defer cs.mu.Unlock()
+ cs.loadAllLocked()
+}
+
+func (cs *CourseStore) GetObstacle(roomID int) *ObstacleInfo {
+ cs.mu.Lock()
+ defer cs.mu.Unlock()
+ if !cs.loaded {
+ cs.loadAllLocked()
+ }
+ return cs.roomToObstacle[roomID]
+}
+
+func (cs *CourseStore) loadAllLocked() {
+ cs.loaded = true
+ cs.roomToObstacle = make(map[int]*ObstacleInfo)
+ localVerbs := make(map[string]bool)
+
+ action.WalkYAMLDir(filepath.Join(cs.dataDir, "courses"), func(path, id string, data []byte) error {
+ var cfg CourseConfig
+ if err := yaml.Unmarshal(data, &cfg); err != nil {
+ return nil
+ }
+ cs.courses[cfg.ID] = &cfg
+
+ totalObstacles := len(cfg.Obstacles)
+ for i, obs := range cfg.Obstacles {
+ nextRoom := 0
+ if i < totalObstacles-1 {
+ nextRoom = cfg.Obstacles[i+1].RoomID
+ }
+
+ completionXP := 0
+ if i == totalObstacles-1 {
+ completionXP = cfg.CompletionXP
+ }
+
+ info := &ObstacleInfo{
+ CourseID: cfg.ID,
+ CourseName: cfg.Name,
+ ObstacleIndex: i,
+ TotalObstacles: totalObstacles,
+ Verb: obs.Verb,
+ Messages: obs.Messages,
+ TicksPerPhase: obs.TicksPerPhase,
+ ObstacleXP: obs.XP,
+ CompletionXP: completionXP,
+ FailDamage: obs.FailDamage,
+ NextRoom: nextRoom,
+ StartRoom: cfg.StartRoom,
+ RequiredLevel: cfg.RequiredLevel,
+ }
+ cs.roomToObstacle[obs.RoomID] = info
+ localVerbs[obs.Verb] = true
+ }
+ return nil
+ })
+
+ obstacleVerbs = localVerbs
+}
diff --git a/internal/game/equip_stats.go b/internal/game/core_equip.go
index 02b8348..02b8348 100644
--- a/internal/game/equip_stats.go
+++ b/internal/game/core_equip.go
diff --git a/internal/game/player_flags.go b/internal/game/core_flags.go
index 6595cc4..6595cc4 100644
--- a/internal/game/player_flags.go
+++ b/internal/game/core_flags.go
diff --git a/internal/game/hacking.go b/internal/game/core_hacking.go
index 3457728..3457728 100644
--- a/internal/game/hacking.go
+++ b/internal/game/core_hacking.go
diff --git a/internal/game/login_account.go b/internal/game/core_login_account.go
index 87f8c62..87f8c62 100644
--- a/internal/game/login_account.go
+++ b/internal/game/core_login_account.go
diff --git a/internal/game/login_char.go b/internal/game/core_login_char.go
index 84f146b..84f146b 100644
--- a/internal/game/login_char.go
+++ b/internal/game/core_login_char.go
diff --git a/internal/game/messages.go b/internal/game/core_messages.go
index 1c9e85a..1c9e85a 100644
--- a/internal/game/messages.go
+++ b/internal/game/core_messages.go
diff --git a/internal/game/core_production.go b/internal/game/core_production.go
new file mode 100644
index 0000000..227c4ad
--- /dev/null
+++ b/internal/game/core_production.go
@@ -0,0 +1,803 @@
+package game
+
+import (
+ "fmt"
+ "math/rand"
+ "sort"
+ "strconv"
+ "strings"
+
+ "thehouseoficarus/internal/action"
+ "thehouseoficarus/internal/color"
+ "thehouseoficarus/internal/engine"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
+ "thehouseoficarus/internal/player"
+
+)
+
+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)
+
+ skill := recipe.EffectiveSkill()
+ if skill != "" {
+ skillLevel := p.Level(player.SkillName(skill))
+ if skillLevel < recipe.Level {
+ sess.WriteLine(fmt.Sprintf("You need level %d %s to do that.", recipe.Level, skill))
+ g.reprompt(sess)
+ return
+ }
+ }
+
+ if !recipe.HasAllItemsQty(p.CountItem) {
+ sess.WriteLine("You don't have the required materials.")
+ g.reprompt(sess)
+ return
+ }
+
+ outDef, _ := g.ItemStore.Load(recipe.Output)
+
+ wait := recipe.Wait
+ if wait <= 0 {
+ wait = 4
+ }
+
+ outputName := recipe.Output
+ if outDef != nil {
+ outputName = outDef.Name
+ }
+
+ p.Action = &action.Action{
+ Type: actionType,
+ TargetID: recipe.ID,
+ TargetName: outputName,
+ Data: map[string]any{
+ "recipe_id": recipe.ID,
+ "phase": 0,
+ "wait": wait,
+ "start_msg": startMsg,
+ "end_msg": endMsg,
+ "remaining": count,
+ },
+ WaitLeft: engine.ToTicks(1),
+ }
+
+ 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)))
+ }
+ }
+ }
+}
+
+func (g *Game) startProductionFromRecipe(sess *net.Session, p *player.Player, recipe *action.RecipeDef, count int) {
+ _, stationName := g.findStation(p.RoomID, recipe.Station)
+ if stationName == "" {
+ stationName = "inventory"
+ }
+
+ firstItem := recipe.FirstItemName(func(id string) (string, bool) {
+ def, err := g.ItemStore.Load(id)
+ if err != nil {
+ return id, false
+ }
+ return def.Name, true
+ })
+
+ info, ok := productionTypes[recipe.Type]
+ if !ok {
+ info = productionTypeInfo{recipe.Type, recipe.Type}
+ }
+ actionType := info.ActionType
+ displayVerb := info.DisplayVerb
+
+ var startMsg, endMsg string
+ if stationName != "inventory" {
+ startMsg = fmt.Sprintf("You start %s %s on the %s.", displayVerb, firstItem, stationName)
+ } else {
+ startMsg = fmt.Sprintf("You start %s %s.", displayVerb, firstItem)
+ }
+ endMsg = fmt.Sprintf("You've finished %s.", displayVerb)
+
+ 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
+ }
+ }
+ }
+ if strings.HasPrefix(recipeID, "combine_") {
+ itemID := strings.TrimPrefix(recipeID, "combine_")
+ if itemDef, err := g.ItemStore.Load(itemID); err == nil && len(itemDef.MadeFrom) > 0 {
+ return g.buildCombineRecipe(itemDef)
+ }
+ }
+ return nil
+}
+
+func (g *Game) advanceProduction(sess *net.Session, p *player.Player) bool {
+ recipeID := p.Action.Data["recipe_id"].(string)
+ phase := p.Action.Data["phase"].(int)
+ wait := p.Action.Data["wait"].(float64)
+ startMsg, _ := p.Action.Data["start_msg"].(string)
+ endMsg, _ := p.Action.Data["end_msg"].(string)
+ remaining, _ := p.Action.Data["remaining"].(int)
+
+ recipe := g.loadProductionRecipe(recipeID)
+ if recipe == nil {
+ g.cancelAction(p)
+ return false
+ }
+
+ if phase == 0 {
+ if startMsg != "" {
+ sess.WriteLine(fmt.Sprintf("\n%s", startMsg))
+ }
+ p.Action.Data["phase"] = 1
+ p.Action.WaitLeft = engine.ToTicks(wait)
+ return true
+ }
+
+ skill := recipe.EffectiveSkill()
+ skillLevel := p.Level(player.SkillName(skill))
+ chance := 1.0
+ if recipe.Success != nil {
+ chance = action.SuccessChance(*recipe.Success, skillLevel, recipe.Level)
+ }
+
+ if rand.Float64() < chance {
+ outputQty := recipe.OutputQty
+ if outputQty <= 0 {
+ outputQty = 1
+ }
+
+ byproducts := g.collectByproducts(p, recipe)
+
+ placed := false
+ outDef, _ := g.ItemStore.Load(recipe.Output)
+ if outDef != nil && outDef.Stackable {
+ for i := 0; i < 28; i++ {
+ slot := p.InvSlot(i)
+ if slot != nil && slot.ItemID == recipe.Output {
+ recipe.ConsumeAll(p.HasItem, p.RemoveItem)
+ slot.Quantity += outputQty
+ placed = true
+ break
+ }
+ }
+ }
+ if !placed {
+ recipe.ConsumeAll(p.HasItem, p.RemoveItem)
+ freeSlot := p.FirstFreeSlot()
+ if freeSlot == -1 {
+ sess.WriteLine("Your inventory is too full!")
+ g.cancelAction(p)
+ return false
+ }
+ p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: recipe.Output, Quantity: outputQty})
+ }
+
+ for _, bp := range byproducts {
+ if slot := p.FirstFreeSlot(); slot >= 0 {
+ p.SetInvSlot(slot, &player.InventorySlot{ItemID: bp, Quantity: 1})
+ }
+ }
+
+ if recipe.XP > 0 {
+ if newLevel := p.AddSkillXP(player.SkillName(skill), recipe.XP); newLevel > 0 {
+ sess.WriteLine(g.colorize(sess, "level_up", fmt.Sprintf("*** You are now level %d %s! ***", newLevel, skill)))
+ }
+ }
+ g.AccountStore.SaveCharacter(p)
+
+ msg := recipe.Message
+ if msg == "" {
+ outputName := recipe.Output
+ if outDef != nil {
+ outputName = outDef.Name
+ }
+ msg = fmt.Sprintf("You produce %s.", outputName)
+ }
+ if p.OptionBool("xp_drops") && recipe.XP > 0 {
+ abbr := player.SkillAbbr[player.SkillName(skill)]
+ msg += g.colorize(sess, "xp", fmt.Sprintf(" (+%dxp %s)", recipe.XP, abbr))
+ }
+ sess.WriteLine(msg)
+ } else {
+ recipe.ConsumeAll(p.HasItem, p.RemoveItem)
+
+ if recipe.Fail != "" {
+ freeSlot := p.FirstFreeSlot()
+ if freeSlot >= 0 {
+ p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: recipe.Fail, Quantity: 1})
+ }
+ }
+ g.AccountStore.SaveCharacter(p)
+
+ msg := recipe.FailMessage
+ if msg == "" {
+ msg = "You fail and the materials are lost."
+ }
+ sess.WriteLine(g.colorize(sess, "damage", msg))
+ }
+
+ if remaining > 0 {
+ remaining--
+ p.Action.Data["remaining"] = remaining
+ if remaining <= 0 {
+ if endMsg != "" {
+ sess.WriteLine(fmt.Sprintf("\n%s", endMsg))
+ }
+ g.cancelAction(p)
+ return false
+ }
+ }
+
+ if g.canContinueProduction(p, recipe) {
+ p.Action.WaitLeft = engine.ToTicks(wait)
+ return true
+ }
+
+ if endMsg != "" {
+ sess.WriteLine(fmt.Sprintf("\n%s", endMsg))
+ }
+ g.cancelAction(p)
+ return false
+}
+
+func (g *Game) collectByproducts(p *player.Player, recipe *action.RecipeDef) []string {
+ var byproducts []string
+ for _, e := range recipe.Consume {
+ if len(e.Byproducts) == 0 {
+ continue
+ }
+ for i, id := range e.Items {
+ if p.HasItem(id) && i < len(e.Byproducts) && e.Byproducts[i] != "" {
+ byproducts = append(byproducts, e.Byproducts[i])
+ break
+ }
+ }
+ }
+ return byproducts
+}
+
+func (g *Game) canContinueProduction(p *player.Player, recipe *action.RecipeDef) bool {
+ if !recipe.HasAllItemsQty(p.CountItem) {
+ return false
+ }
+ outputQty := recipe.OutputQty
+ if outputQty <= 0 {
+ outputQty = 1
+ }
+ outDef, _ := g.ItemStore.Load(recipe.Output)
+ if outDef != nil && outDef.Stackable {
+ for i := 0; i < 28; i++ {
+ slot := p.InvSlot(i)
+ if slot != nil && slot.ItemID == recipe.Output {
+ return true
+ }
+ }
+ }
+ return p.FirstFreeSlot() >= 0
+}
+
+func (g *Game) handleRecipeChoice(sess *net.Session, input string) {
+ input = strings.TrimSpace(input)
+
+ menuData := sess.PendingMenu
+ if len(menuData) == 0 {
+ sess.State = net.StateGame
+ g.reprompt(sess)
+ return
+ }
+
+ if input == "" {
+ sess.PendingMenu = nil
+ sess.State = net.StateGame
+ sess.WriteLine("Never mind.")
+ g.reprompt(sess)
+ return
+ }
+
+ if choice, err := strconv.Atoi(input); err == nil {
+ sess.PendingMenu = nil
+ sess.State = net.StateGame
+ if choice <= 0 || choice > len(menuData) {
+ sess.WriteLine("Never mind.")
+ g.reprompt(sess)
+ return
+ }
+ g.dispatchMenuEntry(sess, menuData[choice-1])
+ return
+ }
+
+ lower := strings.ToLower(input)
+ matchIdx := -1
+ for i, entry := range menuData {
+ name := g.menuEntryName(entry)
+ if name == "" {
+ continue
+ }
+ if strings.ToLower(name) == lower || action.WordPrefixMatch(input, name) {
+ if matchIdx >= 0 {
+ sess.WriteLine("That's ambiguous.")
+ return
+ }
+ matchIdx = i
+ }
+ }
+ if matchIdx >= 0 {
+ sess.PendingMenu = nil
+ sess.State = net.StateGame
+ g.dispatchMenuEntry(sess, menuData[matchIdx])
+ return
+ }
+
+ sess.PendingMenu = nil
+ sess.State = net.StateGame
+ sess.WriteLine("Never mind.")
+ g.reprompt(sess)
+}
+
+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 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 barID, ok := entry["bar_id"]; ok {
+ if def, _ := g.ItemStore.Load(barID); def != nil {
+ return def.Name
+ }
+ return barID
+ }
+ if cid, ok := entry["combine_item"]; ok {
+ if def, _ := g.ItemStore.Load(cid); def != nil {
+ return def.Name
+ }
+ return cid
+ }
+ return ""
+}
+
+func (g *Game) dispatchMenuEntry(sess *net.Session, entry map[string]string) {
+ if cid, ok := entry["combine_item"]; ok {
+ g.promptHowMany(sess, "combine_"+cid)
+ return
+ }
+ if barID, ok := entry["bar_id"]; ok {
+ p := sess.Player
+ allRecipes, _ := g.RecipeStore.LoadAll()
+ g.showSmithTable(sess, p, barID, allRecipes)
+ return
+ }
+ 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
+ }
+ }
+ g.reprompt(sess)
+ return
+ }
+ if rid, ok := entry["recipe_id"]; ok {
+ g.promptHowMany(sess, rid)
+ return
+ }
+ g.reprompt(sess)
+}
+
+func (g *Game) formatRecipeMaterials(r *action.RecipeDef) string {
+ var parts []string
+ for _, e := range r.Consume {
+ itemName := e.Items[0]
+ if def, err := g.ItemStore.Load(e.Items[0]); err == nil {
+ itemName = def.Name
+ }
+ if e.Quantity > 1 {
+ parts = append(parts, fmt.Sprintf("%s x%d", itemName, e.Quantity))
+ } else {
+ parts = append(parts, itemName)
+ }
+ }
+ return strings.Join(parts, ", ")
+}
+
+func (g *Game) showProductionTable(sess *net.Session, p *player.Player, recipes []action.RecipeDef, title, skill, lastFlagKey, promptVerb string, background bool) {
+ sort.Slice(recipes, func(i, j int) bool {
+ return recipes[i].Level < recipes[j].Level
+ })
+
+ mode := g.colorMode(sess)
+ skillLevel := p.Level(player.SkillName(skill))
+ dimSpec := color.Parse("240")
+
+ tbl := &Table{
+ Title: title,
+ Columns: []string{"#", "Product", "Materials", "Level"},
+ }
+
+ for i, r := range recipes {
+ outDef, _ := g.ItemStore.Load(r.Output)
+ productName := r.Output
+ if outDef != nil {
+ productName = outDef.Name
+ }
+
+ outputQty := r.OutputQty
+ if outputQty <= 0 {
+ outputQty = 1
+ }
+ if outDef != nil && outDef.Stackable && outputQty > 1 {
+ productName = fmt.Sprintf("%s x%d", productName, outputQty)
+ }
+
+ matStr := g.formatRecipeMaterials(&r)
+ levelStr := fmt.Sprint(r.Level)
+ numStr := fmt.Sprintf("%d", i+1)
+
+ canMake := skillLevel >= r.Level && r.HasAllItemsQty(p.CountItem)
+ if canMake {
+ productName = g.itemColorize(sess, outDef, productName)
+ } else {
+ productName = color.Render(mode, dimSpec, productName)
+ matStr = color.Render(mode, dimSpec, matStr)
+ levelStr = color.Render(mode, dimSpec, levelStr)
+ numStr = color.Render(mode, dimSpec, numStr)
+ }
+
+ tbl.Rows = append(tbl.Rows, []string{numStr, productName, matStr, levelStr})
+ }
+
+ unicode := p.OptionBool("unicode")
+ sess.WriteLine("")
+ for _, line := range tbl.Render(unicode) {
+ sess.WriteLine(line)
+ }
+
+ lastRecipeID, _ := p.Flags[lastFlagKey].(string)
+ hint := ""
+ if lastRecipeID != "" {
+ for _, r := range recipes {
+ if r.ID == lastRecipeID {
+ if outDef, err := g.ItemStore.Load(r.Output); err == nil {
+ hint = outDef.Name
+ }
+ break
+ }
+ }
+ }
+
+ if hint != "" {
+ sess.Write(fmt.Sprintf("%s what (enter for all %s): ", promptVerb, hint))
+ } else {
+ sess.Write(fmt.Sprintf("%s what: ", promptVerb))
+ }
+
+ sess.PendingMenu = recipeMenuData(recipeDefIDs(recipes))
+ sess.PendingSkill = skill
+ sess.PendingLastFlag = lastFlagKey
+ sess.PendingBackground = background
+ sess.State = net.StateProductChoice
+}
+
+func (g *Game) handleProductChoice(sess *net.Session, input string) {
+ p := sess.Player
+ menuData := sess.PendingMenu
+ skill := sess.PendingSkill
+ lastFlagKey := sess.PendingLastFlag
+ background := sess.PendingBackground
+ sess.PendingMenu = nil
+ sess.PendingSkill = ""
+ sess.PendingLastFlag = ""
+ sess.PendingBackground = false
+ sess.State = net.StateGame
+
+ input = strings.TrimSpace(input)
+
+ allRecipes, err := g.RecipeStore.LoadAll()
+ if err != nil {
+ g.reprompt(sess)
+ return
+ }
+
+ var recipes []action.RecipeDef
+ for _, entry := range menuData {
+ rid := entry["recipe_id"]
+ for _, r := range allRecipes {
+ if r.ID == rid {
+ recipes = append(recipes, r)
+ break
+ }
+ }
+ }
+
+ if len(recipes) == 0 {
+ g.reprompt(sess)
+ return
+ }
+
+ sort.Slice(recipes, func(i, j int) bool {
+ return recipes[i].Level < recipes[j].Level
+ })
+
+ lastRecipeID, _ := p.Flags[lastFlagKey].(string)
+
+ sel, errMsg := g.resolveRecipeByInput(input, recipes, lastRecipeID)
+ switch errMsg {
+ case "ambiguous":
+ sess.WriteLine("That's ambiguous.")
+ g.reprompt(sess)
+ return
+ case "never_mind":
+ sess.WriteLine("Never mind.")
+ g.reprompt(sess)
+ return
+ }
+
+ skillLevel := p.Level(player.SkillName(skill))
+ if skillLevel < sel.Recipe.Level {
+ sess.WriteLine(fmt.Sprintf("You need level %d %s to make that.", sel.Recipe.Level, skill))
+ g.reprompt(sess)
+ return
+ }
+ if !sel.Recipe.HasAllItemsQty(p.CountItem) {
+ sess.WriteLine("You don't have the materials for that.")
+ g.reprompt(sess)
+ return
+ }
+
+ p.EnsureFlags()
+ p.Flags[lastFlagKey] = sel.Recipe.ID
+ g.AccountStore.SaveCharacter(p)
+
+ if background {
+ g.startFletchAction(sess, p, sel.Recipe, sel.Count)
+ } else {
+ g.startProductionFromRecipe(sess, p, sel.Recipe, sel.Count)
+ }
+}
+
+func (g *Game) hasToolType(p *player.Player, toolType string) bool {
+ if itemID, ok := p.Equipment[object.SlotMainHand]; ok {
+ if def, err := g.ItemStore.Load(itemID); err == nil && def.ToolType == toolType {
+ return true
+ }
+ }
+ for i := 0; i < 28; i++ {
+ slot := p.InvSlot(i)
+ if slot == nil {
+ continue
+ }
+ if def, err := g.ItemStore.Load(slot.ItemID); err == nil && def.ToolType == toolType {
+ return true
+ }
+ }
+ return false
+}
+type recipeEntry struct {
+ ItemName string
+ Recipe action.RecipeDef
+}
+
+type productionTypeInfo struct {
+ ActionType string
+ DisplayVerb string
+}
+
+var productionTypes = map[string]productionTypeInfo{
+ "cooking": {"cook", "cooking"},
+ "smelting": {"smelt", "smelting"},
+ "smithing": {"smith", "smithing"},
+ "crafting": {"craft", "crafting"},
+ "combine": {"combine", "combining"},
+ "fletching": {"fletch", "fletching"},
+ "pharmacy": {"mix", "mixing"},
+ "construction": {"construct", "constructing"},
+}
+
+var productionActionTypes map[string]bool
+
+func init() {
+ productionActionTypes = make(map[string]bool)
+ for _, pt := range productionTypes {
+ productionActionTypes[pt.ActionType] = true
+ }
+}
+
+func recipeMenuData(recipeIDs []string) []map[string]string {
+ out := make([]map[string]string, len(recipeIDs))
+ for i, id := range recipeIDs {
+ out[i] = map[string]string{"recipe_id": id}
+ }
+ return out
+}
+
+func recipeDefIDs(defs []action.RecipeDef) []string {
+ ids := make([]string, len(defs))
+ for i, d := range defs {
+ ids[i] = d.ID
+ }
+ return ids
+}
+
+func entryMenuData(entries []recipeEntry) []map[string]string {
+ out := make([]map[string]string, len(entries))
+ for i, e := range entries {
+ out[i] = map[string]string{"recipe_id": e.Recipe.ID}
+ }
+ return out
+}
+
+func (g *Game) promptHowMany(sess *net.Session, recipeID string) {
+ sess.PendingRecipeID = recipeID
+ sess.State = net.StateHowMany
+ sess.Write("How many (return for all)?: ")
+}
+
+type recipeSelection struct {
+ Recipe *action.RecipeDef
+ Count int
+}
+
+func (g *Game) resolveRecipeByInput(input string, recipes []action.RecipeDef, lastRecipeID string) (sel recipeSelection, errMsg string) {
+ if input == "" {
+ if lastRecipeID == "" {
+ return sel, "never_mind"
+ }
+ for i := range recipes {
+ if recipes[i].ID == lastRecipeID {
+ sel.Recipe = &recipes[i]
+ return sel, ""
+ }
+ }
+ return sel, "never_mind"
+ }
+
+ qty, productName := parseQty(input)
+ productName = strings.TrimSpace(productName)
+
+ if idx, err := strconv.Atoi(productName); err == nil && idx > 0 && idx <= len(recipes) {
+ sel.Recipe = &recipes[idx-1]
+ sel.Count = qty
+ return sel, ""
+ }
+
+ matchCount := 0
+ for i := range recipes {
+ outDef, _ := g.ItemStore.Load(recipes[i].Output)
+ name := recipes[i].Output
+ if outDef != nil {
+ name = outDef.Name
+ }
+ if action.WordPrefixMatch(productName, name) {
+ if matchCount == 0 {
+ sel.Recipe = &recipes[i]
+ sel.Count = qty
+ }
+ matchCount++
+ }
+ }
+ if matchCount > 1 {
+ return sel, "ambiguous"
+ }
+ if sel.Recipe == nil {
+ return sel, "never_mind"
+ }
+ return sel, ""
+}
+
+func (g *Game) showMenuTable(sess *net.Session, title string, names []string) {
+ p := sess.Player
+ tbl := &Table{Title: title}
+ for i, name := range names {
+ tbl.Rows = append(tbl.Rows, []string{fmt.Sprintf("%d", i+1), name})
+ }
+ unicode := p.OptionBool("unicode")
+ sess.WriteLine("")
+ for _, line := range tbl.Render(unicode) {
+ sess.WriteLine(line)
+ }
+}
+
+func (g *Game) handleHowMany(sess *net.Session, input string) {
+ p := sess.Player
+ recipeID := sess.PendingRecipeID
+ sess.PendingRecipeID = ""
+ sess.State = net.StateGame
+
+ input = strings.TrimSpace(input)
+
+ var count int
+ if input == "" {
+ count = 0
+ } else if n, err := strconv.Atoi(input); err == nil && n > 0 {
+ count = n
+ } else {
+ sess.WriteLine("Never mind.")
+ g.reprompt(sess)
+ return
+ }
+
+ var recipe *action.RecipeDef
+ if strings.HasPrefix(recipeID, "combine_") {
+ itemID := strings.TrimPrefix(recipeID, "combine_")
+ itemDef, err := g.ItemStore.Load(itemID)
+ if err != nil || len(itemDef.MadeFrom) == 0 {
+ g.reprompt(sess)
+ return
+ }
+ 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
+ }
+ }
+ }
+
+ if recipe == nil {
+ g.reprompt(sess)
+ return
+ }
+
+ g.startProductionFromRecipe(sess, p, recipe, count)
+}
+
+func (g *Game) buildCombineRecipe(def *object.ItemDef) *action.RecipeDef {
+ var consume []action.ConsumeEntry
+ for _, mf := range def.MadeFrom {
+ consume = append(consume, action.ConsumeEntry{
+ Items: mf.Items,
+ Quantity: mf.Quantity,
+ Byproducts: mf.Byproducts,
+ })
+ }
+ wait := def.Ticks
+ if wait <= 0 {
+ wait = 2
+ }
+ return &action.RecipeDef{
+ ID: "combine_" + def.ID,
+ Type: "combine",
+ Wait: wait,
+ Consume: consume,
+ Output: def.ID,
+ Message: fmt.Sprintf("You create %s.", def.Name),
+ }
+}
diff --git a/internal/game/core_skill.go b/internal/game/core_skill.go
new file mode 100644
index 0000000..c02cdf3
--- /dev/null
+++ b/internal/game/core_skill.go
@@ -0,0 +1,15 @@
+package game
+
+import (
+ "fmt"
+
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+)
+
+func (g *Game) awardSkillXP(sess *net.Session, p *player.Player, skill player.SkillName, xp int) {
+ newLevel := p.AddSkillXP(skill, xp)
+ if newLevel > 0 {
+ sess.WriteLine(g.colorize(sess, "level_up", fmt.Sprintf("*** You are now level %d %s! ***", newLevel, skill)))
+ }
+}
diff --git a/internal/game/core_startup.go b/internal/game/core_startup.go
new file mode 100644
index 0000000..ab52762
--- /dev/null
+++ b/internal/game/core_startup.go
@@ -0,0 +1,770 @@
+package game
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+
+ "thehouseoficarus/internal/action"
+)
+
+type StartupIssue struct {
+ Level string
+ Type string
+ Message string
+}
+
+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, validateRoomWiring(g)...)
+
+ return issues
+}
+
+func validateDuplicateIDs(dataDir, subDir, label string) []StartupIssue {
+ dir := filepath.Join(dataDir, subDir)
+ dups := action.CheckDuplicateIDs(dir)
+ var issues []StartupIssue
+ for _, d := range dups {
+ short := make([]string, len(d.Paths))
+ for i, p := range d.Paths {
+ short[i] = strings.TrimPrefix(p, dir+"/")
+ }
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "duplicate",
+ Message: fmt.Sprintf("Duplicate %s ID %q found in: %s",
+ label, d.ID, strings.Join(short, ", ")),
+ })
+ }
+ return issues
+}
+
+func validateRoomReferences(g *Game) []StartupIssue {
+ var issues []StartupIssue
+ roomIndex := g.World.RoomIndex()
+ itemIDs := g.ItemStore.IDSet()
+ mobIDs := g.MobStore.AllDefIDs()
+ objIDs := g.ObjectStore.IDSet()
+
+ for id := range roomIndex {
+ room, err := g.World.LoadRoom(id)
+ if err != nil {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Room %d: failed to load: %v", id, err),
+ })
+ continue
+ }
+ if room.Name == "" {
+ issues = append(issues, StartupIssue{
+ Level: "WARN",
+ Type: "reference",
+ Message: fmt.Sprintf("Room %d: has no name", id),
+ })
+ }
+
+ for dir, exit := range room.Exits {
+ if exit.Room <= 0 {
+ continue
+ }
+ if _, ok := roomIndex[exit.Room]; !ok {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Room %d: exit %q targets nonexistent room %d",
+ id, dir, exit.Room),
+ })
+ }
+ }
+
+ for _, rm := range room.Mobs {
+ if rm.ID == "" {
+ continue
+ }
+ if _, ok := mobIDs[rm.ID]; !ok {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Room %d: references nonexistent mob %q",
+ id, rm.ID),
+ })
+ }
+ for _, wr := range rm.WanderRooms {
+ if _, ok := roomIndex[wr]; !ok {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Room %d: mob %q wander_rooms references nonexistent room %d",
+ id, rm.ID, wr),
+ })
+ }
+ }
+ }
+
+ for _, s := range room.ItemSpawns {
+ if s.ID != "" && !itemIDs[s.ID] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Room %d: spawns nonexistent item %q",
+ id, s.ID),
+ })
+ }
+ }
+
+ for _, robj := range room.Objects {
+ if robj.ID != "" && !objIDs[robj.ID] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Room %d: references nonexistent object %q",
+ id, robj.ID),
+ })
+ }
+ for _, wr := range robj.WanderRooms {
+ if _, ok := roomIndex[wr]; !ok {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Room %d: object %q wander_rooms references nonexistent room %d",
+ id, robj.ID, wr),
+ })
+ }
+ }
+ }
+ }
+
+ return issues
+}
+
+func validateMobReferences(g *Game) []StartupIssue {
+ var issues []StartupIssue
+ itemIDs := g.ItemStore.IDSet()
+ dropIDs := dropTableIDSet(g.DataDir)
+ mobIDs := g.MobStore.AllDefIDs()
+
+ for id := range mobIDs {
+ def, err := g.MobStore.LoadDef(id)
+ if err != nil {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Mob %q: failed to load: %v", id, err),
+ })
+ continue
+ }
+ if def.Name == "" {
+ issues = append(issues, StartupIssue{
+ Level: "WARN",
+ Type: "reference",
+ Message: fmt.Sprintf("Mob %q: has no name", id),
+ })
+ }
+
+ if def.Drops.Remains != "" && !itemIDs[def.Drops.Remains] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Mob %q: drops remains (item %q) does not exist",
+ id, def.Drops.Remains),
+ })
+ }
+
+ for _, d := range def.Drops.Loot {
+ if d.ItemID != "" && !itemIDs[d.ItemID] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Mob %q: loot item %q does not exist",
+ id, d.ItemID),
+ })
+ }
+ if d.Table != "" && !dropIDs[d.Table] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Mob %q: loot table %q does not exist",
+ id, d.Table),
+ })
+ }
+ }
+
+ if def.StealTable != "" && !dropIDs[def.StealTable] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Mob %q: steal_table %q does not exist",
+ id, def.StealTable),
+ })
+ }
+
+ if def.FinishingBlow != "" && !itemIDs[def.FinishingBlow] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Mob %q: finishing_blow item %q does not exist",
+ id, def.FinishingBlow),
+ })
+ }
+ }
+
+ return issues
+}
+
+func validateObjectReferences(g *Game) []StartupIssue {
+ var issues []StartupIssue
+ objIDs := g.ObjectStore.IDSet()
+ itemIDs := g.ItemStore.IDSet()
+ mobIDs := g.MobStore.AllDefIDs()
+ dropIDs := dropTableIDSet(g.DataDir)
+ roomIndex := g.World.RoomIndex()
+
+ for id := range objIDs {
+ obj, err := g.ObjectStore.Load(id)
+ if err != nil {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Object %q: failed to load: %v", id, err),
+ })
+ continue
+ }
+ if obj.Name == "" {
+ issues = append(issues, StartupIssue{
+ Level: "WARN",
+ Type: "reference",
+ Message: fmt.Sprintf("Object %q: has no name", id),
+ })
+ }
+
+ if obj.RemovalItem != "" && !itemIDs[obj.RemovalItem] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Object %q: removal_item %q does not exist",
+ id, obj.RemovalItem),
+ })
+ }
+
+ if obj.StealTable != "" && !dropIDs[obj.StealTable] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Object %q: steal_table %q does not exist",
+ id, obj.StealTable),
+ })
+ }
+
+ if obj.GuardMob != "" && !mobIDs[obj.GuardMob] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Object %q: guard_mob %q does not exist",
+ id, obj.GuardMob),
+ })
+ }
+
+ for _, ui := range obj.UseInteractions {
+ if ui.Item != "" && !itemIDs[ui.Item] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Object %q: use_interaction item %q does not exist",
+ id, ui.Item),
+ })
+ }
+ if ui.Action != nil {
+ issues = append(issues, validateNodeAction(fmt.Sprintf("Object %q: use_interaction", id), ui.Action, itemIDs, roomIndex)...)
+ }
+ }
+
+ if obj.Gather != nil {
+ issues = append(issues, validateGatherConfig(fmt.Sprintf("Object %q: gather", id), obj.Gather, itemIDs, dropIDs)...)
+ }
+
+ if obj.Talk != nil {
+ issues = append(issues, validateTalkConfig(fmt.Sprintf("Object %q: talk", id), obj.Talk, itemIDs, roomIndex)...)
+ }
+
+ if obj.Use != nil {
+ issues = append(issues, validateUseConfig(fmt.Sprintf("Object %q: use", id), obj.Use, itemIDs)...)
+ }
+ }
+
+ return issues
+}
+
+func validateItemReferences(g *Game) []StartupIssue {
+ var issues []StartupIssue
+ itemIDs := g.ItemStore.IDSet()
+ dropIDs := dropTableIDSet(g.DataDir)
+
+ for id := range itemIDs {
+ def, err := g.ItemStore.Load(id)
+ if err != nil {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Item %q: failed to load: %v", id, err),
+ })
+ continue
+ }
+ if def.Name == "" {
+ issues = append(issues, StartupIssue{
+ Level: "WARN",
+ Type: "reference",
+ Message: fmt.Sprintf("Item %q: has no name", id),
+ })
+ }
+
+ if def.SearchTable != "" && !dropIDs[def.SearchTable] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Item %q: search_table %q does not exist",
+ id, def.SearchTable),
+ })
+ }
+
+ if def.SearchMiscTable != "" && !dropIDs[def.SearchMiscTable] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Item %q: search_misc_table %q does not exist",
+ id, def.SearchMiscTable),
+ })
+ }
+
+ for _, mf := range def.MadeFrom {
+ for _, item := range mf.Items {
+ if item != "" && !itemIDs[item] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Item %q: made_from item %q does not exist",
+ id, item),
+ })
+ }
+ }
+ for _, bp := range mf.Byproducts {
+ if bp != "" && !itemIDs[bp] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Item %q: made_from byproduct %q does not exist",
+ id, bp),
+ })
+ }
+ }
+ }
+
+ if def.FarmProduct != "" && !itemIDs[def.FarmProduct] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Item %q (seed): farm_product %q does not exist",
+ id, def.FarmProduct),
+ })
+ }
+ }
+
+ return issues
+}
+
+func validateRecipeReferences(g *Game) []StartupIssue {
+ var issues []StartupIssue
+ itemIDs := g.ItemStore.IDSet()
+
+ allRecipes, err := g.RecipeStore.LoadAll()
+ if err != nil {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Failed to load recipes: %v", err),
+ })
+ return issues
+ }
+
+ recipeIDs := make(map[string]bool)
+ for _, r := range allRecipes {
+ if r.ID != "" {
+ if recipeIDs[r.ID] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "duplicate",
+ Message: fmt.Sprintf("Recipe %q: duplicate ID field (two recipe files with same ID value)",
+ r.ID),
+ })
+ }
+ recipeIDs[r.ID] = true
+ }
+
+ for _, e := range r.Consume {
+ for _, item := range e.Items {
+ if item != "" && !itemIDs[item] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Recipe %q (%s): consumes nonexistent item %q",
+ r.ID, r.Type, item),
+ })
+ }
+ }
+ }
+
+ if r.Output != "" && !itemIDs[r.Output] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Recipe %q (%s): output item %q does not exist",
+ r.ID, r.Type, r.Output),
+ })
+ }
+
+ if r.Fail != "" && !itemIDs[r.Fail] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Recipe %q (%s): fail product %q does not exist",
+ r.ID, r.Type, r.Fail),
+ })
+ }
+ }
+
+ return issues
+}
+
+func validateDropTableReferences(g *Game) []StartupIssue {
+ var issues []StartupIssue
+ dropIDs := dropTableIDSet(g.DataDir)
+ itemIDs := g.ItemStore.IDSet()
+
+ for id := range dropIDs {
+ dt, err := action.LoadDropTable(g.DataDir, id)
+ if err != nil {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Drop table %q: failed to load: %v", id, err),
+ })
+ continue
+ }
+ for _, d := range dt.Drops {
+ if d.ItemID != "" && !itemIDs[d.ItemID] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Drop table %q: drops nonexistent item %q",
+ id, d.ItemID),
+ })
+ }
+ if d.Table != "" && !dropIDs[d.Table] && d.Table != id {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Drop table %q: references nonexistent sub-table %q",
+ id, d.Table),
+ })
+ }
+ }
+ }
+
+ return issues
+}
+
+func validateCourseReferences(g *Game) []StartupIssue {
+ var issues []StartupIssue
+ roomIndex := g.World.RoomIndex()
+
+ courses := g.CourseStore.AllCourses()
+ for id, cfg := range courses {
+ if cfg.StartRoom > 0 {
+ if _, ok := roomIndex[cfg.StartRoom]; !ok {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Course %q: start_room %d does not exist",
+ id, cfg.StartRoom),
+ })
+ }
+ }
+ for _, obs := range cfg.Obstacles {
+ if _, ok := roomIndex[obs.RoomID]; !ok {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Course %q: obstacle room %d does not exist",
+ id, obs.RoomID),
+ })
+ }
+ }
+ }
+
+ return issues
+}
+
+func validateRoomWiring(g *Game) []StartupIssue {
+ var issues []StartupIssue
+ roomIndex := g.World.RoomIndex()
+ cfg := g.ValidationConfig
+
+ sourceSet := make(map[int]bool)
+ for _, s := range cfg.CheckSources {
+ sourceSet[s] = true
+ }
+
+ ignoreTo := make(map[int]bool)
+ for _, r := range cfg.IgnoreUnreachable {
+ ignoreTo[r] = true
+ }
+
+ hasExitTo := make(map[int]bool)
+ for id := range roomIndex {
+ room, err := g.World.LoadRoom(id)
+ if err != nil {
+ continue
+ }
+ for _, exit := range room.Exits {
+ if exit.Room > 0 {
+ hasExitTo[exit.Room] = true
+ }
+ }
+ }
+
+ var orphans []int
+ for id := range roomIndex {
+ if ignoreTo[id] {
+ continue
+ }
+ if sourceSet[id] {
+ continue
+ }
+ if !hasExitTo[id] {
+ orphans = append(orphans, id)
+ }
+ }
+ if len(orphans) > 0 {
+ sort.Ints(orphans)
+ var strs []string
+ for _, id := range orphans {
+ strs = append(strs, fmt.Sprintf("%d", id))
+ }
+ issues = append(issues, StartupIssue{
+ Level: "WARN",
+ Type: "integrity",
+ Message: fmt.Sprintf("Orphan rooms (no exit leads to them): %s",
+ strings.Join(strs, ", ")),
+ })
+ }
+
+ return issues
+}
+
+func validateGatherConfig(prefix string, cfg *action.GatherConfig, itemIDs map[string]bool, dropIDs map[string]bool) []StartupIssue {
+ var issues []StartupIssue
+
+ if cfg.Bait != "" && !itemIDs[cfg.Bait] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("%s: bait item %q does not exist",
+ prefix, cfg.Bait),
+ })
+ }
+
+ for _, d := range cfg.Drops {
+ if d.ItemID != "" && !itemIDs[d.ItemID] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("%s: drop item %q does not exist",
+ prefix, d.ItemID),
+ })
+ }
+ if d.Table != "" && !dropIDs[d.Table] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("%s: drop table %q does not exist",
+ prefix, d.Table),
+ })
+ }
+ }
+
+ return issues
+}
+
+func validateTalkConfig(prefix string, cfg *action.TalkConfig, itemIDs map[string]bool, roomIndex map[int]bool) []StartupIssue {
+ var issues []StartupIssue
+ if cfg == nil {
+ return issues
+ }
+ for nodeID, node := range cfg.Nodes {
+ nodePrefix := fmt.Sprintf("%s: node %q", prefix, nodeID)
+ if node.Action != nil {
+ issues = append(issues, validateNodeAction(nodePrefix, node.Action, itemIDs, roomIndex)...)
+ }
+ }
+ return issues
+}
+
+func validateUseConfig(prefix string, cfg *action.UseConfig, itemIDs map[string]bool) []StartupIssue {
+ var issues []StartupIssue
+ if cfg == nil {
+ return issues
+ }
+
+ for itemID := range cfg.Consume {
+ if itemID != "" && !itemIDs[itemID] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("%s: consumes nonexistent item %q",
+ prefix, itemID),
+ })
+ }
+ }
+
+ if cfg.Reward.ItemID != "" && !itemIDs[cfg.Reward.ItemID] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("%s: reward item %q does not exist",
+ prefix, cfg.Reward.ItemID),
+ })
+ }
+
+ return issues
+}
+
+func validateNodeAction(prefix string, na *action.NodeAction, itemIDs map[string]bool, roomIndex map[int]bool) []StartupIssue {
+ var issues []StartupIssue
+ if na == nil {
+ return issues
+ }
+
+ if na.GiveItem != "" && !itemIDs[na.GiveItem] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("%s: give_item %q does not exist",
+ prefix, na.GiveItem),
+ })
+ }
+
+ if na.TakeItem != "" && !itemIDs[na.TakeItem] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("%s: take_item %q does not exist",
+ prefix, na.TakeItem),
+ })
+ }
+
+ if na.Teleport > 0 {
+ if _, ok := roomIndex[na.Teleport]; !ok {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("%s: teleport to nonexistent room %d",
+ prefix, na.Teleport),
+ })
+ }
+ }
+
+ if na.Shop != nil {
+ for _, si := range na.Shop.Items {
+ if si.ItemID != "" && !itemIDs[si.ItemID] {
+ issues = append(issues, StartupIssue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("%s: shop item %q does not exist",
+ prefix, si.ItemID),
+ })
+ }
+ }
+ }
+
+ return issues
+}
+
+func dropTableIDSet(dataDir string) map[string]bool {
+ dir := filepath.Join(dataDir, "drops")
+ ids := make(map[string]bool)
+ action.WalkYAMLDir(dir, func(path, id string, data []byte) error {
+ ids[id] = true
+ return nil
+ })
+ return ids
+}
+
+func (cs *CourseStore) AllCourses() map[string]*CourseConfig {
+ cs.mu.Lock()
+ defer cs.mu.Unlock()
+ if !cs.loaded {
+ cs.loadAllLocked()
+ }
+ return cs.courses
+}
+
+func (g *Game) ValidateAndLog() {
+ LogStartupIssues(g.ValidateStartup())
+}
+
+func LogStartupIssues(issues []StartupIssue) {
+ if len(issues) == 0 {
+ fmt.Fprintf(os.Stderr, "Startup validation: OK\n")
+ return
+ }
+
+ errors := 0
+ warns := 0
+ for _, issue := range issues {
+ if issue.Level == "ERROR" {
+ errors++
+ } else {
+ warns++
+ }
+ }
+
+ fmt.Fprintf(os.Stderr, "\n=== Startup Validation: %d error(s), %d warning(s) ===\n\n", errors, warns)
+
+ sort.Slice(issues, func(i, j int) bool {
+ if issues[i].Level != issues[j].Level {
+ return issues[i].Level == "ERROR"
+ }
+ return issues[i].Message < issues[j].Message
+ })
+
+ for _, issue := range issues {
+ tag := fmt.Sprintf("[%s]", issue.Level)
+ fmt.Fprintf(os.Stderr, " %-7s %s\n", tag, issue.Message)
+ }
+ fmt.Fprintln(os.Stderr)
+
+ if errors > 0 {
+ fmt.Fprintf(os.Stderr, "*** %d startup errors detected. The server may behave unexpectedly. ***\n", errors)
+ }
+} \ No newline at end of file
diff --git a/internal/game/stations.go b/internal/game/core_stations.go
index d30a35c..d30a35c 100644
--- a/internal/game/stations.go
+++ b/internal/game/core_stations.go
diff --git a/internal/game/types.go b/internal/game/core_types.go
index d41b18a..d41b18a 100644
--- a/internal/game/types.go
+++ b/internal/game/core_types.go
diff --git a/internal/game/core_utils.go b/internal/game/core_utils.go
new file mode 100644
index 0000000..409422e
--- /dev/null
+++ b/internal/game/core_utils.go
@@ -0,0 +1,116 @@
+package game
+
+import (
+ "fmt"
+ "math/rand"
+ "strconv"
+ "strings"
+
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+ "thehouseoficarus/internal/world"
+)
+
+func plural(n int) string {
+ if n == 1 {
+ return ""
+ }
+ return "s"
+}
+
+func parseQty(input string) (int, string) {
+ parts := strings.Fields(input)
+ if len(parts) > 1 {
+ if n, err := strconv.Atoi(parts[0]); err == nil && n > 0 {
+ return n, strings.Join(parts[1:], " ")
+ }
+ }
+ return 0, input
+}
+
+func parseChoiceIndex(s string) (int, error) {
+ var idx int
+ _, err := fmt.Sscanf(s, "%d", &idx)
+ return idx, err
+}
+
+func mobDisplayName(m *world.MobInstance, definite bool) string {
+ if m.Unique {
+ return m.Name
+ }
+ if definite {
+ return "the " + m.Name
+ }
+ return "a " + m.Name
+}
+
+func mobCombatLevel(m *world.MobInstance) int {
+ base := float64(m.Defense+m.MaxHP) / 4.0
+ melee := float64(m.Attack+m.Strength) / 4.0
+ ranged := float64(m.Ranged) * 3.0 / 8.0
+ science := float64(m.Science) * 3.0 / 8.0
+ best := melee
+ if ranged > best {
+ best = ranged
+ }
+ if science > best {
+ best = science
+ }
+ return int(base + best + 0.5)
+}
+
+func uniqueItemNames(matches []itemMatch) []string {
+ seen := make(map[string]bool)
+ var out []string
+ for _, m := range matches {
+ if !seen[m.Name] {
+ seen[m.Name] = true
+ out = append(out, m.Name)
+ }
+ }
+ return out
+}
+
+func (g *Game) showWhichOne(sess *net.Session, matches []itemMatch) {
+ sess.WriteLine("Which one?")
+ seen := make(map[string]bool)
+ for _, m := range matches {
+ if seen[m.Name] {
+ continue
+ }
+ seen[m.Name] = true
+ def, _ := g.ItemStore.Load(m.ID)
+ coloredName := g.itemColorize(sess, def, m.Name)
+ sess.WriteLine(fmt.Sprintf(" - %s", coloredName))
+ }
+}
+
+func randInt(max int) int {
+ if max <= 0 {
+ return 0
+ }
+ return rand.Intn(max)
+}
+
+func formatPickupList(sess *net.Session, picked []string) {
+ for i, name := range picked {
+ if i == len(picked)-1 {
+ sess.WriteLine(name)
+ } else if i == len(picked)-2 {
+ sess.Write(name + " and ")
+ } else {
+ sess.Write(name + ", ")
+ }
+ }
+}
+
+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 {
+ slot.Quality = def.Quality
+ slot.MaxQuality = def.MaxQuality
+ }
+ return slot
+}
+
+
diff --git a/internal/game/course.go b/internal/game/course.go
deleted file mode 100644
index 335c97f..0000000
--- a/internal/game/course.go
+++ /dev/null
@@ -1,141 +0,0 @@
-package game
-
-import (
- "os"
- "path/filepath"
- "sync"
-
- "gopkg.in/yaml.v3"
-)
-
-type ObstacleDef struct {
- RoomID int `yaml:"room_id"`
- Verb string `yaml:"verb"`
- TicksPerPhase float64 `yaml:"ticks_per_phase"`
- XP int `yaml:"xp"`
- FailDamage [2]int `yaml:"fail_damage"`
- Messages []string `yaml:"messages"`
-}
-
-type CourseConfig struct {
- ID string `yaml:"id"`
- Name string `yaml:"name"`
- RequiredLevel int `yaml:"required_level"`
- StartRoom int `yaml:"start_room"`
- CompletionXP int `yaml:"completion_xp"`
- Obstacles []ObstacleDef `yaml:"obstacles"`
-}
-
-type ObstacleInfo struct {
- CourseID string
- CourseName string
- ObstacleIndex int
- TotalObstacles int
- Verb string
- Messages []string
- TicksPerPhase float64
- ObstacleXP int
- CompletionXP int
- FailDamage [2]int
- NextRoom int
- StartRoom int
- RequiredLevel int
-}
-
-var obstacleVerbs = map[string]bool{}
-
-var verbGerund = map[string]string{
- "scramble": "scrambling",
- "jump": "jumping",
- "swing": "swinging",
- "balance": "balancing",
- "climb": "climbing",
- "crawl": "crawling",
- "vault": "vaulting",
- "leap": "leaping",
- "slide": "sliding",
-}
-
-type CourseStore struct {
- dataDir string
- mu sync.Mutex
- courses map[string]*CourseConfig
- roomToObstacle map[int]*ObstacleInfo
- loaded bool
-}
-
-func NewCourseStore(dataDir string) *CourseStore {
- return &CourseStore{
- dataDir: dataDir,
- courses: make(map[string]*CourseConfig),
- }
-}
-
-func (cs *CourseStore) LoadAll() {
- cs.mu.Lock()
- defer cs.mu.Unlock()
- cs.loadAllLocked()
-}
-
-func (cs *CourseStore) GetObstacle(roomID int) *ObstacleInfo {
- cs.mu.Lock()
- defer cs.mu.Unlock()
- if !cs.loaded {
- cs.loadAllLocked()
- }
- return cs.roomToObstacle[roomID]
-}
-
-func (cs *CourseStore) loadAllLocked() {
- cs.loaded = true
- cs.roomToObstacle = make(map[int]*ObstacleInfo)
- localVerbs := make(map[string]bool)
-
- pattern := filepath.Join(cs.dataDir, "courses", "*.yaml")
- files, _ := filepath.Glob(pattern)
-
- for _, f := range files {
- data, err := os.ReadFile(f)
- if err != nil {
- continue
- }
- var cfg CourseConfig
- if err := yaml.Unmarshal(data, &cfg); err != nil {
- continue
- }
- cs.courses[cfg.ID] = &cfg
-
- totalObstacles := len(cfg.Obstacles)
- for i, obs := range cfg.Obstacles {
- nextRoom := 0
- if i < totalObstacles-1 {
- nextRoom = cfg.Obstacles[i+1].RoomID
- }
-
- completionXP := 0
- if i == totalObstacles-1 {
- completionXP = cfg.CompletionXP
- }
-
- info := &ObstacleInfo{
- CourseID: cfg.ID,
- CourseName: cfg.Name,
- ObstacleIndex: i,
- TotalObstacles: totalObstacles,
- Verb: obs.Verb,
- Messages: obs.Messages,
- TicksPerPhase: obs.TicksPerPhase,
- ObstacleXP: obs.XP,
- CompletionXP: completionXP,
- FailDamage: obs.FailDamage,
- NextRoom: nextRoom,
- StartRoom: cfg.StartRoom,
- RequiredLevel: cfg.RequiredLevel,
- }
- cs.roomToObstacle[obs.RoomID] = info
- localVerbs[obs.Verb] = true
- }
- }
-
- obstacleVerbs = localVerbs
-}
diff --git a/internal/game/death.go b/internal/game/death.go
deleted file mode 100644
index d2c1b73..0000000
--- a/internal/game/death.go
+++ /dev/null
@@ -1,66 +0,0 @@
-package game
-
-import (
- "sort"
-
- "thehouseoficarus/internal/player"
-)
-
-func (g *Game) dropItemsOnDeath(p *player.Player) {
- roomID := p.RoomID
-
- if p.Credits > 0 {
- g.World.AddGroundItem(roomID, "credits", p.Credits)
- p.Credits = 0
- }
-
- var items []deathDrop
-
- for slot, inv := range p.Inventory {
- if inv == nil || inv.Quantity <= 0 {
- continue
- }
- val := 0
- if def, err := g.ItemStore.Load(inv.ItemID); err == nil {
- val = def.Value * inv.Quantity
- }
- items = append(items, deathDrop{
- itemID: inv.ItemID,
- quantity: inv.Quantity,
- totalVal: val,
- invSlot: slot,
- })
- }
-
- for eqSlot, itemID := range p.Equipment {
- val := 0
- if def, err := g.ItemStore.Load(itemID); err == nil {
- val = def.Value
- }
- items = append(items, deathDrop{
- itemID: itemID,
- quantity: 1,
- totalVal: val,
- isEquip: true,
- equipSlot: eqSlot,
- })
- }
-
- if len(items) <= 3 {
- return
- }
-
- sort.Slice(items, func(i, j int) bool {
- return items[i].totalVal > items[j].totalVal
- })
-
- for i := 3; i < len(items); i++ {
- it := items[i]
- if it.isEquip {
- delete(p.Equipment, it.equipSlot)
- } else {
- p.SetInvSlot(it.invSlot, nil)
- }
- g.World.AddGroundItem(roomID, it.itemID, it.quantity)
- }
-}
diff --git a/internal/game/game.go b/internal/game/game.go
index b8eadce..5d6e21e 100644
--- a/internal/game/game.go
+++ b/internal/game/game.go
@@ -35,18 +35,19 @@ type QueuedCommand struct {
}
type Game struct {
- World *world.World
- ObjectStore *object.ObjectStore
- ItemStore *object.ItemStore
- AccountStore *player.AccountStore
- MobStore *world.MobStore
- RecipeStore *action.RecipeStore
- CourseStore *CourseStore
- Hub *net.Hub
- Ticks *engine.Engine
- WorldFlags map[string]any
- ColorConfig *config.ColorsConfig
- DataDir string
+ World *world.World
+ ObjectStore *object.ObjectStore
+ ItemStore *object.ItemStore
+ AccountStore *player.AccountStore
+ MobStore *world.MobStore
+ RecipeStore *action.RecipeStore
+ CourseStore *CourseStore
+ Hub *net.Hub
+ Ticks *engine.Engine
+ WorldFlags map[string]any
+ ColorConfig *config.ColorsConfig
+ DataDir string
+ ValidationConfig config.StartupValidationConfig
restTimers map[string]uint64
charsMu sync.Mutex
loggedInChars map[string]*net.Session
@@ -59,7 +60,7 @@ type Game struct {
hackingStates map[string]*hacking.Session
}
-func New(dataDir string, colorConfig *config.ColorsConfig) *Game {
+func New(dataDir string, colorConfig *config.ColorsConfig, valConfig config.StartupValidationConfig) *Game {
g := &Game{
World: world.New(dataDir),
ObjectStore: object.NewObjectStore(dataDir),
@@ -72,6 +73,7 @@ func New(dataDir string, colorConfig *config.ColorsConfig) *Game {
WorldFlags: make(map[string]any),
ColorConfig: colorConfig,
DataDir: dataDir,
+ ValidationConfig: valConfig,
restTimers: make(map[string]uint64),
loggedInChars: make(map[string]*net.Session),
freeQueue: make(map[string][]QueuedCommand),
@@ -83,6 +85,7 @@ func New(dataDir string, colorConfig *config.ColorsConfig) *Game {
}
g.CourseStore.LoadAll()
g.LoadMods()
+ g.ValidateAndLog()
return g
}
diff --git a/internal/game/help.go b/internal/game/help.go
deleted file mode 100644
index 03e2b8c..0000000
--- a/internal/game/help.go
+++ /dev/null
@@ -1,155 +0,0 @@
-package game
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "strings"
-
- "thehouseoficarus/internal/net"
- "gopkg.in/yaml.v3"
-)
-
-type HelpDef struct {
- Name string `yaml:"name"`
- Category string `yaml:"category"`
- Content string `yaml:"description"`
-}
-
-type cmdEntry struct {
- Name string
- Type string
- Desc string
-}
-
-var commandList = []cmdEntry{
- {"alias", "Instant", "Create command shortcuts"},
- {"attack / kill", "Active", "Attack a mob"},
- {"autotrigger / auto", "Instant", "Set a module to auto-trigger"},
- {"bank", "Active", "Open bank interface"},
- {"burn", "Active", "Start a fire"},
- {"chop / cut", "Active", "Chop trees (Woodcutting)"},
- {"clean", "Free", "Clean grimy herbs (Pharmacy)"},
- {"color / colors", "Instant", "Customize display colors"},
- {"colortable", "Instant", "Display color reference chart"},
- {"construct / make", "Active", "Build furniture (Construction)"},
- {"cook", "Active", "Cook raw food on a fire or range"},
- {"craft", "Active", "Craft jewelry (Crafting)"},
- {"description / desc", "Instant", "Set your character description"},
- {"drink", "Free", "Drink a potion"},
- {"drop", "Active", "Drop items to the ground"},
- {"eat", "Free", "Eat food to restore hitpoints"},
- {"equipment / eq", "Instant", "Show equipped items"},
- {"exits", "Instant", "List available exits"},
- {"farm", "Active", "Farm crops (Farming)"},
- {"fish", "Active", "Fish at fishing spots (Fishing)"},
- {"fletch", "Free", "Fletch logs into bows (Fletching)"},
- {"get / take / pick", "Active", "Pick up items from the ground"},
- {"help", "Instant", "Show help topics"},
- {"id / identify", "Active", "Identify herbs (Pharmacy)"},
- {"inventory / i / inv", "Instant", "Show your inventory"},
- {"jack / jackin", "Active", "Jack into a terminal (Hacking)"},
- {"look / l", "Instant", "Look around or examine things"},
- {"map", "Instant", "Display an ASCII map of the area"},
- {"mine", "Active", "Mine rocks (Mining)"},
- {"mix", "Active", "Mix potions (Pharmacy)"},
- {"mods / modlist", "Instant", "List available science modules"},
- {"north / south / east / west / up / down", "Active", "Move in a direction"},
- {"option / options", "Instant", "View or change settings"},
- {"prompt", "Instant", "Set custom command prompt"},
- {"pull / push", "Active", "Interact with objects"},
- {"queued", "Instant", "Show pending tick actions"},
- {"quit", "Active", "Rest and disconnect"},
- {"remove / unwear / unwield", "Free", "Unequip items"},
- {"say", "Instant", "Chat with players in your room"},
- {"score / sc", "Instant", "View your stats and skills"},
- {"search", "Active", "Search items for loot"},
- {"smelt", "Active", "Smelt ore into bars (Smithing)"},
- {"smith", "Active", "Smith bars into items (Smithing)"},
- {"sneak", "Instant", "Toggle sneak mode"},
- {"steal", "Active", "Steal from mobs (Thieving)"},
- {"stoke", "Active", "Add logs to a fire"},
- {"style", "Instant", "Change combat style"},
- {"talk / speak / ask", "Active", "Talk to NPCs"},
- {"tech", "Instant", "Toggle technology abilities"},
- {"trigger", "Active", "Trigger a science module"},
- {"unalias", "Instant", "Remove command shortcuts"},
- {"use", "Active", "Use an object (crafting)"},
- {"walk", "Active", "Pathfind to a room or multi-step walk"},
- {"wear / wield", "Free", "Equip items"},
-}
-
-func LoadHelp(dataDir string) ([]HelpDef, error) {
- dir := filepath.Join(dataDir, "help")
- entries, err := os.ReadDir(dir)
- if err != nil {
- return nil, err
- }
-
- var helps []HelpDef
- for _, entry := range entries {
- if entry.IsDir() || filepath.Ext(entry.Name()) != ".yaml" {
- continue
- }
- data, err := os.ReadFile(filepath.Join(dir, entry.Name()))
- if err != nil {
- continue
- }
- var h HelpDef
- if err := yaml.Unmarshal(data, &h); err != nil {
- continue
- }
- helps = append(helps, h)
- }
- return helps, nil
-}
-
-func (g *Game) doHelp(sess *net.Session, topic string) {
- if topic == "" {
- unicode := true
- if p := sess.Player; p != nil {
- unicode = p.OptionBool("unicode")
- }
-
- sess.WriteLine("")
- t := &Table{
- Title: "Commands",
- Columns: []string{"Command", "Type", "Description"},
- }
- for _, c := range commandList {
- t.Rows = append(t.Rows, []string{c.Name, c.Type, c.Desc})
- }
- for _, line := range t.Render(unicode) {
- sess.WriteLine(line)
- }
- sess.WriteLine("")
- sess.WriteLine("Command types: Instant (runs immediately), Free (queued before active),")
- sess.WriteLine("Active (replaces current action, queued per tick).")
- sess.WriteLine("")
- sess.WriteLine("Use 'help <command>' for detailed usage of a specific command.")
- sess.WriteLine("Use 'option' to view and change settings, 'color' to customize colors.")
- return
- }
-
- helps, err := LoadHelp(g.DataDir)
- if err != nil || len(helps) == 0 {
- sess.WriteLine("No help available.")
- return
- }
-
- topic = strings.ToLower(topic)
- for _, h := range helps {
- if strings.ToLower(h.Name) == topic {
- sess.WriteLine(fmt.Sprintf("\n %s", h.Content))
- return
- }
- }
- sess.WriteLine(fmt.Sprintf("No help found for '%s'.", topic))
-}
-
-func firstLine(s string) string {
- if idx := strings.Index(s, "\n"); idx >= 0 {
- return s[:idx]
- }
- return s
-}
diff --git a/internal/game/production_core.go b/internal/game/production_core.go
deleted file mode 100644
index 42f64e1..0000000
--- a/internal/game/production_core.go
+++ /dev/null
@@ -1,606 +0,0 @@
-package game
-
-import (
- "fmt"
- "math/rand"
- "sort"
- "strconv"
- "strings"
-
- "thehouseoficarus/internal/action"
- "thehouseoficarus/internal/color"
- "thehouseoficarus/internal/engine"
- "thehouseoficarus/internal/net"
- "thehouseoficarus/internal/object"
- "thehouseoficarus/internal/player"
-
-)
-
-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)
-
- skill := recipe.EffectiveSkill()
- if skill != "" {
- skillLevel := p.Level(player.SkillName(skill))
- if skillLevel < recipe.Level {
- sess.WriteLine(fmt.Sprintf("You need level %d %s to do that.", recipe.Level, skill))
- g.reprompt(sess)
- return
- }
- }
-
- if !recipe.HasAllItemsQty(p.CountItem) {
- sess.WriteLine("You don't have the required materials.")
- g.reprompt(sess)
- return
- }
-
- outDef, _ := g.ItemStore.Load(recipe.Output)
-
- wait := recipe.Wait
- if wait <= 0 {
- wait = 4
- }
-
- outputName := recipe.Output
- if outDef != nil {
- outputName = outDef.Name
- }
-
- p.Action = &action.Action{
- Type: actionType,
- TargetID: recipe.ID,
- TargetName: outputName,
- Data: map[string]any{
- "recipe_id": recipe.ID,
- "phase": 0,
- "wait": wait,
- "start_msg": startMsg,
- "end_msg": endMsg,
- "remaining": count,
- },
- WaitLeft: engine.ToTicks(1),
- }
-
- 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)))
- }
- }
- }
-}
-
-func (g *Game) startProductionFromRecipe(sess *net.Session, p *player.Player, recipe *action.RecipeDef, count int) {
- _, stationName := g.findStation(p.RoomID, recipe.Station)
- if stationName == "" {
- stationName = "inventory"
- }
-
- firstItem := recipe.FirstItemName(func(id string) (string, bool) {
- def, err := g.ItemStore.Load(id)
- if err != nil {
- return id, false
- }
- return def.Name, true
- })
-
- info, ok := productionTypes[recipe.Type]
- if !ok {
- info = productionTypeInfo{recipe.Type, recipe.Type}
- }
- actionType := info.ActionType
- displayVerb := info.DisplayVerb
-
- var startMsg, endMsg string
- if stationName != "inventory" {
- startMsg = fmt.Sprintf("You start %s %s on the %s.", displayVerb, firstItem, stationName)
- } else {
- startMsg = fmt.Sprintf("You start %s %s.", displayVerb, firstItem)
- }
- endMsg = fmt.Sprintf("You've finished %s.", displayVerb)
-
- 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
- }
- }
- }
- if strings.HasPrefix(recipeID, "combine_") {
- itemID := strings.TrimPrefix(recipeID, "combine_")
- if itemDef, err := g.ItemStore.Load(itemID); err == nil && len(itemDef.MadeFrom) > 0 {
- return g.buildCombineRecipe(itemDef)
- }
- }
- return nil
-}
-
-func (g *Game) advanceProduction(sess *net.Session, p *player.Player) bool {
- recipeID := p.Action.Data["recipe_id"].(string)
- phase := p.Action.Data["phase"].(int)
- wait := p.Action.Data["wait"].(float64)
- startMsg, _ := p.Action.Data["start_msg"].(string)
- endMsg, _ := p.Action.Data["end_msg"].(string)
- remaining, _ := p.Action.Data["remaining"].(int)
-
- recipe := g.loadProductionRecipe(recipeID)
- if recipe == nil {
- g.cancelAction(p)
- return false
- }
-
- if phase == 0 {
- if startMsg != "" {
- sess.WriteLine(fmt.Sprintf("\n%s", startMsg))
- }
- p.Action.Data["phase"] = 1
- p.Action.WaitLeft = engine.ToTicks(wait)
- return true
- }
-
- skill := recipe.EffectiveSkill()
- skillLevel := p.Level(player.SkillName(skill))
- chance := 1.0
- if recipe.Success != nil {
- chance = action.SuccessChance(*recipe.Success, skillLevel, recipe.Level)
- }
-
- if rand.Float64() < chance {
- outputQty := recipe.OutputQty
- if outputQty <= 0 {
- outputQty = 1
- }
-
- byproducts := g.collectByproducts(p, recipe)
-
- placed := false
- outDef, _ := g.ItemStore.Load(recipe.Output)
- if outDef != nil && outDef.Stackable {
- for i := 0; i < 28; i++ {
- slot := p.InvSlot(i)
- if slot != nil && slot.ItemID == recipe.Output {
- recipe.ConsumeAll(p.HasItem, p.RemoveItem)
- slot.Quantity += outputQty
- placed = true
- break
- }
- }
- }
- if !placed {
- recipe.ConsumeAll(p.HasItem, p.RemoveItem)
- freeSlot := p.FirstFreeSlot()
- if freeSlot == -1 {
- sess.WriteLine("Your inventory is too full!")
- g.cancelAction(p)
- return false
- }
- p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: recipe.Output, Quantity: outputQty})
- }
-
- for _, bp := range byproducts {
- if slot := p.FirstFreeSlot(); slot >= 0 {
- p.SetInvSlot(slot, &player.InventorySlot{ItemID: bp, Quantity: 1})
- }
- }
-
- if recipe.XP > 0 {
- if newLevel := p.AddSkillXP(player.SkillName(skill), recipe.XP); newLevel > 0 {
- sess.WriteLine(g.colorize(sess, "level_up", fmt.Sprintf("*** You are now level %d %s! ***", newLevel, skill)))
- }
- }
- g.AccountStore.SaveCharacter(p)
-
- msg := recipe.Message
- if msg == "" {
- outputName := recipe.Output
- if outDef != nil {
- outputName = outDef.Name
- }
- msg = fmt.Sprintf("You produce %s.", outputName)
- }
- if p.OptionBool("xp_drops") && recipe.XP > 0 {
- abbr := player.SkillAbbr[player.SkillName(skill)]
- msg += g.colorize(sess, "xp", fmt.Sprintf(" (+%dxp %s)", recipe.XP, abbr))
- }
- sess.WriteLine(msg)
- } else {
- recipe.ConsumeAll(p.HasItem, p.RemoveItem)
-
- if recipe.Fail != "" {
- freeSlot := p.FirstFreeSlot()
- if freeSlot >= 0 {
- p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: recipe.Fail, Quantity: 1})
- }
- }
- g.AccountStore.SaveCharacter(p)
-
- msg := recipe.FailMessage
- if msg == "" {
- msg = "You fail and the materials are lost."
- }
- sess.WriteLine(g.colorize(sess, "damage", msg))
- }
-
- if remaining > 0 {
- remaining--
- p.Action.Data["remaining"] = remaining
- if remaining <= 0 {
- if endMsg != "" {
- sess.WriteLine(fmt.Sprintf("\n%s", endMsg))
- }
- g.cancelAction(p)
- return false
- }
- }
-
- if g.canContinueProduction(p, recipe) {
- p.Action.WaitLeft = engine.ToTicks(wait)
- return true
- }
-
- if endMsg != "" {
- sess.WriteLine(fmt.Sprintf("\n%s", endMsg))
- }
- g.cancelAction(p)
- return false
-}
-
-func (g *Game) collectByproducts(p *player.Player, recipe *action.RecipeDef) []string {
- var byproducts []string
- for _, e := range recipe.Consume {
- if len(e.Byproducts) == 0 {
- continue
- }
- for i, id := range e.Items {
- if p.HasItem(id) && i < len(e.Byproducts) && e.Byproducts[i] != "" {
- byproducts = append(byproducts, e.Byproducts[i])
- break
- }
- }
- }
- return byproducts
-}
-
-func (g *Game) canContinueProduction(p *player.Player, recipe *action.RecipeDef) bool {
- if !recipe.HasAllItemsQty(p.CountItem) {
- return false
- }
- outputQty := recipe.OutputQty
- if outputQty <= 0 {
- outputQty = 1
- }
- outDef, _ := g.ItemStore.Load(recipe.Output)
- if outDef != nil && outDef.Stackable {
- for i := 0; i < 28; i++ {
- slot := p.InvSlot(i)
- if slot != nil && slot.ItemID == recipe.Output {
- return true
- }
- }
- }
- return p.FirstFreeSlot() >= 0
-}
-
-func (g *Game) handleRecipeChoice(sess *net.Session, input string) {
- input = strings.TrimSpace(input)
-
- menuData := sess.PendingMenu
- if len(menuData) == 0 {
- sess.State = net.StateGame
- g.reprompt(sess)
- return
- }
-
- if input == "" {
- sess.PendingMenu = nil
- sess.State = net.StateGame
- sess.WriteLine("Never mind.")
- g.reprompt(sess)
- return
- }
-
- if choice, err := strconv.Atoi(input); err == nil {
- sess.PendingMenu = nil
- sess.State = net.StateGame
- if choice <= 0 || choice > len(menuData) {
- sess.WriteLine("Never mind.")
- g.reprompt(sess)
- return
- }
- g.dispatchMenuEntry(sess, menuData[choice-1])
- return
- }
-
- lower := strings.ToLower(input)
- matchIdx := -1
- for i, entry := range menuData {
- name := g.menuEntryName(entry)
- if name == "" {
- continue
- }
- if strings.ToLower(name) == lower || action.WordPrefixMatch(input, name) {
- if matchIdx >= 0 {
- sess.WriteLine("That's ambiguous.")
- return
- }
- matchIdx = i
- }
- }
- if matchIdx >= 0 {
- sess.PendingMenu = nil
- sess.State = net.StateGame
- g.dispatchMenuEntry(sess, menuData[matchIdx])
- return
- }
-
- sess.PendingMenu = nil
- sess.State = net.StateGame
- sess.WriteLine("Never mind.")
- g.reprompt(sess)
-}
-
-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 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 barID, ok := entry["bar_id"]; ok {
- if def, _ := g.ItemStore.Load(barID); def != nil {
- return def.Name
- }
- return barID
- }
- if cid, ok := entry["combine_item"]; ok {
- if def, _ := g.ItemStore.Load(cid); def != nil {
- return def.Name
- }
- return cid
- }
- return ""
-}
-
-func (g *Game) dispatchMenuEntry(sess *net.Session, entry map[string]string) {
- if cid, ok := entry["combine_item"]; ok {
- g.promptHowMany(sess, "combine_"+cid)
- return
- }
- if barID, ok := entry["bar_id"]; ok {
- p := sess.Player
- allRecipes, _ := g.RecipeStore.LoadAll()
- g.showSmithTable(sess, p, barID, allRecipes)
- return
- }
- 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
- }
- }
- g.reprompt(sess)
- return
- }
- if rid, ok := entry["recipe_id"]; ok {
- g.promptHowMany(sess, rid)
- return
- }
- g.reprompt(sess)
-}
-
-func (g *Game) formatRecipeMaterials(r *action.RecipeDef) string {
- var parts []string
- for _, e := range r.Consume {
- itemName := e.Items[0]
- if def, err := g.ItemStore.Load(e.Items[0]); err == nil {
- itemName = def.Name
- }
- if e.Qty > 1 {
- parts = append(parts, fmt.Sprintf("%s x%d", itemName, e.Qty))
- } else {
- parts = append(parts, itemName)
- }
- }
- return strings.Join(parts, ", ")
-}
-
-func (g *Game) showProductionTable(sess *net.Session, p *player.Player, recipes []action.RecipeDef, title, skill, lastFlagKey, promptVerb string, background bool) {
- sort.Slice(recipes, func(i, j int) bool {
- return recipes[i].Level < recipes[j].Level
- })
-
- mode := g.colorMode(sess)
- skillLevel := p.Level(player.SkillName(skill))
- dimSpec := color.Parse("240")
-
- tbl := &Table{
- Title: title,
- Columns: []string{"#", "Product", "Materials", "Level"},
- }
-
- for i, r := range recipes {
- outDef, _ := g.ItemStore.Load(r.Output)
- productName := r.Output
- if outDef != nil {
- productName = outDef.Name
- }
-
- outputQty := r.OutputQty
- if outputQty <= 0 {
- outputQty = 1
- }
- if outDef != nil && outDef.Stackable && outputQty > 1 {
- productName = fmt.Sprintf("%s x%d", productName, outputQty)
- }
-
- matStr := g.formatRecipeMaterials(&r)
- levelStr := fmt.Sprint(r.Level)
- numStr := fmt.Sprintf("%d", i+1)
-
- canMake := skillLevel >= r.Level && r.HasAllItemsQty(p.CountItem)
- if canMake {
- productName = g.itemColorize(sess, outDef, productName)
- } else {
- productName = color.Render(mode, dimSpec, productName)
- matStr = color.Render(mode, dimSpec, matStr)
- levelStr = color.Render(mode, dimSpec, levelStr)
- numStr = color.Render(mode, dimSpec, numStr)
- }
-
- tbl.Rows = append(tbl.Rows, []string{numStr, productName, matStr, levelStr})
- }
-
- unicode := p.OptionBool("unicode")
- sess.WriteLine("")
- for _, line := range tbl.Render(unicode) {
- sess.WriteLine(line)
- }
-
- lastRecipeID, _ := p.Flags[lastFlagKey].(string)
- hint := ""
- if lastRecipeID != "" {
- for _, r := range recipes {
- if r.ID == lastRecipeID {
- if outDef, err := g.ItemStore.Load(r.Output); err == nil {
- hint = outDef.Name
- }
- break
- }
- }
- }
-
- if hint != "" {
- sess.Write(fmt.Sprintf("%s what (enter for all %s): ", promptVerb, hint))
- } else {
- sess.Write(fmt.Sprintf("%s what: ", promptVerb))
- }
-
- sess.PendingMenu = recipeMenuData(recipeDefIDs(recipes))
- sess.PendingSkill = skill
- sess.PendingLastFlag = lastFlagKey
- sess.PendingBackground = background
- sess.State = net.StateProductChoice
-}
-
-func (g *Game) handleProductChoice(sess *net.Session, input string) {
- p := sess.Player
- menuData := sess.PendingMenu
- skill := sess.PendingSkill
- lastFlagKey := sess.PendingLastFlag
- background := sess.PendingBackground
- sess.PendingMenu = nil
- sess.PendingSkill = ""
- sess.PendingLastFlag = ""
- sess.PendingBackground = false
- sess.State = net.StateGame
-
- input = strings.TrimSpace(input)
-
- allRecipes, err := g.RecipeStore.LoadAll()
- if err != nil {
- g.reprompt(sess)
- return
- }
-
- var recipes []action.RecipeDef
- for _, entry := range menuData {
- rid := entry["recipe_id"]
- for _, r := range allRecipes {
- if r.ID == rid {
- recipes = append(recipes, r)
- break
- }
- }
- }
-
- if len(recipes) == 0 {
- g.reprompt(sess)
- return
- }
-
- sort.Slice(recipes, func(i, j int) bool {
- return recipes[i].Level < recipes[j].Level
- })
-
- lastRecipeID, _ := p.Flags[lastFlagKey].(string)
-
- sel, errMsg := g.resolveRecipeByInput(input, recipes, lastRecipeID)
- switch errMsg {
- case "ambiguous":
- sess.WriteLine("That's ambiguous.")
- g.reprompt(sess)
- return
- case "never_mind":
- sess.WriteLine("Never mind.")
- g.reprompt(sess)
- return
- }
-
- skillLevel := p.Level(player.SkillName(skill))
- if skillLevel < sel.Recipe.Level {
- sess.WriteLine(fmt.Sprintf("You need level %d %s to make that.", sel.Recipe.Level, skill))
- g.reprompt(sess)
- return
- }
- if !sel.Recipe.HasAllItemsQty(p.CountItem) {
- sess.WriteLine("You don't have the materials for that.")
- g.reprompt(sess)
- return
- }
-
- p.EnsureFlags()
- p.Flags[lastFlagKey] = sel.Recipe.ID
- g.AccountStore.SaveCharacter(p)
-
- if background {
- g.startFletchAction(sess, p, sel.Recipe, sel.Count)
- } else {
- g.startProductionFromRecipe(sess, p, sel.Recipe, sel.Count)
- }
-}
-
-func (g *Game) hasToolType(p *player.Player, toolType string) bool {
- if itemID, ok := p.Equipment[object.SlotMainHand]; ok {
- if def, err := g.ItemStore.Load(itemID); err == nil && def.ToolType == toolType {
- return true
- }
- }
- for i := 0; i < 28; i++ {
- slot := p.InvSlot(i)
- if slot == nil {
- continue
- }
- if def, err := g.ItemStore.Load(slot.ItemID); err == nil && def.ToolType == toolType {
- return true
- }
- }
- return false
-}
diff --git a/internal/game/production_menu.go b/internal/game/production_menu.go
deleted file mode 100644
index 329110b..0000000
--- a/internal/game/production_menu.go
+++ /dev/null
@@ -1,209 +0,0 @@
-package game
-
-import (
- "fmt"
- "strconv"
- "strings"
-
- "thehouseoficarus/internal/action"
- "thehouseoficarus/internal/net"
- "thehouseoficarus/internal/object"
-)
-
-type recipeEntry struct {
- ItemName string
- Recipe action.RecipeDef
-}
-
-type productionTypeInfo struct {
- ActionType string
- DisplayVerb string
-}
-
-var productionTypes = map[string]productionTypeInfo{
- "cooking": {"cook", "cooking"},
- "smelting": {"smelt", "smelting"},
- "smithing": {"smith", "smithing"},
- "crafting": {"craft", "crafting"},
- "combine": {"combine", "combining"},
- "fletching": {"fletch", "fletching"},
- "pharmacy": {"mix", "mixing"},
- "construction": {"construct", "constructing"},
-}
-
-var productionActionTypes map[string]bool
-
-func init() {
- productionActionTypes = make(map[string]bool)
- for _, pt := range productionTypes {
- productionActionTypes[pt.ActionType] = true
- }
-}
-
-func recipeMenuData(recipeIDs []string) []map[string]string {
- out := make([]map[string]string, len(recipeIDs))
- for i, id := range recipeIDs {
- out[i] = map[string]string{"recipe_id": id}
- }
- return out
-}
-
-func recipeDefIDs(defs []action.RecipeDef) []string {
- ids := make([]string, len(defs))
- for i, d := range defs {
- ids[i] = d.ID
- }
- return ids
-}
-
-func entryMenuData(entries []recipeEntry) []map[string]string {
- out := make([]map[string]string, len(entries))
- for i, e := range entries {
- out[i] = map[string]string{"recipe_id": e.Recipe.ID}
- }
- return out
-}
-
-func (g *Game) promptHowMany(sess *net.Session, recipeID string) {
- sess.PendingRecipeID = recipeID
- sess.State = net.StateHowMany
- sess.Write("How many (return for all)?: ")
-}
-
-type recipeSelection struct {
- Recipe *action.RecipeDef
- Count int
-}
-
-func (g *Game) resolveRecipeByInput(input string, recipes []action.RecipeDef, lastRecipeID string) (sel recipeSelection, errMsg string) {
- if input == "" {
- if lastRecipeID == "" {
- return sel, "never_mind"
- }
- for i := range recipes {
- if recipes[i].ID == lastRecipeID {
- sel.Recipe = &recipes[i]
- return sel, ""
- }
- }
- return sel, "never_mind"
- }
-
- qty, productName := parseQty(input)
- productName = strings.TrimSpace(productName)
-
- if idx, err := strconv.Atoi(productName); err == nil && idx > 0 && idx <= len(recipes) {
- sel.Recipe = &recipes[idx-1]
- sel.Count = qty
- return sel, ""
- }
-
- matchCount := 0
- for i := range recipes {
- outDef, _ := g.ItemStore.Load(recipes[i].Output)
- name := recipes[i].Output
- if outDef != nil {
- name = outDef.Name
- }
- if action.WordPrefixMatch(productName, name) {
- if matchCount == 0 {
- sel.Recipe = &recipes[i]
- sel.Count = qty
- }
- matchCount++
- }
- }
- if matchCount > 1 {
- return sel, "ambiguous"
- }
- if sel.Recipe == nil {
- return sel, "never_mind"
- }
- return sel, ""
-}
-
-func (g *Game) showMenuTable(sess *net.Session, title string, names []string) {
- p := sess.Player
- tbl := &Table{Title: title}
- for i, name := range names {
- tbl.Rows = append(tbl.Rows, []string{fmt.Sprintf("%d", i+1), name})
- }
- unicode := p.OptionBool("unicode")
- sess.WriteLine("")
- for _, line := range tbl.Render(unicode) {
- sess.WriteLine(line)
- }
-}
-
-func (g *Game) handleHowMany(sess *net.Session, input string) {
- p := sess.Player
- recipeID := sess.PendingRecipeID
- sess.PendingRecipeID = ""
- sess.State = net.StateGame
-
- input = strings.TrimSpace(input)
-
- var count int
- if input == "" {
- count = 0
- } else if n, err := strconv.Atoi(input); err == nil && n > 0 {
- count = n
- } else {
- sess.WriteLine("Never mind.")
- g.reprompt(sess)
- return
- }
-
- var recipe *action.RecipeDef
- if strings.HasPrefix(recipeID, "combine_") {
- itemID := strings.TrimPrefix(recipeID, "combine_")
- itemDef, err := g.ItemStore.Load(itemID)
- if err != nil || len(itemDef.MadeFrom) == 0 {
- g.reprompt(sess)
- return
- }
- 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
- }
- }
- }
-
- if recipe == nil {
- g.reprompt(sess)
- return
- }
-
- g.startProductionFromRecipe(sess, p, recipe, count)
-}
-
-func (g *Game) buildCombineRecipe(def *object.ItemDef) *action.RecipeDef {
- var consume []action.ConsumeEntry
- for _, mf := range def.MadeFrom {
- consume = append(consume, action.ConsumeEntry{
- Items: mf.Items,
- Qty: mf.Qty,
- Byproducts: mf.Byproducts,
- })
- }
- wait := def.Ticks
- if wait <= 0 {
- wait = 2
- }
- return &action.RecipeDef{
- ID: "combine_" + def.ID,
- Type: "combine",
- Wait: wait,
- Consume: consume,
- Output: def.ID,
- Message: fmt.Sprintf("You create %s.", def.Name),
- }
-}
diff --git a/internal/game/science.go b/internal/game/science.go
deleted file mode 100644
index 8c7f0af..0000000
--- a/internal/game/science.go
+++ /dev/null
@@ -1,231 +0,0 @@
-package game
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "sort"
- "strings"
-
- "gopkg.in/yaml.v3"
-
- "thehouseoficarus/internal/object"
- "thehouseoficarus/internal/player"
-)
-
-type ModCategory string
-
-const (
- ModCombat ModCategory = "combat"
- ModUtility ModCategory = "utility"
- ModEnchant ModCategory = "enchant"
- ModProcessing ModCategory = "processing"
- ModTransport ModCategory = "transport"
-)
-
-type ModDef struct {
- ID string `yaml:"id"`
- Name string `yaml:"name"`
- Level int `yaml:"level"`
- MaxHit int `yaml:"max_hit"`
- BaseXP int `yaml:"base_xp"`
- JunkCost map[string]int `yaml:"junk_cost"`
- Category ModCategory `yaml:"category"`
- Element string `yaml:"element"`
- Destination int `yaml:"destination"`
-}
-
-var AllMods []*ModDef
-
-var modByID map[string]*ModDef
-
-func (g *Game) LoadMods() error {
- dir := filepath.Join(g.DataDir, "modules")
- entries, err := os.ReadDir(dir)
- if err != nil {
- return err
- }
- AllMods = nil
- modByID = make(map[string]*ModDef)
- for _, e := range entries {
- if e.IsDir() || filepath.Ext(e.Name()) != ".yaml" {
- continue
- }
- data, err := os.ReadFile(filepath.Join(dir, e.Name()))
- if err != nil {
- continue
- }
- var m ModDef
- if err := yaml.Unmarshal(data, &m); err != nil {
- continue
- }
- AllMods = append(AllMods, &m)
- modByID[m.ID] = AllMods[len(AllMods)-1]
- }
- modByID = make(map[string]*ModDef, len(AllMods))
- for _, m := range AllMods {
- modByID[m.ID] = m
- }
- return nil
-}
-
-func GetMod(id string) *ModDef {
- return modByID[id]
-}
-
-func FindMod(input string) *ModDef {
- if m, ok := modByID[input]; ok {
- return m
- }
- lower := strings.ToLower(strings.ReplaceAll(input, " ", "_"))
- for _, m := range AllMods {
- if strings.HasPrefix(m.ID, lower) {
- return m
- }
- }
- lowerSpace := strings.ToLower(input)
- for _, m := range AllMods {
- if strings.HasPrefix(strings.ToLower(m.Name), lowerSpace) {
- return m
- }
- }
- return nil
-}
-
-type chipEntry struct {
- Input string
- Output string
- Qty int
-}
-
-var enchantMap = map[string]map[string]string{
- "enchant_1": {
- "sapphire_ring": "ring_of_recoil",
- "sapphire_necklace": "necklace_of_passage",
- "sapphire_bracelet": "bracelet_of_clay",
- },
- "enchant_2": {
- "emerald_ring": "ring_of_dueling",
- "emerald_necklace": "binding_necklace",
- "emerald_bracelet": "bracelet_of_slaughter",
- },
- "enchant_3": {
- "ruby_ring": "ring_of_forging",
- "ruby_necklace": "digsite_pendant",
- "ruby_bracelet": "inoculation_bracelet",
- },
- "enchant_4": {
- "diamond_ring": "ring_of_life",
- "diamond_necklace": "phoenix_necklace",
- "diamond_bracelet": "abyssal_bracelet",
- },
-}
-
-var chipMap = map[string]chipEntry{
- "chip_sapphire": {"sapphire_bolts", "sapphire_bolts_e", 10},
- "chip_emerald": {"emerald_bolts", "emerald_bolts_e", 10},
- "chip_ruby": {"ruby_bolts", "ruby_bolts_e", 10},
- "chip_diamond": {"diamond_bolts", "diamond_bolts_e", 10},
-}
-
-func (g *Game) hasDeckEquipped(p *player.Player) bool {
- itemID, ok := p.Equipment[object.SlotMainHand]
- if !ok {
- return false
- }
- def, err := g.ItemStore.Load(itemID)
- if err != nil {
- return false
- }
- return def.WeaponType == object.WeaponScience
-}
-
-func (g *Game) equippedProvidesJunk(p *player.Player) string {
- itemID, ok := p.Equipment[object.SlotMainHand]
- if !ok {
- return ""
- }
- def, err := g.ItemStore.Load(itemID)
- if err != nil {
- return ""
- }
- return def.ProvidesJunk
-}
-
-func (g *Game) effectiveJunkCost(p *player.Player, mod *ModDef) map[string]int {
- cost := make(map[string]int)
- for k, v := range mod.JunkCost {
- cost[k] = v
- }
-
- hasDeck := g.hasDeckEquipped(p)
-
- if hasDeck {
- delete(cost, "scrap_metal")
- }
-
- providesJunk := g.equippedProvidesJunk(p)
- if providesJunk != "" {
- delete(cost, providesJunk)
- }
-
- return cost
-}
-
-func (g *Game) hasJunkCost(p *player.Player, mod *ModDef) bool {
- cost := g.effectiveJunkCost(p, mod)
- for itemID, qty := range cost {
- if p.CountItem(itemID) < qty {
- return false
- }
- }
- return true
-}
-
-func (g *Game) consumeJunkCost(p *player.Player, mod *ModDef) bool {
- cost := g.effectiveJunkCost(p, mod)
- for itemID, qty := range cost {
- if !p.RemoveItem(itemID, qty) {
- return false
- }
- }
- g.AccountStore.SaveCharacter(p)
- return true
-}
-
-func (g *Game) junkCostString(p *player.Player, mod *ModDef) string {
- cost := g.effectiveJunkCost(p, mod)
- delete(cost, "scrap_metal")
- if len(cost) == 0 {
- return "free"
- }
- var parts []string
- for itemID, qty := range cost {
- def, _ := g.ItemStore.Load(itemID)
- name := itemID
- if def != nil {
- name = def.Name
- }
- name = strings.TrimSuffix(name, "junk")
- if qty > 1 {
- parts = append(parts, fmt.Sprintf("%d %s", qty, name))
- } else {
- parts = append(parts, name)
- }
- }
- sort.Strings(parts)
- return strings.Join(parts, ", ")
-}
-
-func (g *Game) totalEquipScienceAttack(p *player.Player) int {
- total := 0
- for _, itemID := range p.Equipment {
- def, err := g.ItemStore.Load(itemID)
- if err == nil {
- total += def.Stats.ScienceAttack
- }
- }
- return total
-}
-
-
diff --git a/internal/game/assassin.go b/internal/game/sys_assassin.go
index 1927399..1927399 100644
--- a/internal/game/assassin.go
+++ b/internal/game/sys_assassin.go
diff --git a/internal/game/buff.go b/internal/game/sys_buff.go
index 9449731..9449731 100644
--- a/internal/game/buff.go
+++ b/internal/game/sys_buff.go
diff --git a/internal/game/sys_combat.go b/internal/game/sys_combat.go
new file mode 100644
index 0000000..030bed1
--- /dev/null
+++ b/internal/game/sys_combat.go
@@ -0,0 +1,114 @@
+package game
+
+import (
+ "fmt"
+ "sort"
+
+ "thehouseoficarus/internal/combat"
+ "thehouseoficarus/internal/engine"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+)
+
+func (g *Game) dropItemsOnDeath(p *player.Player) {
+ roomID := p.RoomID
+
+ if p.Credits > 0 {
+ g.World.AddGroundItem(roomID, "credits", p.Credits)
+ p.Credits = 0
+ }
+
+ var items []deathDrop
+
+ for slot, inv := range p.Inventory {
+ if inv == nil || inv.Quantity <= 0 {
+ continue
+ }
+ val := 0
+ if def, err := g.ItemStore.Load(inv.ItemID); err == nil {
+ val = def.Value * inv.Quantity
+ }
+ items = append(items, deathDrop{
+ itemID: inv.ItemID,
+ quantity: inv.Quantity,
+ totalVal: val,
+ invSlot: slot,
+ })
+ }
+
+ for eqSlot, itemID := range p.Equipment {
+ val := 0
+ if def, err := g.ItemStore.Load(itemID); err == nil {
+ val = def.Value
+ }
+ items = append(items, deathDrop{
+ itemID: itemID,
+ quantity: 1,
+ totalVal: val,
+ isEquip: true,
+ equipSlot: eqSlot,
+ })
+ }
+
+ if len(items) <= 3 {
+ return
+ }
+
+ sort.Slice(items, func(i, j int) bool {
+ return items[i].totalVal > items[j].totalVal
+ })
+
+ for i := 3; i < len(items); i++ {
+ it := items[i]
+ if it.isEquip {
+ delete(p.Equipment, it.equipSlot)
+ } else {
+ p.SetInvSlot(it.invSlot, nil)
+ }
+ g.World.AddGroundItem(roomID, it.itemID, it.quantity)
+ }
+}
+
+func (g *Game) checkAggro(sess *net.Session) {
+ p := sess.Player
+
+ if combat.GetCombat(p.Name) != nil {
+ return
+ }
+
+ playerLevel := p.CombatLevel()
+ mobs := g.MobStore.MobsInRoom(p.RoomID)
+
+ for _, mob := range mobs {
+ if !mob.Aggressive || mob.HP <= 0 || mob.Protected {
+ continue
+ }
+ if combat.IsMobInCombat(mob.InstanceID) {
+ continue
+ }
+ mobLevel := mobCombatLevel(mob)
+ if playerLevel > mobLevel*2 {
+ continue
+ }
+ aggMob := mob
+ g.Ticks.Subscribe(engine.ToTicks(1), func() bool {
+ if combat.GetCombat(p.Name) != nil {
+ return false
+ }
+ if aggMob.HP <= 0 || aggMob.RoomID != p.RoomID {
+ return false
+ }
+ if combat.IsMobInCombat(aggMob.InstanceID) {
+ return false
+ }
+ attacker := aggMob.Name
+ if !aggMob.Unique {
+ attacker = "The " + aggMob.Name
+ }
+ sess.WriteLine(fmt.Sprintf("\n%s attacks you!", g.colorize(sess, "mob_name", attacker)))
+ g.startCombat(sess, p, aggMob)
+ return false
+ })
+ break
+ }
+}
diff --git a/internal/game/construction.go b/internal/game/sys_construction.go
index 06713cd..06713cd 100644
--- a/internal/game/construction.go
+++ b/internal/game/sys_construction.go
diff --git a/internal/game/sys_science.go b/internal/game/sys_science.go
new file mode 100644
index 0000000..23aeef4
--- /dev/null
+++ b/internal/game/sys_science.go
@@ -0,0 +1,220 @@
+package game
+
+import (
+ "fmt"
+ "path/filepath"
+ "sort"
+ "strings"
+
+ "gopkg.in/yaml.v3"
+
+ "thehouseoficarus/internal/action"
+ "thehouseoficarus/internal/object"
+ "thehouseoficarus/internal/player"
+)
+
+type ModCategory string
+
+const (
+ ModCombat ModCategory = "combat"
+ ModUtility ModCategory = "utility"
+ ModEnchant ModCategory = "enchant"
+ ModProcessing ModCategory = "processing"
+ ModTransport ModCategory = "transport"
+)
+
+type ModDef struct {
+ ID string `yaml:"id"`
+ Name string `yaml:"name"`
+ Level int `yaml:"level"`
+ MaxHit int `yaml:"max_hit"`
+ BaseXP int `yaml:"base_xp"`
+ JunkCost map[string]int `yaml:"junk_cost"`
+ Category ModCategory `yaml:"category"`
+ Element string `yaml:"element"`
+ Destination int `yaml:"destination"`
+}
+
+var AllMods []*ModDef
+
+var modByID map[string]*ModDef
+
+func (g *Game) LoadMods() error {
+ dir := filepath.Join(g.DataDir, "modules")
+ AllMods = nil
+ modByID = make(map[string]*ModDef)
+ action.WalkYAMLDir(dir, func(path, id string, data []byte) error {
+ var m ModDef
+ if err := yaml.Unmarshal(data, &m); err != nil {
+ return nil
+ }
+ AllMods = append(AllMods, &m)
+ return nil
+ })
+ modByID = make(map[string]*ModDef, len(AllMods))
+ for _, m := range AllMods {
+ modByID[m.ID] = m
+ }
+ return nil
+}
+
+func GetMod(id string) *ModDef {
+ return modByID[id]
+}
+
+func FindMod(input string) *ModDef {
+ if m, ok := modByID[input]; ok {
+ return m
+ }
+ lower := strings.ToLower(strings.ReplaceAll(input, " ", "_"))
+ for _, m := range AllMods {
+ if strings.HasPrefix(m.ID, lower) {
+ return m
+ }
+ }
+ lowerSpace := strings.ToLower(input)
+ for _, m := range AllMods {
+ if strings.HasPrefix(strings.ToLower(m.Name), lowerSpace) {
+ return m
+ }
+ }
+ return nil
+}
+
+type chipEntry struct {
+ Input string
+ Output string
+ Qty int
+}
+
+var enchantMap = map[string]map[string]string{
+ "enchant_1": {
+ "sapphire_ring": "ring_of_recoil",
+ "sapphire_necklace": "necklace_of_passage",
+ "sapphire_bracelet": "bracelet_of_clay",
+ },
+ "enchant_2": {
+ "emerald_ring": "ring_of_dueling",
+ "emerald_necklace": "binding_necklace",
+ "emerald_bracelet": "bracelet_of_slaughter",
+ },
+ "enchant_3": {
+ "ruby_ring": "ring_of_forging",
+ "ruby_necklace": "digsite_pendant",
+ "ruby_bracelet": "inoculation_bracelet",
+ },
+ "enchant_4": {
+ "diamond_ring": "ring_of_life",
+ "diamond_necklace": "phoenix_necklace",
+ "diamond_bracelet": "abyssal_bracelet",
+ },
+}
+
+var chipMap = map[string]chipEntry{
+ "chip_sapphire": {"sapphire_bolts", "sapphire_bolts_e", 10},
+ "chip_emerald": {"emerald_bolts", "emerald_bolts_e", 10},
+ "chip_ruby": {"ruby_bolts", "ruby_bolts_e", 10},
+ "chip_diamond": {"diamond_bolts", "diamond_bolts_e", 10},
+}
+
+func (g *Game) hasDeckEquipped(p *player.Player) bool {
+ itemID, ok := p.Equipment[object.SlotMainHand]
+ if !ok {
+ return false
+ }
+ def, err := g.ItemStore.Load(itemID)
+ if err != nil {
+ return false
+ }
+ return def.WeaponType == object.WeaponScience
+}
+
+func (g *Game) equippedProvidesJunk(p *player.Player) string {
+ itemID, ok := p.Equipment[object.SlotMainHand]
+ if !ok {
+ return ""
+ }
+ def, err := g.ItemStore.Load(itemID)
+ if err != nil {
+ return ""
+ }
+ return def.ProvidesJunk
+}
+
+func (g *Game) effectiveJunkCost(p *player.Player, mod *ModDef) map[string]int {
+ cost := make(map[string]int)
+ for k, v := range mod.JunkCost {
+ cost[k] = v
+ }
+
+ hasDeck := g.hasDeckEquipped(p)
+
+ if hasDeck {
+ delete(cost, "scrap_metal")
+ }
+
+ providesJunk := g.equippedProvidesJunk(p)
+ if providesJunk != "" {
+ delete(cost, providesJunk)
+ }
+
+ return cost
+}
+
+func (g *Game) hasJunkCost(p *player.Player, mod *ModDef) bool {
+ cost := g.effectiveJunkCost(p, mod)
+ for itemID, qty := range cost {
+ if p.CountItem(itemID) < qty {
+ return false
+ }
+ }
+ return true
+}
+
+func (g *Game) consumeJunkCost(p *player.Player, mod *ModDef) bool {
+ cost := g.effectiveJunkCost(p, mod)
+ for itemID, qty := range cost {
+ if !p.RemoveItem(itemID, qty) {
+ return false
+ }
+ }
+ g.AccountStore.SaveCharacter(p)
+ return true
+}
+
+func (g *Game) junkCostString(p *player.Player, mod *ModDef) string {
+ cost := g.effectiveJunkCost(p, mod)
+ delete(cost, "scrap_metal")
+ if len(cost) == 0 {
+ return "free"
+ }
+ var parts []string
+ for itemID, qty := range cost {
+ def, _ := g.ItemStore.Load(itemID)
+ name := itemID
+ if def != nil {
+ name = def.Name
+ }
+ name = strings.TrimSuffix(name, "junk")
+ if qty > 1 {
+ parts = append(parts, fmt.Sprintf("%d %s", qty, name))
+ } else {
+ parts = append(parts, name)
+ }
+ }
+ sort.Strings(parts)
+ return strings.Join(parts, ", ")
+}
+
+func (g *Game) totalEquipScienceAttack(p *player.Player) int {
+ total := 0
+ for _, itemID := range p.Equipment {
+ def, err := g.ItemStore.Load(itemID)
+ if err == nil {
+ total += def.Stats.ScienceAttack
+ }
+ }
+ return total
+}
+
+
diff --git a/internal/game/tech.go b/internal/game/sys_technology.go
index a24c66d..a24c66d 100644
--- a/internal/game/tech.go
+++ b/internal/game/sys_technology.go
diff --git a/internal/game/color.go b/internal/game/ui_color.go
index 962728a..962728a 100644
--- a/internal/game/color.go
+++ b/internal/game/ui_color.go
diff --git a/internal/game/ui_help.go b/internal/game/ui_help.go
new file mode 100644
index 0000000..151d1ba
--- /dev/null
+++ b/internal/game/ui_help.go
@@ -0,0 +1,152 @@
+package game
+
+import (
+ "fmt"
+ "path/filepath"
+ "strings"
+
+ "thehouseoficarus/internal/action"
+ "thehouseoficarus/internal/net"
+ "gopkg.in/yaml.v3"
+)
+
+type HelpDef struct {
+ Name string `yaml:"name"`
+ Category string `yaml:"category"`
+ Content string `yaml:"description"`
+}
+
+type cmdEntry struct {
+ Name string
+ Type string
+ Desc string
+}
+
+var commandList = []cmdEntry{
+ {"alias", "Instant", "Create command shortcuts"},
+ {"attack / kill", "Active", "Attack a mob"},
+ {"autotrigger / auto", "Instant", "Set a module to auto-trigger"},
+ {"bank", "Active", "Open bank interface"},
+ {"burn", "Active", "Start a fire"},
+ {"chop / cut", "Active", "Chop trees (Woodcutting)"},
+ {"clean", "Free", "Clean grimy herbs (Pharmacy)"},
+ {"color / colors", "Instant", "Customize display colors"},
+ {"colortable", "Instant", "Display color reference chart"},
+ {"construct / make", "Active", "Build furniture (Construction)"},
+ {"cook", "Active", "Cook raw food on a fire or range"},
+ {"craft", "Active", "Craft jewelry (Crafting)"},
+ {"description / desc", "Instant", "Set your character description"},
+ {"drink", "Free", "Drink a potion"},
+ {"drop", "Active", "Drop items to the ground"},
+ {"eat", "Free", "Eat food to restore hitpoints"},
+ {"equipment / eq", "Instant", "Show equipped items"},
+ {"exits", "Instant", "List available exits"},
+ {"farm", "Active", "Farm crops (Farming)"},
+ {"fish", "Active", "Fish at fishing spots (Fishing)"},
+ {"fletch", "Free", "Fletch logs into bows (Fletching)"},
+ {"get / take / pick", "Active", "Pick up items from the ground"},
+ {"help", "Instant", "Show help topics"},
+ {"id / identify", "Active", "Identify herbs (Pharmacy)"},
+ {"inventory / i / inv", "Instant", "Show your inventory"},
+ {"jack / jackin", "Active", "Jack into a terminal (Hacking)"},
+ {"look / l", "Instant", "Look around or examine things"},
+ {"map", "Instant", "Display an ASCII map of the area"},
+ {"mine", "Active", "Mine rocks (Mining)"},
+ {"mix", "Active", "Mix potions (Pharmacy)"},
+ {"mods / modlist", "Instant", "List available science modules"},
+ {"north / south / east / west / up / down", "Active", "Move in a direction"},
+ {"option / options", "Instant", "View or change settings"},
+ {"prompt", "Instant", "Set custom command prompt"},
+ {"pull / push", "Active", "Interact with objects"},
+ {"queued", "Instant", "Show pending tick actions"},
+ {"quit", "Active", "Rest and disconnect"},
+ {"remove / unwear / unwield", "Free", "Unequip items"},
+ {"say", "Instant", "Chat with players in your room"},
+ {"score / sc", "Instant", "View your stats and skills"},
+ {"search", "Active", "Search items for loot"},
+ {"smelt", "Active", "Smelt ore into bars (Smithing)"},
+ {"smith", "Active", "Smith bars into items (Smithing)"},
+ {"sneak", "Instant", "Toggle sneak mode"},
+ {"steal", "Active", "Steal from mobs (Thieving)"},
+ {"stoke", "Active", "Add logs to a fire"},
+ {"style", "Instant", "Change combat style"},
+ {"talk / speak / ask", "Active", "Talk to NPCs"},
+ {"tech", "Instant", "Toggle technology abilities"},
+ {"trigger", "Active", "Trigger a science module"},
+ {"unalias", "Instant", "Remove command shortcuts"},
+ {"use", "Active", "Use an object (crafting)"},
+ {"walk", "Active", "Pathfind to a room or multi-step walk"},
+ {"wear / wield", "Free", "Equip items"},
+}
+
+func LoadHelp(dataDir string) ([]HelpDef, error) {
+ dir := filepath.Join(dataDir, "help")
+ var helps []HelpDef
+ action.WalkYAMLDir(dir, func(path, id string, data []byte) error {
+ var h HelpDef
+ if err := yaml.Unmarshal(data, &h); err != nil {
+ return nil
+ }
+ helps = append(helps, h)
+ return nil
+ })
+ return helps, nil
+}
+
+func (g *Game) doHelp(sess *net.Session, topic string) {
+ if topic == "" {
+ unicode := true
+ if p := sess.Player; p != nil {
+ unicode = p.OptionBool("unicode")
+ }
+
+ sess.WriteLine("")
+ t := &Table{
+ Title: "Commands",
+ Columns: []string{"Command", "Type", "Description"},
+ }
+ for _, c := range commandList {
+ t.Rows = append(t.Rows, []string{c.Name, c.Type, c.Desc})
+ }
+ for _, line := range t.Render(unicode) {
+ sess.WriteLine(line)
+ }
+ sess.WriteLine("")
+ sess.WriteLine("Command types: Instant (runs immediately), Free (queued before active),")
+ sess.WriteLine("Active (replaces current action, queued per tick).")
+ sess.WriteLine("")
+ sess.WriteLine("Use 'help <command>' for detailed usage of a specific command.")
+ sess.WriteLine("Use 'option' to view and change settings, 'color' to customize colors.")
+ return
+ }
+
+ helps, err := LoadHelp(g.DataDir)
+ if err != nil || len(helps) == 0 {
+ sess.WriteLine("No help available.")
+ return
+ }
+
+ topic = strings.ToLower(topic)
+ for _, h := range helps {
+ if strings.ToLower(h.Name) == topic {
+ sess.WriteLine(fmt.Sprintf("\n %s", h.Content))
+ return
+ }
+ }
+ sess.WriteLine(fmt.Sprintf("No help found for '%s'.", topic))
+}
+
+func firstLine(s string) string {
+ if idx := strings.Index(s, "\n"); idx >= 0 {
+ return s[:idx]
+ }
+ return s
+}
+
+func (g *Game) executeHelp(sess *net.Session, args []string, rawInput string) {
+ if len(args) == 0 {
+ g.doHelp(sess, "")
+ } else {
+ g.doHelp(sess, strings.Join(args, " "))
+ }
+}
diff --git a/internal/game/map.go b/internal/game/ui_map.go
index 32d3d7c..32d3d7c 100644
--- a/internal/game/map.go
+++ b/internal/game/ui_map.go
diff --git a/internal/game/prompt.go b/internal/game/ui_prompt.go
index 2ecf367..2ecf367 100644
--- a/internal/game/prompt.go
+++ b/internal/game/ui_prompt.go
diff --git a/internal/game/table.go b/internal/game/ui_table.go
index b96dca1..b96dca1 100644
--- a/internal/game/table.go
+++ b/internal/game/ui_table.go
diff --git a/internal/game/utils.go b/internal/game/utils.go
deleted file mode 100644
index 9148ea3..0000000
--- a/internal/game/utils.go
+++ /dev/null
@@ -1,121 +0,0 @@
-package game
-
-import (
- "fmt"
- "math/rand"
- "strconv"
- "strings"
-
- "thehouseoficarus/internal/net"
- "thehouseoficarus/internal/player"
- "thehouseoficarus/internal/world"
-)
-
-func plural(n int) string {
- if n == 1 {
- return ""
- }
- return "s"
-}
-
-func parseQty(input string) (int, string) {
- parts := strings.Fields(input)
- if len(parts) > 1 {
- if n, err := strconv.Atoi(parts[0]); err == nil && n > 0 {
- return n, strings.Join(parts[1:], " ")
- }
- }
- return 0, input
-}
-
-func parseChoiceIndex(s string) (int, error) {
- var idx int
- _, err := fmt.Sscanf(s, "%d", &idx)
- return idx, err
-}
-
-func mobDisplayName(m *world.MobInstance, definite bool) string {
- if m.Unique {
- return m.Name
- }
- if definite {
- return "the " + m.Name
- }
- return "a " + m.Name
-}
-
-func mobCombatLevel(m *world.MobInstance) int {
- base := float64(m.Defense+m.MaxHP) / 4.0
- melee := float64(m.Attack+m.Strength) / 4.0
- ranged := float64(m.Ranged) * 3.0 / 8.0
- science := float64(m.Science) * 3.0 / 8.0
- best := melee
- if ranged > best {
- best = ranged
- }
- if science > best {
- best = science
- }
- return int(base + best + 0.5)
-}
-
-func uniqueItemNames(matches []itemMatch) []string {
- seen := make(map[string]bool)
- var out []string
- for _, m := range matches {
- if !seen[m.Name] {
- seen[m.Name] = true
- out = append(out, m.Name)
- }
- }
- return out
-}
-
-func (g *Game) showWhichOne(sess *net.Session, matches []itemMatch) {
- sess.WriteLine("Which one?")
- seen := make(map[string]bool)
- for _, m := range matches {
- if seen[m.Name] {
- continue
- }
- seen[m.Name] = true
- def, _ := g.ItemStore.Load(m.ID)
- coloredName := g.itemColorize(sess, def, m.Name)
- sess.WriteLine(fmt.Sprintf(" - %s", coloredName))
- }
-}
-
-func randInt(max int) int {
- if max <= 0 {
- return 0
- }
- return rand.Intn(max)
-}
-
-func formatPickupList(sess *net.Session, picked []string) {
- for i, name := range picked {
- if i == len(picked)-1 {
- sess.WriteLine(name)
- } else if i == len(picked)-2 {
- sess.Write(name + " and ")
- } else {
- sess.Write(name + ", ")
- }
- }
-}
-
-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 {
- slot.Quality = def.Quality
- slot.MaxQuality = def.MaxQuality
- }
- return slot
-}
-
-func (g *Game) awardSkillXP(sess *net.Session, p *player.Player, skill player.SkillName, xp int) {
- newLevel := p.AddSkillXP(skill, xp)
- if newLevel > 0 {
- sess.WriteLine(g.colorize(sess, "level_up", fmt.Sprintf("*** You are now level %d %s! ***", newLevel, skill)))
- }
-}
diff --git a/internal/object/item.go b/internal/object/item.go
index 7c682a4..8ec1565 100644
--- a/internal/object/item.go
+++ b/internal/object/item.go
@@ -74,7 +74,7 @@ type ItemDef struct {
type MadeFromEntry struct {
Items []string `yaml:"items"`
- Qty int `yaml:"qty"`
+ Quantity int `yaml:"quantity"`
Byproducts []string `yaml:"byproducts"`
}
diff --git a/internal/object/item_store.go b/internal/object/item_store.go
index 9e932ba..d75179f 100644
--- a/internal/object/item_store.go
+++ b/internal/object/item_store.go
@@ -4,28 +4,34 @@ import (
"fmt"
"os"
"path/filepath"
- "strings"
+ "thehouseoficarus/internal/action"
"gopkg.in/yaml.v3"
)
type ItemStore struct {
- dataDir string
- cache map[string]*ItemDef
+ dataDir string
+ pathIndex map[string]string
+ cache map[string]*ItemDef
}
func NewItemStore(dataDir string) *ItemStore {
- return &ItemStore{
- dataDir: dataDir,
- cache: make(map[string]*ItemDef),
+ s := &ItemStore{
+ dataDir: dataDir,
+ pathIndex: action.BuildPathIndex(filepath.Join(dataDir, "items")),
+ cache: make(map[string]*ItemDef),
}
+ return s
}
func (s *ItemStore) Load(id string) (*ItemDef, error) {
if def, ok := s.cache[id]; ok {
return def, nil
}
- path := filepath.Join(s.dataDir, "items", id+".yaml")
+ path, ok := s.pathIndex[id]
+ if !ok {
+ return nil, fmt.Errorf("read item %s: no such item", id)
+ }
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read item %s: %w", id, err)
@@ -40,21 +46,31 @@ func (s *ItemStore) Load(id string) (*ItemDef, error) {
func (s *ItemStore) LoadAll() ([]*ItemDef, error) {
dir := filepath.Join(s.dataDir, "items")
- entries, err := os.ReadDir(dir)
- if err != nil {
- return nil, err
- }
var defs []*ItemDef
- for _, e := range entries {
- if e.IsDir() || filepath.Ext(e.Name()) != ".yaml" {
- continue
+ err := action.WalkYAMLDir(dir, func(path, id string, data []byte) error {
+ if def, ok := s.cache[id]; ok {
+ defs = append(defs, def)
+ return nil
}
- id := strings.TrimSuffix(e.Name(), ".yaml")
- def, err := s.Load(id)
- if err != nil {
- continue
+ var def ItemDef
+ if err := yaml.Unmarshal(data, &def); err != nil {
+ return nil
}
- defs = append(defs, def)
+ s.cache[id] = &def
+ defs = append(defs, &def)
+ return nil
+ })
+ return defs, err
+}
+
+func (s *ItemStore) PathIndex() map[string]string {
+ return s.pathIndex
+}
+
+func (s *ItemStore) IDSet() map[string]bool {
+ ids := make(map[string]bool)
+ for id := range s.pathIndex {
+ ids[id] = true
}
- return defs, nil
+ return ids
}
diff --git a/internal/object/object.go b/internal/object/object.go
index 4d8db69..826a315 100644
--- a/internal/object/object.go
+++ b/internal/object/object.go
@@ -3,7 +3,7 @@ package object
import "thehouseoficarus/internal/action"
type UseInteraction struct {
- Item string `yaml:"item"`
+ Item string `yaml:"item_id"`
Condition *action.Condition `yaml:"condition"`
Message string `yaml:"message"`
Action *action.NodeAction `yaml:"action"`
diff --git a/internal/object/store.go b/internal/object/store.go
index a884601..a11ff7a 100644
--- a/internal/object/store.go
+++ b/internal/object/store.go
@@ -5,18 +5,21 @@ import (
"os"
"path/filepath"
+ "thehouseoficarus/internal/action"
"gopkg.in/yaml.v3"
)
type ObjectStore struct {
- dataDir string
- cache map[string]*ObjectDef
+ dataDir string
+ pathIndex map[string]string
+ cache map[string]*ObjectDef
}
func NewObjectStore(dataDir string) *ObjectStore {
return &ObjectStore{
- dataDir: dataDir,
- cache: make(map[string]*ObjectDef),
+ dataDir: dataDir,
+ pathIndex: action.BuildPathIndex(filepath.Join(dataDir, "objects")),
+ cache: make(map[string]*ObjectDef),
}
}
@@ -24,7 +27,10 @@ func (s *ObjectStore) Load(id string) (*ObjectDef, error) {
if def, ok := s.cache[id]; ok {
return def, nil
}
- path := filepath.Join(s.dataDir, "objects", fmt.Sprintf("%s.yaml", id))
+ path, ok := s.pathIndex[id]
+ if !ok {
+ return nil, fmt.Errorf("read object %s: no such object", id)
+ }
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read object %s: %w", id, err)
@@ -36,3 +42,15 @@ func (s *ObjectStore) Load(id string) (*ObjectDef, error) {
s.cache[id] = &def
return &def, nil
}
+
+func (s *ObjectStore) PathIndex() map[string]string {
+ return s.pathIndex
+}
+
+func (s *ObjectStore) IDSet() map[string]bool {
+ ids := make(map[string]bool)
+ for id := range s.pathIndex {
+ ids[id] = true
+ }
+ return ids
+}
diff --git a/internal/world/mob.go b/internal/world/mob.go
index f7988ce..9d07532 100644
--- a/internal/world/mob.go
+++ b/internal/world/mob.go
@@ -144,6 +144,7 @@ func (m *MobInstance) StartRegen() {
type MobStore struct {
dataDir string
mu sync.Mutex
+ pathIndex map[string]string
defs map[string]*MobDef
instances map[string]*MobInstance
}
@@ -151,6 +152,7 @@ type MobStore struct {
func NewMobStore(dataDir string) *MobStore {
return &MobStore{
dataDir: dataDir,
+ pathIndex: action.BuildPathIndex(filepath.Join(dataDir, "mobs")),
defs: make(map[string]*MobDef),
instances: make(map[string]*MobInstance),
}
@@ -164,7 +166,10 @@ func (s *MobStore) LoadDef(id string) (*MobDef, error) {
}
s.mu.Unlock()
- path := filepath.Join(s.dataDir, "mobs", id+".yaml")
+ path, ok := s.pathIndex[id]
+ if !ok {
+ return nil, fmt.Errorf("read mob %s: no such mob", id)
+ }
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read mob %s: %w", id, err)
@@ -180,6 +185,14 @@ func (s *MobStore) LoadDef(id string) (*MobDef, error) {
return &def, nil
}
+func (s *MobStore) AllDefIDs() map[string]bool {
+ ids := make(map[string]bool)
+ for id := range s.pathIndex {
+ ids[id] = true
+ }
+ return ids
+}
+
func (s *MobStore) GetInstance(id string) *MobInstance {
s.mu.Lock()
defer s.mu.Unlock()
diff --git a/internal/world/room.go b/internal/world/room.go
index c0f531e..f006acc 100644
--- a/internal/world/room.go
+++ b/internal/world/room.go
@@ -41,8 +41,8 @@ var ExitOrder = []ExitDir{
}
type SpawnDef struct {
- ItemID string `yaml:"item_id"`
- Quantity int `yaml:"quantity"`
+ ID string `yaml:"id"`
+ Quantity int `yaml:"quantity"`
RespawnTicks float64 `yaml:"respawn_ticks"`
}
@@ -72,7 +72,7 @@ type Room struct {
MapSymbol string `yaml:"map_symbol"`
Exits map[ExitDir]ExitDef `yaml:"exits"`
Objects []RoomObject `yaml:"objects"`
- Spawns []SpawnDef `yaml:"spawns"`
+ ItemSpawns []SpawnDef `yaml:"item_spawns"`
Mobs []RoomMob `yaml:"mobs"`
OnEnter []EnterStep `yaml:"on_enter"`
}
diff --git a/internal/world/world.go b/internal/world/world.go
index 3094c54..0b40918 100644
--- a/internal/world/world.go
+++ b/internal/world/world.go
@@ -7,36 +7,38 @@ import (
"strings"
"sync"
+ "thehouseoficarus/internal/action"
"gopkg.in/yaml.v3"
)
type World struct {
- dataDir string
- mu sync.Mutex
- groundItems map[int][]*groundEntry
- seeded map[int]bool
- objStates map[string]*ObjState
- objMoves []ObjMove
+ dataDir string
+ mu sync.Mutex
+ roomPathIndex map[int]string
+ groundItems map[int][]*groundEntry
+ seeded map[int]bool
+ objStates map[string]*ObjState
+ objMoves []ObjMove
}
func New(dataDir string) *World {
return &World{
- dataDir: dataDir,
- groundItems: make(map[int][]*groundEntry),
- seeded: make(map[int]bool),
- objStates: make(map[string]*ObjState),
+ dataDir: dataDir,
+ roomPathIndex: action.BuildRoomIndex(filepath.Join(dataDir, "rooms")),
+ groundItems: make(map[int][]*groundEntry),
+ seeded: make(map[int]bool),
+ objStates: make(map[string]*ObjState),
}
}
func (w *World) LoadRoom(id int) (*Room, error) {
- path := filepath.Join(w.dataDir, "rooms", fmt.Sprintf("%d.yaml", id))
+ path, ok := w.roomPathIndex[id]
+ if !ok {
+ return nil, fmt.Errorf("read room %d: no such room", id)
+ }
data, err := os.ReadFile(path)
if err != nil {
- path = filepath.Join(w.dataDir, "rooms", "player_housing", fmt.Sprintf("%d.yaml", id))
- data, err = os.ReadFile(path)
- if err != nil {
- return nil, fmt.Errorf("read room %d: %w", id, err)
- }
+ return nil, fmt.Errorf("read room %d: %w", id, err)
}
var room Room
if err := yaml.Unmarshal(data, &room); err != nil {
@@ -46,8 +48,8 @@ func (w *World) LoadRoom(id int) (*Room, error) {
if room.Exits == nil {
room.Exits = make(map[ExitDir]ExitDef)
}
- if room.Spawns == nil {
- room.Spawns = make([]SpawnDef, 0)
+ if room.ItemSpawns == nil {
+ room.ItemSpawns = make([]SpawnDef, 0)
}
if room.Mobs == nil {
room.Mobs = make([]RoomMob, 0)
@@ -84,10 +86,10 @@ func (w *World) SeedGroundItems(roomID int) {
w.mu.Lock()
defer w.mu.Unlock()
- for _, s := range room.Spawns {
+ for _, s := range room.ItemSpawns {
merged := false
for _, e := range w.groundItems[roomID] {
- if e.quantity > 0 && strings.EqualFold(e.itemID, s.ItemID) &&
+ if e.quantity > 0 && strings.EqualFold(e.itemID, s.ID) &&
(e.reserveTimer <= 0 || e.reservedFor == "") &&
e.despawnTimer <= 0 {
e.quantity += s.Quantity
@@ -100,7 +102,7 @@ func (w *World) SeedGroundItems(roomID int) {
}
if !merged {
e := &groundEntry{
- itemID: s.ItemID,
+ itemID: s.ID,
quantity: s.Quantity,
isSpawn: true,
respawnDelay: int(s.RespawnTicks),
@@ -111,6 +113,14 @@ func (w *World) SeedGroundItems(roomID int) {
}
}
+func (w *World) RoomIndex() map[int]bool {
+ ids := make(map[int]bool)
+ for id := range w.roomPathIndex {
+ ids[id] = true
+ }
+ return ids
+}
+
func (w *World) Tick() {
w.mu.Lock()
defer w.mu.Unlock()
diff --git a/worldbuilding_guide/construction.md b/worldbuilding_guide/construction.md
index d4ba840..5775fbf 100644
--- a/worldbuilding_guide/construction.md
+++ b/worldbuilding_guide/construction.md
@@ -96,7 +96,7 @@ station: [workbench]
tool: saw
consume:
- items: [oak_logs]
- qty: 1
+ quantity: 1
output: oak_planks
message: "You saw the oak logs into sturdy oak planks."
```
@@ -111,7 +111,7 @@ wait: 6
station: [workbench]
consume:
- items: [planks]
- qty: 3
+ quantity: 3
output: wooden_table
message: "You build a sturdy wooden table."
```
diff --git a/worldbuilding_guide/items.md b/worldbuilding_guide/items.md
index a1ca8c1..2e36349 100644
--- a/worldbuilding_guide/items.md
+++ b/worldbuilding_guide/items.md
@@ -149,10 +149,10 @@ color: "222"
ticks: 2
made_from:
- items: [pot_of_flour]
- qty: 1
+ quantity: 1
byproducts: [empty_pot]
- items: [bucket_of_water, pitcher_of_water]
- qty: 1
+ quantity: 1
byproducts: [empty_bucket, empty_pitcher]
```
@@ -169,10 +169,10 @@ Use `""` for items that produce no byproduct:
```yaml
made_from:
- items: [pot_of_flour, loose_flour]
- qty: 1
+ quantity: 1
byproducts: [empty_pot, ""] # pot returns empty_pot, loose flour returns nothing
- items: [bucket_of_water]
- qty: 1
+ quantity: 1
byproducts: [empty_bucket]
```
@@ -181,10 +181,10 @@ Omit `byproducts` entirely if no items in that slot produce a byproduct:
```yaml
made_from:
- items: [pot_of_flour]
- qty: 1
+ quantity: 1
byproducts: [empty_pot] # returns empty_pot
- items: [salt]
- qty: 1 # no byproducts field — salt is consumed entirely
+ quantity: 1 # no byproducts field — salt is consumed entirely
```
See also `recipes.md` for station-based recipes.
diff --git a/worldbuilding_guide/objects.md b/worldbuilding_guide/objects.md
index a7da5e6..38eaeeb 100644
--- a/worldbuilding_guide/objects.md
+++ b/worldbuilding_guide/objects.md
@@ -124,9 +124,9 @@ Simple message (no action):
id: anvil
name: anvil
use_interactions:
- - item: silver_bar
+ - item_id: silver_bar
message: "You should use this with a mold at a furnace."
- - item: gold_bar
+ - item_id: gold_bar
message: "You should use this with a mold at a furnace."
```
@@ -135,11 +135,11 @@ Puzzle interaction (take item, set flag):
id: crystal_slot
name: crystal slot
use_interactions:
- - item: crystal_key
+ - item_id: crystal_key
condition:
flag: crystal_inserted
message: "The crystal key is already in the slot."
- - item: crystal_key
+ - item_id: crystal_key
message: "You insert the crystal key into the slot. It clicks into place."
action:
take_item: crystal_key
@@ -150,7 +150,7 @@ use_interactions:
Quest item exchange:
```yaml
use_interactions:
- - item: ancient_scroll
+ - item_id: ancient_scroll
condition:
player_flag: quest_started
message: "You place the scroll on the pedestal. The door rumbles open!"
@@ -166,7 +166,7 @@ use_interactions:
| Field | Type | Description |
| ----------- | ---------- | ------------------------------------------------ |
-| `item` | string | Item ID that triggers this interaction |
+| `item_id` | string | Item ID that triggers this interaction |
| `condition` | Condition | Optional condition (same as exits/talk/use_interactions) |
| `message` | string | Message shown to the player |
| `action` | NodeAction | Optional actions (same as talk node actions) |
diff --git a/worldbuilding_guide/recipes.md b/worldbuilding_guide/recipes.md
index 81a1240..29b513a 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/<id>.yaml`.
+Recipe files live in `data/recipes/<type>/<id>.yaml` (e.g., `data/recipes/smithing/smith_bronze_dagger.yaml`).
### Recipe vs MadeFrom
@@ -39,7 +39,7 @@ Each consume entry defines an ingredient slot with multiple valid items. The fir
```yaml
consume:
- items: [item_id, alternative_id, ...]
- qty: 1
+ quantity: 1
byproducts: [byproduct_for_item, byproduct_for_alt, ...]
```
@@ -48,10 +48,10 @@ consume:
```yaml
consume:
- items: [bucket_of_water, vial_of_water]
- qty: 1
+ quantity: 1
byproducts: [empty_bucket, ""] # bucket returns empty, vial is consumed entirely
- items: [herb]
- qty: 1 # no byproducts — herb is consumed entirely
+ quantity: 1 # no byproducts — herb is consumed entirely
```
### Station-based recipe (cooking on a fire)
@@ -66,7 +66,7 @@ wait: 6
station: [fire, cooking_range]
consume:
- items: [raw_trout]
- qty: 1
+ quantity: 1
output: trout
fail: burnt_fish
message: "Cooked to perfection. It looks great!"
@@ -85,7 +85,7 @@ wait: 6
station: [cooking_range]
consume:
- items: [bread_dough]
- qty: 1
+ quantity: 1
output: bread
fail: burnt_meat
message: "You bake the dough into a fresh loaf of bread."
@@ -106,9 +106,9 @@ wait: 4
station: [furnace]
consume:
- items: [iron_ore]
- qty: 1
+ quantity: 1
- items: [coal]
- qty: 2
+ quantity: 2
output: steel_bar
message: "You remove a white hot steel bar!"
fail_message: "You fail to smelt a usable bar."
@@ -137,7 +137,7 @@ wait: 4
station: [anvil]
consume:
- items: [steel_bar]
- qty: 5
+ quantity: 5
output: steel_platebody
message: "You hammer out a steel platebody."
```
@@ -154,7 +154,7 @@ wait: 4
station: [anvil]
consume:
- items: [iron_bar]
- qty: 1
+ quantity: 1
output: iron_nails
output_qty: 15
message: "You hammer out some iron nails."
@@ -171,9 +171,9 @@ name: bread dough
color: "222"
made_from:
- items: [pot_of_flour]
- qty: 1
+ quantity: 1
- items: [bucket_of_water, pitcher_of_water, vial_of_water]
- qty: 1
+ quantity: 1
```
The player uses `use flour on water` to combine them. The system finds that `bread_dough` can be made from these ingredients and produces it.
diff --git a/worldbuilding_guide/rooms.md b/worldbuilding_guide/rooms.md
index f6e74ea..3d6db7d 100644
--- a/worldbuilding_guide/rooms.md
+++ b/worldbuilding_guide/rooms.md
@@ -67,14 +67,14 @@ exits:
blocked_message: "The bridge is out, and the toll collector blocks the path."
```
-### Spawns — ground items that respawn
+### Item Spawns — ground items that respawn
```yaml
-spawns:
- - item_id: bronze_pickaxe
+item_spawns:
+ - id: bronze_pickaxe
quantity: 1
respawn_ticks: 30 # reappears 30 ticks (18 seconds) after being picked up
- - item_id: copper_ore
+ - id: copper_ore
quantity: 3
respawn_ticks: 50
```