Jewellery tabs by slot, loupe doubles crafts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-20 22:15:57 +01:00
parent 8d6d23261e
commit 2816ca8606
3 changed files with 25 additions and 8 deletions

View file

@ -50,7 +50,7 @@ Alpha. One season, bandit raids plus two friendly events, six buildings.
without a smithy. Past quartz each piece needs a shard from that tier's without a smithy. Past quartz each piece needs a shard from that tier's
area. Rings for offence (swing speed, crit, power), amulets for defence (max area. Rings for offence (swing speed, crit, power), amulets for defence (max
HP, ward, regen), trinkets for the rest (XP, double gathering, HP, ward, regen), trinkets for the rest (XP, double gathering,
masterwork). double crafting), each in its own tab.
- **Tools and stations:** smiths make every tool: one per gathering skill and - **Tools and stations:** smiths make every tool: one per gathering skill and
one per crafting skill (chisel, gem drill, hammer, cleaver, mortar and one per crafting skill (chisel, gem drill, hammer, cleaver, mortar and
pestle, awl, shears). Builders make every station, a second slot for each pestle, awl, shears). Builders make every station, a second slot for each

17
game.py
View file

@ -236,7 +236,8 @@ PROSPECT_ODDS = 0.15 # ...for this chance at its gem
GEM_ROCKS = {"mine_stone": "quartz", "mine_copper": "quartz", "mine_iron": "garnet", "mine_coal": "amethyst", GEM_ROCKS = {"mine_stone": "quartz", "mine_copper": "quartz", "mine_iron": "garnet", "mine_coal": "amethyst",
"mine_mithril": "sapphire", "mine_adamantite": "emerald", "mine_runite": "diamond"} "mine_mithril": "sapphire", "mine_adamantite": "emerald", "mine_runite": "diamond"}
PROSPECT = ["stone", "iron_ore", "coal", "mithril_ore", "adamantite_ore", "runite_ore"] PROSPECT = ["stone", "iron_ore", "coal", "mithril_ore", "adamantite_ore", "runite_ore"]
# Rings for offence, amulets for defence, trinkets for everything else. # Rings for offence, amulets for defence, trinkets for everything else; each its own tab.
JEWEL_GROUPS = {"ring": "Rings", "amulet": "Amulets", "trinket": "Trinkets"}
# key, slot, noun, stat, bonus by tier, bands, gems, shards, level above the tier, glyph # key, slot, noun, stat, bonus by tier, bands, gems, shards, level above the tier, glyph
JEWELLERY = [ JEWELLERY = [
("haste", "ring", "ring of haste", "haste", [5, 7, 8, 9, 10, 11], 1, 1, 1, 2, "diamond-ring"), ("haste", "ring", "ring of haste", "haste", [5, 7, 8, 9, 10, 11], 1, 1, 1, 2, "diamond-ring"),
@ -247,7 +248,7 @@ JEWELLERY = [
("mending", "amulet", "amulet of mending", "regen", [10, 20, 30, 40, 50, 60], 2, 1, 2, 8, "tribal-pendant"), ("mending", "amulet", "amulet of mending", "regen", [10, 20, 30, 40, 50, 60], 2, 1, 2, 8, "tribal-pendant"),
("hourglass", "trinket", "hourglass", "xp", [2, 3, 5, 6, 8, 10], 1, 2, 1, 5, "hourglass"), ("hourglass", "trinket", "hourglass", "xp", [2, 3, 5, 6, 8, 10], 1, 2, 1, 5, "hourglass"),
("charm", "trinket", "lucky charm", "double", [2, 3, 4, 5, 6, 8], 1, 2, 1, 5, "clover"), ("charm", "trinket", "lucky charm", "double", [2, 3, 4, 5, 6, 8], 1, 2, 1, 5, "clover"),
("loupe", "trinket", "loupe", "masterwork", [2, 3, 4, 4, 5, 6], 1, 2, 1, 5, "magnifying-glass"), ("loupe", "trinket", "loupe", "craft_double", [2, 3, 4, 4, 5, 6], 1, 2, 1, 5, "magnifying-glass"),
] ]
# How each jewellery bonus reads: name, unit, glyph, tint (and a sign, if it takes away). # How each jewellery bonus reads: name, unit, glyph, tint (and a sign, if it takes away).
JEWEL_STATS = { JEWEL_STATS = {
@ -259,7 +260,7 @@ JEWEL_STATS = {
"regen": {"name": "HP regen", "unit": "%", "glyph": "heart-plus", "tint": "#e06a8a"}, "regen": {"name": "HP regen", "unit": "%", "glyph": "heart-plus", "tint": "#e06a8a"},
"xp": {"name": "XP", "unit": "%", "glyph": "sparkles", "tint": "#f0d060"}, "xp": {"name": "XP", "unit": "%", "glyph": "sparkles", "tint": "#f0d060"},
"double": {"name": "Double gathering", "unit": "%", "glyph": "clover", "tint": "#5fae5a"}, "double": {"name": "Double gathering", "unit": "%", "glyph": "clover", "tint": "#5fae5a"},
"masterwork": {"name": "Masterwork", "unit": "%", "glyph": "anvil", "tint": "#d07a3c"}, "craft_double": {"name": "Double crafting", "unit": "%", "glyph": "anvil", "tint": "#d07a3c"},
} }
ITEMS["silver_ore"] = {"name": "Silver ore", "icon": "⚪", "value": 6} ITEMS["silver_ore"] = {"name": "Silver ore", "icon": "⚪", "value": 6}
ITEMS["silver_bar"] = {"name": "Silver bar", "icon": "🥈", "value": 15, "tier": 1} ITEMS["silver_bar"] = {"name": "Silver bar", "icon": "🥈", "value": 15, "tier": 1}
@ -867,12 +868,12 @@ def _jewel_actions() -> list[dict]:
{"gold_bar": 1}, group="Bars"), {"gold_bar": 1}, group="Bars"),
] ]
for tier, (gem, gem_name, *_rest) in enumerate(GEMS): for tier, (gem, gem_name, *_rest) in enumerate(GEMS):
for key, _slot, noun, _stat, _bonus, bands, gems, shards, offset, _glyph in JEWELLERY: for key, slot, noun, _stat, _bonus, bands, gems, shards, offset, _glyph in JEWELLERY:
inputs = {BANDS[tier]: bands, gem: gems, **({reagent("jewel", tier): shards} if tier else {})} inputs = {BANDS[tier]: bands, gem: gems, **({reagent("jewel", tier): shards} if tier else {})}
actions.append(_action(f"jc_{gem}_{key}", "jewelcrafting", f"{gem_name} {noun}", TIER_LEVELS[tier] + offset, actions.append(_action(f"jc_{gem}_{key}", "jewelcrafting", f"{gem_name} {noun}", TIER_LEVELS[tier] + offset,
35 + 5 * (bands + gems) + 2 * tier, 35 + 5 * (bands + gems) + 2 * tier,
round([15, 32, 52, 75, 100, 130][tier] * (bands + gems) / 2), round([15, 32, 52, 75, 100, 130][tier] * (bands + gems) / 2),
inputs, {f"{gem}_{key}": 1}, group=gem_name)) inputs, {f"{gem}_{key}": 1}, group=JEWEL_GROUPS[slot]))
return actions return actions
@ -2064,7 +2065,7 @@ def _complete(world: dict, player: dict, action: dict, now: float, rng: random.R
cls = CLASSES[player["cls"]] cls = CLASSES[player["cls"]]
lucky = bool(action["outputs"]) and ( lucky = bool(action["outputs"]) and (
action["skill"] in cls["craft"] or (player["cls"] == "miner" and action["skill"] == "mining") action["skill"] in cls["craft"] or (player["cls"] == "miner" and action["skill"] == "mining")
) and rng.random() < LUCKY_CHANCE + gear_bonus(player, "masterwork") / 100 ) and rng.random() < LUCKY_CHANCE
outputs = {item: qty * (2 if lucky else 1) for item, qty in action["outputs"].items()} outputs = {item: qty * (2 if lucky else 1) for item, qty in action["outputs"].items()}
if lucky: if lucky:
summary["lucky"] += 1 summary["lucky"] += 1
@ -2072,7 +2073,9 @@ def _complete(world: dict, player: dict, action: dict, now: float, rng: random.R
for item, qty in action["outputs"].items(): for item, qty in action["outputs"].items():
outputs[item] += qty outputs[item] += qty
summary["lucky"] += 1 summary["lucky"] += 1
if action["skill"] in GATHERING and rng.random() < gear_bonus(player, "double") / 100: # Lucky charm doubles gathering, loupe doubles crafting, for any class.
jewel = "double" if action["skill"] in GATHERING else "craft_double" if action["skill"] in CRAFTING else None
if jewel and action["outputs"] and rng.random() < gear_bonus(player, jewel) / 100:
for item, qty in action["outputs"].items(): for item, qty in action["outputs"].items():
outputs[item] += qty outputs[item] += qty
summary["lucky"] += 1 summary["lucky"] += 1

View file

@ -1723,6 +1723,20 @@ class JewelcraftingTest(unittest.TestCase):
p["equip"]["ring"] = "garnet_might" p["equip"]["ring"] = "garnet_might"
self.assertEqual(game.combat_power(p), game.combat_power({**p, "equip": {**p["equip"], "ring": None}}) + 4) self.assertEqual(game.combat_power(p), game.combat_power({**p, "equip": {**p["equip"], "ring": None}}) + 4)
def test_loupe_doubles_any_class_crafts(self):
self.ann["xp"]["jewelcrafting"] = game.XP_TABLE[15]
for loupe, rings in ((None, 1), ("quartz_loupe", 2)):
self.ann["equip"]["trinket"] = loupe
self.ann["bank"] = {"copper_bar": 1, "quartz": 1}
game.start_action(self.world, self.ann, "jc_quartz_haste", T0)
game.process_player(self.world, self.ann, T0 + 1000, Always())
self.assertEqual(self.ann["bank"], {"quartz_haste": rings})
def test_jewellery_tabs_by_slot(self):
self.assertEqual(game.ACTION_BY_ID["jc_garnet_haste"]["group"], "Rings")
self.assertEqual(game.ACTION_BY_ID["jc_diamond_warding"]["group"], "Amulets")
self.assertEqual(game.ACTION_BY_ID["jc_quartz_loupe"]["group"], "Trinkets")
def test_miners_cut_jewels(self): def test_miners_cut_jewels(self):
self.assertIn("jewelcrafting", game.CLASSES["miner"]["fast"]) self.assertIn("jewelcrafting", game.CLASSES["miner"]["fast"])
self.assertIn("jewelcrafting", game.CLASSES["miner"]["craft"]) self.assertIn("jewelcrafting", game.CLASSES["miner"]["craft"])