diff --git a/README.md b/README.md index 665a22a..5b89d68 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,8 @@ Alpha. One season, bandit raids plus two friendly events, six buildings. - **Events:** raids are announced a day or two ahead. Reconnaissance from scouts, informants and the spymaster's guild narrows down when, how strong, and finally where their camp is, so fighters can sabotage it. Fighters can - also drill the militia before any raid, up to half a defence per - townsperson. Favour never buys results directly: master builders, + also drill the militia before any raid: a full drill takes as much work as + full intel on that threat and adds one defence per townsperson. Favour never buys results directly: master builders, informants and drill sergeants double what players put in for a while, XP included. Each intel level takes more scouting than the last and more for a bigger band, and diff --git a/game.py b/game.py index 9618f2b..3b9ce94 100644 --- a/game.py +++ b/game.py @@ -371,7 +371,7 @@ ACTIONS = [ _action("cook_meat", "cooking", "Cooked meat", 5, 20, 7, {"raw_meat": 1, "logs": 1}, {"cooked_meat": 1}), _action("cook_stew", "cooking", "Stew", 15, 35, 16, {"raw_meat": 1, "vegetables": 2, "logs": 1}, {"stew": 2}), - _action("drill_militia", "combat", "Drill the militia", 1, 50, 10, special="drill"), + _action("drill_militia", "combat", "Drill the militia", 1, 60, 12, special="drill"), _action("fight_wolves", "combat", "Wolves in the woods", 1, 60, 14, difficulty=2, damage=2, gold=2, drops=[["raw_meat", 1, 0.6], ["hide", 1, 0.5], ["bones", 1, 1.0]]), _action("fight_scouts", "combat", "Bandit scouts", 10, 75, 30, difficulty=14, damage=6, gold=10, @@ -1021,7 +1021,7 @@ def can_start(world: dict, player: dict, action: dict) -> str | None: threat = upcoming_raid(world) if not threat: return "No raid to prepare for" - if threat.get("drilled", 0) >= drill_cap(world): + if threat.get("drilled", 0) >= drill_needed(threat): return "The militia is as drilled as it can be" if "difficulty" in action and player["hp"] < max_hp(player) * TOO_HURT and not edible(player): return "Too hurt to fight. Rest, or bring food" @@ -1120,7 +1120,7 @@ def _complete(world: dict, player: dict, action: dict, now: float, rng: random.R boost = min(city.get("drill_boost", 0), gain) city["drill_boost"] = city.get("drill_boost", 0) - boost xp = DRILL_XP * (gain + boost) * mods["xp"] - threat["drilled"] = min(drill_cap(world), threat.get("drilled", 0) + gain + boost) + threat["drilled"] = min(drill_needed(threat), threat.get("drilled", 0) + gain + boost) if action.get("special") == "labour": city = world["city"] site = city["construction"] @@ -2510,17 +2510,22 @@ def happenings_view(world: dict, now: float) -> dict: MASTER_BUILDER_LABOUR = 50 # boosted labour per hire: doubled labour and XP for whoever works it MASTER_BUILDER_FAVOUR = 150 # +50% for every further hire on the same site INFORMANT_FAVOUR = 250 -# Drilling: fighters train the townsfolk before a raid, a little defence per -# session (more for a seasoned fighter), up to half the population. -DRILL_PER_ACTION = 0.5 # plus as much again every 20 combat levels -DRILL_XP = 20 # combat XP per point of defence drilled -DRILL_SHARE = 0.5 # most drilled defence: this much per townsperson -DRILL_SERGEANT_POINTS = 15 # boosted drilling per hire: doubled defence and XP +# Drilling: fighters train the townsfolk before a raid. A full drill takes as +# many points as full intel on the same threat, so fighters and scouts put in +# about the same time; the defence it's worth grows with the population. +DRILL_PER_ACTION = 1.0 # drill points per session, +25% every 20 combat levels +DRILL_XP = 12 # combat XP per drill point +DRILL_SHARE = 1.0 # defence of a fully drilled militia, per townsperson +DRILL_SERGEANT_POINTS = 30 # boosted drilling per hire: doubled points and XP DRILL_SERGEANT_FAVOUR = 150 # +50% for every further hire against the same threat def drill_per_action(player: dict) -> float: - return DRILL_PER_ACTION * (1 + skill_level(player, "combat") // 20) + return DRILL_PER_ACTION * (1 + 0.25 * (skill_level(player, "combat") // 20)) + + +def drill_needed(threat: dict) -> int: + return intel_costs(threat)[-1] def drill_cap(world: dict) -> float: @@ -2528,8 +2533,11 @@ def drill_cap(world: dict) -> float: def drilled(world: dict) -> float: + """Defence the drilling is worth so far: a share of the cap, as far as the drill has got.""" threat = upcoming_raid(world) - return min(drill_cap(world), threat.get("drilled", 0)) if threat else 0 + if not threat: + return 0 + return drill_cap(world) * min(1.0, threat.get("drilled", 0) / drill_needed(threat)) def favours(world: dict, now: float) -> dict: @@ -2556,10 +2564,11 @@ def favours(world: dict, now: float) -> dict: "sergeant": { "cost": round(DRILL_SERGEANT_FAVOUR * (1 + 0.5 * (raid or {}).get("sergeants", 0))), "why": "No raid to prepare for" if not raid else - "The militia is as drilled as it can be" if drilled(world) >= drill_cap(world) else None, + "The militia is as drilled as it can be" if raid.get("drilled", 0) >= drill_needed(raid) else None, "drill": DRILL_SERGEANT_POINTS, "boost": round(city.get("drill_boost", 0), 1), - "drilled": round(drilled(world), 1), "cap": drill_cap(world), + "points": int((raid or {}).get("drilled", 0)), "needed": drill_needed(raid) if raid else 0, + "defence": round(drilled(world), 1), "cap": drill_cap(world), }, } @@ -2593,7 +2602,7 @@ def spend_favour(world: dict, player: dict, kind: str, now: float) -> str | None raid["sergeants"] = raid.get("sergeants", 0) + 1 city["drill_boost"] = city.get("drill_boost", 0) + DRILL_SERGEANT_POINTS log(world, now, "favour", f"{player['name']} hired a drill sergeant: the next {DRILL_SERGEANT_POINTS} " - f"drilled defence counts double", player["id"]) + f"drill points count double", player["id"]) return None diff --git a/static/app.js b/static/app.js index 4033dae..38bad07 100644 --- a/static/app.js +++ b/static/app.js @@ -495,8 +495,9 @@ function actionCard(a) { } if (a.special === "drill") { const s = S.favours.sergeant, boost = s.boost; - extra = `