Herblore rework: tiered potion ladders per skill

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-19 21:28:00 +01:00
parent 632a12e8bc
commit f810e16eb8
4 changed files with 125 additions and 51 deletions

103
game.py
View file

@ -186,7 +186,7 @@ for _raw, _cooked, _level, _action_id, _verb in FISH:
# id, name, icon, farming level, seconds, xp, value
HERBS = [
("guam_leaf", "Guam leaf", "🍀", 5, 45, 12, 3),
("guam_leaf", "Guam leaf", "🍀", 1, 45, 12, 3),
("marrentill", "Marrentill", "🌼", 15, 50, 20, 5),
("tarromin", "Tarromin", "🌺", 25, 55, 30, 8),
("harralander", "Harralander", "🌻", 35, 60, 42, 12),
@ -194,24 +194,50 @@ HERBS = [
("snapdragon", "Snapdragon", "🌸", 60, 70, 75, 28),
]
POTION_CHARGES = 50
# id, name, herblore level, herb, secondary, skill, effect
POTIONS = [
("trackers_tincture", "Tracker's tincture", 1, "guam_leaf", "feathers", "hunting", {"speed": 0.2}),
("lumberjacks_tonic", "Lumberjack's tonic", 6, "guam_leaf", "logs", "woodcutting", {"speed": 0.2}),
("strength_potion", "Strength potion", 10, "guam_leaf", "bones", "combat", {"power": 6}),
("anglers_brew", "Angler's brew", 15, "marrentill", "raw_shrimp", "fishing", {"speed": 0.2}),
("prospectors_draught", "Prospector's draught", 18, "marrentill", "copper_ore", "mining", {"double": 0.15}),
("growth_potion", "Growth potion", 20, "marrentill", "wheat", "farming", {"speed": 0.2}),
("defence_potion", "Defence potion", 25, "tarromin", "iron_ore", "combat", {"reduce": 0.15}),
("chefs_spice", "Chef's spice", 28, "tarromin", "vegetables", "cooking", {"keep": 0.15}),
("bonedust_tonic", "Bonedust tonic", 30, "tarromin", "big_bones", "prayer", {"bones": 0.5}),
("smiths_focus", "Smith's focus", 35, "harralander", "coal", "smithing", {"keep": 0.15}),
("masons_patience", "Mason's patience", 38, "harralander", "cut_stone", "masonry", {"keep": 0.15}),
("tanners_oil", "Tanner's oil", 40, "harralander", "raw_meat", "leatherworking", {"keep": 0.15}),
("regeneration_potion", "Regeneration potion", 45, "ranarr", "cooked_trout", "combat", {"heal": 4}),
("super_strength", "Super strength", 50, "ranarr", "dragon_bones", "combat", {"power": 14}),
("super_defence", "Super defence", 60, "snapdragon", "blue_dragonhide", "combat", {"reduce": 0.30}),
# Potions come in tiers like everything else: tier n is brewed from the nth herb
# and the nth rung of the skill's own ladder, needs the tier's Herblore level
# (none for tier I), and is stronger the higher it goes. Ladders stop where the
# skill runs out of higher things to do.
ROMAN = ["I", "II", "III", "IV", "V", "VI"]
SPEEDS = [0.10, 0.15, 0.20, 0.25, 0.30, 0.35]
_ores = ["copper_ore", "iron_ore", "coal", "mithril_ore", "adamantite_ore", "runite_ore"]
# id, name, skill, effect, strength by tier, secondary by tier
POTION_LINES = [
("mining", "Mining potion", "mining", "speed", SPEEDS, _ores),
("woodcutting", "Woodcutting potion", "woodcutting", "speed", SPEEDS, [tree[0] for tree in TREES]),
("fishing", "Fishing potion", "fishing", "speed", SPEEDS, [fish[0][0] for fish in FISH]),
("farming", "Farming potion", "farming", "speed", SPEEDS,
["wheat", "vegetables", "guam_leaf", "marrentill", "tarromin", "harralander"]),
("hunting", "Hunting potion", "hunting", "speed", SPEEDS[:4], ["feathers", "raw_meat", "boar_hide", "bear_pelt"]),
("construction", "Construction potion", "construction", "speed", SPEEDS, [plank for plank, _name in PLANKS]),
("smithing", "Smithing potion", "smithing", "keep", SPEEDS, [f"{metal[0]}_bar" for metal in METALS]),
("cooking", "Cooking potion", "cooking", "keep", SPEEDS, [fish[1][0] for fish in FISH]),
("leatherworking", "Leatherworking potion", "leatherworking", "keep", SPEEDS,
[leather[1][0] for leather in LEATHERS]),
("masonry", "Masonry potion", "masonry", "keep", SPEEDS[:3], ["stone", "bricks", "cut_stone"]),
("prayer", "Prayer potion", "prayer", "bones", [0.25, 0.5, 0.75], ["bones", "big_bones", "dragon_bones"]),
("strength", "Strength potion", "combat", "power", [4, 7, 11, 15, 20, 26], [leather[0][0] for leather in LEATHERS]),
("defence", "Defence potion", "combat", "reduce", [0.10, 0.14, 0.18, 0.22, 0.26, 0.30],
[f"{metal[0]}_shield" for metal in METALS]),
]
# id, name, herblore level, herb, secondary, skill, effect
POTIONS = []
POTION_TIER = {}
for _line, _name, _skill, _key, _strengths, _seconds in POTION_LINES:
for _tier, (_strength, _second) in enumerate(zip(_strengths, _seconds)):
_id = f"{_line}_potion_{_tier + 1}"
POTIONS.append((_id, f"{_name} {ROMAN[_tier]}", TIER_LEVELS[_tier], HERBS[_tier][0], _second, _skill,
{_key: _strength}))
POTION_TIER[_id] = _tier
# Potions from before the tiers, and what they became.
OLD_POTIONS = {
"trackers_tincture": "hunting_potion_3", "lumberjacks_tonic": "woodcutting_potion_3",
"strength_potion": "strength_potion_2", "anglers_brew": "fishing_potion_3",
"prospectors_draught": "mining_potion_2", "growth_potion": "farming_potion_3",
"defence_potion": "defence_potion_2", "chefs_spice": "cooking_potion_2", "bonedust_tonic": "prayer_potion_2",
"smiths_focus": "smithing_potion_2", "masons_patience": "masonry_potion_2", "tanners_oil": "leatherworking_potion_2",
"regeneration_potion": "defence_potion_3", "super_strength": "strength_potion_4", "super_defence": "defence_potion_5",
}
def describe_potion(skill: str, effect: dict) -> str:
@ -226,9 +252,7 @@ def describe_potion(skill: str, effect: dict) -> str:
return f"+{round(effect['bones'] * 100)}% prayer points and devotion from bones"
if "power" in effect:
return f"+{effect['power']} combat power"
if "reduce" in effect:
return f"-{round(effect['reduce'] * 100)}% damage taken in fights"
return f"Heals {effect['heal']} HP every fight"
return f"-{round(effect['reduce'] * 100)}% damage taken in fights"
for _id, _name, _icon, _level, _time, _xp, _value in HERBS:
@ -348,7 +372,7 @@ ACTIONS = [
*[_action(f"grow_{herb}", "farming", f"Grow {name.lower()}", level, seconds, xp, outputs={herb: 1})
for herb, name, _icon, level, seconds, xp, _value in HERBS],
*[_action(f"brew_{potion}", "herblore", name, level, 30 + level // 3, round(4 + level * 1.6),
{herb: 1, second: 1}, {potion: 1}, group="Combat potions" if skill == "combat" else "Skill potions")
{herb: 1, second: 1}, {potion: 1}, group=f"Tier {ROMAN[POTION_TIER[potion]]}")
for potion, name, level, herb, second, skill, _effect in POTIONS],
_action("hunt_birds", "hunting", "Snare birds", 1, 25, 6, outputs={"feathers": 3}),
_action("hunt_deer", "hunting", "Hunt deer", 8, 40, 13, outputs={"raw_meat": 2, "hide": 1}),
@ -499,7 +523,7 @@ RAID_TRAITS = ["lightly armoured, heavy on archers", "mounted and fast, few bows
# --- world ---------------------------------------------------------------
WORLD_VERSION = 10
WORLD_VERSION = 11
def new_world(now: float, speed: float = 1.0) -> dict:
@ -606,9 +630,32 @@ def migrate(world: dict, now: float | None = None) -> dict:
for building in world["city"]["buildings"].values():
building.setdefault("fittings", [])
world["version"] = 10
if world["version"] < 11:
_migrate_potions(world)
world["version"] = 11
return world
def _migrate_potions(world: dict) -> None:
"""Potions from before the tiers become their tiered equivalents, uses and all."""
def renamed(stock: dict) -> None:
for old, new in OLD_POTIONS.items():
if old in stock:
stock[new] = stock.get(new, 0) + stock.pop(old)
renamed(world["city"]["stockpile"])
for player in world["players"].values():
renamed(player["bank"])
for active in player["potions"].values(): # regeneration was combat too, so slots still match
if active["item"] in OLD_POTIONS:
active["item"] = OLD_POTIONS[active["item"]]
action = player["action"]
if action:
for key in ("id", "resume"):
old = (action.get(key) or "").removeprefix("brew_")
if old in OLD_POTIONS:
action[key] = f"brew_{OLD_POTIONS[old]}"
def _sell_useless_stock(world: dict, now: float) -> None:
"""The council sells what the city can't use, into the treasury."""
city = world["city"]
@ -2742,11 +2789,11 @@ def _assign_glyphs() -> None:
ITEMS[f"{prefix}_{suffix}"].update(glyph=glyph, tint=tint)
for herb, tint in HERB_TINTS.items():
ITEMS[herb].update(glyph="herbs-bundle", tint=tint)
for potion, *_rest, skill, _effect in POTIONS:
if skill == "combat":
ITEMS[potion].update(glyph="potion-ball", tint="#e05a5a")
else:
ITEMS[potion].update(glyph="round-bottom-flask", tint=SKILL_GLYPHS[skill][1])
flasks = ["round-bottom-flask", "round-bottom-flask", "standing-potion", "standing-potion",
"bubbling-flask", "bubbling-flask"]
for potion, *_rest, skill, effect in POTIONS:
tint = "#e05a5a" if "power" in effect else "#8fa8c8" if "reduce" in effect else SKILL_GLYPHS[skill][1]
ITEMS[potion].update(glyph=flasks[POTION_TIER[potion]], tint=tint)
for skill in SKILLS:
skill["glyph"], skill["tint"] = SKILL_GLYPHS[skill["id"]]
for key, spec in CLASSES.items():