diff --git a/README.md b/README.md index 6e808df..d4f363d 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,9 @@ Alpha. One season, bandit raids plus two friendly events, six buildings. ## What's in it - **Skills:** mining, woodcutting, farming, hunting, fishing, smithing, - masonry, construction, cooking, leatherworking, herblore, combat, prayer and - reconnaissance. + construction (with masonry), cooking, leatherworking, herblore, combat, + prayer and reconnaissance. Smiths also make hardware for builders, one kind + per metal (copper rivets to rune locks), which fittings and buildings need. - **Combat:** fights cost HP (less when out-levelled or armoured); below half health you eat food from your bank, and out of food you retreat, losing nothing but the fight. HP recovers 25% an hour. @@ -24,10 +25,11 @@ Alpha. One season, bandit raids plus two friendly events, six buildings. city). Gear needs a combat level to wear, tools a skill level to use. - **Classes are trades,** from gathering to finished goods, so each player has a quiet gathering side and a busier crafting side: miner (mining, smithing), - builder (woodcutting, construction, masonry), homesteader (farming, cooking, + builder (woodcutting, construction), homesteader (farming, cooking, herblore) and ranger (hunting, fishing, leatherworking, cooking). A trade's skills are 20% faster, 1 in 10 of its crafts make double, and each has one - bonus of its own. Every class can train every skill, scout and drill. + bonus of its own. Homesteaders farm differently: two plots, either one crop + 30% faster or two crops at once sharing a 20% faster pace. Every class can train every skill, scout and drill. Building costs draw on all four trades: thatch, flax for rope, hide glue, leather and provisions as well as wood, stone and metal. - **Bank:** 12 slots of 1,000 each to start; more than a stack of one item diff --git a/app.py b/app.py index e7bb4bc..f3540ae 100644 --- a/app.py +++ b/app.py @@ -112,8 +112,8 @@ def player_view(world: dict, player: dict, now: float) -> dict: view["power"] = game.combat_power(player) view["perk_costs"] = {perk: game.perk_cost(player, perk) for perk in player["perks"]} if player["action"]: - action = game.ACTION_BY_ID[player["action"]["id"]] - view["action"] = {**player["action"], "duration": game.action_time(world, player, action)} + action = game.growing(player) + view["action"] = {**player["action"], "growing": action["id"], "duration": game.action_time(world, player, action)} view["times"] = {action["id"]: game.action_time(world, player, action) for action in game.ACTIONS} view["blocked"] = {action["id"]: game.can_start(world, player, action) for action in game.ACTIONS} view["drill_per_action"] = game.drill_per_action(player) @@ -370,6 +370,8 @@ class Handler(BaseHTTPRequestHandler): player["cls"], player["repick"] = cls, False game.log(world, now, "join", f"{player['name']} took up the {game.CLASSES[cls]['name'].lower()}'s trade", player["id"]) return None + if path == "/api/plot": + return game.plant_second_plot(world, player, body.get("id") or None) if path == "/api/perk": return game.buy_perk(player, str(body.get("perk"))) if path == "/api/settings": diff --git a/game.py b/game.py index 657aec2..97abf16 100644 --- a/game.py +++ b/game.py @@ -167,6 +167,20 @@ for _tier, (_key, _name, _ore, _coal, _bar_icon, _bar_value, _smithy, _wood) in "name": f"{_name} {_tool}", "icon": _icon, "value": round(2 * _bar_value * 1.4), "tier": _tier, "slot": f"tool_{_skill}", "tool": _skill, "speed": TOOL_SPEED[_tier], "req": [_skill, EQUIP_LEVELS[_tier]], } +# Hardware: what the smith makes for builders, one kind per metal, used in +# fittings and in building levels in place of plain bars. +# id, singular, plural, bars used, pieces made +HARDWARE = [ + ("copper_rivets", "Copper rivet", "Copper rivets", 1, 5), + ("iron_nails", "Iron nail", "Iron nails", 1, 5), + ("steel_hinges", "Steel hinge", "Steel hinges", 1, 2), + ("mithril_brackets", "Mithril bracket", "Mithril brackets", 1, 2), + ("adamant_chain", "Adamant chain", "Adamant chains", 2, 1), + ("rune_lock", "Rune lock", "Rune locks", 2, 1), +] +for (_id, _name, _plural, _bars, _made), _metal in zip(HARDWARE, METALS): + ITEMS[_id] = {"name": _name, "plural": _plural, "icon": "🔩", "value": max(1, round(_bars * _metal[5] * 1.2 / _made)), + "tier": METALS.index(_metal)} PLANKS = [("plank", "Plank"), ("oak_plank", "Oak plank"), ("willow_plank", "Willow plank"), ("maple_plank", "Maple plank"), ("yew_plank", "Yew plank"), ("magic_plank", "Enchanted plank")] for (_log, _name, _icon, _value, _action_id, _verb), (_plank, _plank_name) in zip(TREES, PLANKS): @@ -221,7 +235,6 @@ POTION_LINES = [ ("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], @@ -242,7 +255,7 @@ OLD_POTIONS = { "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", + "smiths_focus": "smithing_potion_2", "masons_patience": "construction_potion_2", "tanners_oil": "leatherworking_potion_2", "regeneration_potion": "defence_potion_3", "super_strength": "strength_potion_4", "super_defence": "defence_potion_5", } @@ -280,7 +293,6 @@ SKILLS = [ {"id": "hunting", "name": "Hunting", "icon": "🦌", "group": "Gathering"}, {"id": "fishing", "name": "Fishing", "icon": "🎣", "group": "Gathering"}, {"id": "smithing", "name": "Smithing", "icon": "🔨", "group": "Crafting"}, - {"id": "masonry", "name": "Masonry", "icon": "🧱", "group": "Crafting"}, {"id": "construction", "name": "Construction", "icon": "🏗️", "group": "Crafting"}, {"id": "cooking", "name": "Cooking", "icon": "🍳", "group": "Crafting"}, {"id": "leatherworking", "name": "Leatherworking", "icon": "🪡", "group": "Crafting"}, @@ -291,7 +303,7 @@ SKILLS = [ ] SKILL_IDS = [skill["id"] for skill in SKILLS] GATHERING = {"mining", "woodcutting", "farming", "hunting", "fishing"} -CRAFTING = {"smithing", "masonry", "construction", "cooking", "leatherworking", "herblore"} +CRAFTING = {"smithing", "construction", "cooking", "leatherworking", "herblore"} # A class is a whole trade, from gathering to the finished goods: its skills are # 20% faster, 1 in 10 of its crafts make double, and it has one bonus of its own. @@ -304,16 +316,14 @@ CLASSES = { "signature": "Prospector: 1 in 10 mining trips bring back double", }, "builder": { - "name": "Builder", "icon": "🏗️", "fast": ["woodcutting", "construction", "masonry"], - "craft": ["construction", "masonry"], - "bonus": "Woodcutting, construction and masonry 20% faster", - "masterwork": "Masterwork: 1 in 10 construction and masonry crafts make double", + "name": "Builder", "icon": "🏗️", "fast": ["woodcutting", "construction"], "craft": ["construction"], + "bonus": "Woodcutting and construction (masonry included) 20% faster", + "masterwork": "Masterwork: 1 in 10 construction crafts make double", "signature": "Foreman: +1 labour every time you work a building site", }, "homesteader": { - "name": "Homesteader", "icon": "🌾", "fast": ["farming", "cooking", "herblore"], - "craft": ["cooking", "herblore"], - "bonus": "Farming, cooking and herblore 20% faster", + "name": "Homesteader", "icon": "🌾", "fast": ["cooking", "herblore"], "craft": ["cooking", "herblore"], + "bonus": "Cooking and herblore 20% faster. Two plots: farm one crop 30% faster, or two crops at once", "masterwork": "Masterwork: 1 in 10 cooking and herblore crafts make double", "signature": "Steady hand: potions you drink last 25% longer", }, @@ -354,6 +364,10 @@ def _tiered_actions() -> list[dict]: actions.append(_action(f"smith_{key}_bar", "smithing", f"{name} bar", TIER_LEVELS[tier], 20 + 4 * tier, bar_xp[tier], inputs, {f"{key}_bar": 1}, group="Bars", city={"smithy": smithy} if tier >= 2 else {})) + hardware, _name, _plural, bars, made = HARDWARE[tier] + actions.append(_action(f"smith_{hardware}", "smithing", _plural, TIER_LEVELS[tier] + 2, 25 + 2 * tier, + bars * piece_xp[tier], {f"{key}_bar": bars}, {hardware: made}, group="Hardware", + city=city)) for piece, _slot, noun, bars, offset, _power, _armour in PIECES: actions.append(_action(f"smith_{key}_{piece}", "smithing", f"{name} {noun}", TIER_LEVELS[tier] + offset, 25 + 5 * bars + 2 * tier, bars * piece_xp[tier], {f"{key}_bar": bars}, @@ -413,10 +427,10 @@ ACTIONS = [ *[_action(f"saw_{plank}", "construction", f"Saw {name.lower()}s", TIER_LEVELS[tier], 25 + 3 * tier, [6, 14, 22, 32, 44, 58][tier], {TREES[tier][0]: 2}, {plank: 1}, group="Planks") for tier, (plank, name) in enumerate(PLANKS)], - _action("mason_bricks", "masonry", "Bricks", 1, 25, 6, {"stone": 2, "logs": 1}, {"bricks": 2}), - _action("mason_cut_stone", "masonry", "Cut stone", 10, 30, 12, {"stone": 3}, {"cut_stone": 1}), - _action("mason_beam", "masonry", "Reinforced beam", 20, 45, 30, - {"oak_logs": 2, "iron_bar": 1}, {"beam": 1}), + _action("mason_bricks", "construction", "Bricks", 1, 25, 6, {"stone": 2, "logs": 1}, {"bricks": 2}, group="Masonry"), + _action("mason_cut_stone", "construction", "Cut stone", 10, 30, 12, {"stone": 3}, {"cut_stone": 1}, group="Masonry"), + _action("mason_beam", "construction", "Reinforced beam", 20, 45, 30, + {"oak_logs": 2, "iron_nails": 5}, {"beam": 1}, group="Masonry"), _action("cook_bread", "cooking", "Bread", 1, 20, 5, {"wheat": 2, "logs": 1}, {"bread": 1}), _action("cook_meat", "cooking", "Cooked meat", 5, 20, 7, {"raw_meat": 1, "logs": 1}, {"cooked_meat": 1}), _action("cook_stew", "cooking", "Stew", 15, 35, 16, @@ -457,23 +471,23 @@ ACTION_BY_ID = {action["id"]: action for action in ACTIONS} BUILDINGS = { "houses": { "name": "Houses", "icon": "🏠", "max": 8, "gold": 200, - "base": {"logs": 300, "bricks": 150, "thatch": 300, "provisions": 80}, - "advanced": {"oak_logs": 100, "leather": 80, "rope": 40, "iron_bar": 30}, - "late": {"willow_logs": 80, "oak_plank": 35, "hide_glue": 50}, - "final": {"maple_logs": 50, "willow_plank": 30, "hard_leather": 60}, + "base": {"logs": 300, "bricks": 150, "thatch": 300, "provisions": 80, "copper_rivets": 60}, + "advanced": {"oak_logs": 100, "leather": 80, "rope": 40, "iron_nails": 150}, + "late": {"willow_logs": 80, "oak_plank": 35, "hide_glue": 50, "steel_hinges": 20}, + "final": {"maple_logs": 50, "willow_plank": 30, "hard_leather": 60, "mithril_brackets": 15}, "effect": "Room for 20 more people per level", }, "walls": { "name": "Walls", "icon": "🏰", "max": 5, "gold": 300, "base": {"stone": 1000, "bricks": 250, "provisions": 90}, - "advanced": {"cut_stone": 150, "beam": 20, "rope": 50, "iron_bar": 40}, - "late": {"steel_bar": 80, "oak_plank": 25, "hide_glue": 50}, - "final": {"mithril_bar": 50, "maple_plank": 20, "hide_glue": 60}, + "advanced": {"cut_stone": 150, "beam": 20, "rope": 50, "iron_nails": 200}, + "late": {"steel_bar": 60, "oak_plank": 25, "hide_glue": 50, "steel_hinges": 30}, + "final": {"mithril_brackets": 60, "maple_plank": 20, "hide_glue": 60, "adamant_chain": 10}, "effect": "+20 defence per level, and raiders carry off less", }, "barracks": { "name": "Barracks", "icon": "⛺", "max": 5, "gold": 300, - "base": {"logs": 300, "bricks": 200, "thatch": 200, "leather": 80, "copper_sword": 25, "provisions": 80}, + "base": {"logs": 300, "bricks": 200, "thatch": 200, "leather": 80, "copper_sword": 25, "provisions": 80, "copper_rivets": 60}, "advanced": {"iron_sword": 20, "hard_leather": 40}, "late": {"steel_sword": 12, "oak_plank": 30, "hide_glue": 40}, "final": {"mithril_sword": 8, "willow_plank": 25, "bear_leather": 40}, @@ -490,44 +504,44 @@ BUILDINGS = { "temple": { "name": "Temple", "icon": "⛪", "max": 3, "gold": 300, "base": {"stone": 700, "bricks": 200, "cut_stone": 60, "provisions": 60}, - "advanced": {"beam": 15, "rope": 40, "iron_bar": 30}, - "late": {"willow_logs": 80, "oak_plank": 35, "hide_glue": 40}, + "advanced": {"beam": 15, "rope": 40, "iron_nails": 150}, + "late": {"willow_logs": 80, "oak_plank": 35, "hide_glue": 40, "steel_hinges": 20}, "effect": "Offerings ward the city in raids; +1 prayer point per bone buried and +25% devotion per level", }, "granary": { "name": "Granary", "icon": "🌾", "max": 3, "gold": 150, - "base": {"logs": 200, "bricks": 100, "plank": 40, "thatch": 300, "provisions": 50}, - "advanced": {"cut_stone": 40, "rope": 40, "iron_bar": 20}, - "late": {"oak_plank": 35, "hide_glue": 35}, + "base": {"logs": 200, "bricks": 100, "plank": 40, "thatch": 300, "provisions": 50, "copper_rivets": 40}, + "advanced": {"cut_stone": 40, "rope": 40, "iron_nails": 100}, + "late": {"oak_plank": 35, "hide_glue": 35, "steel_hinges": 15}, "effect": "Protects a quarter of the food from locusts and rats per level", }, "lumber_shed": { "name": "Lumber shed", "icon": "🪵", "max": 3, "gold": 120, - "base": {"logs": 200, "stone": 250, "plank": 50, "thatch": 180, "provisions": 40}, - "advanced": {"oak_plank": 40, "rope": 40, "iron_bar": 20}, + "base": {"logs": 200, "stone": 250, "plank": 50, "thatch": 180, "provisions": 40, "copper_rivets": 40}, + "advanced": {"oak_plank": 40, "rope": 40, "iron_nails": 100}, "late": {"willow_plank": 25, "hide_glue": 35}, "effect": "Keeps a quarter of the stored wood safe from rot per level", }, "well": { "name": "Well", "icon": "🪣", "max": 3, "gold": 120, "base": {"stone": 500, "bricks": 80, "rope": 50, "provisions": 40}, - "advanced": {"cut_stone": 40, "leather": 60, "iron_bar": 25}, - "late": {"oak_plank": 25, "hide_glue": 35}, + "advanced": {"cut_stone": 40, "leather": 60, "iron_nails": 125}, + "late": {"oak_plank": 25, "hide_glue": 35, "steel_hinges": 15}, "effect": "Each level cuts fire losses by a quarter and may save the building", }, "spymaster": { "name": "Spymaster's guild", "icon": "🕵️", "max": 3, "gold": 400, "base": {"logs": 200, "bricks": 150, "hide": 120, "feathers": 300, "provisions": 60}, - "advanced": {"cut_stone": 80, "hard_leather": 40, "iron_bar": 20}, - "late": {"bear_leather": 50, "oak_plank": 25}, + "advanced": {"cut_stone": 80, "hard_leather": 40, "iron_nails": 100}, + "late": {"bear_leather": 50, "oak_plank": 25, "steel_hinges": 15}, "effect": "+25% reconnaissance per level, and its agents gather some every hour on their own", }, "storehouse": { "name": "Storehouse", "icon": "📦", "max": 5, "gold": 200, - "base": {"logs": 400, "stone": 800, "thatch": 200, "provisions": 60}, - "advanced": {"oak_logs": 150, "cut_stone": 100, "rope": 50, "iron_bar": 30}, - "late": {"willow_logs": 120, "oak_plank": 45, "hide_glue": 45}, - "final": {"yew_logs": 40, "yew_plank": 20, "bear_leather": 40}, + "base": {"logs": 400, "stone": 800, "thatch": 200, "provisions": 60, "copper_rivets": 60}, + "advanced": {"oak_logs": 150, "cut_stone": 100, "rope": 50, "iron_nails": 150}, + "late": {"willow_logs": 120, "oak_plank": 45, "hide_glue": 45, "steel_hinges": 25}, + "final": {"yew_logs": 40, "yew_plank": 20, "bear_leather": 40, "rune_lock": 6}, "effect": "+3 bank slots and +250 per stack for everyone, per level", }, } @@ -564,7 +578,7 @@ RAID_TRAITS = ["lightly armoured, heavy on archers", "mounted and fast, few bows # --- world --------------------------------------------------------------- -WORLD_VERSION = 12 +WORLD_VERSION = 13 def new_world(now: float, speed: float = 1.0) -> dict: @@ -681,6 +695,10 @@ def migrate(world: dict, now: float | None = None) -> dict: player["repick"] = True _return_overfunding(world) world["version"] = 12 + if world["version"] < 13: + _fold_masonry(world) + _return_overfunding(world) + world["version"] = 13 return world @@ -704,6 +722,32 @@ def _migrate_potions(world: dict) -> None: action[key] = f"brew_{OLD_POTIONS[old]}" +def _fold_masonry(world: dict) -> None: + """Masonry became part of Construction: its XP, potions and brews carry over.""" + moved = {f"masonry_potion_{tier}": f"construction_potion_{tier}" for tier in (1, 2, 3)} + def renamed(stock: dict) -> None: + for old, new in moved.items(): + if old in stock: + stock[new] = stock.get(new, 0) + stock.pop(old) + renamed(world["city"]["stockpile"]) + for player in world["players"].values(): + player["xp"]["construction"] = player["xp"].get("construction", 0) + player["xp"].pop("masonry", 0) + renamed(player["bank"]) + active = player["potions"].pop("masonry", None) + if active: + item = moved.get(active["item"], active["item"]) + if "construction" in player["potions"]: + player["bank"][item] = player["bank"].get(item, 0) + 1 # a fresh one back, rather than lose it + else: + player["potions"]["construction"] = {"item": item, "charges": active["charges"]} + action = player["action"] + if action: + for key in ("id", "resume"): + brew = (action.get(key) or "").removeprefix("brew_") + if brew in moved: + action[key] = f"brew_{moved[brew]}" + + def _return_overfunding(world: dict) -> None: """After building costs change, anything a fund holds beyond its new target goes back to the stockpile.""" city = world["city"] @@ -1038,6 +1082,9 @@ def action_time(world: dict, player: dict, action: dict) -> float: seconds = action["time"] if action["skill"] in CLASSES[player["cls"]]["fast"]: seconds *= CLASS_SPEED + if action["skill"] == "farming" and player["cls"] == "homesteader": + # Focus on one plot for 30% faster, or split the same 20% pace between two crops. + seconds *= PLOTS_SPEED if two_plots(player) else FOCUS_SPEED tool = player["equip"].get(f"tool_{action['skill']}") if tool: seconds *= 1 - ITEMS[tool]["speed"] @@ -1051,6 +1098,48 @@ def action_time(world: dict, player: dict, action: dict) -> float: return seconds / speed(world) +FOCUS_SPEED = 0.7 # a homesteader tending one plot +PLOTS_SPEED = 0.8 # each harvest when tending two: together, one crop's worth at 20% faster + + +def two_plots(player: dict) -> bool: + current = player["action"] + return bool(current and current.get("plot2")) + + +def growing(player: dict) -> dict: + """The action the player is on right now: with two plots, harvests alternate between them.""" + current = player["action"] + if current.get("plot2") and current.get("turn"): + return ACTION_BY_ID[current["plot2"]] + return ACTION_BY_ID[current["id"]] + + +def plant_second_plot(world: dict, player: dict, action_id: str | None) -> str | None: + """Let a homesteader's second plot grow another crop, or clear it.""" + current = player["action"] + if not action_id: + if current: + current.pop("plot2", None) + current.pop("turn", None) + return None + if player["cls"] != "homesteader": + return "Only homesteaders tend two plots" + action = ACTION_BY_ID.get(action_id) + if not action or action["skill"] != "farming" or action.get("special"): + return "That isn't a crop" + if not current or ACTION_BY_ID[current["id"]]["skill"] != "farming" or "left" in current: + return "Plant your first plot first" + if action_id == current["id"]: + return "That's already growing in your first plot" + reason = can_start(world, player, action) + if reason: + return reason + current["plot2"] = action_id + current["turn"] = 0 + return None + + def with_potion(blessed: dict, brew: dict) -> dict: """A fight's prayer boosts with a combat potion's added on top.""" return {**blessed, "power": blessed["power"] + brew.get("power", 0), @@ -1295,9 +1384,13 @@ def process_player(world: dict, player: dict, now: float, rng: random.Random | N current = player["action"] if not current: return summary - action = ACTION_BY_ID[current["id"]] limit = min(now, player["last_active"] + OFFLINE_CAP / speed(world)) while player["action"]: + action = growing(player) + if current.get("plot2") and can_start(world, player, action): + # The second crop can't grow right now (a problem, too low a level): tend the first only. + current["turn"] = 0 + action = growing(player) duration = action_time(world, player, action) if current["start"] + duration > limit: break @@ -1323,13 +1416,17 @@ def process_player(world: dict, player: dict, now: float, rng: random.Random | N _complete(world, player, action, current["start"] + duration, rng, summary) summary["done"] += 1 current["start"] += duration + if current.get("plot2"): + current["turn"] = 1 - current.get("turn", 0) if player["action"] is current and "left" in current: current["left"] -= 1 if current["left"] <= 0: back = current.get("resume") if back: + plot2 = current.get("resume_plot2") player["action"] = current = {"id": back, "start": current["start"]} - action = ACTION_BY_ID[back] + if plot2: + current.update(plot2=plot2, turn=0) summary["resumed"] = back else: stop(player) @@ -1352,12 +1449,18 @@ def start_action(world: dict, player: dict, action_id: str, now: float, once: bo if reason: return reason entry = {"id": action_id, "start": now} + current = player["action"] if once: - current = player["action"] back = current and (current.get("resume") or current["id"]) entry["left"] = 1 if back and back != action_id: entry["resume"] = back + plot2 = current.get("resume_plot2") or current.get("plot2") + if plot2: + entry["resume_plot2"] = plot2 + elif current and current.get("plot2") and current["plot2"] != action_id and action["skill"] == "farming": + # Replanting the first plot leaves the second one growing. + entry.update(plot2=current["plot2"], turn=0) player["action"] = entry player["stopped"] = None return None @@ -2176,7 +2279,7 @@ ACTIONS.extend(PROBLEM_ACTIONS) ACTION_BY_ID.update({action["id"]: action for action in PROBLEM_ACTIONS}) ITEMS["water_barrel"] = {"name": "Water barrel", "icon": "🛢️", "value": 20} _barrel = _action("cooper_barrel", "construction", "Water barrel", 10, 35, 18, - {"oak_plank": 2, "iron_bar": 1}, {"water_barrel": 1}, group="Supplies") + {"oak_plank": 2, "iron_nails": 4}, {"water_barrel": 1}, group="Supplies") ACTIONS.append(_barrel) ACTION_BY_ID[_barrel["id"]] = _barrel @@ -2185,29 +2288,29 @@ ACTION_BY_ID[_barrel["id"]] = _barrel # building stands; damage pauses it like the rest of the building. # id, name, building, building level, construction level, inputs, effect, glyph, tint FITTINGS = [ - ("grain_bins", "Grain bins", "granary", 1, 15, {"oak_plank": 20, "iron_bar": 5}, + ("grain_bins", "Grain bins", "granary", 1, 15, {"oak_plank": 20, "iron_nails": 25}, "The city eats 10% less food", "grain-bundle", "#e3c060"), - ("saw_benches", "Saw benches", "lumber_shed", 1, 15, {"oak_plank": 20, "iron_bar": 10}, + ("saw_benches", "Saw benches", "lumber_shed", 1, 15, {"oak_plank": 20, "iron_nails": 40}, "Sawing planks is 20% faster", "hand-saw", "#8f5e30"), - ("shelving", "Shelving", "storehouse", 2, 20, {"oak_plank": 40, "iron_bar": 10}, + ("shelving", "Shelving", "storehouse", 2, 20, {"oak_plank": 40, "iron_nails": 40}, "+1 bank slot for everyone", "bookshelf", "#8f5e30"), - ("map_table", "Map table", "spymaster", 1, 30, {"willow_plank": 25, "hard_leather": 10}, + ("map_table", "Map table", "spymaster", 1, 30, {"willow_plank": 25, "hard_leather": 10, "steel_hinges": 4}, "Scouting gathers one more point of reconnaissance", "treasure-map", "#a3b86c"), - ("well_pump", "Well pump", "well", 2, 30, {"willow_plank": 25, "steel_bar": 10}, + ("well_pump", "Well pump", "well", 2, 30, {"willow_plank": 25, "steel_hinges": 10}, "Half of all fires are put out before they spread", "water-tank", "#5aa0d8"), - ("furniture", "Furniture", "houses", 2, 35, {"willow_plank": 40, "leather": 30}, + ("furniture", "Furniture", "houses", 2, 35, {"willow_plank": 40, "leather": 30, "steel_hinges": 10}, "Population grows 25% faster", "sofa", "#a3b86c"), - ("weapon_racks", "Weapon racks", "barracks", 2, 40, {"willow_plank": 30, "steel_bar": 20}, + ("weapon_racks", "Weapon racks", "barracks", 2, 40, {"willow_plank": 30, "steel_hinges": 16}, "Militia defence +50%", "spears", "#d4dae2"), - ("hoardings", "Hoardings", "walls", 3, 45, {"maple_plank": 40, "mithril_bar": 10}, + ("hoardings", "Hoardings", "walls", 3, 45, {"maple_plank": 40, "mithril_brackets": 20}, "+25 defence", "palisade", "#d0663a"), - ("bellows", "Great bellows", "smithy", 3, 50, {"maple_plank": 30, "bear_leather": 15}, + ("bellows", "Great bellows", "smithy", 3, 50, {"maple_plank": 30, "bear_leather": 15, "mithril_brackets": 6}, "Smithing another 10% faster", "bellows", "#d0663a"), - ("carved_altar", "Carved altar", "temple", 2, 60, {"yew_plank": 30, "cut_stone": 60}, + ("carved_altar", "Carved altar", "temple", 2, 60, {"yew_plank": 30, "cut_stone": 60, "adamant_chain": 4}, "+1 prayer point per bone buried, and offerings can ward 15 more", "star-altar", "#6f9a4a"), - ("enchanted_chests", "Enchanted chests", "storehouse", 4, 75, {"magic_plank": 40, "rune_bar": 5}, + ("enchanted_chests", "Enchanted chests", "storehouse", 4, 75, {"magic_plank": 40, "rune_lock": 6}, "+500 per bank stack for everyone", "open-treasure-chest", "#a77be8"), - ("enchanted_hearths", "Enchanted hearths", "houses", 4, 80, {"magic_plank": 50, "red_dragon_leather": 5}, + ("enchanted_hearths", "Enchanted hearths", "houses", 4, 80, {"magic_plank": 50, "red_dragon_leather": 5, "adamant_chain": 6}, "Everyone recovers HP twice as fast", "fireplace", "#a77be8"), ] FITTING_BY_ID = {} @@ -2785,6 +2888,7 @@ PIECE_GLYPHS = {"sword": "pointy-sword", "shield": "round-shield", "helmet": "vi TOOL_GLYPHS = {"pickaxe": "war-pick", "axe": "wood-axe", "hoe": "sickle", "fishing rod": "fishing-pole", "hunting knife": "bowie-knife", "hammer": "claw-hammer"} LEATHER_GLYPHS = {"cap": "hood", "legs": "trousers", "body": "leather-vest"} +HARDWARE_GLYPHS = ["screw", "nails", "door-handle", "metal-plate", "crossed-chains", "padlock"] GLYPHS = { "stone": ("stone-block", "#a8a29e"), "wheat": ("wheat", "#e3c060"), "vegetables": ("carrot", "#f08a3c"), "feathers": ("feather", "#e8e8ee"), "raw_meat": ("meat", "#e06a6a"), "arrows": ("arrow-cluster", "#c9b28a"), @@ -2804,7 +2908,7 @@ GLYPHS = { SKILL_GLYPHS = { "mining": ("mining", "#b8b2aa"), "woodcutting": ("wood-axe", "#c98a4a"), "farming": ("wheat", "#e3c060"), "hunting": ("deer-head", "#c49a6c"), "fishing": ("fishing-pole", "#5aa0d8"), "smithing": ("anvil", "#d07a3c"), - "masonry": ("brick-wall", "#c66a45"), "construction": ("hammer-nails", "#d1a46a"), + "construction": ("hammer-nails", "#d1a46a"), "cooking": ("cooking-pot", "#e0703a"), "leatherworking": ("sewing-needle", "#d6a26e"), "herblore": ("cauldron", "#7fbf5a"), "combat": ("crossed-swords", "#e05a5a"), "prayer": ("prayer", "#e9dfc6"), "reconnaissance": ("spyglass", "#6f8fec"), @@ -2837,11 +2941,12 @@ UI_GLYPHS = ["two-coins", "sparkles", "hearts", "prayer", "checked-shield"] def _assign_glyphs() -> None: for item, (glyph, tint) in GLYPHS.items(): ITEMS[item].update(glyph=glyph, tint=tint) - for key, _name, ore, *_rest in METALS: + for tier, (key, _name, ore, *_rest) in enumerate(METALS): tint = METAL_TINTS[key] if ore: ITEMS[ore[0]].update(glyph="ore", tint=tint) ITEMS[f"{key}_bar"].update(glyph="metal-bar", tint=tint) + ITEMS[HARDWARE[tier][0]].update(glyph=HARDWARE_GLYPHS[tier], tint=tint) for piece, glyph in PIECE_GLYPHS.items(): ITEMS[f"{key}_{piece}"].update(glyph=glyph, tint=tint) for tool, glyph in TOOL_GLYPHS.items(): diff --git a/static/app.js b/static/app.js index 8ee5644..1fdf7c3 100644 --- a/static/app.js +++ b/static/app.js @@ -277,7 +277,7 @@ function topbar() { if (!S.me) title = "Welcome to Guildhall"; let chips = ""; if (S.me?.action) { - const action = C.actions.find((a) => a.id === S.me.action.id); + const action = C.actions.find((a) => a.id === (S.me.action.growing || S.me.action.id)); chips += ``; } else if (S.me) { chips += `Idle${S.me.stopped ? ` · ${esc(S.me.stopped)}` : ""}`; @@ -343,6 +343,7 @@ function skillPage(id) { if (id === "prayer") html += prayerPanel(); if (id === "construction") html += S.city.construction ? siteBanner() : `