diff options
| author | historia <[not public]> | 2026-06-19 18:33:43 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-06-19 18:33:43 -0400 |
| commit | 2bec24859af8f03c80a0a58e3240519942450167 (patch) | |
| tree | dcb8baa5591fdeca51e179a63a08d46b014cb85c /skill_plans/pharmacy.md | |
| parent | 0ea4eec5dd2122c6704216971eb1249276297867 (diff) | |
| download | thehouseoficarus-2bec24859af8f03c80a0a58e3240519942450167.tar.gz | |
readme
Diffstat (limited to 'skill_plans/pharmacy.md')
| -rw-r--r-- | skill_plans/pharmacy.md | 2421 |
1 files changed, 0 insertions, 2421 deletions
diff --git a/skill_plans/pharmacy.md b/skill_plans/pharmacy.md deleted file mode 100644 index 5e0b7af..0000000 --- a/skill_plans/pharmacy.md +++ /dev/null @@ -1,2421 +0,0 @@ -# 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: <xp>` 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 <herb>` -- Clean only grimy herbs matching `<herb>` (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 <potion>` -- Mix a specific potion -- `mix 10 <potion>` -- 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/<herb>.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/<potion>.yaml` | 16 | Finished potions | -| `data/items/<reagent>.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> -name: "grimy <display_name>" -color: "65" -description: "A grimy <display_name> leaf. It needs to be cleaned before it can be used." -value: <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> -name: "<display_name>" -color: "48" -description: "A clean <display_name> leaf, ready for use in pharmacy." -value: <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: <name>_unf -name: "unfinished <display_name>" -color: "<color>" -description: "An unfinished potion with <herb> floating in water. Needs a secondary ingredient." -value: <value> -stackable: false -made_from: - - items: [<herb_id>] - 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 <herb_name>`) -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_<filter>` -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 <potion>` or `mix 10 <potion>`) -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 <herb> 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 <potion> Mix a specific potion - mix 10 <potion> 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) |
