Wiki: player-facing rewrite of every layer + add a scenario FAQ, linked from Home
This commit is contained in:
@@ -4,98 +4,91 @@
|
||||
it becomes real again.*
|
||||
|
||||
This is the spine of the whole mod. Everything else — shivs, tunnels, searches, corruption — is a
|
||||
different way to add to, or remove from, the private list this page describes.
|
||||
different way to add to, or remove from, the private stash this page describes.
|
||||
|
||||
## While it is concealed, there is no Thing
|
||||
## While it is concealed, there is no item
|
||||
|
||||
The single most important rule, and the source states it plainly:
|
||||
The single most important rule:
|
||||
|
||||
> `MapComponent_Contraband` tracks what each pawn is hiding. **Concealed contraband is not a Thing.
|
||||
> It is a secret.** It is not in the gear tab, there is no icon, and the player is told nothing —
|
||||
> because an item you can see in a UI is not contraband, it is inventory, and there would be nothing
|
||||
> The prison quietly tracks what each pawn is hiding. **Concealed contraband is not an item on the
|
||||
> map. It is a secret.** It is not in the gear tab, there is no icon, and the player is told nothing —
|
||||
> because an item you can see in the UI is not contraband, it is inventory, and there would be nothing
|
||||
> for a warden to discover and no reason to ever order a search.
|
||||
|
||||
Concretely, a concealed item lives as a `ConcealedItem` row in a `List` on the map component. It
|
||||
carries the def, a `progress` value, the material it ended up made of, an optional source it is
|
||||
being harvested from, tunnel state if it is a pick, and an opaque `contentTag`. It holds **no
|
||||
spawned object** anywhere in the world until one of two things happens:
|
||||
A concealed item is only a hidden fact about a pawn: what it is, how far along it is, what material it
|
||||
ended up made of, an optional source it is being whittled from, its tunnel state if it is a pick, and
|
||||
whatever it holds inside. It has **no physical object** anywhere in the world until one of two things
|
||||
happens:
|
||||
|
||||
| Moment | What happens | Method |
|
||||
|---|---|---|
|
||||
| The prisoner **uses** it | It materialises into their inventory as a real `Thing` | `TryRedeem` |
|
||||
| A warden **finds** it | It is deleted from the list; it never becomes a `Thing` at all | `Confiscate` |
|
||||
| Moment | What happens |
|
||||
|---|---|
|
||||
| The prisoner **uses** it | It materialises into their inventory as a real item |
|
||||
| A warden **finds** it | It is deleted; it never becomes an item at all |
|
||||
|
||||
A corollary the search page leans on hard: **an empty search must still cost time.** If the
|
||||
`Search` job only ever appeared when there was genuinely something to find, the mere *offer* of the
|
||||
job would be the discovery. You have to be able to toss a clean cell and come up with nothing.
|
||||
A corollary the search page leans on hard: **an empty search must still cost time.** If the search
|
||||
job only ever appeared when there was genuinely something to find, the mere *offer* of the job would
|
||||
be the discovery. You have to be able to toss a clean cell and come up with nothing.
|
||||
|
||||
## Where a thing can hide: `ConcealSite`
|
||||
## Where a thing can hide
|
||||
|
||||
Every contraband def declares where it can be hidden. This is not decoration — it decides **who can
|
||||
ever find it**.
|
||||
|
||||
```csharp
|
||||
[Flags] enum ConcealSite { None = 0, OnBody = 1, InCell = 2 }
|
||||
```
|
||||
Every piece of contraband can hide in one of two places, and this decides **who can ever find it**:
|
||||
|
||||
| Site | Holds | Found by |
|
||||
|---|---|---|
|
||||
| `OnBody` | small: a shiv, pills, a phone | searching the **pawn** |
|
||||
| `InCell` | bulky: a rifle, a carboy, a crude pick | tossing the **room** |
|
||||
| **On the body** | small: a shiv, pills, a phone | searching the **pawn** |
|
||||
| **In the cell** | bulky: a rifle, a carboy, a crude pick | tossing the **room** |
|
||||
|
||||
A warden turning over a prisoner and their cell reaches both (`OnBody | InCell`). The reason the
|
||||
enum is split at all: a **constable** in Justice's now-shipping policing layer, frisking a colonist
|
||||
named in a street crime, reaches only `OnBody`, so whatever is under their floorboards stays there
|
||||
until somebody has grounds to search the *room*. The split between "who may search" and "what a
|
||||
search reaches" is deliberate — see [Warden Search](Warden-Search.md).
|
||||
A warden turning over a prisoner and their cell reaches both. The reason the two are split at all: a
|
||||
**constable** in Justice's policing layer, frisking a colonist named in a street crime, only reaches
|
||||
what is on the body, so whatever is under their floorboards stays there until somebody has grounds to
|
||||
search the *room*. The split between "who may search" and "what a search reaches" is deliberate — see
|
||||
[Warden Search](Warden-Search.md).
|
||||
|
||||
## Three sources of contraband, and no list of items
|
||||
## Three kinds of contraband, and no list of items
|
||||
|
||||
At startup, `ContrabandUtility` scans every `ThingDef` and decides whether it is contraband. There
|
||||
is no hardcoded list of item names — there are three recognisers:
|
||||
There is no hardcoded list of contraband item names. At the start of a game, the mod looks at every
|
||||
item in the game and decides, once, whether it counts as contraband, in one of three ways:
|
||||
|
||||
1. **Explicit.** Any def carrying `CompProperties_Contraband`. This is the opt-in, and it is how a
|
||||
mod plugs in its own `acquireWorker` / `useWorker` / `escapeWorker` behaviour.
|
||||
2. **Every drug** (`ThingDef.IsDrug`) — inferred automatically.
|
||||
3. **Every weapon** (`ThingDef.IsWeapon`) that is not `destroyOnDrop` — inferred from **mass**.
|
||||
1. **Explicit.** Anything a mod has deliberately marked as contraband. This is the opt-in, and it is
|
||||
how a mod plugs in its own behaviour for how the item is acquired, used, or used to escape.
|
||||
2. **Every drug** — recognised automatically.
|
||||
3. **Every weapon** that isn't destroyed when dropped — recognised from its **mass**.
|
||||
|
||||
The inference (`ContrabandUtility.Infer`) fills in concealability and hiding place so a modder never
|
||||
For anything not marked explicitly, the mod fills in how well it hides and where, so a modder never
|
||||
has to:
|
||||
|
||||
| Kind | Test | `concealability` | `hideIn` |
|
||||
| Kind | Test | Concealability | Hides |
|
||||
|---|---|---|---|
|
||||
| Hard drug | `drugCategory == Hard` | **0.75** | `OnBody \| InCell` |
|
||||
| Other drug | any other `IsDrug` | **0.5** | `OnBody \| InCell` |
|
||||
| Light weapon | `Mass ≤ 2.0` | **0.7** | `OnBody \| InCell` |
|
||||
| Heavy weapon | `2.0 < Mass ≤ 10.0` | **0.35** | `InCell` only |
|
||||
| Very heavy weapon | `Mass > 10.0` | — | **not contraband** (a minigun hides nowhere) |
|
||||
| Hard drug | drug rated Hard | **0.75** | on body or in cell |
|
||||
| Other drug | any other drug | **0.5** | on body or in cell |
|
||||
| Light weapon | mass ≤ 2.0 kg | **0.7** | on body or in cell |
|
||||
| Heavy weapon | 2.0 kg < mass ≤ 10.0 kg | **0.35** | in cell only |
|
||||
| Very heavy weapon | mass > 10.0 kg | — | **not contraband** (a minigun hides nowhere) |
|
||||
|
||||
`concealability` runs 0..1 where **0 = a warden finds it every time and 1 = never**; higher is
|
||||
harder to find. Drugs and inferred weapons get **no `acquireWorker`** (you cannot synthesise yayo in
|
||||
a bare cell, and you cannot conjure a rifle) and **no `useWorker`** — because vanilla already knows
|
||||
what to do with them. `JobGiver_SatisfyChemicalNeed` takes a drug; an armed prisoner *is* the
|
||||
payload. Getting the item into the cell is the entire problem, and that is all these routes solve.
|
||||
**Concealability** runs 0 to 1, where **0 = a warden finds it every time and 1 = never**; higher is
|
||||
harder to find. Drugs and inferred weapons get **no crafting route** (you cannot synthesise yayo in a
|
||||
bare cell, and you cannot conjure a rifle) and **no special use behaviour** — because vanilla already
|
||||
knows what to do with them. A prisoner takes a drug on their own; an armed prisoner *is* the payload.
|
||||
Getting the item into the cell is the entire problem, and that is all these routes solve.
|
||||
|
||||
Nothing here mutates another mod's defs. It is a lookup keyed on the def, computed once, held in a
|
||||
dictionary. Modded drugs and weapons are covered without ever being named.
|
||||
Nothing here edits another mod's items. It is a one-time reading of each item's own stats. Modded
|
||||
drugs and weapons are covered without ever being named.
|
||||
|
||||
## The poll: no Harmony
|
||||
## The heartbeat
|
||||
|
||||
`MapComponent_Contraband` runs on a heartbeat. Every **250 ticks** (`CheckIntervalTicks`) it visits
|
||||
every pawn who could be concealing something and does all the ongoing work: intake, tunnel progress,
|
||||
and improvisation. If nothing on the map is self-acquirable and there are no escape tools registered,
|
||||
it returns immediately and costs nothing.
|
||||
The prison does its ongoing work on a heartbeat. Every **250 ticks** it visits every pawn who could
|
||||
be concealing something and does all the ongoing work: intake, tunnel progress, and improvisation. If
|
||||
nothing on the map can be self-made and no escape tools are in play, it does nothing and costs
|
||||
nothing.
|
||||
|
||||
Polling — rather than patching — is a deliberate architecture choice. **The tick already visits
|
||||
every held pawn**, so intake needs no Harmony hook on the capture event: a pawn who is in custody but
|
||||
not yet in the `intakeProcessed` set is, by definition, one who was newly taken. The whole mod ships
|
||||
zero Harmony patches, and this is a large part of why.
|
||||
Because the heartbeat already visits every held pawn, intake needs no special trigger on the capture
|
||||
event: a pawn who is in custody but has not yet been processed is, by definition, one who was newly
|
||||
taken.
|
||||
|
||||
### Who counts as a concealer
|
||||
|
||||
`ContrabandUtility.Concealers(map)` is **not** just prisoners. It delegates to Core's
|
||||
secured-context predicate and yields every pawn whose belongings are anyone's business — prisoners,
|
||||
slaves, **and free colonists**:
|
||||
The set of pawns being tracked is **not** just prisoners. It is every pawn whose belongings are
|
||||
anyone's business — prisoners, slaves, ward patients, **and free colonists**:
|
||||
|
||||
> A free colonist forbidden their drug by a drug policy or an ideoligion is precisely the pawn who
|
||||
> keeps a private stash — and vanilla already denies them, with no valve.
|
||||
@@ -107,124 +100,102 @@ authority is not.
|
||||
## Intake: the moment of capture
|
||||
|
||||
Vanilla never strips a captive *on capture* — a downed pawn keeps whatever they walked in with, and
|
||||
the warden path just hauls them to a cell. Intake is Contraband's answer to that gap.
|
||||
the warden just hauls them to a cell. Intake is Contraband's answer to that gap, and it runs on the
|
||||
first heartbeat that sees a newly-held pawn:
|
||||
|
||||
`RunIntake(pawn)` runs from the poll, on the first pass that sees a newly-held pawn:
|
||||
|
||||
1. **Held only.** The pawn must be humanlike and `IsHeld` (prisoner, slave, ward patient). A free
|
||||
colonist is a concealer, but nobody frisks and processes *them* on the way into a cell, so their
|
||||
1. **Held only.** The pawn must be a person and in custody — prisoner, slave, or ward patient. A free
|
||||
colonist is tracked, but nobody frisks and processes *them* on the way into a cell, so their
|
||||
carried gear is never intaken.
|
||||
2. **Once.** The pawn's `thingIDNumber` is added to a `HashSet<int>`. The set stores an `int`, not a
|
||||
pawn reference, so it holds no ghost of a captive who has since died. A second pass finds them
|
||||
already processed and does nothing.
|
||||
3. **Even while downed.** Intake runs *before* the "must be awake" gate. The stash a captive walked
|
||||
in with is hidden the moment they are in custody, not when they wake — everything *after* intake
|
||||
2. **Once.** Each captive is processed a single time; a later pass finds them already done and moves
|
||||
on. (It remembers by an ID, so it holds no ghost of a captive who has since died.)
|
||||
3. **Even while downed.** Intake runs *before* the "must be awake" gate. The stash a captive walked in
|
||||
with is hidden the moment they are in custody, not when they wake — everything *after* intake
|
||||
(whittling, digging) needs them awake, but hiding what is already on you does not.
|
||||
4. **Only what can ride on a body.** `IsIntakeConcealable` accepts a thing only if its contraband
|
||||
props include the `OnBody` flag. A shiv up a sleeve is intaken; a rifle or a suit of armour is
|
||||
too bulky to palm and stays as ordinary strippable gear the player confiscates for free.
|
||||
4. **Only what can ride on a body.** A thing can be intaken only if it is small enough to hide on the
|
||||
body. A shiv up a sleeve is intaken; a rifle or a suit of armour is too bulky to palm and stays as
|
||||
ordinary strippable gear the player confiscates for free.
|
||||
5. **Per-item disposition.** For each concealable thing the captive carries, the mod rolls whether
|
||||
*this* pawn would hide *this* item:
|
||||
*this* pawn would hide *this* item — a base chance of **0.6**, then scaled by the pawn's nature ×
|
||||
nurture (Core's propensity). The roll is **fixed per (pawn, item)** — a settled fact, not a fresh
|
||||
coin each tick. The frightened cook surrenders the knife; the disposed lifer palms it, and always
|
||||
would have.
|
||||
|
||||
```
|
||||
Propensity.Would(pawn, 0.6, SaltIntake ^ item.thingIDNumber)
|
||||
```
|
||||
When a pawn does hide an item, the real object is **removed from the game** and a hidden secret takes
|
||||
its place, preserving what the item was made of and — critically — anything it holds inside.
|
||||
|
||||
Base chance 0.6, then scaled by the pawn's nature × nurture (Core's `Propensity`). The roll is
|
||||
**seeded per (pawn, item)** — a fixed fact, not a fresh coin each tick. The frightened cook
|
||||
surrenders the knife; the disposed lifer palms it, and always would have.
|
||||
## Preserving contents without understanding them
|
||||
|
||||
When a pawn does hide an item, the real `Thing` is **destroyed** (equipment via
|
||||
`DestroyEquipment`, inventory via `Destroy(Vanish)`) and a concealed secret is created in its place,
|
||||
preserving what the item was made of (`stuff`) and — critically — its `contentTag`.
|
||||
A concealed vessel is not just "a jar" — it is a jar *of* something (hooch, piss, a fermented blend),
|
||||
and that something has to survive being hidden and come back when the item is used. Contraband stores
|
||||
what a vessel contains as a single opaque label and **has no idea what it means**.
|
||||
|
||||
## The content tag: preserving contents without understanding them
|
||||
When the item is concealed, that label is captured; when the item comes back out, the label is used
|
||||
to pour the contents back into the freshly-made thing. This is the exact seam **Foul Play** uses to
|
||||
keep a smuggled carboy's contents intact. Without a bridge mod, no content is captured and nothing is
|
||||
lost — a shiv is just a shiv.
|
||||
|
||||
A concealed vessel is not just "a jar" — it is a jar *of* something (hooch, piss, a fermented
|
||||
blend), and that something has to survive being hidden and come back on redemption. Contraband
|
||||
stores a single opaque string, `contentTag`, and **has no idea what it means**:
|
||||
|
||||
```csharp
|
||||
// A bridge mod (Foul Play) fills these in. Null (no bridge) = items have no contents.
|
||||
ContrabandUtility.ContentTagOf; // Func<Thing, string> — read on conceal
|
||||
ContrabandUtility.ApplyContent; // Action<Thing, string> — applied on redeem
|
||||
```
|
||||
|
||||
On concealment, `ContentTagOf(thing)` captures the tag (e.g. "a jar of a piss+hooch cocktail"). On
|
||||
redemption, `ApplyContent(newThing, tag)` pours that content back into the freshly-made thing. This
|
||||
is the exact seam **Foul Play** uses to keep a smuggled carboy's contents intact. Without a bridge,
|
||||
both delegates are null, no content is captured, and nothing is lost — a shiv is just a shiv.
|
||||
|
||||
Fermentation itself lives in Foul Play, not here. A concealed vessel materialises **fresh** and
|
||||
ferments afterward like any other, which suits the theme: fresh-brewed piss is weak, and time is
|
||||
what makes it a weapon. (`concealedTick` records how long it was hidden, for any bridge that wants to
|
||||
restore pre-aging — Contraband keeps the number but never acts on it.) See
|
||||
Fermentation itself lives in Foul Play, not here. A concealed vessel comes back out **fresh** and
|
||||
ferments afterward like any other, which suits the theme: fresh-brewed piss is weak, and time is what
|
||||
makes it a weapon. (The prison remembers how long the vessel was hidden, for any bridge mod that
|
||||
wants to restore its aging, but it never acts on that number itself.) See
|
||||
[Compatibility](Compatibility.md).
|
||||
|
||||
## Redemption: the secret becomes a Thing
|
||||
## Redemption: the secret becomes real
|
||||
|
||||
`TryRedeem(pawn, def)` is where a secret turns real:
|
||||
When a hidden item is finally used, it turns real:
|
||||
|
||||
1. Choose the material — the harvested `stuff`, or the def's default if it was made from stuff.
|
||||
2. `ThingMaker.MakeThing(def, stuff)`.
|
||||
3. If there was a `contentTag`, call `ApplyContent` to restore the contents.
|
||||
4. `TryAdd` it to the pawn's inventory. If that fails, the thing is vanished and nothing is lost.
|
||||
5. Remove the secret from the list.
|
||||
1. It is made from the material it was whittled from (or the item's default material, if it was never
|
||||
made from a material).
|
||||
2. Anything it held inside is poured back in.
|
||||
3. It is placed into the pawn's inventory. If that somehow fails, nothing is dropped and nothing is
|
||||
lost.
|
||||
4. The secret is cleared from the stash.
|
||||
|
||||
The trigger for redemption is the breakout duty tree. `ThinkTreeInjection` runs once at startup and
|
||||
mutates two duty defs directly (an XML patch on a `DutyDef` think tree silently no-ops in 1.6, so it
|
||||
is done in C#):
|
||||
The trigger for this is a breakout. When a prisoner enters an escape or assault-the-colony state, they
|
||||
will arm themselves and then use whatever they are hiding — the same moment a base-game prisoner would
|
||||
reach for a combat drug. The one contraband hook Ludeon wrote and then left reading an always-empty
|
||||
inventory is exactly where this slots in. For each thing a pawn is hiding:
|
||||
|
||||
| Duty | Injected | Where |
|
||||
|---|---|---|
|
||||
| `PrisonerEscape` | `JobGiver_ArmSelf`, then `JobGiver_UseContraband` | **after** `JobGiver_TakeCombatEnhancingDrug` |
|
||||
| `PrisonerAssaultColony` | `JobGiver_ArmSelf`, then `JobGiver_UseContraband` | **before** `JobGiver_AIFightEnemies` |
|
||||
- **Inert contraband** (a drug, a stashed rifle — nothing special to "use"): just materialise it and
|
||||
get out of the way. Vanilla takes the drug on its own; a weapon in inventory is a weapon. But a
|
||||
stashed **weapon** only comes out for someone who would actually use it — the same person who would
|
||||
not stop to pick a rifle off the floor does not become a fighter because the knife was in their
|
||||
sock, and a pawn incapable of violence never does at all. If they qualify, the weapon moves into
|
||||
their hands; otherwise it stays hidden and they run. See [Improvised Weapons](Improvised-Weapons.md).
|
||||
- **Active contraband** (something with a special use — e.g. Foul Play's throw-the-carboy):
|
||||
materialise it, then hand off to that item's own use behaviour.
|
||||
|
||||
`JobGiver_UseContraband` sits right beside `JobGiver_TakeCombatEnhancingDrug` — the one contraband
|
||||
hook Ludeon wrote and then left reading an always-empty inventory. For each thing a pawn is hiding:
|
||||
## Confiscation, and the door every route comes through
|
||||
|
||||
- **Inert contraband** (a drug, a stashed rifle — no `useWorker`): just materialise it and get out
|
||||
of the way. Vanilla takes the drug via `JobGiver_SatisfyChemicalNeed`; a weapon in inventory is a
|
||||
weapon. But a stashed **weapon** only comes out for someone who would actually use it
|
||||
(`Disposition.WouldArmSelf`) — the same person who would not stop to pick a rifle off the floor
|
||||
does not become a fighter because the knife was in their sock, and a pawn incapable of violence
|
||||
never does at all. If they qualify, the weapon is moved from inventory into their equipment slot;
|
||||
otherwise it stays hidden and they run. See [Improvised Weapons](Improvised-Weapons.md).
|
||||
- **Active contraband** (a `useWorker` — e.g. Foul Play's throw-the-carboy): materialise it, then
|
||||
hand off to the worker's `TryGiveJob`, which produces the actual use job.
|
||||
When a warden finds contraband, it is simply removed. It **never becomes a real item** — the warden
|
||||
found it, and there is nothing to drop on the floor. (This is why searching is not a way for the
|
||||
colony to *acquire* a prisoner's drugs; it is a way to *deny* them.)
|
||||
|
||||
## Confiscation and the door every route comes through
|
||||
At the other end, there is one single door that **every** supply route comes through — intake uses it,
|
||||
a bent warden uses it ([Corruption](Corruption.md)), and a smuggling gangmate
|
||||
([Institution: Gangs](Compatibility.md)) uses it. It hands a prisoner something already finished and
|
||||
hidden, optionally with contents. One door, many keys.
|
||||
|
||||
`Confiscate(pawn, def)` simply removes the secret. It **never becomes a Thing** — the warden found
|
||||
it, and there is nothing to drop on the floor. (This is why searching is not a way for the colony to
|
||||
*acquire* a prisoner's drugs; it is a way to *deny* them.)
|
||||
## Saving
|
||||
|
||||
At the other end, `Conceal(prisoner, def, ...)` is the public door **every** supply route comes
|
||||
through — intake calls it, a bent warden calls it ([Corruption](Corruption.md)), and a smuggling
|
||||
gangmate ([Institution: Gangs](Compatibility.md)) calls it. It hands a prisoner something already
|
||||
finished and hidden, optionally with a `contentTag`. One door, many keys.
|
||||
|
||||
## Persistence
|
||||
|
||||
`MapComponent_Contraband.ExposeData` deep-saves the secret list, the per-pawn search cooldowns, and
|
||||
the `intakeProcessed` set. On load it prunes any row whose pawn or def failed to resolve — prisoners
|
||||
who died or left take their secrets with them, and a null key would throw on the next tick.
|
||||
Concealed contraband is saved with your game — the whole stash, the per-pawn search cooldowns, and who
|
||||
has already been intaken. On load, any secret whose pawn or item no longer exists is quietly dropped:
|
||||
prisoners who died or left take their secrets with them.
|
||||
|
||||
## Quick reference
|
||||
|
||||
| Constant | Value | Meaning |
|
||||
|---|---|---|
|
||||
| `CheckIntervalTicks` | 250 | poll cadence for intake / tunnels / improvisation |
|
||||
| Heartbeat | every 250 ticks | poll cadence for intake / tunnels / improvisation |
|
||||
| Intake base chance | 0.60 | per-item, before nature × nurture |
|
||||
| Intake gate | `IsHeld` + `OnBody` | only held pawns, only body-hideable items |
|
||||
| Hard-drug concealability | 0.75 | inferred |
|
||||
| Intake gate | held + body-hideable | only pawns in custody, only items small enough to palm |
|
||||
| Hard-drug concealability | 0.75 | inferred from being a hard drug |
|
||||
| Other-drug concealability | 0.50 | inferred |
|
||||
| Light-weapon concealability | 0.70 | `Mass ≤ 2.0`, hides on body |
|
||||
| Heavy-weapon concealability | 0.35 | `2.0 < Mass ≤ 10.0`, `InCell` only |
|
||||
| Unhideable mass | > 10.0 | not contraband |
|
||||
| Light-weapon concealability | 0.70 | mass ≤ 2.0 kg, hides on body |
|
||||
| Heavy-weapon concealability | 0.35 | 2.0–10.0 kg, in cell only |
|
||||
| Unhideable mass | > 10.0 kg | not contraband |
|
||||
|
||||
---
|
||||
|
||||
*Part of the **Institution** suite — Core · Contraband · Justice · Gangs, alongside Foul Play and
|
||||
Ward. Each stands alone; together they interlock.*
|
||||
**
|
||||
|
||||
Reference in New Issue
Block a user