Skip to content

Effects schema reference

effects.xml is the file inside a job directory that defines which stats, skills, and brothel properties change at the end of each shift. This page covers every element in the schema.

If you are new to job data files, start with jobs-cookbook for the directory layout and worked recipes. For the <When> condition grammar used inside <Group>, see when-conditions.


The root of effects.xml. Contains any mix of <SetStat>, <SetSkill>, <SetBrothel>, <RandomChoice>, <Group>, <Bind>, and <Mod> entries. All top-level entries that are not inside a <Group> fire unconditionally every shift.

<?xml version="1.0" encoding="UTF-8"?>
<Effects>
<!-- entries here -->
</Effects>

Entries are executed in document order. There is no short-circuit evaluation between top-level entries. Every entry runs independently.


Adjusts a girl’s stat by a fixed or random amount.

<SetStat target="self" stat="Tiredness" delta="5"/>
<SetStat target="self" stat="Happiness" delta_min="1" delta_max="3"/>
Attribute Required Notes
target Yes Who receives the change. See “Targets” below.
stat Yes Stat name. See “Stat names” below.
delta One of delta or delta_min+delta_max Fixed integer delta. May be negative.
delta_min With delta_max Minimum of a uniform random range (inclusive). Both bounds are required together, delta may not stand beside them, and the draw is for target="self" only; anything else is refused at load. Allowed inside an <Option>. Game 1.21, unreleased.
delta_max With delta_min Maximum of a uniform random range (inclusive).

If delta_min > delta_max the engine swaps them silently, so delta_min="-3" delta_max="-1" and delta_min="-1" delta_max="-3" are equivalent. This is intentional for readability when expressing “lose between 1 and 3 points.”

Target Meaning
self The girl performing the job.
brothel.girls All girls currently assigned to the same brothel. Useful for morale/training auras.
brothel.others Game 1.21, unreleased. Every other worker in the venue, not the one working the shift, as a temporary change that fades week by week (the game normalises temporary stat changes towards zero by about 30 per cent each week). temporary="true" is required with this target, and temporary on any other target refuses the file at load. The Farm Manager raises the farm’s Obedience this way, by 1, 2 or 3 with her performance.

Charisma, Happiness, Libido, Constitution, Intelligence, Confidence, Mana, Agility, Fame, Level, AskPrice, House, Exp, Age, Obedience, Spirit, Beauty, Tiredness, Health, PCFear, PCLove, PCHate.


Adjusts a girl’s skill by a fixed or random amount.

<SetSkill target="self" skill="Service" delta="1"/>
<SetSkill target="self" skill="NormalSex" delta_min="2" delta_max="4"/>
<SetSkill target="self" skill="Lesbian" delta_min="-3" delta_max="-1"/>
Attribute Required Notes
target Yes Who receives the change. Only self is supported.
skill Yes Skill name. See “Skill names” below.
delta One of delta or delta_min+delta_max Fixed integer delta. May be negative.
delta_min With delta_max Minimum of a uniform random range (inclusive).
delta_max With delta_min Maximum of a uniform random range (inclusive).

Anal, Magic, BDSM, NormalSex, Beastiality, Group, Lesbian, Service, Strip, Combat, Performance, Farming, AnimalHandling, Crafting, Herbalism, Brewing, Cooking, Medicine, OralSex, TittySex, Handjob, Footjob.

Eleven of these arrive with game 1.21, which is not released yet, and they are not all in the same state. OralSex, TittySex, Handjob and Footjob are already weighed by the Escort job, so setting them does change a shift. The seven occupational skills (Farming, AnimalHandling, Crafting, Herbalism, Brewing, Cooking, Medicine) are accepted everywhere but no job reads them yet, so setting one changes nothing in a shift until the Farm jobs are wired to them.


<SetBrothel>: apply a brothel property delta

Section titled “<SetBrothel>: apply a brothel property delta”

Adjusts a numeric property on one or more brothels.

<SetBrothel target="player.brothels" key="Fame" delta="3" clamp_max="100"/>
<SetBrothel target="brothel" key="Filthiness" delta="2"/>
Attribute Required Notes
target Yes Which brothel(s) to update. See “Targets” below.
key Yes Property name. See “Key names” below.
delta Yes Integer delta. May be a literal or a bind reference ($bindname). See <Bind> below.
clamp_min No If present, the result is clamped to this minimum after the delta is applied.
clamp_max No If present, the result is clamped to this maximum after the delta is applied. clamp_min > clamp_max on one entry is a load error.
Target Meaning
brothel The brothel the girl is currently assigned to.
player.brothels Every brothel the player owns. Used by jobs that promote the player’s business city-wide (for example, a recruiter who raises Fame everywhere at once).
Key Notes
Fame How well-known the brothel is (0-100). Affects customer traffic.
Filthiness Cleanliness level of the building. High values reduce happiness and income.
Happiness Aggregate morale of girls in the brothel (0-100).

Selects exactly one <Option> at random, weighted by the weight attribute. Only the selected option’s children execute.

<RandomChoice>
<Option weight="6"><SetStat target="self" stat="Exp" delta="0"/></Option>
<Option weight="2"><SetSkill target="self" skill="Service" delta="1"/></Option>
<Option weight="2"><SetSkill target="self" skill="Strip" delta="1"/></Option>
</RandomChoice>
Attribute Notes
weight Positive integer. Selection probability is weight / sum_of_all_weights.

An <Option> may contain any mix of <SetStat>, <SetSkill>, <Item>, <ProductionScale> and, from game 1.21, <Resource>; every leaf in the winning option fires. A nested <RandomChoice> is refused at load, and so is <SetBrothel> inside an option. A <Resource> in an option gives a random amount by writing one option per count, with message and scale="false" working as they do at the top level; sale="true" inside an option is refused at load, so a sale stays where its value reconciles. An empty <Option> is not supported; use a <SetStat delta="0"> as a no-op placeholder when you want a probability sink (a branch that intentionally does nothing).

There is no minimum number of options, but a <RandomChoice> with a single option is equivalent to an unconditional entry and should be simplified.

An <Option> may carry one <When>, using the same grammar as a <Group>’s. Options whose condition is false are removed from the draw before the weights are added up, so the choice is “one of the ones that apply” rather than “one of all of them, and nothing if she did not qualify”.

<RandomChoice>
<Option weight="1">
<When><Skill name="Combat" ge="50"/></When>
<Item name="Manual of Arms"/>
</Option>
<Option weight="1">
<When><Skill name="Magic" ge="50"/></When>
<Item name="Manual of Magic"/>
</Option>
<Option weight="1"><Item name="Plain Note"/></Option>
</RandomChoice>

What follows from that:

  • A worker who qualifies for two of five options draws between those two at their own weights, and never rolls a blank.
  • Weights only mean anything within the eligible subset. A heavily weighted option that is not eligible cannot crowd out the others, because it is not in the draw at all.
  • If nothing qualifies, nothing in the <RandomChoice> fires. No option is forced.
  • An <Option> without a <When> is always eligible, exactly as before.
  • At most one <When> per option; two is a load error rather than a silent last-wins.

An item and what it costs are one thing. Anything else in the same <Option> as an <Item> (for an <Item> placed directly in a <Group>, the rest of that group) is treated as the item’s cost. If the item cannot be delivered, because the player’s inventory is full or the shift’s resource step failed, the cost is not charged either, and the player is told the item was left behind and nothing was spent on it. So put a book’s mana cost in the option that grants the book, and put anything the worker gets whether or not the book arrives (experience, say) in another group.

This is what lets a job hand out at most one item a shift from a list a worker only partly qualifies for. One gated <Group> per item cannot do that: groups do not short-circuit each other, so every group that passes fires and there is no upper bound on how many items one shift can produce.


Groups one or more entries under an optional <When> gate. All entries inside the group execute if the <When> condition passes (or if no <When> is present). Groups do not short-circuit each other: if two groups are present, both are evaluated independently every shift.

<Group>
<When>
<Stat name="Tiredness" lt="50"/>
</When>
<SetStat target="self" stat="Happiness" delta="1"/>
<SetStat target="self" stat="Tiredness" delta="3"/>
</Group>

A <Group> may also contain <Bind> declarations (see below). Binds declared inside a group are scoped to that group and visible to the group’s <When> check and all entries inside it.

Effects that depend on a threshold condition (such as “fire penalty if perf < 20, fire bonus if perf >= 20”) must use two separate groups with complementary <When> clauses. Do not assume the absence of one group’s <When> implies the other ran; the two groups are independent:

<Group>
<When><Bind name="score" lt="20"/></When>
<SetStat target="self" stat="Tiredness" delta="2"/>
</Group>
<Group>
<When><Bind name="score" ge="20"/></When>
<SetStat target="self" stat="Happiness" delta="1"/>
<SetStat target="self" stat="Tiredness" delta="4"/>
</Group>

Both groups are always evaluated. The first fires when the condition is true; the second fires when its own condition is true. Together they cover the full range.


Computes an integer value from a formula and assigns it a name. The name can then be referenced in <When><Bind name="..." ge="N"/></When> and in delta="$name" on <SetBrothel>.

<Bind name="score" expr="(Charisma + Intelligence + Service) / 3"/>
Attribute Notes
name Identifier used to reference the value. Lowercase, no spaces.
expr Arithmetic expression over girl stats and skills. Integer arithmetic; division truncates toward zero.

All stat names and skill names listed under <SetStat> and <SetSkill> above are valid. For example: Charisma, Intelligence, Service, Happiness, Tiredness.

More sources arrive with game 1.21, which is not released yet. The Farm’s Marketer reads the first three to decide how much of the estate’s surplus to sell, so setting one of them in your own job changes a shift the same way it changes hers.

Source Reads
perf This shift’s performance score: the same number <When><Performance ge="..."/></When> tests.
stock(core/goods) How much of that resource there is now, read exactly as a <Stock resource="..."> condition reads it: an estate resource from the estate store, a building-scoped one from this building’s shelf.
demand(core/goods) How much of that resource the estate actually consumed in the last completed week. Stock sold away is excluded, so a seller cannot inflate the figure she is reading by selling.
setting(mana_reserve) A setting of the building this shift is worked in, as the player set it for that building (see the buildings reference). A number setting reads its value; a switch reads 1 on and 0 off (before engine 1.21 round 6 a switch read 0). A setting the building does not offer reads 0, and the pack validator reports it as an error. The Farmer multiplies her rebound chance by setting(magic_assist) so it is nothing on a farm with the switch off.
counter(herd_milk) What earlier workers of this shift have added to that named counter, across every building on the estate (see <Counter>). A counter nobody has added to reads 0.
state(filthiness) The venue’s state as her shift starts, by the same keys <BrothelStat> reads: working_girls, filthiness, advertising_budget. Any other key is a parse error.
picked(farm_marketer.mood.great) 1 when the shift’s picked message line has a <Text id> that starts with that string, else 0. The line is picked before effects.xml runs, so an effect can follow the scene the line tells. The Farm’s Marketer adds 10 * picked(farm_marketer.mood.great) - 10 * picked(farm_marketer.mood.bad) to her price scale, so the price and the story come from one draw, and the sale stays in effects.xml where its value is reconciled.

These sources are also available to the <Bind> in a message line’s <When> (messages/work.xml), computed with the shift’s own figures, so a line can be chosen on the same numbers the effects use: a Milker who reads cap as 0 because an earlier milker emptied the herd gets the line written for that. A message line’s <When> may also test a bind declared in effects.xml by name (<Bind name="bcap_way" ge="1"/>), which is how the Hunter’s lines know whether the extra beast followed her home.

Rules worth knowing before you write one:

  • The thing inside stock() and demand() is a resource id, not an identifier, so it may contain /, . and -. Write it bare: stock(core/goods), no quotes. The thing inside setting() is the setting’s id as the building declares it; the thing inside counter() is a counter name, letters, digits and underscores only.
  • An identifier the language does not know is a parse error, and the message lists what is allowed.
  • A resource id that no installed package defines does not read as zero: the whole job is rejected at load and cannot be worked. That is deliberate, so a typo fails loudly once instead of quietly every shift.
  • All of them read 0 where the value does not exist yet: perf before the shift has been scored (in <Eligibility> and in <Performance> factors), and stock(), demand(), setting() and counter() on a surface with no building.

A <Bind> may reference any bind declared earlier in the same <Group>, by name, anywhere its identifier could appear in expr. Binds are evaluated in declaration order, so a later bind sees the value of every bind above it.

<!-- Two-step bind: tier reads score -->
<Bind name="score" expr="(Charisma + Intelligence + Service) / 3"/>
<Bind name="tier" expr="score / 20"/>

The inline-formula workaround still works for packs that target older engine versions:

<!-- Inline form, equivalent and back-compatible -->
<Bind name="score" expr="(Charisma + Intelligence + Service) / 3"/>
<Bind name="delta" expr="(Charisma + Intelligence + Service) / 60"/>

A bind cannot reference a sibling declared later, and binds in one group are not visible from a different group.

A <Bind> may be declared at the top level of <Effects> (visible everywhere) or inside a <Group> (scoped to that group). The group-scoped form is preferred when the value is only used inside that group.


Produces or consumes a resource as part of the shift. The whole set of resource operations in one shift is applied as a single transaction: if any requirement cannot be met, none of them happen.

<Resource id="core/goods" require="20" consume="20" sale="true"/>
<Resource id="core/food" produce="400"/>
Attribute Notes
id Required. A package-qualified resource id (core/goods, MyPack/towels). A legacy bare name (beasts) is normalised to its core/ form. An id no installed package defines rejects the whole job at load.
produce Amount added. A non-negative integer, or $bindname.
consume Amount removed. Same shape.
require Amount that must be present for the operation to happen at all. Same shape.
target estate (default) or building. The resource’s own StorageScope decides which is allowed.
sale true or false (default false). Any other value is a load error.
value What this part of a sale fetched, in gold, before any commission. A non-negative integer or $bindname. Only on sale="true" with a plain consume; anywhere else it is a load error.
message Game 1.21, unreleased. A line added under the shift’s report when the resource step commits, ${name} replaced. Do not repeat the amount in it: the store line under the report states what moved. A rejected step shows no such line.
scale Game 1.21, unreleased. true (default) or false; anything else is a load error. false keeps this produce out of the shift’s <ProductionScale>, for an amount that should not follow her mood or a charm: the Farmer’s finds use it.
reduce Game 1.21, unreleased. Takes this shift’s own produce of that resource down by N, after the shift’s scale, never below zero. It never touches the store and is not demand. It stands alone: produce, consume, consumeUpTo, require, sale, value or scale beside it refuse the file. Allowed inside an <Option>. The Farmer’s finds pay for themselves with it: each plant and each fruit of knowledge is one reduce on core/food.

At least one of produce, consume or require must be present.

When the resource step is rejected, the picked line of narration is withheld, and with it everything that line carried: its stat and skill changes, its <Enjoyment> and its <SetBrothel> changes. A <Group needs="materials"> is withheld the same way. The job’s other groups stand.

sale="true" (game 1.21, unreleased) marks a consume as stock sold away rather than stock the estate wanted. The difference only shows up in demand(): a sale is left out of it, so a job that sells cannot drive up the demand figure it reads to decide how much to sell. Use it on a selling job, and leave it off when the stock is being used.

value on a sale (game 1.21, unreleased) says what that stock sold for, so the Accounting screen can show each stock’s takings instead of one sum for the whole sale. It pays nothing: the money is still whatever <Revenue> and <Earnings> the job gives. The values of one shift must add up to that shift’s Revenue plus Earnings; the game checks it every shift, and a shift where they do not is shown in Accounting as not reconciled rather than as a price. The Farm’s Marketer computes each part in a bind and uses the same binds for both:

<Bind name="goods_gross" expr="$goods_sold * 16"/>
<!-- ... -->
<Resource id="core/goods" consume="$goods_sold" sale="true" value="$goods_gross"/>
<Revenue delta="$net"/>
<Earnings delta="$cut"/>

A job that sells gets a line under its message saying what was sold and, where value is given, for how much. The message itself therefore does not need to guess quantities, and should not: it is picked before the effects decide the sale.

<ProductionScale>: scale what this shift produces (game 1.21, unreleased)

Section titled “<ProductionScale>: scale what this shift produces (game 1.21, unreleased)”

Multiplies every produce amount the shift stages, once, rounded down, after every scale in the shift has been applied. The job’s base bands stay what they are; the scale is an event on top of them, such as a charm that lands or one that slips. Two scales in one shift multiply. Consumes and sales are untouched.

<ProductionScale percent="125" message="${name} worked a charm into the rows, and the field gave more."/>
Attribute Notes
percent Required. A whole number above 0, or $bindname. 100 changes nothing.
message Optional. Appended to her report after the shift’s own line, ${name} replaced. Shown only when the shift’s resource step went through.
outcome Optional. A word for what happened, reported when the group is observed (see observe below). The game never reads it; landed and slipped are the convention the Farm’s jobs use. Must not be empty.

Allowed directly in a <Group> and inside a <RandomChoice><Option>. The option form is the useful one: one draw decides between outcomes, and each outcome carries its own scale, stat changes and message.

An <Option> weight may be a bind, so the odds of a draw can follow a competence:

<Bind name="succeed" expr="clamp(35 + Magic / 2, 0, 85)"/>
<Bind name="fail" expr="100 - $succeed"/>
<RandomChoice>
<Option weight="$succeed">...</Option>
<Option weight="$fail">...</Option>
</RandomChoice>

This is one roll with complementary odds. Two separate <RandomChance> leaves would be two independent rolls, which can both land or both miss. A resolved weight below 0 counts as 0; if every weight resolves to 0 nothing is picked.

Marks a group as an attempt at the work rather than a consequence of the shift existing. When the shift’s resource step is rejected (no materials), everything the group changed on the worker is withheld: mana it would have spent, an injury it would have caused. The job’s own gains outside such groups stand, as they always have for a shift without materials.

The Farm’s five material-working jobs use all three together for magical assistance: a <BuildingSetting> the player turns on per farm, a Magic and Mana gate, one draw weighted by Magic, a scale up on success and a scale down plus an injury on failure.

Names a group as an attempt worth counting. Nothing changes in play. When the game runs under measurement (the economy observation layer, off in ordinary play), every shift reports the group once: who, which job, which building, week and shift, whether the group’s <When> opened, whether the attempt was made (the gate opened and, for a needs="materials" group, the shift’s materials were there), and the outcome word of the <ProductionScale> that fired. The label is yours; the game passes it through. Empty is an error.

A condition directly under the observed group’s <When> may carry reason="...": one sentence, ${name} allowed. When that condition is the first one that fails, the sentence goes into the worker’s report for the shift and into the measurement record, so the player reads why the policy did not act this week. A condition without reason closes the gate silently, which is right for the policy switch itself: a building with the option off should not produce a line every week. When the gate opens but the shift’s materials are missing, the record says materials and the worker’s report already carries the materials line.

<Group needs="materials" observe="magic">
<Bind name="spare" expr="Mana - 15 - setting(mana_reserve)"/>
<When>
<BuildingSetting id="magic_assist"/>
<Skill name="Magic" ge="25" reason="${name} has too little skill in magic to try a charm."/>
<Stat name="Mana" ge="15" reason="${name} has no mana to spare for a charm this shift."/>
<Bind name="spare" ge="0" reason="${name} kept her mana above this building's reserve and tried no charm."/>
</When>
...
<Option weight="$succeed">
<ProductionScale percent="125" outcome="landed" message="..."/>
</Option>
<Option weight="$fail">
<ProductionScale percent="60" outcome="slipped" message="..."/>
<SetStat target="self" stat="Health" delta="-5"/>
</Option>
</Group>

An observed group may have no effects at all. It is kept, because it exists to report its gate; every other group with nothing in it is dropped at load as before. The Farm’s Milker uses one: a first group that only observes and carries the reasons, then the band groups that do the work.

<Counter>: a shared per-shift limit (game 1.21, unreleased)

Section titled “<Counter>: a shared per-shift limit (game 1.21, unreleased)”

A named counter the estate keeps for the shift being worked. A job adds to it with <Counter> and any job reads it with counter(<name>) in a <Bind>, so several workers of one shift, in one building or across all of them, can share a limit that none of them owns: a herd that gives only so much milk in one shift, however many milkers stand at it.

<Group>
<Bind name="cap" expr="clamp(stock(core/beasts) * 10 - counter(herd_milk), 0, 300)"/>
<Bind name="out" expr="clamp($cap, 0, 180)"/>
<When>
<Stock resource="core/beasts" ge="1"/>
<Bind name="cap" ge="1"/>
<Performance ge="150" le="299"/>
</When>
<Resource id="core/drinks" produce="$out"/>
<Counter name="herd_milk" add="$out"/>
</Group>
Attribute Notes
name Required. Letters, digits and underscores, nothing else, so counter() can read it. Empty or anything else refuses the job.
add Required. A whole number of at least 0, or $bindname. A negative literal refuses the job; a bind that resolves to 0 or below adds nothing.

Rules:

  • Allowed directly in a <Group> (or at the top level), not inside a <RandomChoice><Option>.
  • The add happens only when the shift’s resource step commits. A shift whose materials were missing, or whose transaction was rejected, adds nothing, so the next worker does not find a limit eaten by work that never happened.
  • Counters are per shift: the day and the night are separate slots, so the night starts from zero. The weekly settlement clears them all.
  • They are saved, as <Counter Id Day Night> under <Resources>, so a save between the day and the night keeps what the day already took.
  • A counter is estate-wide. Two Farms milking the same shift share herd_milk, which is right when the pens are the estate’s; a limit that should be one building’s needs a name that only that building’s jobs use.

The shipped example is resources/jobs/milker/effects.xml: one observed group with the two reasons (empty pens, herd already milked dry), then four band groups that each produce the smaller of their own figure and what is left, and add what they took.

One counter name is the engine’s own: herd_care. Where the weekly beast loss is settled, the engine reads herd_care (both shift slots) and subtracts it from the loss, never below zero and never as a gain; resources/jobs/farm_vet/effects.xml is the job that adds to it (3, 2 or 1 by band, with an observed gate on the herd). Every other counter name means only what the data that reads it makes it mean.

<TradeForWorker>: give stock away for a worker (game 1.21, unreleased)

Section titled “<TradeForWorker>: give stock away for a worker (game 1.21, unreleased)”

The building hands over a quantity of a stored resource and a worker arrives. Either all of it happens or none of it does: not enough of the resource, or no room for her, and nothing moves. The worker’s report says which.

<Group>
<When>
<BuildingSetting id="marketer_may_trade"/>
<Stock resource="core/food" ge="2000"/>
<RandomChance pct="4"/>
</When>
<TradeForWorker resource="core/food" amount="2000"
message="A merchant would not take coin. He wanted ${amount} ${resource}, and ${worker} came back with ${name}."/>
</Group>
Attribute Notes
resource Required. A package-qualified resource id. The trade pays from the estate store; a building-scoped resource can never pay for it.
amount Required. A whole number above 0. A trade that costs nothing is not a trade, and the job is refused at load.
message Optional. Her event line. ${name} is the worker on shift, ${worker} the one who arrived, ${amount} and ${resource} the price. ${resource} is the resource’s display Name; a resource declared without one shows its local id instead, so give your resources names.

Three things to know before using it.

Whether it is allowed is your <When>. The effect itself does not ask anybody. The Farm puts it behind a <BuildingSetting> the player switches on per building, and a job that offers a trade with no gate at all will trade whenever the roll lands.

It runs before the shift’s own <Resource> operations, and it takes its price out of that shift’s sale. A seller who sells her surplus would otherwise look at an empty shelf every time; and without the second half she would try to sell food the trade had already spent, and the whole sale would be refused. The shift’s outcomes are not re-rolled: only the quantities and the money move, in the same proportion, so what she sold, what it fetched, and what the building booked still agree.

The worker arrives resting and starts next week. She is not given a job, and the week that brought her does not run her. In Accounting the resource shows under Traded, its own column: it is neither a sale nor a loss, and it counts as demand, so a seller’s reserve grows to cover it.

The price and the odds are yours to set. Nothing in the engine knows what a fair trade is.

<Mod>: C++ pipeline bridge (legacy <RunHelper> accepted)

Section titled “<Mod>: C++ pipeline bridge (legacy <RunHelper> accepted)”

Triggers a named C++ sub-pipeline from inside the data-driven executor. The element is registry-based: any name parses successfully, and the engine looks the name up at apply time. The legacy spelling <RunHelper name="..."/> is still accepted as a synonym.

<Mod name="whore_act"/>

<Mod> may appear inside <Effects> or inside <Performance>. The latter is the right surface when the hook is producing a synergy multiplier the performance score should consume.

Attribute Notes
name Name of the registered helper to invoke. Looked up in the engine’s mod registry at apply time.
Name What it does Direction
whore_act Runs the full per-customer loop for whore-archetype jobs: customer selection, refusal check, act performance, side effects. (none; pipeline)
work.barcook Bar food synergy producer; publishes a per-Brothel food-quality scalar BarMaid + BarWaitress + BarWhore consume. publisher (headcount/scalar)
work.barmaid Reads bar-staff headcount (Cook + Waitress) to scale tip income. consumer (headcount)
work.waitress Reads BarMaid + BarCook presence to scale service multiplier. consumer (headcount)
work.barsinger Reads piano_present to apply doubled-performance bonus when a Pianist is on shift. consumer (presence flag)
work.piano Publishes piano_present binary flag the Singer reads. publisher (presence flag)
work.barwhore Reads bar-staff headcount to scale income. consumer (headcount)
work.barstripper Reads sleazybarmaid_present. consumer (presence flag)
work.sleazybarmaid Reads barstripper_present (mutual cross-consumer pair). consumer (presence flag)
work.sleazywaitress Reads barstripper_present. consumer (presence flag)
work.advertising Placeholder; the advertising level is still computed by the engine’s own job code. placeholder
work.security Placeholder; the security level and its deterrence are still computed by the engine’s own job code. placeholder
work.masseuse Placeholder; happy-ending scaling against customer satisfaction is not data-driven yet. placeholder

Most of the per-job hooks above are placeholders today: the engine records that the hook fired, but the synergy effect itself is still whatever the engine’s own job code computes. Declaring the hook in a pack job is harmless and forward-compatible; it does not yet change the numbers. The feature compatibility page tracks when that changes.

For details on writing synergy-aware message variants that consume these hooks (and the four partner-direction shapes they come in) see [visible-synergies](/docs/pack-authoring/reference/visible-synergies/).

Unknown <Mod> names parse successfully but silently no-op at apply time. This is intentional for forward-compat with future engine versions that may register the name; packs authored against a newer engine load cleanly on an older one rather than failing at parse time.


1. Fixed stat penalty + optional skill gain

Section titled “1. Fixed stat penalty + optional skill gain”

A simple job that always costs tiredness and has a 40% chance of gaining one of two skills:

<Effects>
<SetStat target="self" stat="Tiredness" delta="5"/>
<SetStat target="self" stat="Exp" delta="2"/>
<RandomChoice>
<Option weight="6"><SetStat target="self" stat="Exp" delta="0"/></Option>
<Option weight="2"><SetSkill target="self" skill="Service" delta="1"/></Option>
<Option weight="2"><SetSkill target="self" skill="Strip" delta="1"/></Option>
</RandomChoice>
</Effects>

Six out of ten shifts are no-ops on the <RandomChoice>; two out of ten gain Service; two out of ten gain Strip. The total weight is 10, matching the percent(40) then percent(50) pattern from legacy C++ (see resources/jobs/houserecruiter/effects.xml for the full worked example with a derived performance bind).

2. Orientation-gated training (single file, multiple job slots)

Section titled “2. Orientation-gated training (single file, multiple job slots)”

Three job slots that share one data directory but gate different effects on a <JobParam>:

<Effects>
<Group>
<When><JobParam name="orientation" value="straight"/></When>
<SetSkill target="self" skill="NormalSex" delta_min="2" delta_max="4"/>
<SetSkill target="self" skill="Anal" delta_min="1" delta_max="2"/>
<SetSkill target="self" skill="Lesbian" delta_min="-1" delta_max="-3"/>
<SetStat target="self" stat="Tiredness" delta="5"/>
<SetStat target="self" stat="Exp" delta="2"/>
</Group>
<Group>
<When><JobParam name="orientation" value="bi"/></When>
<!-- ... -->
</Group>
<Group>
<When><JobParam name="orientation" value="lesbian"/></When>
<!-- ... -->
</Group>
</Effects>

The three groups are all evaluated every shift; only the one whose <JobParam> matches the registered slot parameter fires. See resources/jobs/houseso/effects.xml for the full file.

3. Random skill pick from a pool (two independent draws)

Section titled “3. Random skill pick from a pool (two independent draws)”

Two consecutive <RandomChoice> blocks to pick two skills from a pool of seven, each with equal probability. The same skill may be picked twice; the executor accumulates deltas correctly:

<Effects>
<SetStat target="self" stat="Exp" delta="2"/>
<SetStat target="self" stat="Tiredness" delta="4"/>
<RandomChoice>
<Option weight="1"><SetSkill target="self" skill="NormalSex" delta_min="1" delta_max="1"/></Option>
<Option weight="1"><SetSkill target="self" skill="Anal" delta_min="1" delta_max="1"/></Option>
<Option weight="1"><SetSkill target="self" skill="BDSM" delta_min="1" delta_max="1"/></Option>
<Option weight="1"><SetSkill target="self" skill="Group" delta_min="1" delta_max="1"/></Option>
<Option weight="1"><SetSkill target="self" skill="Lesbian" delta_min="1" delta_max="1"/></Option>
<Option weight="1"><SetSkill target="self" skill="Service" delta_min="1" delta_max="1"/></Option>
<Option weight="1"><SetSkill target="self" skill="Strip" delta_min="1" delta_max="1"/></Option>
</RandomChoice>
<RandomChoice>
<!-- identical block; each draw is independent -->
...
</RandomChoice>
</Effects>

See resources/jobs/basictraining/effects.xml for the full file.

Compute a derived performance value with <Bind>, gate two complementary groups on it, and use a bind reference in delta="$delta":

<Effects>
<Group>
<Bind name="score" expr="(Charisma + Intelligence + Service) / 3"/>
<When><Bind name="score" lt="20"/></When>
<SetStat target="self" stat="Tiredness" delta="2"/>
</Group>
<Group>
<Bind name="score" expr="(Charisma + Intelligence + Service) / 3"/>
<Bind name="delta" expr="(Charisma + Intelligence + Service) / 60"/>
<When><Bind name="score" ge="20"/></When>
<SetBrothel target="player.brothels" key="Fame" delta="$delta" clamp_max="100"/>
<SetStat target="self" stat="Happiness" delta="1"/>
<SetStat target="self" stat="Tiredness" delta="4"/>
<SetStat target="self" stat="Exp" delta="2"/>
</Group>
</Effects>

The perf bind is declared in both groups independently because binds in one group are not visible from another. Inside a single group a later bind may reference earlier ones.


The elements above (<SetStat>, <SetSkill>, <SetBrothel>, etc.) are specific to effects.xml inside job directories. Consumable items use a parallel but distinct <effect type="..."> syntax inside their <use> block. The full table lives in items-reference. For convenience the trait-related types are listed here:

Grants the named trait to the consumer.

<effect type="grant_trait" name="Caffeine Addict"/>

With weeks="N": the trait is temporary and removed after N weeks.

Removes the named trait from the consumer. Used by cures and condition-fix items.

<effect type="remove_trait" name="Chlamydia"/>

value= and weeks= are ignored. There is no timed-removal semantics — if the trait re-applies later (e.g. the girl re-catches the disease), that is the disease loop’s job, not the cure’s.


  • when-conditions: full grammar for <When> conditions used inside <Group>
  • jobs-reference: full schema for all other job files (performance, wage, gains, messages)