jengine-borough
A living town: villagers with needs, skills and moods who pick their own work by priority, buildings and work orders, hauling and trade done by the people, districts bought or conquered, and a storyteller that writes the town’s chronicle.
| Kind | Project package: linked into the builds of the projects that list it |
| Source | packages/jengine-borough |
| Version | 0.1.0 |
| Entry point | jengine_borough::BoroughPackage |
| Script namespace | Borough |
| Listed by | projects/thornwick |
Using it
Section titled “Using it”List it in the top-level "packages" array of project.jengine:
{ "packages": [ { "name": "jengine-borough", "source": { "type": "path", "path": "../../packages/jengine-borough" } } ]}Then rebuild the editor viewport for the project (jengine viewport-build projects/<name>, or the
editor’s Rebuild viewport banner). Native and web builds link it on their own.
A scene that uses its components must be a v2 scene ("version": 2): a v1 scene has no field
for a package component and keeps it as inert custom data.
A script can check that the package is linked with if Borough == nil then return end.
Components
Section titled “Components”| Scene key | Rust type | |
|---|---|---|
borough_town | TownTable | package only: needs a v2 scene |
Script commands
Section titled “Script commands”Commands run in every runtime and return nothing; a package publishes what scripts read onto components (see the notes below). The flat name also works.
| Call | Flat name |
|---|---|
Borough.found(e, seed, difficulty, name) | borough_found |
Borough.demo(e, seed) | borough_demo |
Borough.load(e, text) | borough_load |
Borough.save(e) | borough_save |
Borough.stop(e) | borough_stop |
Borough.speed(e, x) | borough_speed |
Borough.autopilot(e, on) | borough_autopilot |
Borough.pointer(e, x, y) | borough_pointer |
Borough.press(e, x, y) | borough_press |
Borough.drag(e, x, y) | borough_drag |
Borough.release(e, x, y) | borough_release |
Borough.order_at(e, x, y) | borough_order_at |
Borough.tool(e, kind) | borough_tool |
Borough.select(e, kind, id) | borough_select |
Borough.panel(e, name) | borough_panel |
Borough.demolish(e, building) | borough_demolish |
Borough.pause_building(e, building, on) | borough_pause_building |
Borough.limit(e, building, n) | borough_limit |
Borough.bill(e, building, recipe, mode, n) | borough_bill |
Borough.bill_remove(e, building, index) | borough_bill_remove |
Borough.bill_pause(e, building, index, on) | borough_bill_pause |
Borough.priority(e, person, work, level) | borough_priority |
Borough.assign(e, person, building) | borough_assign |
Borough.draft(e, person, on) | borough_draft |
Borough.draft_all(e, on) | borough_draft_all |
Borough.march(e, person, x, y) | borough_march |
Borough.acquire(e, district) | borough_acquire |
Borough.attack(e, district) | borough_attack |
Borough.answer(e, letter, choice) | borough_answer |
Borough.tax(e, level) | borough_tax |
Borough.rations(e, level) | borough_rations |
Borough.trade(e, good, sell, keep, buy_to) | borough_trade |
Borough.buy_food(e, on) | borough_buy_food |
Borough.festival(e) | borough_festival |
Borough.deal(e, faction, act) | borough_deal |
Borough.trigger(e, event) | borough_trigger |
Scenes that use it
Section titled “Scenes that use it”projects/thornwick/assets/scenes/founding.sceneprojects/thornwick/assets/scenes/howto.sceneprojects/thornwick/assets/scenes/legends.sceneprojects/thornwick/assets/scenes/title.sceneprojects/thornwick/assets/scenes/town.scene
From packages/jengine-borough/README.md.
A living town. Villagers with needs, skills, traits and moods pick their own
work by the priorities you give them; buildings are laid out, carried to and
built by the people; workshops follow work orders; haulers and merchants move
goods and trade them by your rules; the land is cut into districts you claim,
buy or take by force; and a storyteller sends raids, caravans, plagues,
wanderers and lords’ demands, and writes it all into the town’s chronicle.
It is what Thornwick (projects/thornwick) is made of - RimWorld and
Dwarf Fortress in a medieval frontier town, with a city-builder’s districts.
The rules have no ECS in them: town::Town is plain data that serializes
whole (that is a save), and is stepped with Town::update(content, dt).
draw puts a town on screen with ordinary components; bot runs one without
a player; lib.rs is the seam.
How the town lives
Section titled “How the town lives”- Villagers (
person,act) get hungry, tired and bored, fall sick and get hurt. Their mood is their needs plus the thoughts they carry (a wedding, a death, a raid survived, meagre rations, harsh taxes); a low one makes them sulk, binge or smash things, and a long one makes them leave. - Neighbours (
social): every villager keeps opinions of the people they spend time with. Bonds grow out of chance talks - most of all of an evening at the tavern or the chapel - shaped by their traits (shared ones draw them together, opposite ones apart; hotheads and the gloomy are poor company), kinship and mood, and a grudge colours every word after it. So friends and rivals are grown, not rolled: friends cheer and grieve each other and are who marries, rivals sour each other’s days and trade insults, and a hothead who loathes someone starts a brawl - bruises, sometimes a villager knocked flat, never a death. - The dead (
burial): a body lies where it fell until a hauler carries it to a graveyard (a zone, one grave a plot) and buries it. Passing a body shakes a villager, and a body left a day and a half weighs on the whole town; a burial comforts those who loved them, more with a chapel. Bodies nobody buries are taken by the crows after twelve days. - Homes (
homes): every house is a cottage at first, and rises to a town house and then a merchant’s house when its household has what such a home wants - better food, a market, a tavern and a chapel in reach, a road to the door, cloth and ale, and the stone and planks to rebuild. Each level sleeps one more, cheers its household and pays more in taxes, so where the player puts a market decides which streets rise first. What a house still wants is in the inspector, reason by reason. - Work (
jobs): each villager ranks eleven kinds of work 1-4 or never. An idle villager goes down their ranks and takes the nearest job of the first kind that has one, speaking for what it uses (a tree, a field tile, materials on their way to a site) so two never walk to the same tree. Skills make work faster and grow with doing it; no tools makes it slower. - Building: a laid-out building is a site; its materials are carried from the nearest heap that has them (a store, a workshop’s output, a pile) and builders raise it. Fields and pastures are zones of any size.
- Production: fields (sown spring to autumn, lost to frost if unreaped),
fishing huts (food in every season - thin under ice - from water that
fills again over two days), pastures (wool, mutton, lambs), woodcutters (fell and replant), quarries
(stone, and ore where there is a vein), workshops following work orders
(
building::Bill: make N, keep N in stock, or always). - Trade (
trade): caravans come in from the edge and stand at the market (or the town hall); villagers set to Trade deal with them one bargain at a time - selling above the stock to keep, buying up to the stock wanted, and food when the town has under three days of it. Taxes bring gold daily. - The realm (
realm,realm_rules): districts are claimed (wild, for gold), bought (from a lord who likes the town), or taken - an attack turns out the district’s garrison, the drafted militia beats it, and standing there takes it. A lord’s seat taken ends the house. - Diplomacy (
diplomacy): the reeve moves a lord’s temper on purpose - a gift of gold (each soon after another counts for less, and one can buy a peace), a demand for tribute (paid by a lord the town’s armed might outmatches half again, who hates the town for it; refused by any other, maybe with war), or a marriage (a single villager weds into the house and leaves for good; once a year a house). An ally (60 and up) sells land at half price. - Violence (
combat): raiders go for the stores, soldiers for the town hall, wolves for the sheep; foes plan through walls and hack at what is in the way. A raid breaks at half its number. Manned watchtowers shoot. The fallen are carried home by whoever can heal. - The storyteller (
story): every half day it looks at the town. A threat comes after enough calm days (fewer when cruel), sized by wealth, people and age and capped by the town’s adults; between threats come the rest, weighted kinder to a miserable town. Every event is a row in a data table whoseeffectnames its handler; some ask a question, answered with their last choice if left too long. - Life (
life): births, weddings, ageing, the seasons, cloth for the winter, homes found, fires spreading - and once a day the numbers for the town’s graphs. A villager who slays three foes earns a name (the Bold, Wolfbane) that the chronicle remembers. - Milestones (
goals): a ladder from the first house to bringing down a lord; the next one is in the HUD, each reached is chronicled.
The whole rulebook - goods, buildings, recipes, events, traits - is data:
content/*.json, compiled in, and overridden by a project’s borough_*
data tables (see projects/thornwick/tools/gen_data.py).
Component ("version": 2 scenes)
Section titled “Component ("version": 2 scenes)”borough_town on one entity:
"borough_town": { "land": "Land", "demo": false, "seed": 0, "difficulty": "steady", "speed": 1.0 }land names an entity with a tilemap whose layers are ground, shade,
marks and features (y-sorted) and whose tilesets follow
draw::tiles (Thornwick’s tools/gen_tiles.py builds them). With demo the
package founds a town itself and lets the bot run it (a title screen). An
ambient_light anywhere in the scene is turned with the time of day. Every
texture the package draws must be preloaded (Thornwick’s scenes give the
director a sprite_library of them).
Script commands
Section titled “Script commands”Namespace Borough; e is the borough_town entity. Lua counts from 1.
| Call | Does |
|---|---|
found(e, seed, difficulty, name) | a new town (gentle / steady / cruel; seed 0 from the clock) |
demo(e, seed) | a town the bot runs |
load(e, text) / save(e) | a town from a save / the town as JSON in save next frame |
stop(e), speed(e, x), autopilot(e, on) | end it; clock speed (0 pauses, up to 8); the bot runs it |
pointer(e, x, y), press, drag, release | the pointer in world units: tools and selection |
order_at(e, x, y) | the other button: march the drafted there, or put the tool down |
tool(e, kind) | a building id, chop / mine / road / clear / wall / gate / demolish, or "" |
select(e, kind, id) | person / building / foe / district / none |
panel(e, name) | publish people, goods, realm, letters or chronicle |
demolish(e, b), pause_building(e, b, on) | tear down or cancel; pause |
limit(e, b, n) | a woodcutter’s or quarry’s stopping point: rest when the town holds n (0: never) |
bill(e, b, recipe, mode, n), bill_remove(e, b, i), bill_pause(e, b, i, on) | work orders (count / until / forever) |
priority(e, person, work, level), assign(e, person, b) | work ranks; put someone to work at a building (0 frees them) |
draft(e, person, on), draft_all(e, on), march(e, person, x, y) | the militia (person 0: all drafted) |
acquire(e, district), attack(e, district) | claim or buy; march on |
deal(e, faction, act) | deal with a lord: gift (gold for goodwill), demand (tribute - paid only by a lord the town’s arms outmatch) or marry (a single villager weds into the house and leaves) |
answer(e, letter, choice) | answer a letter |
trigger(e, event) | make an event (a row of borough_events) happen now - for scenarios and testing |
tax(e, 0-3), rations(e, 0-2), trade(e, good, sell, keep, buy_to), buy_food(e, on), festival(e) | decrees |
What scripts read
Section titled “What scripts read”The borough custom-component bag on the entity: hud (date, time, speed,
gold, people, mood, food in days, every good’s count, foes, letters waiting,
map size), selected (the inspected villager, building, district or foe in
full), tool (what is held and why it cannot go there), hover (the tile
under the pointer), catalog (the build menu), letters, panel (the open
panel’s data), summary and home (where to start the camera), events
(this frame’s happenings: built, death, letter_threat, caravan,
chronicle, …), message (an order’s answer), save and errors.
Modules
Section titled “Modules”| Module | What it is |
|---|---|
town | the town, its step, placement, stores, claims, walkable regions |
person | a villager: needs, mood, skills, traits, priorities |
jobs / act | choosing a job / doing it (walking, hauling, building, farming, crafting, healing) |
building | a building, its stock and supplies, work orders |
map, path, worldgen | the land; A* that plans foes through walls; a new land, checked |
realm, realm_rules | districts and factions; claiming, buying, sieges |
combat, trade, story, life | foes and towers; caravans; the storyteller; growth, moods, days |
goals | the milestones |
social | bonds, friends and rivals, insults and brawls |
burial, homes | the dead and the graveyard; houses that rise a level |
diplomacy | gifts, demands for tribute and marriages with the lords |
orders | every player command, applied in one place |
bot | the mayor who is not a person |
content | the rulebook, as data |
view, draw | what scripts read; the town on screen |
cargo test -p jengine-borough- the rules module by module; every event fired and answered;town_lives(the bot keeps three towns fed and growing for a year, a save goes on exactly as the town would have, a cruel storyteller is crueller); friendship and rivalry grown from traits, a brawl that never kills; a house that rises only when its wants are met; a door its own building would wall in, refused; gifts that count for less and buy peace, tribute only from the outmatched, a marriage that sends a villager away;thornwick_scenes(the shipped scenes played through their own Lua: founding, building by clicking, panels, a gift to a lord, the militia, a letter, saving and continuing).--release --test chronicle -- --ignored --nocaptureprints a bot town day by day (SEED,DAYS);surveysums up twelve - deaths by cause, house levels, graves, friendships, rivalries, brawls and weddings.