Townsfolk mood: needs, kindling, clothes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
002e990dc4
commit
58a3e66454
6 changed files with 385 additions and 30 deletions
220
game.py
220
game.py
|
|
@ -514,6 +514,38 @@ for _tier, (_id, _name, *_rest) in enumerate(HERBS):
|
|||
ITEMS[f"{_id}_salve"] = {"name": f"{_name.removesuffix(' leaf')} salve", "plural": f"{_name.removesuffix(' leaf')} salves", "icon": "🩹", "kit": "salve",
|
||||
"tier": _tier, "mend": _tier + 1, "value": 0}
|
||||
|
||||
# --- townsfolk needs -----------------------------------------------------------
|
||||
# The townsfolk's mood (0 to 1) follows what the city gives them, a little each hour.
|
||||
# Each settlement tier adds a need: a hamlet only wants food, a village firewood too,
|
||||
# a town clothes for the season as well.
|
||||
NEEDS = {"food": 0.4, "warmth": 0.3, "clothes": 0.3} # weight in the mood
|
||||
NEEDS_BY_TIER = [("food",), ("food", "warmth"), ("food", "warmth", "clothes"), ("food", "warmth", "clothes")]
|
||||
FOOD_DAYS = [2, 4, 7, 10] # days of food stored for full marks, by tier
|
||||
FOOD_KINDS = 4 # kinds of food, each enough for a day, for full marks
|
||||
FUEL_DAYS = 2 # days of fuel stored for full marks
|
||||
HEARTH_BURN = [0.01, 0.005, 0.015, 0.025] # fuel per person per game hour, spring to winter
|
||||
KINDLING_YIELD = [2, 3, 4, 5, 6, 8] # from one log of each tier
|
||||
KINDLING_XP = [3, 7, 15, 23, 33, 45]
|
||||
SEASON_CLOTHES = ["smock", "smock", "cloak", "cloak"] # each person wears out one a season
|
||||
MOOD_DRIFT = 0.5 / 48 # per game hour: Content to either end in two days
|
||||
PROBLEM_MOOD = 0.05 # off the mood for each minor threat left unsolved
|
||||
# Sudden news moves the mood at once, then it drifts back.
|
||||
MOOD_SHOCKS = {"raid_won": 0.05, "raid_lost": -0.2, "dragon_won": 0.05, "dragon_lost": -0.25,
|
||||
"fire": -0.05, "storm": -0.05, "festival": 0.1, "trouble": -0.03}
|
||||
# Up to `below`: name, icon, growth, guards (militia), townsfolk labour and taxes, leaving a day
|
||||
MOODS = [
|
||||
{"below": 0.2, "name": "Miserable", "icon": "😠", "growth": 0, "militia": 0.8, "work": 0.5, "leave": 0.03},
|
||||
{"below": 0.4, "name": "Unhappy", "icon": "😟", "growth": 0, "militia": 0.9, "work": 0.8, "leave": 0},
|
||||
{"below": 0.6, "name": "Content", "icon": "😐", "growth": 1.0, "militia": 1.0, "work": 1.0, "leave": 0},
|
||||
{"below": 0.8, "name": "Happy", "icon": "🙂", "growth": 1.1, "militia": 1.05, "work": 1.1, "leave": 0},
|
||||
{"below": 9.0, "name": "Joyful", "icon": "😄", "growth": 1.25, "militia": 1.1, "work": 1.25, "leave": 0},
|
||||
]
|
||||
TROUBLE_ODDS = 1 / 24 # an hour, while Miserable: about once a day
|
||||
FUEL = ("kindling", *FIREWOOD) # what the hearths burn, kindling first; one each an hour's warmth
|
||||
ITEMS["kindling"] = {"name": "Kindling", "plural": "Kindling", "icon": "🪵", "value": 1}
|
||||
ITEMS["smock"] = {"name": "Summer smock", "plural": "Summer smocks", "icon": "👕", "value": 12}
|
||||
ITEMS["cloak"] = {"name": "Winter cloak", "plural": "Winter cloaks", "icon": "🧥", "value": 24}
|
||||
|
||||
# Health potions: the one heal you take into a fight, drunk once a fight when you're low.
|
||||
# Each tier is the tier's herb and its cooked fish.
|
||||
HEALTH_HEAL = [20, 40, 65, 90, 115, 140]
|
||||
|
|
@ -1367,6 +1399,12 @@ ACTIONS = [
|
|||
*[_action(f"tend_all_{log}", "firekeeping", "Tend all fires", TIER_LEVELS[tier], 30, 0,
|
||||
{log: 1}, special="tend", tend=None, tier=tier, group="All fires")
|
||||
for tier, log in enumerate(FIREWOOD)],
|
||||
# Kindling for the townsfolk's hearths: better logs split into more.
|
||||
*[_action(f"split_{log}", "firekeeping", f"Split {ITEMS[log]['name'].lower()}", TIER_LEVELS[tier], 25,
|
||||
KINDLING_XP[tier], {log: 1}, {"kindling": KINDLING_YIELD[tier]}, group="Kindling")
|
||||
for tier, log in enumerate(FIREWOOD)],
|
||||
_action("sew_smock", "tailoring", "Summer smock", 3, 30, 12, {"linen": 2}, {"smock": 1}, group="Townsfolk"),
|
||||
_action("sew_cloak", "tailoring", "Winter cloak", 8, 40, 24, {"linen": 4}, {"cloak": 1}, group="Townsfolk"),
|
||||
]
|
||||
|
||||
# --- dungeon loot --------------------------------------------------------------
|
||||
|
|
@ -1586,7 +1624,7 @@ RAID_TRAITS = ["lightly armoured, heavy on archers", "mounted and fast, few bows
|
|||
|
||||
# --- world ---------------------------------------------------------------
|
||||
|
||||
WORLD_VERSION = 34
|
||||
WORLD_VERSION = 35
|
||||
|
||||
|
||||
def new_world(now: float, speed: float = 1.0) -> dict:
|
||||
|
|
@ -1611,6 +1649,7 @@ def new_world(now: float, speed: float = 1.0) -> dict:
|
|||
"repelled": 0,
|
||||
"fires": {key: new_fire(now) for key in FIRES},
|
||||
"wages": 0, "monuments": {}, "built": [], "carving": None,
|
||||
"happiness": 0.5, "clothes_due": 0.0, "unheated": False,
|
||||
},
|
||||
"events": [],
|
||||
"next_event_at": now + 12 * HOUR / speed,
|
||||
|
|
@ -1847,6 +1886,10 @@ def migrate(world: dict, now: float | None = None) -> dict:
|
|||
for player in world["players"].values():
|
||||
player.setdefault("gave", {})
|
||||
world["version"] = 34
|
||||
if world["version"] < 35:
|
||||
# Townsfolk needs: everyone starts Content and drifts to what the city gives them.
|
||||
world["city"].update(happiness=0.5, clothes_due=0.0, unheated=False)
|
||||
world["version"] = 35
|
||||
return world
|
||||
|
||||
|
||||
|
|
@ -2155,6 +2198,10 @@ def city_uses(item: str) -> list[str]:
|
|||
spec = ITEMS[item]
|
||||
if "food" in spec:
|
||||
uses.append("the granary")
|
||||
if item in FUEL:
|
||||
uses.append("the hearths")
|
||||
if item in SEASON_CLOTHES:
|
||||
uses.append("the townsfolk")
|
||||
if spec.get("slot") in ("weapon", "shield") or "kit" in spec or item == "arrows":
|
||||
uses.append("the armoury")
|
||||
if item == "water_barrel":
|
||||
|
|
@ -4282,7 +4329,7 @@ def growth_per_day(world: dict) -> float:
|
|||
"""New townsfolk a day while there's room: settlers for the homes, plus families."""
|
||||
pop = world["city"]["population"]
|
||||
growth = SETTLERS_PER_EMPTY_HOME * max(0.0, housing(world) - pop) + FAMILY_GROWTH * pop
|
||||
return growth * city_rate(world, "growth") * (1.25 if fitted(world, "furniture") else 1)
|
||||
return growth * city_rate(world, "growth") * (1.25 if fitted(world, "furniture") else 1) * mood(world)["growth"]
|
||||
|
||||
|
||||
def population_view(world: dict) -> dict:
|
||||
|
|
@ -4298,17 +4345,137 @@ def population_view(world: dict) -> dict:
|
|||
growth = -pop * 0.05
|
||||
else:
|
||||
growth = 0.0
|
||||
leaving = 0.0 if city["starving"] else pop * mood(world)["leave"]
|
||||
food = sum(city["stockpile"].get(item, 0) * ITEMS[item]["food"] for item in FOOD_ORDER)
|
||||
tier = city["tier"]
|
||||
following = TIERS[tier + 1] if tier + 1 < len(TIERS) else None
|
||||
return {
|
||||
"population": int(pop), "housing": room, "growth": round(growth, 1),
|
||||
"population": int(pop), "housing": room, "growth": round(growth - leaving, 1), "leaving": round(leaving, 1),
|
||||
"halted": not mood(world)["growth"],
|
||||
"food": food, "food_days": round(food / (pop * 0.5), 1) if pop else None,
|
||||
"tier": tier, "tier_name": TIERS[tier]["name"],
|
||||
"next_tier": following and {"name": following["name"], "pop": following["pop"]},
|
||||
}
|
||||
|
||||
|
||||
def mood(world: dict) -> dict:
|
||||
"""The townsfolk's mood band right now, with what it does."""
|
||||
score = world["city"]["happiness"]
|
||||
return next(band for band in MOODS if score < band["below"])
|
||||
|
||||
|
||||
def _meals(stock: dict, item: str) -> float:
|
||||
return stock.get(item, 0) * ITEMS[item]["food"]
|
||||
|
||||
|
||||
def hearth_burn(world: dict, now: float) -> tuple[float, float]:
|
||||
"""Fuel the city burns an hour: (homes, weather and hardships). Hamlets don't keep hearths."""
|
||||
pop = world["city"]["population"]
|
||||
homes = pop * HEARTH_BURN[season(world, now)["index"]] if "warmth" in NEEDS_BY_TIER[world["city"]["tier"]] else 0.0
|
||||
return homes, pop * sum(source.get("firewood", 0) for source in _modifier_sources(world))
|
||||
|
||||
|
||||
def happiness(world: dict, now: float) -> dict:
|
||||
"""Each need scored 0..1 with its have / need rows, the modifiers, and the mood they pull towards."""
|
||||
city = world["city"]
|
||||
pop = max(1.0, city["population"])
|
||||
stock = city["stockpile"]
|
||||
tier = city["tier"]
|
||||
day = pop * 0.5 # meals a day
|
||||
meals = sum(_meals(stock, item) for item in FOOD_ORDER)
|
||||
kinds = sum(1 for item in FOOD_ORDER if _meals(stock, item) >= day)
|
||||
want_meals = math.ceil(FOOD_DAYS[tier] * day)
|
||||
supply = min(1.0, meals / want_meals)
|
||||
variety = min(1.0, kinds / FOOD_KINDS)
|
||||
needs = {"food": {"score": 0.0 if city["starving"] else 0.5 * supply + 0.5 * variety, "rows": [
|
||||
{"item": "bread", "label": "Food (any)", "have": int(meals), "want": want_meals, "any": "food"},
|
||||
{"item": "stew", "label": "Kinds of food", "have": kinds, "want": FOOD_KINDS, "kinds": True},
|
||||
]}}
|
||||
season_now = season(world, now)["index"]
|
||||
burn = 24 * sum(hearth_burn(world, now))
|
||||
fuel = sum(stock.get(item, 0) for item in FUEL)
|
||||
want_fuel = math.ceil(FUEL_DAYS * burn)
|
||||
needs["warmth"] = {"score": 0.0 if city.get("unheated") else min(1.0, fuel / want_fuel) if want_fuel else 1.0,
|
||||
"rows": [{"item": "kindling", "label": "Firewood (any)", "have": fuel, "want": want_fuel,
|
||||
"any": "fuel"}]}
|
||||
garment, later = SEASON_CLOTHES[season_now], SEASON_CLOTHES[(season_now + 1) % 4]
|
||||
rows = [{"item": garment, "label": ITEMS[garment]["plural"], "have": stock.get(garment, 0), "want": math.ceil(pop)}]
|
||||
if later != garment:
|
||||
rows.append({"item": later, "label": f"{ITEMS[later]['plural']} (next season)", "have": stock.get(later, 0),
|
||||
"want": math.ceil(pop), "next": True})
|
||||
needs["clothes"] = {"score": min(1.0, stock.get(garment, 0) / pop), "rows": rows}
|
||||
active = NEEDS_BY_TIER[tier]
|
||||
base = sum(NEEDS[key] * needs[key]["score"] for key in active) / sum(NEEDS[key] for key in active)
|
||||
problems = sum(1 for problem in city["problems"] if problem["kind"] in MINOR_THREATS)
|
||||
mods = [{"name": "Unsolved problems", "value": -PROBLEM_MOOD * problems}] if problems else []
|
||||
mods += [{"name": effect["name"], "value": effect["mood"]} for effect in city["effects"] if effect.get("mood")]
|
||||
target = max(0.0, min(1.0, base + sum(m["value"] for m in mods)))
|
||||
band = mood(world)
|
||||
return {
|
||||
"score": round(city["happiness"], 3), "target": round(target, 3), "name": band["name"], "icon": band["icon"],
|
||||
"heading": next(b["name"] for b in MOODS if target < b["below"]),
|
||||
"effects": {key: band[key] for key in ("growth", "militia", "work", "leave")},
|
||||
"needs": {key: {**needs[key], "score": round(needs[key]["score"], 3), "weight": NEEDS[key]} for key in active},
|
||||
"locked": [key for key in NEEDS if key not in active],
|
||||
"mods": mods,
|
||||
}
|
||||
|
||||
|
||||
def shock_mood(world: dict, now: float, kind: str) -> None:
|
||||
"""News that moves the townsfolk at once: a raid, a fire, a festival."""
|
||||
city = world["city"]
|
||||
before = mood(world)["name"]
|
||||
city["happiness"] = max(0.0, min(1.0, city["happiness"] + MOOD_SHOCKS[kind]))
|
||||
_mood_news(world, now, before)
|
||||
|
||||
|
||||
def _mood_news(world: dict, now: float, before: str) -> None:
|
||||
band = mood(world)
|
||||
if band["name"] != before:
|
||||
log(world, now, "city", f"{band['icon']} The townsfolk are {band['name'].lower()}.")
|
||||
|
||||
|
||||
def _townsfolk_hour(world: dict, now: float, rng: random.Random) -> None:
|
||||
"""Clothes wear out, the mood drifts towards what the city gives, and misery makes trouble."""
|
||||
city = world["city"]
|
||||
stock = city["stockpile"]
|
||||
pop = city["population"]
|
||||
if "clothes" in NEEDS_BY_TIER[city["tier"]]:
|
||||
garment = SEASON_CLOTHES[season(world, now)["index"]]
|
||||
city["clothes_due"] = city.get("clothes_due", 0) + pop / (SEASON_DAYS * 24)
|
||||
while city["clothes_due"] >= 1 and stock.get(garment):
|
||||
take_stock_qty(stock, garment, 1)
|
||||
city["clothes_due"] -= 1
|
||||
city["clothes_due"] = min(city["clothes_due"], 1) # nothing to wear out: no debt piles up
|
||||
before = mood(world)["name"]
|
||||
gap = happiness(world, now)["target"] - city["happiness"]
|
||||
city["happiness"] += max(-MOOD_DRIFT, min(MOOD_DRIFT, gap))
|
||||
_mood_news(world, now, before)
|
||||
if mood(world)["name"] == "Miserable" and rng.random() < TROUBLE_ODDS:
|
||||
_trouble(world, now, rng)
|
||||
|
||||
|
||||
def _trouble(world: dict, now: float, rng: random.Random) -> None:
|
||||
"""What miserable townsfolk do: down tools, help themselves to the stockpile, or riot."""
|
||||
city = world["city"]
|
||||
kind = rng.choice(["strike", "theft", "riot"])
|
||||
if kind == "strike":
|
||||
_add_effect(world, now, 12, kind="strike", name="Strike", icon="✊", labour=0, text="No townsfolk labour")
|
||||
_record(world, now, "hardship", "strike", "✊", "The townsfolk have downed tools",
|
||||
["For 12 hours they won't work on the building site."])
|
||||
elif kind == "theft":
|
||||
stock = city["stockpile"]
|
||||
taken = _take_share(stock, [item for item in stock if item not in _food_items(stock)], 0.05)
|
||||
_record(world, now, "hardship", "theft", "🫳", "Townsfolk helped themselves to the stockpile",
|
||||
[f"They took {_listing(taken)}." if taken else "There was little worth taking."])
|
||||
else:
|
||||
name = _damage_building(world, now, rng)
|
||||
_record(world, now, "hardship", "riot", "🔥", "A riot broke out in the streets",
|
||||
[f"The {name.lower()} {'were' if name.endswith('s') else 'was'} damaged and needs repairs." if name
|
||||
else "Nothing was badly damaged."])
|
||||
shock_mood(world, now, "trouble")
|
||||
|
||||
|
||||
def kit_slot(item: str) -> str | None:
|
||||
"""Where a defender carries it: a gear slot, or city-only kit like guard jerkins."""
|
||||
return ITEMS[item].get("slot") or ITEMS[item].get("kit")
|
||||
|
|
@ -4423,7 +4590,8 @@ def defence(world: dict, guards: int | None = None) -> dict:
|
|||
stock = city["stockpile"]
|
||||
posts = guard_posts(world)
|
||||
armed = guards_on_duty(world) if guards is None else min(guards, posts)
|
||||
militia = 0.3 * building_level(world, "barracks") * pop * (1.5 if fitted(world, "weapon_racks") else 1)
|
||||
militia = (0.3 * building_level(world, "barracks") * pop * (1.5 if fitted(world, "weapon_racks") else 1)
|
||||
* mood(world)["militia"])
|
||||
parts = {
|
||||
"townsfolk": 10,
|
||||
"walls": 20 * building_level(world, "walls") + 25 * fitted(world, "hoardings"),
|
||||
|
|
@ -4541,15 +4709,17 @@ def tick(world: dict, now: float, rng: random.Random | None = None) -> bool:
|
|||
def _city_hour(world: dict, now: float) -> None:
|
||||
city = world["city"]
|
||||
pop = city["population"]
|
||||
band = mood(world)
|
||||
if city["construction"]:
|
||||
city["construction"]["labour"] += pop * TOWNSFOLK_LABOUR
|
||||
city["construction"]["labour"] += pop * TOWNSFOLK_LABOUR * band["work"] * city_rate(world, "labour")
|
||||
threat = upcoming_raid(world)
|
||||
if threat and building_level(world, "spymaster"):
|
||||
threat["recon"] = min(intel_costs(threat)[-1], threat["recon"] + SPYMASTER_AGENTS * building_level(world, "spymaster"))
|
||||
city["food_due"] += (pop * 0.5 / 24 * city_rate(world, "food") * (1.5 if city.get("cold") else 1)
|
||||
* (0.9 if fitted(world, "grain_bins") else 1))
|
||||
stock = city["stockpile"]
|
||||
for item in FOOD_ORDER:
|
||||
# The biggest pile goes first, so the smaller kinds last and the table stays varied.
|
||||
for item in sorted(FOOD_ORDER, key=lambda i: -_meals(stock, i)):
|
||||
while city["food_due"] >= ITEMS[item]["food"] and stock.get(item, 0) > 0:
|
||||
stock[item] -= 1
|
||||
if not stock[item]:
|
||||
|
|
@ -4568,7 +4738,9 @@ def _city_hour(world: dict, now: float) -> None:
|
|||
city["population"] = min(room, pop + growth_per_day(world) / 24)
|
||||
elif pop > room:
|
||||
city["population"] = max(room, pop - pop * 0.05 / 24)
|
||||
city["treasury"] += pop * 0.5 / 24
|
||||
if band["leave"]:
|
||||
city["population"] = max(5.0, city["population"] - pop * band["leave"] / 24)
|
||||
city["treasury"] += pop * 0.5 / 24 * band["work"]
|
||||
|
||||
|
||||
def labour_needed(level: int, repair: bool) -> float:
|
||||
|
|
@ -4869,6 +5041,7 @@ def _resolve_raid(world: dict, event: dict, now: float, rng: random.Random) -> N
|
|||
if garrison:
|
||||
lines.append("Everyone in the garrison earned 25 favour and combat experience.")
|
||||
log(world, now, "raid", f"{upper_first(event["group"])} were driven off")
|
||||
shock_mood(world, now, "raid_won")
|
||||
else:
|
||||
severity = min(1.0, 1 - ratio)
|
||||
broken = wear_kit(world, wear_share(ratio), rng)
|
||||
|
|
@ -4907,6 +5080,7 @@ def _resolve_raid(world: dict, event: dict, now: float, rng: random.Random) -> N
|
|||
for entry in garrison:
|
||||
gain_xp(world, world["players"][entry["id"]], "combat", round(attack), now)
|
||||
log(world, now, "raid", f"{upper_first(event["group"])} broke into the city")
|
||||
shock_mood(world, now, "raid_lost")
|
||||
report["lines"] = lines
|
||||
event["report"] = report
|
||||
_pay_guards(world, report)
|
||||
|
|
@ -4945,6 +5119,7 @@ def _resolve_dragon(world: dict, event: dict, now: float, rng: random.Random) ->
|
|||
if garrison:
|
||||
lines.append("Everyone in the garrison earned 40 favour, combat experience, dragon bones and Ironhill reagents.")
|
||||
log(world, now, "raid", report["title"])
|
||||
shock_mood(world, now, "dragon_won")
|
||||
else:
|
||||
severity = min(1.0, 1 - guard["total"] / attack)
|
||||
broken = wear_kit(world, 0.5, rng)
|
||||
|
|
@ -4966,6 +5141,7 @@ def _resolve_dragon(world: dict, event: dict, now: float, rng: random.Random) ->
|
|||
for entry in garrison:
|
||||
gain_xp(world, world["players"][entry["id"]], "combat", round(attack), now)
|
||||
log(world, now, "raid", report["title"])
|
||||
shock_mood(world, now, "dragon_lost")
|
||||
report["lines"] = lines
|
||||
event["report"] = report
|
||||
_pay_guards(world, report)
|
||||
|
|
@ -5324,6 +5500,7 @@ def _hardship(world: dict, now: float, rng: random.Random, kind: str | None = No
|
|||
if taken:
|
||||
lines.append(f"The wind scattered {_listing(taken)}.")
|
||||
_record(world, now, "hardship", kind, "🌪️", "A wild storm battered the city", lines)
|
||||
shock_mood(world, now, "storm")
|
||||
elif kind == "rats":
|
||||
city["problems"].append({"kind": "rats", "started": now, "progress": {}, "work": 0})
|
||||
_record(world, now, "hardship", kind, "🐀", "Rats have got into the granary",
|
||||
|
|
@ -5346,6 +5523,7 @@ def _hardship(world: dict, now: float, rng: random.Random, kind: str | None = No
|
|||
lines.append(f"The {name.lower()} {'were' if name.endswith('s') else 'was'} damaged and needs repairs.")
|
||||
lines.append("Water barrels in the stockpile (five per fire) would have put it out.")
|
||||
_record(world, now, "hardship", kind, "🔥", "Fire swept through part of the city", lines)
|
||||
shock_mood(world, now, "fire")
|
||||
elif kind == "winter":
|
||||
_add_effect(world, now, 24, kind="winter", name="Harsh winter", icon="🥶", firewood=0.1,
|
||||
text="Burns stockpile logs hourly")
|
||||
|
|
@ -5418,8 +5596,9 @@ def _fortune(world: dict, now: float, rng: random.Random, kind: str | None = Non
|
|||
_record(world, now, "fortune", kind, "🐟", "The salmon are running",
|
||||
["Fishing yields are doubled for 6 hours."])
|
||||
elif kind == "festival":
|
||||
_add_effect(world, now, 12, kind="festival", name="Festival", icon="🎉", growth=2, favour=1.5,
|
||||
text="Growth doubled, +50% favour from donations")
|
||||
_add_effect(world, now, 12, kind="festival", name="Festival", icon="🎉", growth=2, favour=1.5, mood=0.1,
|
||||
text="Growth doubled, +50% favour from donations, +10% mood")
|
||||
shock_mood(world, now, "festival")
|
||||
_record(world, now, "fortune", kind, "🎉", "The city is holding a festival",
|
||||
["For 12 hours, the population grows twice as fast and donations earn 50% more favour."])
|
||||
elif kind == "noble":
|
||||
|
|
@ -5549,20 +5728,26 @@ def _happenings_hour(world: dict, now: float, rng: random.Random) -> None:
|
|||
if problem.get("until") and problem["until"] <= now:
|
||||
_solve(world, problem, now)
|
||||
stock = city["stockpile"]
|
||||
burn = city["population"] * sum(source.get("firewood", 0) for source in _modifier_sources(world))
|
||||
city["firewood_due"] = city.get("firewood_due", 0) + burn
|
||||
cold = False
|
||||
homes, weather = hearth_burn(world, now)
|
||||
city["firewood_due"] = city.get("firewood_due", 0) + homes + weather
|
||||
short = False
|
||||
while city["firewood_due"] >= 1:
|
||||
log_item = next((item for item in FIREWOOD if stock.get(item)), None)
|
||||
if not log_item:
|
||||
cold = True
|
||||
fuel = next((item for item in FUEL if stock.get(item)), None)
|
||||
if not fuel:
|
||||
short = True
|
||||
city["firewood_due"] = 0
|
||||
break
|
||||
take_stock_qty(stock, log_item, 1)
|
||||
take_stock_qty(stock, fuel, 1)
|
||||
city["firewood_due"] -= 1
|
||||
# Cold homes make for unhappy townsfolk; in snow or a cold snap they also eat more to keep warm.
|
||||
cold = short and weather > 0
|
||||
unheated = homes + weather > 0 and not any(stock.get(item) for item in FUEL)
|
||||
if cold and not city.get("cold"):
|
||||
log(world, now, "city", "The firewood has run out. People are cold and eating more to stay warm.")
|
||||
elif unheated and not city.get("unheated"):
|
||||
log(world, now, "city", "The firewood has run out. Homes are cold.")
|
||||
city["cold"] = cold
|
||||
city["unheated"] = unheated
|
||||
for problem in city["problems"]:
|
||||
spec = PROBLEMS[problem["kind"]]
|
||||
if spec.get("steal"):
|
||||
|
|
@ -5571,6 +5756,7 @@ def _happenings_hour(world: dict, now: float, rng: random.Random) -> None:
|
|||
_take_share(stock, _food_items(stock), spec["steal_food"] * (1 - 0.25 * building_level(world, "granary")))
|
||||
if spec.get("steal_gold"):
|
||||
city["treasury"] *= 1 - spec["steal_gold"]
|
||||
_townsfolk_hour(world, now, rng)
|
||||
|
||||
|
||||
def _happenings_tracks(world: dict, now: float, rng: random.Random) -> bool:
|
||||
|
|
@ -6160,6 +6346,7 @@ REAGENT_GLYPHS = {"heavy": "chain-mail", "medium": "scale-mail", "light": "yarn"
|
|||
WEAPON_GLYPHS = {"hammer": "warhammer", "staff": "wizard-staff", "bow": "pocket-bow"}
|
||||
HARDWARE_GLYPHS = ["screw", "nails", "door-handle", "metal-plate", "crossed-chains", "padlock"]
|
||||
GLYPHS = {
|
||||
"kindling": ("wood-pile", "#c9a06a"), "smock": ("shirt", "#d8d0b8"), "cloak": ("cloak", "#8a6fd0"),
|
||||
"stone": ("stone-block", "#a8a29e"), "wheat": ("wheat", "#e3c060"), "vegetables": ("carrot", "#f08a3c"),
|
||||
"feathers": ("feather", "#e8e8ee"), "raw_meat": ("meat", "#e06a6a"), "arrows": ("arrow-cluster", "#c9b28a"),
|
||||
"bricks": ("brick-pile", "#c66a45"), "cut_stone": ("stone-stack", "#b8b2aa"), "beam": ("wood-beam", "#9a6a3a"),
|
||||
|
|
@ -6390,6 +6577,7 @@ PATCH_NOTES = [
|
|||
{"date": "2026-10-28", "title": "Queue", "notes": [
|
||||
("new", "Queue: ▲ on #1 swaps it with the current make."),
|
||||
("new", "Updated Guard kit, added recipes for crafting professions to contribute to defence of town without needing combat-related loot."),
|
||||
("new", "Townsfolk mood: food, firewood and seasonal clothes. Kindling (Firekeeping), smocks and cloaks (Tailoring)."),
|
||||
]},
|
||||
{"date": "2026-10-27", "title": "Prayer", "notes": [
|
||||
("new", "Gathering can be queued."),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue