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:
parent
f2e4ea39de
commit
366a195c6d
4 changed files with 91 additions and 10 deletions
65
game.py
65
game.py
|
|
@ -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 ---------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue