# Pharmacy Skill Implementation Plan ## 1. Overview Pharmacy replaces the Alchemy skill everywhere in the codebase. It is a production skill centered on creating potions from herbs and reagents. **Production chain:** 1. **Obtain grimy herbs** -- mob drops, farming (future), or ground spawns 2. **Clean herbs** (`clean` command) -- background action, awards Pharmacy XP, level-gated per herb 3. **Create unfinished potions** (`use` command) -- combine clean herb + vial of water via MadeFrom (no XP, no level req) 4. **Finish potions** (`mix` command) -- combine unfinished potion + reagent via recipe system, awards Pharmacy XP, level-gated **Potion effects are NOT implemented yet.** Potions are items only. A future update will add consumable effects (stat boosts, healing, curing). For now, potions have `description` text describing their intended effect and a `value` for trading. --- ## 2. Skill Rename (Alchemy -> Pharmacy) ### Files to change **`internal/player/player.go`** -- 3 locations: - Line 24: `Alchemy SkillName = "alchemy"` -> `Pharmacy SkillName = "pharmacy"` - Line 37: `Alchemy, Thieving,` -> `Pharmacy, Thieving,` - Line 56: `Alchemy: "alc",` -> `Pharmacy: "pha",` **`data/help/` YAML files** -- if any reference "alchemy" in text, update to "pharmacy". Currently none exist for alchemy, so no changes needed. New help files will be created. **`AGENTS.md`** -- Update the Skills table: - Change `Alchemy` to `Pharmacy` in the Production skills row - Update the `SkillAbbr` reference from `"alc"` to `"pha"` **No other Go files reference "alchemy" or "Alchemy" directly** (confirmed by grep). The skill constant `Alchemy` is only used in `player.go` in 3 places. **Existing player save files** -- Any character YAML files with `alchemy: ` in their skills map will need the key renamed to `pharmacy`. Add a migration note: on load, if `skills` map contains key `"alchemy"`, copy its value to `"pharmacy"` and delete the old key. Add this to `player.go` or to the account loading logic. ### Migration code (in `internal/player/` account loader or `New()`) After unmarshaling a Player from YAML, add: ```go if xp, ok := p.Skills["alchemy"]; ok { p.Skills[Pharmacy] = xp delete(p.Skills, "alchemy") } ``` Place this in the `AccountStore.LoadCharacter()` function (or wherever character YAML is deserialized) right after the unmarshal call. --- ## 3. Commands ### `clean` -- Free command (background action) | Property | Value | |---|---| | Classification | `ClassFree` | | Verb | `clean` | | Action type | Background (like `fletch`) | | ActionType const | `ActionCleaning` | | Station required | No | | Tick interval | 2 ticks per herb | | Cancels active action | No | | Cancels background action | Yes (replaces fletching etc.) | **Usage:** - `clean` -- Start cleaning all grimy herbs in inventory (auto-selects, processes one every 2 ticks) - `clean ` -- Clean only grimy herbs matching `` (e.g., `clean guam` cleans only grimy guam) **Behavior:** The player types `clean`. The command scans inventory for any item that has a `clean` type recipe. It starts a background action that cleans one herb every 2 ticks. After each herb is cleaned, it scans for the next. It stops when no more grimy herbs remain (or no more that match the filter). Outputs a message per herb cleaned. Can be done while walking, fighting, etc. (background action). ### `mix` -- Active command (production) | Property | Value | |---|---| | Classification | `ClassActive` | | Verb | `mix` | | Action type | Active (uses standard production) | | ActionType key | `"mix"` in productionTypes | | Station required | No | | Tick interval | Per recipe `wait` field (default 4) | **Usage:** - `mix` -- Show table of available pharmacy recipes, select by number/name - `mix ` -- Mix a specific potion - `mix 10 ` -- Mix a specific quantity **Behavior:** Works like `cook` but with no station requirement. Shows a production table of all `pharmacy` type recipes the player can make. Uses standard production cycle (start message, timed loop, success check, XP, output). ### Command dispatch additions **`classifyCommand()` in `game.go`:** ```go case "eat", "fletch", "clean": return ClassFree ``` And: ```go case "quit", "use", "burn", "stoke", "search", "walk", "cook", "smelt", "smith", "craft", "mix": return ClassActive ``` **`executeCommand()` in `game.go`:** Add two new cases: ```go case "clean": g.doClean(sess, strings.Join(args, " ")) return case "mix": g.doMix(sess, strings.Join(args, " ")) return ``` ### Option additions Add to `OptionDefs` in `player.go`: ```go {"mix_all", OptBool, false, nil, "Auto-start mixing when only one product is possible"}, ``` --- ## 4. New Files to Create ### Go files (in `internal/game/`) | File | Purpose | |---|---| | `cmd_clean.go` | `doClean()` handler -- find grimy herbs, start background clean action | | `cmd_mix.go` | `doMix()` handler -- find pharmacy recipes, show table or start production | | `action_clean.go` | `startCleanAction()`, `advanceClean()` -- background action lifecycle | ### YAML data files | Path | Count | Description | |---|---|---| | `data/items/grimy_*.yaml` | 14 | Grimy herb items | | `data/items/.yaml` | 14 | Clean herb items (guam, marrentill, etc.) | | `data/items/vial_of_water.yaml` | 1 | Vial of water | | `data/items/vial.yaml` | 1 | Empty vial | | `data/items/*_unf.yaml` | 14 | Unfinished potions | | `data/items/.yaml` | 16 | Finished potions | | `data/items/.yaml` | ~11 | Secondary ingredients | | `data/recipes/clean_*.yaml` | 14 | Clean recipes (type: clean) | | `data/recipes/mix_*.yaml` | 16 | Mix recipes (type: pharmacy) | | `data/help/clean.yaml` | 1 | Help for clean command | | `data/help/mix.yaml` | 1 | Help for mix command | | `data/help/pharmacy.yaml` | 1 | Help for pharmacy skill | **Total: ~102 new YAML files, 3 new Go files.** --- ## 5. Code Changes to Existing Files ### `internal/player/player.go` 1. **Rename constant** (line 24): ``` OLD: Alchemy SkillName = "alchemy" NEW: Pharmacy SkillName = "pharmacy" ``` 2. **Update AllSkills** (line 37): ``` OLD: Alchemy, Thieving, Agility, Construction, Scavenging, Hacking, Assassin, Farming, NEW: Pharmacy, Thieving, Agility, Construction, Scavenging, Hacking, Assassin, Farming, ``` 3. **Update SkillAbbr** (line 56): ``` OLD: Alchemy: "alc", NEW: Pharmacy: "pha", ``` 4. **Add option** to `OptionDefs` slice (after `smelt_all`): ```go {"mix_all", OptBool, false, nil, "Auto-start mixing when only one product is possible"}, ``` 5. **Add migration** in the character loading path. In `internal/player/account.go` (or wherever `LoadCharacter` unmarshals), after unmarshal add: ```go if xp, ok := p.Skills["alchemy"]; ok { p.Skills[Pharmacy] = xp delete(p.Skills, "alchemy") } ``` ### `internal/game/game.go` 1. **`classifyCommand()`** -- Add `"clean"` to the `ClassFree` case and `"mix"` to the `ClassActive` case: ```go case "eat", "fletch", "clean": return ClassFree ``` ```go case "quit", "use", "burn", "stoke", "search", "walk", "cook", "smelt", "smith", "craft", "mix": return ClassActive ``` 2. **`executeCommand()`** -- Add two cases before the `default:` block: ```go case "clean": g.doClean(sess, strings.Join(args, " ")) return case "mix": g.doMix(sess, strings.Join(args, " ")) return ``` ### `internal/game/action_state.go` 1. **Add ActionType constant:** ```go ActionCleaning ActionType = "cleaning" ActionMixing ActionType = "mixing" ``` 2. **Add Description() cases:** ```go case ActionCleaning: return "cleaning herbs" case ActionMixing: return "mixing " + a.TargetName ``` ### `internal/game/action.go` 1. **Add `"clean"` to the background action advance switch** (in `AdvanceActions`, around line 254): ```go switch p.BackgroundAction.Type { case "fletch": g.advanceFletch(sess, p) case "clean": g.advanceClean(sess, p) } ``` ### `internal/game/action_production.go` 1. **Add pharmacy to `productionTypes` map:** ```go 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"}, } ``` ### `internal/object/item.go` No changes needed. Grimy herbs and clean herbs are normal items. The `clean_into` relationship is encoded in the recipe system, not on ItemDef. ### `internal/net/server.go` No new session states needed. The `mix` command uses existing `StateProductChoice` and `StateHowMany`. The `clean` command doesn't prompt -- it just starts. ### `AGENTS.md` Update all references from "Alchemy" to "Pharmacy" and "alc" to "pha". Update the Skills table, the Adding a New Production Skill section, the Commands table (add `clean` and `mix`), and the Action Types table (add `cleaning` and `mixing`). --- ## 6. Herbs -- Full Item YAML All herbs share a common pattern. Grimy herbs are uncleaned; clean herbs are the output. ### Grimy Herbs Each grimy herb follows this template: ```yaml id: grimy_ name: "grimy " color: "65" description: "A grimy leaf. It needs to be cleaned before it can be used." value: stackable: false ``` #### `data/items/grimy_guam.yaml` ```yaml id: grimy_guam name: "grimy guam" color: "65" description: "A grimy guam leaf. It needs to be cleaned before it can be used." value: 2 stackable: false ``` #### `data/items/grimy_marrentill.yaml` ```yaml id: grimy_marrentill name: "grimy marrentill" color: "65" description: "A grimy marrentill leaf. It needs to be cleaned before it can be used." value: 4 stackable: false ``` #### `data/items/grimy_tarromin.yaml` ```yaml id: grimy_tarromin name: "grimy tarromin" color: "65" description: "A grimy tarromin leaf. It needs to be cleaned before it can be used." value: 7 stackable: false ``` #### `data/items/grimy_harralander.yaml` ```yaml id: grimy_harralander name: "grimy harralander" color: "65" description: "A grimy harralander leaf. It needs to be cleaned before it can be used." value: 12 stackable: false ``` #### `data/items/grimy_ranarr.yaml` ```yaml id: grimy_ranarr name: "grimy ranarr" color: "65" description: "A grimy ranarr leaf. It needs to be cleaned before it can be used." value: 25 stackable: false ``` #### `data/items/grimy_toadflax.yaml` ```yaml id: grimy_toadflax name: "grimy toadflax" color: "65" description: "A grimy toadflax leaf. It needs to be cleaned before it can be used." value: 18 stackable: false ``` #### `data/items/grimy_irit.yaml` ```yaml id: grimy_irit name: "grimy irit" color: "65" description: "A grimy irit leaf. It needs to be cleaned before it can be used." value: 30 stackable: false ``` #### `data/items/grimy_avantoe.yaml` ```yaml id: grimy_avantoe name: "grimy avantoe" color: "65" description: "A grimy avantoe leaf. It needs to be cleaned before it can be used." value: 35 stackable: false ``` #### `data/items/grimy_kwuarm.yaml` ```yaml id: grimy_kwuarm name: "grimy kwuarm" color: "65" description: "A grimy kwuarm leaf. It needs to be cleaned before it can be used." value: 40 stackable: false ``` #### `data/items/grimy_snapdragon.yaml` ```yaml id: grimy_snapdragon name: "grimy snapdragon" color: "65" description: "A grimy snapdragon leaf. It needs to be cleaned before it can be used." value: 50 stackable: false ``` #### `data/items/grimy_cadantine.yaml` ```yaml id: grimy_cadantine name: "grimy cadantine" color: "65" description: "A grimy cadantine leaf. It needs to be cleaned before it can be used." value: 55 stackable: false ``` #### `data/items/grimy_lantadyme.yaml` ```yaml id: grimy_lantadyme name: "grimy lantadyme" color: "65" description: "A grimy lantadyme leaf. It needs to be cleaned before it can be used." value: 60 stackable: false ``` #### `data/items/grimy_dwarf_weed.yaml` ```yaml id: grimy_dwarf_weed name: "grimy dwarf weed" color: "65" description: "A grimy dwarf weed leaf. It needs to be cleaned before it can be used." value: 65 stackable: false ``` #### `data/items/grimy_torstol.yaml` ```yaml id: grimy_torstol name: "grimy torstol" color: "65" description: "A grimy torstol leaf. It needs to be cleaned before it can be used." value: 75 stackable: false ``` ### Clean Herbs Each clean herb follows this template: ```yaml id: name: "" color: "48" description: "A clean leaf, ready for use in pharmacy." value: stackable: false ``` #### `data/items/guam.yaml` ```yaml id: guam name: "guam" color: "48" description: "A clean guam leaf, ready for use in pharmacy." value: 3 stackable: false ``` #### `data/items/marrentill.yaml` ```yaml id: marrentill name: "marrentill" color: "48" description: "A clean marrentill leaf, ready for use in pharmacy." value: 6 stackable: false ``` #### `data/items/tarromin.yaml` ```yaml id: tarromin name: "tarromin" color: "48" description: "A clean tarromin leaf, ready for use in pharmacy." value: 10 stackable: false ``` #### `data/items/harralander.yaml` ```yaml id: harralander name: "harralander" color: "48" description: "A clean harralander leaf, ready for use in pharmacy." value: 16 stackable: false ``` #### `data/items/ranarr.yaml` ```yaml id: ranarr name: "ranarr" color: "48" description: "A clean ranarr leaf, ready for use in pharmacy." value: 35 stackable: false ``` #### `data/items/toadflax.yaml` ```yaml id: toadflax name: "toadflax" color: "48" description: "A clean toadflax leaf, ready for use in pharmacy." value: 25 stackable: false ``` #### `data/items/irit.yaml` ```yaml id: irit name: "irit" color: "48" description: "A clean irit leaf, ready for use in pharmacy." value: 40 stackable: false ``` #### `data/items/avantoe.yaml` ```yaml id: avantoe name: "avantoe" color: "48" description: "A clean avantoe leaf, ready for use in pharmacy." value: 48 stackable: false ``` #### `data/items/kwuarm.yaml` ```yaml id: kwuarm name: "kwuarm" color: "48" description: "A clean kwuarm leaf, ready for use in pharmacy." value: 54 stackable: false ``` #### `data/items/snapdragon.yaml` ```yaml id: snapdragon name: "snapdragon" color: "48" description: "A clean snapdragon leaf, ready for use in pharmacy." value: 65 stackable: false ``` #### `data/items/cadantine.yaml` ```yaml id: cadantine name: "cadantine" color: "48" description: "A clean cadantine leaf, ready for use in pharmacy." value: 70 stackable: false ``` #### `data/items/lantadyme.yaml` ```yaml id: lantadyme name: "lantadyme" color: "48" description: "A clean lantadyme leaf, ready for use in pharmacy." value: 78 stackable: false ``` #### `data/items/dwarf_weed.yaml` ```yaml id: dwarf_weed name: "dwarf weed" color: "48" description: "A clean dwarf weed leaf, ready for use in pharmacy." value: 85 stackable: false ``` #### `data/items/torstol.yaml` ```yaml id: torstol name: "torstol" color: "48" description: "A clean torstol leaf, ready for use in pharmacy." value: 100 stackable: false ``` --- ## 7. Reagents -- Full Item YAML Secondary ingredients used to finish potions. Some are sci-fi renamed. #### `data/items/eye_of_newt.yaml` ```yaml id: eye_of_newt name: "eye of newt" color: "215" description: "A preserved eye of newt, suspended in synthetic fluid. Used in pharmacy." value: 3 stackable: false ``` #### `data/items/limpwurt_root.yaml` ```yaml id: limpwurt_root name: "limpwurt root" color: "130" description: "A thick, gnarled root with natural stimulant properties." value: 8 stackable: false ``` #### `data/items/arachnid_enzyme.yaml` ```yaml id: arachnid_enzyme name: "arachnid enzyme" color: "196" description: "A viscous red enzyme harvested from spider egg sacs. Potent catalytic agent." value: 12 stackable: false ``` #### `data/items/antitoxin_powder.yaml` ```yaml id: antitoxin_powder name: "antitoxin powder" color: "255" description: "A fine white powder derived from synthetic horn material. Neutralizes biological toxins." value: 15 stackable: false ``` #### `data/items/white_berries.yaml` ```yaml id: white_berries name: "white berries" color: "255" description: "Pale berries from a hardy bush. They have defensive biochemical properties." value: 10 stackable: false ``` #### `data/items/snape_grass.yaml` ```yaml id: snape_grass name: "snape grass" color: "120" description: "A long blade of snape grass. It crackles with faint static charge." value: 10 stackable: false ``` #### `data/items/chocolate_dust.yaml` ```yaml id: chocolate_dust name: "chocolate dust" color: "94" description: "Finely ground chocolate. A surprisingly effective pharmaceutical binding agent." value: 5 stackable: false ``` #### `data/items/blight_spore.yaml` ```yaml id: blight_spore name: "blight spore" color: "58" description: "A desiccated fungal spore from the deadlands. Pulsing with residual bio-energy." value: 20 stackable: false ``` #### `data/items/potato_cactus.yaml` ```yaml id: potato_cactus name: "potato cactus" color: "106" description: "A bulbous cactus with medicinal properties. Thrives in the arid zones." value: 18 stackable: false ``` #### `data/items/catalyst_wine.yaml` ```yaml id: catalyst_wine name: "catalyst wine" color: "124" description: "A volatile crimson wine infused with unstable compounds. Handle with care." value: 30 stackable: false ``` #### `data/items/crushed_nest.yaml` ```yaml id: crushed_nest name: "crushed nest" color: "94" description: "Finely crushed bird's nest material. Contains trace minerals used in advanced pharmacy." value: 25 stackable: false ``` #### `data/items/vial_of_water.yaml` ```yaml id: vial_of_water name: "vial of water" color: "39" description: "A small glass vial filled with purified water. The base for all potions." value: 2 stackable: false ``` #### `data/items/vial.yaml` ```yaml id: vial name: "vial" color: "253" description: "An empty glass vial." value: 1 stackable: false ``` --- ## 8. Potions -- Full Item YAML ### Unfinished Potions Each unfinished potion is created by using a clean herb on a vial of water. They use `made_from` for item-on-item combining via the `use` command. No level requirement. No XP. Instant (ticks: 0 means instant combine in the `use` system). Template: ```yaml id: _unf name: "unfinished " color: "" description: "An unfinished potion with floating in water. Needs a secondary ingredient." value: stackable: false made_from: - items: [] qty: 1 - items: [vial_of_water] qty: 1 ticks: 0 ``` #### `data/items/stim_potion_unf.yaml` ```yaml id: stim_potion_unf name: "unfinished stim potion" 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 ``` #### `data/items/bio_serum_unf.yaml` ```yaml id: bio_serum_unf name: "unfinished bio serum" 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 ``` #### `data/items/amp_potion_unf.yaml` ```yaml id: amp_potion_unf name: "unfinished amp potion" 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 ``` #### `data/items/nano_restore_unf.yaml` ```yaml id: nano_restore_unf name: "unfinished nano restore" 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 ``` #### `data/items/stim_cell_unf.yaml` ```yaml id: stim_cell_unf name: "unfinished stim cell" 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 ``` **Note:** Nano Restore and Stim Cell share the same herb (harralander). The unfinished potions for harralander-based recipes are the same item. Simplify: use a single `harralander_unf` item instead, and both finished potions use it as input. **Revised approach for shared herbs:** Use a single unfinished potion per herb (named by herb, not by final potion): #### `data/items/guam_potion_unf.yaml` ```yaml 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 ``` #### `data/items/marrentill_potion_unf.yaml` ```yaml 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 ``` #### `data/items/tarromin_potion_unf.yaml` ```yaml 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 ``` #### `data/items/harralander_potion_unf.yaml` ```yaml 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 ``` #### `data/items/ranarr_potion_unf.yaml` ```yaml 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 ``` #### `data/items/toadflax_potion_unf.yaml` ```yaml 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 ``` #### `data/items/irit_potion_unf.yaml` ```yaml 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 ``` #### `data/items/avantoe_potion_unf.yaml` ```yaml 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 ``` #### `data/items/kwuarm_potion_unf.yaml` ```yaml 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 ``` #### `data/items/snapdragon_potion_unf.yaml` ```yaml 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 ``` #### `data/items/cadantine_potion_unf.yaml` ```yaml 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 ``` #### `data/items/lantadyme_potion_unf.yaml` ```yaml 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 ``` #### `data/items/dwarf_weed_potion_unf.yaml` ```yaml 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 ``` #### `data/items/torstol_potion_unf.yaml` ```yaml 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 ``` ### Finished Potions All potions are non-stackable. Effects are NOT implemented yet -- just items with descriptions. | Potion (sci-fi name) | RS equivalent | Herb | Reagent | Level | XP | |---|---|---|---|---|---| | Stim Potion | Attack potion | guam | eye_of_newt | 3 | 25 | | Bio Serum | Antipoison | marrentill | antitoxin_powder | 5 | 38 | | Amp Potion | Strength potion | tarromin | limpwurt_root | 12 | 50 | | Nano Restore | Stat restore | harralander | arachnid_enzyme | 22 | 63 | | Stim Cell | Energy potion | harralander | chocolate_dust | 26 | 68 | | Shield Potion | Defence potion | ranarr | white_berries | 30 | 75 | | Tech Serum | Prayer potion | ranarr | snape_grass | 38 | 88 | | Super Stim | Super attack | irit | eye_of_newt | 45 | 100 | | Super Bio Serum | Super antipoison | irit | antitoxin_powder | 48 | 106 | | Super Stim Cell | Super energy | avantoe | blight_spore | 52 | 118 | | Super Amp | Super strength | kwuarm | limpwurt_root | 55 | 125 | | Full Restore | Super restore | snapdragon | arachnid_enzyme | 63 | 143 | | Super Shield | Super defence | cadantine | white_berries | 66 | 150 | | Targeting Serum | Ranging potion | dwarf_weed | catalyst_wine | 72 | 163 | | Science Serum | Magic potion | lantadyme | potato_cactus | 76 | 173 | | Restoration Compound | Saradomin brew | toadflax | crushed_nest | 81 | 180 | #### `data/items/stim_potion.yaml` ```yaml id: stim_potion name: "stim potion" color: "196" description: "A bubbling red potion that temporarily boosts attack capability. [Effect not yet implemented]" value: 30 stackable: false ``` #### `data/items/bio_serum.yaml` ```yaml id: bio_serum name: "bio serum" color: "48" description: "A clear green serum that neutralizes biological toxins. [Effect not yet implemented]" value: 45 stackable: false ``` #### `data/items/amp_potion.yaml` ```yaml id: amp_potion name: "amp potion" color: "255" description: "A milky white potion that temporarily amplifies physical strength. [Effect not yet implemented]" value: 55 stackable: false ``` #### `data/items/nano_restore.yaml` ```yaml id: nano_restore name: "nano restore" color: "208" description: "An orange potion containing nanobots that restore diminished attributes. [Effect not yet implemented]" value: 70 stackable: false ``` #### `data/items/stim_cell.yaml` ```yaml id: stim_cell name: "stim cell" color: "226" description: "A bright yellow energy supplement that restores stamina. [Effect not yet implemented]" value: 65 stackable: false ``` #### `data/items/shield_potion.yaml` ```yaml id: shield_potion name: "shield potion" color: "39" description: "A luminous blue potion that temporarily reinforces defensive capability. [Effect not yet implemented]" value: 85 stackable: false ``` #### `data/items/tech_serum.yaml` ```yaml id: tech_serum name: "tech serum" color: "51" description: "A cyan serum that temporarily enhances technology interface capability. [Effect not yet implemented]" value: 100 stackable: false ``` #### `data/items/super_stim.yaml` ```yaml id: super_stim name: "super stim" color: "160" description: "A potent dark-red stimulant. Dramatically boosts attack capability. [Effect not yet implemented]" value: 120 stackable: false ``` #### `data/items/super_bio_serum.yaml` ```yaml id: super_bio_serum name: "super bio serum" color: "34" description: "A concentrated antitoxin. Provides extended poison immunity. [Effect not yet implemented]" value: 130 stackable: false ``` #### `data/items/super_stim_cell.yaml` ```yaml id: super_stim_cell name: "super stim cell" color: "220" description: "An advanced energy compound that fully restores stamina. [Effect not yet implemented]" value: 140 stackable: false ``` #### `data/items/super_amp.yaml` ```yaml id: super_amp name: "super amp" color: "231" description: "A dangerously concentrated strength amplifier. Handle carefully. [Effect not yet implemented]" value: 155 stackable: false ``` #### `data/items/full_restore.yaml` ```yaml id: full_restore name: "full restore" color: "207" description: "A shimmering pink potion. Restores all diminished stats simultaneously. [Effect not yet implemented]" value: 200 stackable: false ``` #### `data/items/super_shield.yaml` ```yaml id: super_shield name: "super shield" color: "27" description: "A deep indigo potion that provides significant defensive enhancement. [Effect not yet implemented]" value: 175 stackable: false ``` #### `data/items/targeting_serum.yaml` ```yaml id: targeting_serum name: "targeting serum" color: "70" description: "A dark green serum that sharpens ranged targeting systems. [Effect not yet implemented]" value: 190 stackable: false ``` #### `data/items/science_serum.yaml` ```yaml id: science_serum name: "science serum" color: "93" description: "A violet serum that heightens scientific cognition and casting ability. [Effect not yet implemented]" value: 200 stackable: false ``` #### `data/items/restoration_compound.yaml` ```yaml id: restoration_compound name: "restoration compound" color: "214" description: "A golden compound that restores hitpoints and boosts defense, but drains other stats. [Effect not yet implemented]" value: 250 stackable: false ``` --- ## 9. Recipes -- Full YAML ### Clean Recipes (type: clean) These are used by the `clean` background action. Each has `type: clean` and `skill: pharmacy`. #### `data/recipes/clean_guam.yaml` ```yaml 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." ``` #### `data/recipes/clean_marrentill.yaml` ```yaml 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." ``` #### `data/recipes/clean_tarromin.yaml` ```yaml 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." ``` #### `data/recipes/clean_harralander.yaml` ```yaml 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." ``` #### `data/recipes/clean_ranarr.yaml` ```yaml 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." ``` #### `data/recipes/clean_toadflax.yaml` ```yaml 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." ``` #### `data/recipes/clean_irit.yaml` ```yaml 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." ``` #### `data/recipes/clean_avantoe.yaml` ```yaml 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." ``` #### `data/recipes/clean_kwuarm.yaml` ```yaml 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." ``` #### `data/recipes/clean_snapdragon.yaml` ```yaml 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." ``` #### `data/recipes/clean_cadantine.yaml` ```yaml 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." ``` #### `data/recipes/clean_lantadyme.yaml` ```yaml 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." ``` #### `data/recipes/clean_dwarf_weed.yaml` ```yaml 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." ``` #### `data/recipes/clean_torstol.yaml` ```yaml 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." ``` ### Mix Recipes (type: pharmacy) These are used by the `mix` command via the standard production system. No station required (station field omitted or empty). #### `data/recipes/mix_stim_potion.yaml` ```yaml 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." ``` #### `data/recipes/mix_bio_serum.yaml` ```yaml 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." ``` #### `data/recipes/mix_amp_potion.yaml` ```yaml 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." ``` #### `data/recipes/mix_nano_restore.yaml` ```yaml 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." ``` #### `data/recipes/mix_stim_cell.yaml` ```yaml 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." ``` #### `data/recipes/mix_shield_potion.yaml` ```yaml 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." ``` #### `data/recipes/mix_tech_serum.yaml` ```yaml 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." ``` #### `data/recipes/mix_super_stim.yaml` ```yaml 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." ``` #### `data/recipes/mix_super_bio_serum.yaml` ```yaml 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." ``` #### `data/recipes/mix_super_stim_cell.yaml` ```yaml 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." ``` #### `data/recipes/mix_super_amp.yaml` ```yaml 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." ``` #### `data/recipes/mix_full_restore.yaml` ```yaml 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." ``` #### `data/recipes/mix_super_shield.yaml` ```yaml 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." ``` #### `data/recipes/mix_targeting_serum.yaml` ```yaml 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." ``` #### `data/recipes/mix_science_serum.yaml` ```yaml 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." ``` #### `data/recipes/mix_restoration_compound.yaml` ```yaml 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." ``` --- ## 10. Clean Mechanic ### Overview `clean` is a **Free** command that starts a **background action** (like fletching). It auto-scans the player's inventory for grimy herbs and cleans them one at a time. ### Flow 1. Player types `clean` (or `clean `) 2. `doClean()` in `cmd_clean.go` is called 3. It loads all recipes with `type: "clean"` from the recipe store 4. If a filter was provided (e.g., `clean guam`), filter recipes to those whose consume item matches `grimy_` 5. Find the first recipe the player has materials AND level for 6. If none found, output "You don't have any herbs to clean." and return 7. Cancel any existing background action 8. Start a background action with type `"clean"` and initial `wait: 1` (phase 0) 9. Set `BackgroundActionState` to `&ActionState{Type: ActionCleaning}` 10. Output "You begin cleaning herbs." ### Advance logic (`advanceClean`) Called every tick when `p.BackgroundAction.Type == "clean"`: 1. **Phase 0** (first tick): Set phase to 1, set WaitLeft to `engine.ToTicks(2)` (2 ticks). Return. 2. **Phase 1** (production tick): - Load all `type: "clean"` recipes - If a filter was stored in `Data["filter"]`, restrict to matching recipes - Sort recipes by level ascending - Find the first recipe the player has items AND level for - If none found: output "You've run out of herbs to clean.", cancel background action, return - Consume the grimy herb, place the clean herb in the same inventory slot - Award XP via `p.AddSkillXP(player.Pharmacy, recipe.XP)` - Check for level-up, output message - Output the recipe's message (e.g., "You clean the grimy guam leaf.") with XP drop - Save character - Check if there are more herbs to clean (scan again) - If yes: set `WaitLeft = engine.ToTicks(2)`, continue - If no: output "You've finished cleaning herbs.", cancel background action ### In-slot replacement When cleaning a herb, the clean herb should replace the grimy herb in the **same inventory slot** rather than consuming from one slot and adding to a free slot. This keeps inventory tidy. Implementation: ```go for i := 0; i < 28; i++ { slot := p.InvSlot(i) if slot != nil && slot.ItemID == recipe.Consume[0].Items[0] { slot.ItemID = recipe.Output break } } ``` This avoids the consume+place pattern and preserves slot position. ### Filter behavior - `clean` with no args: cleans any grimy herb, cycling through all types - `clean guam`: only cleans `grimy_guam`. Stored as `Data["filter"] = "guam"`. Recipe matching checks if the consume item ID contains the filter string. ### Interaction with other actions - `clean` does NOT cancel the player's active action (gathering, combat, movement) - `clean` DOES cancel any existing background action (fletching) - Starting a new active action does NOT cancel cleaning (background actions are independent) - `get`, `drop`, `quit` cancel the active action but NOT background actions (existing behavior) --- ## 11. Mix Mechanic ### Overview `mix` is an **Active** command that uses the standard production system. No station required. Works anywhere. ### Flow 1. Player types `mix` (or `mix ` or `mix 10 `) 2. `doMix()` in `cmd_mix.go` is called 3. It loads all recipes with `type: "pharmacy"` from the recipe store 4. If no args: show production table of all pharmacy recipes (like cook does) 5. If args provided: match by name, start production if unambiguous ### Implementation (`cmd_mix.go`) Follow the `cmd_cook.go` pattern closely: ```go package game import ( "fmt" "strings" "thehouseoficarus/internal/action" "thehouseoficarus/internal/net" "thehouseoficarus/internal/player" ) func (g *Game) doMix(sess *net.Session, input string) { p := sess.Player.(*player.Player) g.CancelAction(p) allRecipes, err := g.RecipeStore.LoadAll() if err != nil { sess.WriteLine("Error loading recipes.") return } if input == "" { g.showMixMenu(sess, p, allRecipes) return } qty, itemName := parseQty(input) _ = qty // Find matching pharmacy recipes by output name var matched []action.RecipeDef for _, r := range allRecipes { if r.Type != "pharmacy" { continue } outDef, _ := g.ItemStore.Load(r.Output) name := r.Output if outDef != nil { name = outDef.Name } if world.WordPrefixMatch(itemName, name) { matched = append(matched, r) } } if len(matched) == 0 { sess.WriteLine("You can't mix that.") return } // Filter to available var available []action.RecipeDef for _, r := range matched { if r.HasAllItems(p.HasItem) && p.Level(player.Pharmacy) >= r.Level { available = append(available, r) } } if len(available) == 0 { sess.WriteLine("You don't have the materials for that.") return } if len(available) == 1 { g.promptHowMany(sess, available[0].ID) return } // Ambiguous -- show menu sess.State = net.StateRecipeChoice var names []string for _, r := range available { names = append(names, g.recipeName(sess, &r)) } g.showMenuTable(sess, "What would you like to mix?", names) sess.PendingMenu = recipeMenuData(available) } func (g *Game) showMixMenu(sess *net.Session, p *player.Player, allRecipes []action.RecipeDef) { seen := make(map[string]bool) var entries []recipeEntry for _, r := range allRecipes { if r.Type != "pharmacy" { continue } if seen[r.ID] { continue } if !r.HasAllItems(p.HasItem) { continue } seen[r.ID] = true entries = append(entries, recipeEntry{g.recipeName(sess, &r), r}) } if len(entries) == 0 { sess.WriteLine("You don't have anything you can mix.") return } if len(entries) == 1 { if p.OptionBool("mix_all") { g.startProductionFromRecipe(sess, p, &entries[0].Recipe, 0) return } g.promptHowMany(sess, entries[0].Recipe.ID) return } sess.State = net.StateRecipeChoice var names []string for _, e := range entries { names = append(names, e.ItemName) } g.showMenuTable(sess, "What would you like to mix?", names) sess.PendingMenu = entryMenuData(entries) } ``` ### `use` command integration The `use` command already handles MadeFrom combines. When a player does `use guam on vial of water`, it will find the `guam_potion_unf` item's MadeFrom definition and create the combine recipe automatically. No extra code needed. When a player does `use eye_of_newt on guam potion`, the `use` command should also check pharmacy recipes via `RecipeStore.FindByItems()`. This already works because `FindByItems` searches all recipe types. The production system will handle it. --- ## 12. Action Lifecycle ### Clean Action Lifecycle **Files:** `cmd_clean.go`, `action_clean.go` #### `cmd_clean.go` -- `doClean(sess, input)` 1. Get player 2. Check not in combat (if so, "You can't do that during combat!") 3. Load all recipes with `type == "clean"` 4. If `input` provided, filter recipes to those whose consume items match the filter 5. Find any recipe the player can do (has items + has level) 6. If none: "You don't have any herbs to clean." return 7. Cancel existing background action 8. Set `p.BackgroundAction`: ```go p.BackgroundAction = &action.Action{ Type: "clean", TargetID: "clean_herbs", Data: map[string]any{ "phase": 0, "filter": input, // "" for all herbs }, WaitLeft: engine.ToTicks(1), } p.BackgroundActionState = &ActionState{Type: ActionCleaning} ``` 9. Output: `"\nYou begin cleaning herbs."` #### `action_clean.go` -- `advanceClean(sess, p)` ```go func (g *Game) advanceClean(sess *net.Session, p *player.Player) { phase, _ := p.BackgroundAction.Data["phase"].(int) filter, _ := p.BackgroundAction.Data["filter"].(string) if phase == 0 { p.BackgroundAction.Data["phase"] = 1 p.BackgroundAction.WaitLeft = engine.ToTicks(2) return } allRecipes, err := g.RecipeStore.LoadAll() if err != nil { g.CancelBackgroundAction(p) return } // Find first cleanable herb var recipe *action.RecipeDef for _, r := range allRecipes { if r.Type != "clean" { continue } if filter != "" { // Check if this recipe's output or consume matches the filter if !strings.Contains(r.Consume[0].Items[0], filter) && !strings.Contains(r.Output, filter) { continue } } if p.Level(player.Pharmacy) < r.Level { continue } if !r.HasAllItems(p.HasItem) { continue } recipe = &r break } if recipe == nil { sess.WriteLine("\nYou've finished cleaning herbs.") g.CancelBackgroundAction(p) return } // In-slot replacement consumeID := recipe.Consume[0].Items[0] for i := 0; i < 28; i++ { slot := p.InvSlot(i) if slot != nil && slot.ItemID == consumeID { slot.ItemID = recipe.Output break } } // Award XP if recipe.XP > 0 { if newLevel := p.AddSkillXP(player.Pharmacy, recipe.XP); newLevel > 0 { sess.WriteLine(g.colorize(sess, "level_up", fmt.Sprintf("*** You are now level %d pharmacy! ***", newLevel))) } } g.AccountStore.SaveCharacter(p) // Output message msg := recipe.Message if msg == "" { outDef, _ := g.ItemStore.Load(recipe.Output) outputName := recipe.Output if outDef != nil { outputName = outDef.Name } msg = fmt.Sprintf("You clean a %s.", outputName) } if p.OptionBool("xp_drops") && recipe.XP > 0 { abbr := player.SkillAbbr[player.Pharmacy] msg += g.colorize(sess, "xp", fmt.Sprintf(" (+%dxp %s)", recipe.XP, abbr)) } sess.WriteLine(msg) // Check for more herbs hasMore := false for _, r := range allRecipes { if r.Type != "clean" { continue } if filter != "" && !strings.Contains(r.Consume[0].Items[0], filter) && !strings.Contains(r.Output, filter) { continue } if p.Level(player.Pharmacy) >= r.Level && r.HasAllItems(p.HasItem) { hasMore = true break } } if !hasMore { sess.WriteLine("\nYou've finished cleaning herbs.") g.CancelBackgroundAction(p) return } p.BackgroundAction.WaitLeft = engine.ToTicks(2) } ``` ### Mix Action Lifecycle Mix uses the standard production system entirely. No custom advance function needed. The `advanceProduction()` function handles it because `"mix"` is registered in `productionActionTypes` via `productionTypes`. Flow: 1. `doMix()` -> user selects recipe -> `promptHowMany()` -> `handleHowMany()` -> `startProductionFromRecipe()` 2. `startProductionFromRecipe()` creates `p.Action` with type `"mix"`, actionType from `productionTypes["pharmacy"]` 3. Each tick, `AdvanceActions()` calls `advanceProduction()` for `"mix"` type 4. Production cycle: start message -> wait -> skill check -> consume + output -> XP -> repeat or end --- ## 13. XP Table ### Herb Cleaning XP | Herb | Pharmacy Level | Clean XP | |---|---|---| | Guam | 3 | 3 | | Marrentill | 5 | 4 | | Tarromin | 11 | 5 | | Harralander | 20 | 6 | | Ranarr | 25 | 8 | | Toadflax | 30 | 8 | | Irit | 40 | 9 | | Avantoe | 48 | 10 | | Kwuarm | 54 | 11 | | Snapdragon | 59 | 12 | | Cadantine | 65 | 13 | | Lantadyme | 67 | 13 | | Dwarf Weed | 70 | 14 | | Torstol | 75 | 15 | ### Potion Mixing XP | Potion | Pharmacy Level | Mix XP | Herb | Reagent | |---|---|---|---|---| | Stim Potion | 3 | 25 | Guam | Eye of Newt | | Bio Serum | 5 | 38 | Marrentill | Antitoxin Powder | | Amp Potion | 12 | 50 | Tarromin | Limpwurt Root | | Nano Restore | 22 | 63 | Harralander | Arachnid Enzyme | | Stim Cell | 26 | 68 | Harralander | Chocolate Dust | | Shield Potion | 30 | 75 | Ranarr | White Berries | | Tech Serum | 38 | 88 | Ranarr | Snape Grass | | Super Stim | 45 | 100 | Irit | Eye of Newt | | Super Bio Serum | 48 | 106 | Irit | Antitoxin Powder | | Super Stim Cell | 52 | 118 | Avantoe | Blight Spore | | Super Amp | 55 | 125 | Kwuarm | Limpwurt Root | | Full Restore | 63 | 143 | Snapdragon | Arachnid Enzyme | | Super Shield | 66 | 150 | Cadantine | White Berries | | Targeting Serum | 72 | 163 | Dwarf Weed | Catalyst Wine | | Science Serum | 76 | 173 | Lantadyme | Potato Cactus | | Restoration Compound | 81 | 180 | Toadflax | Crushed Nest | ### Unfinished Potions Adding a clean herb to a vial of water gives **0 XP** and has **no level requirement**. This is an instant combine via MadeFrom, not a recipe. ### Total XP per potion (clean + mix) Example: Stim Potion = 3 (clean guam) + 25 (mix) = 28 total pharmacy XP per potion. --- ## 14. Mob Drops Grimy herbs should be added to existing mob drop tables. Higher-level mobs drop rarer herbs. ### Existing mobs to modify #### `data/mobs/man.yaml` -- Add to loot table: ```yaml - item_id: "grimy_guam" weight: 8 - item_id: "grimy_marrentill" weight: 4 - item_id: "grimy_tarromin" weight: 2 ``` #### `data/mobs/guard.yaml` -- Add to loot table: ```yaml - item_id: "grimy_guam" weight: 6 - item_id: "grimy_marrentill" weight: 5 - item_id: "grimy_tarromin" weight: 4 - item_id: "grimy_harralander" weight: 3 - item_id: "grimy_ranarr" weight: 1 ``` ### Suggested new mobs (future) Higher-tier mobs (when added) should drop higher-tier herbs. Guidelines: | Mob Combat Level | Herb Drops | |---|---| | 1-10 | Guam, Marrentill | | 10-25 | Tarromin, Harralander | | 25-50 | Ranarr, Toadflax, Irit | | 50-75 | Avantoe, Kwuarm, Snapdragon | | 75-100 | Cadantine, Lantadyme | | 100+ | Dwarf Weed, Torstol | ### Shared drop table (optional) Create `data/drops/herb_table_low.yaml` etc. for reusable herb drop tables: #### `data/drops/herb_table_low.yaml` ```yaml id: herb_table_low entries: - item_id: grimy_guam weight: 10 - item_id: grimy_marrentill weight: 7 - item_id: grimy_tarromin weight: 4 - item_id: grimy_harralander weight: 2 ``` #### `data/drops/herb_table_mid.yaml` ```yaml id: herb_table_mid entries: - item_id: grimy_harralander weight: 8 - item_id: grimy_ranarr weight: 6 - item_id: grimy_toadflax weight: 5 - item_id: grimy_irit weight: 4 - item_id: grimy_avantoe weight: 2 ``` #### `data/drops/herb_table_high.yaml` ```yaml id: herb_table_high entries: - item_id: grimy_kwuarm weight: 7 - item_id: grimy_snapdragon weight: 5 - item_id: grimy_cadantine weight: 4 - item_id: grimy_lantadyme weight: 3 - item_id: grimy_dwarf_weed weight: 2 - item_id: grimy_torstol weight: 1 ``` Mobs can reference these via `table: herb_table_low` in their loot entries (existing drop table system). ### Reagent sources Reagents are obtained from: - **Shops** (future shop system): Eye of Newt, Vial of Water, Vial, Chocolate Dust - **Mob drops**: Limpwurt Root, Arachnid Enzyme, White Berries, Snape Grass, Antitoxin Powder - **Gathering** (future): Blight Spore, Potato Cactus - **Other skills**: Crushed Nest (from search on bird's nest), Catalyst Wine (future quest/gathering) For initial implementation, consider adding ground spawns or shop items for basic reagents (eye_of_newt, vial_of_water) so players can train pharmacy from level 1. --- ## 15. Help Files #### `data/help/clean.yaml` ```yaml name: "clean" category: "Commands" description: | Clean grimy herbs from your inventory. Usage: clean Clean all grimy herbs clean Clean only a specific herb type Cleaning is a background action that runs alongside other activities. You can clean herbs while walking, gathering, or even during combat. Each herb type requires a minimum Pharmacy level to clean. You earn Pharmacy XP for each herb cleaned. The command processes one herb every 2 ticks and automatically moves on to the next grimy herb in your inventory. It stops when you have no more grimy herbs (or no more matching the filter). Grimy herbs are obtained as drops from mobs or through farming. Clean herbs are used to create unfinished potions by combining them with a vial of water. See also: mix, pharmacy, use ``` #### `data/help/mix.yaml` ```yaml name: "mix" category: "Commands" description: | Mix potions from unfinished potions and secondary reagents. Usage: mix Show all potions you can mix right now mix Mix a specific potion mix 10 Mix a specific quantity Mixing does not require a station and can be done anywhere. Before mixing starts, you are prompted "How many?" -- press return for all, type a number, or type anything else to cancel. The Pharmacy skill check determines success or failure. Higher levels yield higher success rates. You can also mix by using items directly: use eye of newt on guam potion To create unfinished potions, use a clean herb on a vial of water: use guam on vial of water See also: clean, pharmacy, use ``` #### `data/help/pharmacy.yaml` ```yaml name: "pharmacy" category: "Skills" description: | Pharmacy is the skill of creating potions from herbs and reagents. It replaces the old Alchemy discipline. The production chain: 1. Obtain grimy herbs (mob drops, farming) 2. Clean herbs with the "clean" command (Pharmacy XP) 3. Use clean herbs on vials of water to create unfinished potions (no XP) 4. Mix unfinished potions with reagents using the "mix" command (Pharmacy XP) Potions provide temporary stat boosts, healing, and other effects when consumed. Higher-level potions require rarer herbs and reagents. Herbs (ordered by level): Guam (3), Marrentill (5), Tarromin (11), Harralander (20), Ranarr (25), Toadflax (30), Irit (40), Avantoe (48), Kwuarm (54), Snapdragon (59), Cadantine (65), Lantadyme (67), Dwarf Weed (70), Torstol (75) See also: clean, mix, score ``` --- ## Implementation Order Recommended sequence for implementation: 1. **Skill rename** -- Change `Alchemy` to `Pharmacy` in `player.go` (3 lines). Add migration code. Update AGENTS.md. Run `make vet` and `make test`. 2. **Add ActionType constants** -- Add `ActionCleaning` and `ActionMixing` to `action_state.go` with `Description()` cases. 3. **Add productionTypes entry** -- Add `"pharmacy"` to `productionTypes` map in `action_production.go`. 4. **Add option** -- Add `mix_all` to `OptionDefs` in `player.go`. 5. **Create all item YAML files** -- All herbs (grimy + clean), reagents, vials, unfinished potions, finished potions. Total ~57 items. 6. **Create all recipe YAML files** -- 14 clean recipes + 16 mix recipes. Total 30 recipes. 7. **Create `cmd_mix.go`** -- Follow `cmd_cook.go` pattern. Register in `classifyCommand()` and `executeCommand()`. 8. **Create `cmd_clean.go`** -- The doClean handler. Register in `classifyCommand()` and `executeCommand()`. 9. **Create `action_clean.go`** -- The `advanceClean()` background action. Register in `AdvanceActions()` switch. 10. **Update mob drop tables** -- Add grimy herbs to existing mobs. Create shared herb drop tables. 11. **Create help YAML files** -- `clean.yaml`, `mix.yaml`, `pharmacy.yaml`. 12. **Test** -- Run `make build`, `make vet`, `make test`. Connect via telnet and verify: - `score` shows Pharmacy (not Alchemy) - Grimy herbs can be obtained (add to inventory manually or kill mobs) - `clean` works as background action - `use guam on vial of water` creates unfinished potion - `mix` shows available recipes and produces potions - XP is awarded correctly - Level-up messages display correctly --- ## File Checklist ### New Go files (3) - [ ] `internal/game/cmd_clean.go` - [ ] `internal/game/cmd_mix.go` - [ ] `internal/game/action_clean.go` ### Modified Go files (5) - [ ] `internal/player/player.go` (rename Alchemy->Pharmacy, add option) - [ ] `internal/game/game.go` (classifyCommand, executeCommand) - [ ] `internal/game/action_state.go` (ActionCleaning, ActionMixing) - [ ] `internal/game/action.go` (AdvanceActions background switch) - [ ] `internal/game/action_production.go` (productionTypes map) ### New YAML item files (57) - [ ] 14 grimy herbs: `grimy_guam.yaml` through `grimy_torstol.yaml` - [ ] 14 clean herbs: `guam.yaml` through `torstol.yaml` - [ ] 14 unfinished potions: `guam_potion_unf.yaml` through `torstol_potion_unf.yaml` - [ ] 16 finished potions (see Section 8) - [ ] 11 reagents (see Section 7) - [ ] 2 vials: `vial.yaml`, `vial_of_water.yaml` ### New YAML recipe files (30) - [ ] 14 clean recipes: `clean_guam.yaml` through `clean_torstol.yaml` - [ ] 16 mix recipes: `mix_stim_potion.yaml` through `mix_restoration_compound.yaml` ### New YAML help files (3) - [ ] `data/help/clean.yaml` - [ ] `data/help/mix.yaml` - [ ] `data/help/pharmacy.yaml` ### New YAML drop tables (3, optional) - [ ] `data/drops/herb_table_low.yaml` - [ ] `data/drops/herb_table_mid.yaml` - [ ] `data/drops/herb_table_high.yaml` ### Modified YAML files (2+) - [ ] `data/mobs/man.yaml` (add herb drops) - [ ] `data/mobs/guard.yaml` (add herb drops) ### Documentation - [ ] `AGENTS.md` (update skill name, add commands, add action types)