Rings of precision become rings of haste
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
6883b707ad
commit
5facec9ee8
4 changed files with 43 additions and 12 deletions
|
|
@ -48,7 +48,7 @@ Alpha. One season, bandit raids plus two friendly events, six buildings.
|
||||||
diamond), and jewellers prospect spare ore for more (5 ore, 15%). Bands
|
diamond), and jewellers prospect spare ore for more (5 ore, 15%). Bands
|
||||||
are copper, then silver and gold, which the jeweller mines and smelts
|
are copper, then silver and gold, which the jeweller mines and smelts
|
||||||
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 (accuracy, 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).
|
masterwork).
|
||||||
- **Tiers:** six RuneScape-style tiers (copper, iron, steel, mithril,
|
- **Tiers:** six RuneScape-style tiers (copper, iron, steel, mithril,
|
||||||
|
|
|
||||||
33
game.py
33
game.py
|
|
@ -221,7 +221,7 @@ PROSPECT = ["stone", "iron_ore", "coal", "mithril_ore", "adamantite_ore", "runit
|
||||||
# Rings for offence, amulets for defence, trinkets for everything else.
|
# Rings for offence, amulets for defence, trinkets for everything else.
|
||||||
# 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 = [
|
||||||
("precision", "ring", "ring of precision", "accuracy", [4, 8, 12, 16, 20, 25], 1, 1, 1, 2, "diamond-ring"),
|
("haste", "ring", "ring of haste", "haste", [5, 7, 8, 9, 10, 11], 1, 1, 1, 2, "diamond-ring"),
|
||||||
("fury", "ring", "ring of fury", "crit", [2, 3, 5, 6, 8, 10], 1, 1, 1, 2, "big-diamond-ring"),
|
("fury", "ring", "ring of fury", "crit", [2, 3, 5, 6, 8, 10], 1, 1, 1, 2, "big-diamond-ring"),
|
||||||
("might", "ring", "ring of might", "power", [2, 4, 6, 9, 12, 15], 1, 1, 1, 2, "swirl-ring"),
|
("might", "ring", "ring of might", "power", [2, 4, 6, 9, 12, 15], 1, 1, 1, 2, "swirl-ring"),
|
||||||
("vigour", "amulet", "amulet of vigour", "hp", [5, 10, 16, 23, 31, 40], 2, 1, 2, 8, "heart-necklace"),
|
("vigour", "amulet", "amulet of vigour", "hp", [5, 10, 16, 23, 31, 40], 2, 1, 2, 8, "heart-necklace"),
|
||||||
|
|
@ -231,9 +231,9 @@ JEWELLERY = [
|
||||||
("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", "masterwork", [2, 3, 4, 4, 5, 6], 1, 2, 1, 5, "magnifying-glass"),
|
||||||
]
|
]
|
||||||
# How each jewellery bonus reads: name, unit, glyph, tint.
|
# How each jewellery bonus reads: name, unit, glyph, tint (and a sign, if it takes away).
|
||||||
JEWEL_STATS = {
|
JEWEL_STATS = {
|
||||||
"accuracy": {"name": "Accuracy", "unit": "%", "glyph": "archery-target", "tint": "#e0b04a"},
|
"haste": {"name": "Swing time", "unit": "%", "glyph": "stopwatch", "tint": "#9fb3c8", "sign": "-"},
|
||||||
"crit": {"name": "Crit chance", "unit": "%", "glyph": "striking-diamonds", "tint": "#e05a5a"},
|
"crit": {"name": "Crit chance", "unit": "%", "glyph": "striking-diamonds", "tint": "#e05a5a"},
|
||||||
"power": {"name": "Power", "unit": "", "glyph": "biceps", "tint": "#e06a6a"},
|
"power": {"name": "Power", "unit": "", "glyph": "biceps", "tint": "#e06a6a"},
|
||||||
"hp": {"name": "Max HP", "unit": "", "glyph": "hearts", "tint": "#e06a8a"},
|
"hp": {"name": "Max HP", "unit": "", "glyph": "hearts", "tint": "#e06a8a"},
|
||||||
|
|
@ -1033,7 +1033,7 @@ RAID_TRAITS = ["lightly armoured, heavy on archers", "mounted and fast, few bows
|
||||||
|
|
||||||
# --- world ---------------------------------------------------------------
|
# --- world ---------------------------------------------------------------
|
||||||
|
|
||||||
WORLD_VERSION = 19
|
WORLD_VERSION = 20
|
||||||
|
|
||||||
|
|
||||||
def new_world(now: float, speed: float = 1.0) -> dict:
|
def new_world(now: float, speed: float = 1.0) -> dict:
|
||||||
|
|
@ -1187,9 +1187,29 @@ def migrate(world: dict, now: float | None = None) -> dict:
|
||||||
for player in world["players"].values():
|
for player in world["players"].values():
|
||||||
player.setdefault("pinned", [])
|
player.setdefault("pinned", [])
|
||||||
world["version"] = 19
|
world["version"] = 19
|
||||||
|
if world["version"] < 20:
|
||||||
|
_precision_to_haste(world)
|
||||||
|
world["version"] = 20
|
||||||
return world
|
return world
|
||||||
|
|
||||||
|
|
||||||
|
def _precision_to_haste(world: dict) -> None:
|
||||||
|
"""Rings of precision became rings of haste: same gem, same slot."""
|
||||||
|
rename = lambda item: item.replace("_precision", "_haste") if item and item.endswith("_precision") else item
|
||||||
|
swap = lambda items: {rename(item): qty for item, qty in items.items()}
|
||||||
|
world["city"]["stockpile"] = swap(world["city"]["stockpile"])
|
||||||
|
for player in world["players"].values():
|
||||||
|
player["bank"] = swap(player["bank"])
|
||||||
|
player["equip"] = {slot: rename(item) for slot, item in player["equip"].items()}
|
||||||
|
action = player["action"]
|
||||||
|
if action:
|
||||||
|
for key in ("id", "resume"):
|
||||||
|
if key in action:
|
||||||
|
action[key] = rename(action[key])
|
||||||
|
for order in world.get("orders", []):
|
||||||
|
order["item"], order["action"] = rename(order["item"]), rename(order["action"])
|
||||||
|
|
||||||
|
|
||||||
# The combat rework: the old area monsters became races, their trophies carry over by
|
# The combat rework: the old area monsters became races, their trophies carry over by
|
||||||
# weapon line and tier, dragonhide became the dragons' reagents, and the leathers past
|
# weapon line and tier, dragonhide became the dragons' reagents, and the leathers past
|
||||||
# bear are hunted now.
|
# bear are hunted now.
|
||||||
|
|
@ -1578,8 +1598,9 @@ def fighter(player: dict, blessed: dict, brew: dict, enemy: dict | None = None)
|
||||||
damage = style["hit"] * (CLASS_STYLE_BONUS if CLASS_STYLE.get(player["cls"]) == style_id else 1)
|
damage = style["hit"] * (CLASS_STYLE_BONUS if CLASS_STYLE.get(player["cls"]) == style_id else 1)
|
||||||
lands = affinity(enemy, style["damage"]) if enemy else 1
|
lands = affinity(enemy, style["damage"]) if enemy else 1
|
||||||
taken = blessed["taken"] * (1 - brew.get("reduce", 0))
|
taken = blessed["taken"] * (1 - brew.get("reduce", 0))
|
||||||
return {"style": style_id, "damage": style["damage"], "interval": style["interval"],
|
return {"style": style_id, "damage": style["damage"],
|
||||||
"accuracy": (10 + 2 * power) * style["accuracy"] * (1 + gear_bonus(player, "accuracy") / 100),
|
"interval": round(style["interval"] * (1 - gear_bonus(player, "haste") / 100), 2),
|
||||||
|
"accuracy": (10 + 2 * power) * style["accuracy"],
|
||||||
"max_hit": max(2, round((2 + power / 3) * damage * lands)),
|
"max_hit": max(2, round((2 + power / 3) * damage * lands)),
|
||||||
"crit": style["crit"] + gear_bonus(player, "crit") / 100,
|
"crit": style["crit"] + gear_bonus(player, "crit") / 100,
|
||||||
"first": style.get("first", False), "affinity": lands,
|
"first": style.get("first", False), "affinity": lands,
|
||||||
|
|
|
||||||
|
|
@ -670,7 +670,7 @@ function animateFight(now) {
|
||||||
// A piece of jewellery's bonus, e.g. "+8% accuracy".
|
// A piece of jewellery's bonus, e.g. "+8% accuracy".
|
||||||
function jewelText(it) {
|
function jewelText(it) {
|
||||||
const st = C.jewel_stats[it.jewel];
|
const st = C.jewel_stats[it.jewel];
|
||||||
return `+${it[it.jewel]}${st.unit} ${st.name.toLowerCase()}`;
|
return `${st.sign || "+"}${it[it.jewel]}${st.unit} ${st.name.toLowerCase()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Power, armour and ward, and the armour's weight.
|
// Power, armour and ward, and the armour's weight.
|
||||||
|
|
@ -703,7 +703,7 @@ function gearChips(it, who = S.me) {
|
||||||
cell(glyph("magic-shield", "#a77be8"), it.ward, `+${it.ward} ward: less damage from magic`)];
|
cell(glyph("magic-shield", "#a77be8"), it.ward, `+${it.ward} ward: less damage from magic`)];
|
||||||
} else if (it.jewel) {
|
} else if (it.jewel) {
|
||||||
const st = C.jewel_stats[it.jewel];
|
const st = C.jewel_stats[it.jewel];
|
||||||
row = [cell(glyph(st.glyph, st.tint), `+${it[it.jewel]}${st.unit}`, jewelText(it)), "<span></span>", "<span></span>"];
|
row = [cell(glyph(st.glyph, st.tint), `${st.sign || "+"}${it[it.jewel]}${st.unit}`, jewelText(it)), "<span></span>", "<span></span>"];
|
||||||
}
|
}
|
||||||
let req = "", bonus = "";
|
let req = "", bonus = "";
|
||||||
if (it.req) {
|
if (it.req) {
|
||||||
|
|
|
||||||
|
|
@ -1650,7 +1650,7 @@ class JewelcraftingTest(unittest.TestCase):
|
||||||
self.ann = self.world["players"]["ann"]
|
self.ann = self.world["players"]["ann"]
|
||||||
|
|
||||||
def test_recipes_take_a_band_a_gem_and_past_quartz_a_shard(self):
|
def test_recipes_take_a_band_a_gem_and_past_quartz_a_shard(self):
|
||||||
self.assertEqual(game.ACTION_BY_ID["jc_quartz_precision"]["inputs"], {"copper_bar": 1, "quartz": 1})
|
self.assertEqual(game.ACTION_BY_ID["jc_quartz_haste"]["inputs"], {"copper_bar": 1, "quartz": 1})
|
||||||
self.assertEqual(game.ACTION_BY_ID["jc_garnet_vigour"]["inputs"],
|
self.assertEqual(game.ACTION_BY_ID["jc_garnet_vigour"]["inputs"],
|
||||||
{"silver_bar": 2, "garnet": 1, "watchtower_shard": 2})
|
{"silver_bar": 2, "garnet": 1, "watchtower_shard": 2})
|
||||||
self.assertEqual(game.ACTION_BY_ID["jc_diamond_charm"]["inputs"],
|
self.assertEqual(game.ACTION_BY_ID["jc_diamond_charm"]["inputs"],
|
||||||
|
|
@ -1679,9 +1679,9 @@ class JewelcraftingTest(unittest.TestCase):
|
||||||
p["xp"]["combat"] = game.XP_TABLE[20]
|
p["xp"]["combat"] = game.XP_TABLE[20]
|
||||||
before = game.fighter(p, game.NO_BLESSING, {})
|
before = game.fighter(p, game.NO_BLESSING, {})
|
||||||
hp = game.max_hp(p)
|
hp = game.max_hp(p)
|
||||||
p["equip"].update(ring="garnet_precision", amulet="garnet_vigour", trinket="garnet_hourglass")
|
p["equip"].update(ring="garnet_haste", amulet="garnet_vigour", trinket="garnet_hourglass")
|
||||||
after = game.fighter(p, game.NO_BLESSING, {})
|
after = game.fighter(p, game.NO_BLESSING, {})
|
||||||
self.assertAlmostEqual(after["accuracy"], before["accuracy"] * 1.08)
|
self.assertAlmostEqual(after["interval"], round(before["interval"] * 0.93, 2))
|
||||||
self.assertEqual(game.max_hp(p), hp + 10)
|
self.assertEqual(game.max_hp(p), hp + 10)
|
||||||
game.gain_xp(self.world, p, "mining", 100, T0)
|
game.gain_xp(self.world, p, "mining", 100, T0)
|
||||||
self.assertAlmostEqual(p["xp"]["mining"], 103)
|
self.assertAlmostEqual(p["xp"]["mining"], 103)
|
||||||
|
|
@ -1707,3 +1707,13 @@ class JewelcraftingTest(unittest.TestCase):
|
||||||
self.assertEqual(self.ann["xp"]["jewelcrafting"], 0)
|
self.assertEqual(self.ann["xp"]["jewelcrafting"], 0)
|
||||||
self.assertEqual({slot: self.ann["equip"][slot] for slot in ("ring", "amulet", "trinket")},
|
self.assertEqual({slot: self.ann["equip"][slot] for slot in ("ring", "amulet", "trinket")},
|
||||||
{"ring": None, "amulet": None, "trinket": None})
|
{"ring": None, "amulet": None, "trinket": None})
|
||||||
|
|
||||||
|
def test_rings_of_precision_became_haste(self):
|
||||||
|
self.world["version"] = 19
|
||||||
|
self.ann["bank"] = {"garnet_precision": 2, "garnet": 1}
|
||||||
|
self.ann["equip"]["ring"] = "quartz_precision"
|
||||||
|
self.ann["action"] = {"id": "jc_garnet_precision", "start": T0}
|
||||||
|
game.migrate(self.world, T0)
|
||||||
|
self.assertEqual(self.ann["bank"], {"garnet_haste": 2, "garnet": 1})
|
||||||
|
self.assertEqual(self.ann["equip"]["ring"], "quartz_haste")
|
||||||
|
self.assertEqual(self.ann["action"]["id"], "jc_garnet_haste")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue