Raids: alarm at the gates, battle replay and report popup for everyone
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
fcaabf5ae2
commit
a35bcd6512
5 changed files with 146 additions and 7 deletions
39
game.py
39
game.py
|
|
@ -743,7 +743,7 @@ RAID_TRAITS = ["lightly armoured, heavy on archers", "mounted and fast, few bows
|
|||
|
||||
# --- world ---------------------------------------------------------------
|
||||
|
||||
WORLD_VERSION = 14
|
||||
WORLD_VERSION = 15
|
||||
|
||||
|
||||
def new_world(now: float, speed: float = 1.0) -> dict:
|
||||
|
|
@ -872,9 +872,29 @@ def migrate(world: dict, now: float | None = None) -> dict:
|
|||
stop(player, "Shields are made in Construction now")
|
||||
_return_overfunding(world)
|
||||
world["version"] = 14
|
||||
if world["version"] < 15:
|
||||
# Raid reports pop up once for everyone; ones from before that don't.
|
||||
latest = last_threat(world)
|
||||
for player in world["players"].values():
|
||||
player["seen_threat"] = latest["id"] if latest else 0
|
||||
world["version"] = 15
|
||||
return world
|
||||
|
||||
|
||||
def last_threat(world: dict) -> dict | None:
|
||||
"""The most recent raid or dragon attack that has played out."""
|
||||
return next((e for e in reversed(world["events"]) if e["status"] == "done" and e["kind"] in THREATS
|
||||
and e.get("report")), None)
|
||||
|
||||
|
||||
def unseen_threat(world: dict, player: dict) -> dict | None:
|
||||
"""The latest raid or dragon attack this player hasn't watched yet, if it happened while they were here."""
|
||||
event = last_threat(world)
|
||||
if event and event["id"] > player.get("seen_threat", 0) and event["resolved"] >= player["joined"]:
|
||||
return event
|
||||
return None
|
||||
|
||||
|
||||
def _migrate_potions(world: dict) -> None:
|
||||
"""Potions from before the tiers become their tiered equivalents, uses and all."""
|
||||
def renamed(stock: dict) -> None:
|
||||
|
|
@ -2221,16 +2241,22 @@ SIZE_LABELS = [(30, "small"), (60, "medium"), (100, "large"), (math.inf, "huge")
|
|||
WINDOW_HOURS = [24, 8, 2, 0]
|
||||
|
||||
|
||||
THREATS = ("raid", "dragon")
|
||||
GATES_WARNING = 300 # seconds of warning before a threat strikes
|
||||
|
||||
|
||||
def event_view(world: dict, event: dict, now: float) -> dict:
|
||||
"""What the city knows about an upcoming event, filtered by intel."""
|
||||
level = intel_level(world, event, now)
|
||||
half = WINDOW_HOURS[level] * HOUR / speed(world)
|
||||
costs = intel_costs(event) if "strength" in event else [0, 0, 0]
|
||||
centre = event["fire_at"] + event["jitter"] * half * 0.6
|
||||
# In the last few minutes the sentries see them coming, whatever the intel.
|
||||
gates = event["kind"] in THREATS and event["fire_at"] - now <= GATES_WARNING / speed(world)
|
||||
view = {
|
||||
"id": event["id"], "kind": event["kind"], "intel": level,
|
||||
"id": event["id"], "kind": event["kind"], "intel": level, "at_gates": gates,
|
||||
"earliest": max(now, centre - half), "latest": max(now, centre + half),
|
||||
"exact": event["fire_at"] if level >= 3 else None,
|
||||
"exact": event["fire_at"] if level >= 3 or gates else None,
|
||||
"recon": int(min(event["recon"], costs[-1])), "recon_full": costs[-1],
|
||||
"recon_next": max(0, round(costs[level] - event["recon"])) if level < 3 else 0,
|
||||
}
|
||||
|
|
@ -2250,6 +2276,8 @@ def event_view(world: dict, event: dict, now: float) -> dict:
|
|||
view["strength"] = round(strength)
|
||||
view["trait"] = event["trait"]
|
||||
view["requests"] = council_requests(world, event)
|
||||
if gates:
|
||||
view["headline"] = "The dragon is overhead!"
|
||||
elif event["kind"] == "raid":
|
||||
strength = event["strength"] * (1 - event["sabotage"])
|
||||
view["headline"] = "Travellers speak of trouble on the roads."
|
||||
|
|
@ -2269,6 +2297,8 @@ def event_view(world: dict, event: dict, now: float) -> dict:
|
|||
view["camp"] = True
|
||||
view["sabotage"] = round(event["sabotage"] * 100)
|
||||
view["requests"] = council_requests(world, event)
|
||||
if gates:
|
||||
view["headline"] = "Raiders at the gates!"
|
||||
elif event["kind"] == "merchant":
|
||||
view["headline"] = "A merchant caravan is on its way. Sell prices +50% while it's here."
|
||||
else:
|
||||
|
|
@ -2378,7 +2408,8 @@ def _resolve_raid(world: dict, event: dict, now: float, rng: random.Random) -> N
|
|||
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 "."))
|
||||
loot = [f"{gold:,} gold"] if gold else []
|
||||
lines.append("The raiders carried off " + " and ".join(loot + ([stolen] if stolen else [])) + ".")
|
||||
built = [key for key, b in city["buildings"].items() if b["level"] and not b["damaged"]]
|
||||
if severity > 0.35 and built:
|
||||
key = rng.choice(built)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue