Unique scenes cookbook
Four recipes for authoring scenes that reflect a girl’s personality, her current situation, and (where one specific girl is involved) her name. All examples use real, shipped syntax — compare against resources/Jobs/ and resources/Packages/Sample_Full/.
For the complete predicate list, see reference/when-conditions.md. For full job structure see reference/jobs-reference.md.
Scope. This cookbook covers what’s pack-authorable today — XML + the
<When>engine, plus optional per-girl Lua overrides. Long arcs (“after 30 in-game days…”) aren’t pack-authorable yet; see “Known gap” at the bottom.
Recipe 1: Personality-gated shift text
Section titled “Recipe 1: Personality-gated shift text”When to use this shape: you want a job to read differently depending on the girl’s traits or stats. A Shy girl bartending should sound different from a Charming one. The base text bag still fires for everyone else.
Exemplar: resources/Jobs/barmaid/messages/work.xml
Inside any text bag, attach a <When> block. Variants with a matching <When> are eligible alongside the unconditional ones; the engine picks among the eligible bag entries by weight=.
<Text id="barmaid.work.perfect.psychic" weight="3"> <When> <Performance ge="245"/> <Trait id="Psychic"/> </When></Text>
<Text id="barmaid.work.great.beauty" weight="3"> <When> <Performance ge="185" le="244"/> <Stat name="Beauty" ge="85"/> </When></Text>A bare <When> with multiple children means all must match (implicit AND). The first variant fires only when the girl scored a “perfect” shift and has the Psychic trait. The second wants a “great” shift and Beauty ≥ 85.
Authoring rule: keep at least one unconditional variant in each performance bucket, so a girl who matches no gate still has something to say.
Recipe 2: Multi-trait personality blend
Section titled “Recipe 2: Multi-trait personality blend”When to use this shape: the line should fire when she has any of a related cluster of traits, or when several conditions all need to hold.
Use the combinators from reference/when-conditions.md:
<!-- Either Charming or Seductive triggers this line --><Text id="streetwalker.work.flirt" weight="2"> <When> <Any> <Trait id="Charming"/> <Trait id="Seductive"/> </Any> </When></Text>
<!-- Shy girl, working at night, sober: a specific personality moment --><Text id="barmaid.work.shy_night" weight="2"> <When> <Trait id="Shy"/> <DayNight value="night"/> <None> <Status id="Pregnant"/> <Status id="Drugged"/> </None> </When></Text><Any> = OR. Implicit AND when children sit directly under <When>. <None> = “none of these” (sugar for not-any).
Recipe 3: A scene for one specific named girl
Section titled “Recipe 3: A scene for one specific named girl”When to use this shape: you’ve written a unique character and want lines that only she can roll. This is the closest thing to “Sarah’s bespoke scene” — a 1.15 addition.
Exemplar — defining the girl: resources/Packages/Sample_Full/Girls.girlsx
<Girls> <Girl Name='Sample Girl' Desc='A cheerful young woman with a quick wit and a quicker smile.' Charisma='40' Beauty='50' Confidence='50' Spirit='60' Age='22' Status='Slave'> <Trait Name='Quick Learner'/> <Trait Name='Optimistic'/> </Girl></Girls>The bespoke text in a job’s messages/work.xml:
<Text id="barmaid.work.signature.sample_girl" weight="5"> <When> <Girl name="Sample Girl"/> <Performance ge="185"/> </When></Text>Then in text/en.xml:
<Text id="barmaid.work.signature.sample_girl"> Sample Girl winked at every regular by name, and the tips landed before the drinks did.</Text><Girl name="..."/> matches the girl’s display name exactly. Combine with <Trait>, <Stat>, <Performance> to scope further — “this scene plays only for Sarah, only when she’s Pregnant, only on a great shift” is one nested <When>.
Pair this with the UniqueRegistry (1.15+) which guarantees at most one live Sarah at a time, so the bespoke content never feels duplicated.
Recipe 4: Reacting to a life change (status flags)
Section titled “Recipe 4: Reacting to a life change (status flags)”When to use this shape: her situation has changed — pregnancy, slave status, drugged, poisoned — and the scene should acknowledge it.
<!-- Pregnant girl tending bar --><Text id="barmaid.work.pregnant" weight="2"> <When> <Status id="Pregnant"/> </When></Text>
<!-- Slave with low Spirit cracking a real smile --><Text id="barmaid.work.spirit_breakthrough" weight="3"> <When> <Status id="Slave"/> <Stat name="Spirit" le="30"/> <Performance ge="185"/> </When></Text><Status> covers run-time conditions the engine tracks per girl. <Stat> reads any of her current stats with ge/le/gt/lt/eq. Compose freely.
Recipe 5: Per-girl interaction script (Lua override)
Section titled “Recipe 5: Per-girl interaction script (Lua override)”When to use this shape: the <When>-gated text bag isn’t expressive enough — you want a branching conversation tree, custom buttons, or stat changes triggered by player choices.
Exemplar: resources/Packages/Sample_Full/Characters/MeetGirl.lua and docs/modding-and-packages/interactions.md.
In the girl’s XML, point TRIGGER_TALK at your custom script. From inside that Lua file you can:
- Branch on
wm.girl.traits,wm.girl.stats,wm.girl.lifetime.*(acts, pregnancies, wear tier, broken-threshold flag) - Show choices, mutate stats, grant items, push other screens
Use this surface when the XML <When> engine runs out — typically: conversation trees, mini-quests, anything that needs player choice mid-scene. For passive shift narration, prefer Recipes 1–4.
Known gap: long-arc history
Section titled “Known gap: long-arc history”<When> can read her current traits, stats, status, and the current shift’s performance. It cannot natively express “30 in-game days after she started this job” or “after her third miscarriage”. Those need Lua reading wm.girl.lifetime.* counters.
Roadmap signal: the 2.x track aims to expose declarative time-and-history triggers to packs so long-arc storytelling lands without writing Lua.
Until then, the practical pattern is:
- Use XML for everything that depends on who she is right now.
- Drop into Lua only for arc progression and player-choice scenes.