Raids wear out the armoury: issued swords and shields break, worst first

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-19 14:51:05 +01:00
parent f2e4ea39de
commit 366a195c6d
4 changed files with 91 additions and 10 deletions

65
game.py
View file

@ -1448,6 +1448,45 @@ def kit_count(stock: dict, slot: str) -> int:
return sum(qty for item, qty in stock.items() if ITEMS[item].get("slot") == slot)
def wear_share(ratio: float) -> float:
"""Share of the issued swords and shields a fight breaks: 10% in an easy win, 30% in a close one, 40% in defeat."""
if ratio < 1:
return 0.4
return 0.1 + 0.2 * max(0.0, 2 - ratio)
def wear_kit(world: dict, share: float, rng: random.Random) -> dict:
"""Break `share` of the swords and shields handed to defenders, worst first. Spares are untouched."""
stock = world["city"]["stockpile"]
broken = {}
for slot, stat in (("weapon", "power"), ("shield", "armour")):
people = int(world["city"]["population"] // 2)
issued = []
for item in sorted((i for i in stock if ITEMS[i].get("slot") == slot), key=lambda i: -ITEMS[i].get(stat, 0)):
take = min(people, stock[item])
issued.append((item, take))
people -= take
if not people:
break
breaks = int(sum(take for _item, take in issued) * share + rng.random())
for item, take in reversed(issued):
lost = min(breaks, take)
if lost:
take_stock_qty(stock, item, lost)
broken[item] = lost
breaks -= lost
return broken
def _wear_lines(broken: dict, arrows: int) -> list[str]:
lines = []
if arrows:
lines.append(f"The defenders used {amount('arrows', arrows)}.")
if broken:
lines.append(f"{upper_first(_listing(broken))} {'was' if sum(broken.values()) == 1 else 'were'} broken in the fighting.")
return lines
def equip_problem(player: dict, item: str) -> str | None:
"""Why the player can't wear or use this yet, or None."""
req = ITEMS[item].get("req")
@ -1752,9 +1791,11 @@ def _resolve_raid(world: dict, event: dict, now: float, rng: random.Random) -> N
loot = round(attack * 2)
city["treasury"] += loot
city["repelled"] += 1
_use_up(stock, "arrows", 0.3)
broken = wear_kit(world, wear_share(ratio), rng)
arrows = _use_up(stock, "arrows", 0.3)
report["title"] = f"Victory! {upper_first(event["group"])} were driven off"
lines.append(f"The raiders fled and left {loot} gold worth of plunder behind.")
lines.extend(_wear_lines(broken, arrows))
for entry in garrison:
player = world["players"][entry["id"]]
player["favour"] += 25
@ -1764,6 +1805,7 @@ def _resolve_raid(world: dict, event: dict, now: float, rng: random.Random) -> N
log(world, now, "raid", f"{upper_first(event["group"])} were driven off")
else:
severity = min(1.0, 1 - ratio)
broken = wear_kit(world, wear_share(ratio), rng)
shelter = 1 - 0.08 * building_level(world, "walls")
lost_people = int(city["population"] * (0.1 + 0.3 * severity))
city["population"] = max(5.0, city["population"] - lost_people)
@ -1778,9 +1820,10 @@ def _resolve_raid(world: dict, event: dict, now: float, rng: random.Random) -> N
del stock[item]
gold = int(city["treasury"] * share)
city["treasury"] -= gold
_use_up(stock, "arrows", 0.6)
arrows = _use_up(stock, "arrows", 0.6)
report["title"] = f"Defeat. {upper_first(event["group"])} broke into the city"
lines.append(f"{lost_people} townsfolk were killed or fled.")
lines.extend(_wear_lines(broken, arrows))
if gold or taken:
stolen = ", ".join(amount(item, qty) for item, qty in taken.items())
lines.append(f"The raiders carried off {gold:,} gold" + (f" and {stolen}." if stolen else "."))
@ -1817,9 +1860,11 @@ def _resolve_dragon(world: dict, event: dict, now: float, rng: random.Random) ->
if guard["total"] >= attack:
city["repelled"] += 1
city["treasury"] += round(attack)
_use_up(stock, "arrows", 0.5)
broken = wear_kit(world, min(0.5, wear_share(guard["total"] / attack) * 1.25), rng)
arrows = _use_up(stock, "arrows", 0.5)
report["title"] = f"Victory! {upper_first(event['group'])} was driven off"
lines.append(f"It fled wounded, dropping a hoard worth {round(attack):,} gold.")
lines.extend(_wear_lines(broken, arrows))
for entry in garrison:
player = world["players"][entry["id"]]
player["favour"] += 40
@ -1831,15 +1876,17 @@ def _resolve_dragon(world: dict, event: dict, now: float, rng: random.Random) ->
log(world, now, "raid", report["title"])
else:
severity = min(1.0, 1 - guard["total"] / attack)
broken = wear_kit(world, 0.5, rng)
lost = int(city["population"] * (0.05 + 0.15 * severity))
city["population"] = max(5.0, city["population"] - lost)
share = min(0.8, 0.2 + 0.4 * severity)
burned = _take_share(stock, WOOD, share * (1 - 0.25 * building_level(world, "lumber_shed")))
burned.update(_take_share(stock, _food_items(stock), share * (1 - 0.25 * building_level(world, "granary"))))
name = _damage_building(world, now, rng)
_use_up(stock, "arrows", 0.7)
arrows = _use_up(stock, "arrows", 0.7)
report["title"] = f"{upper_first(event['group'])} set the city ablaze"
lines.append(f"{lost} townsfolk were lost in the flames.")
lines.extend(_wear_lines(broken, arrows))
if burned:
lines.append(f"It burned {_listing(burned)}.")
if name:
@ -1865,11 +1912,11 @@ def _work_problem(world: dict, player: dict, action: dict, now: float) -> None:
_solve(world, problem, now, player["name"])
def _use_up(stock: dict, item: str, share: float) -> None:
if stock.get(item):
stock[item] -= int(stock[item] * share)
if not stock[item]:
del stock[item]
def _use_up(stock: dict, item: str, share: float) -> int:
used = int(stock.get(item, 0) * share)
if used:
take_stock_qty(stock, item, used)
return used
# --- happenings: weather, seasons, hardships, fortunes, minor threats ---------