Files
institution/Wiki/contraband/Concealment-and-Intake.md
T
flan 3530100ed5 Aggregate the whole suite wiki onto the Institution page
Wiki/Home.md is the suite's front door; every layer's wiki now mirrors here
under Wiki/<layer>/ (core, contraband, justice, gangs, ward), so the whole
suite reads in one place with the index linking to the local pages. The
layer repos stay canonical -- Tools/sync-wiki.sh refreshes the aggregated
copy on demand. The README's main page points to it.
2026-07-15 20:30:38 +00:00

13 KiB
Raw Blame History

Concealment and Intake

How a hidden item exists without existing, how it gets onto a prisoner in the first place, and how 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.

While it is concealed, there is no Thing

The single most important rule, and the source states it plainly:

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 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:

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

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

Every contraband def declares where it can be hidden. This is not decoration — it decides who can ever find it.

[Flags] enum ConcealSite { None = 0, OnBody = 1, InCell = 2 }
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

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.

Three sources 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:

  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.

The inference (ContrabandUtility.Infer) fills in concealability and hiding place so a modder never has to:

Kind Test concealability hideIn
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)

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.

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.

The poll: no Harmony

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.

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.

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:

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.

Whether anyone may search a given concealer is a separate question with a separate answer (the warden's authority reaches prisoners; a future cop's reaches colonists). Tracking is pawn-agnostic; 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.

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 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 (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.

  5. Per-item disposition. For each concealable thing the captive carries, the mod rolls whether this pawn would hide this item:

    Propensity.Would(pawn, 0.6, SaltIntake ^ item.thingIDNumber)
    

    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.

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.

The content tag: preserving contents without understanding them

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:

// 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 Compatibility.

Redemption: the secret becomes a Thing

TryRedeem(pawn, def) is where a secret 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.

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#):

Duty Injected Where
PrisonerEscape JobGiver_ArmSelf, then JobGiver_UseContraband after JobGiver_TakeCombatEnhancingDrug
PrisonerAssaultColony JobGiver_ArmSelf, then JobGiver_UseContraband before JobGiver_AIFightEnemies

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:

  • 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.
  • 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.

Confiscation and the door every route comes through

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.)

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), and a smuggling gangmate (Institution: Gangs) 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.

Quick reference

Constant Value Meaning
CheckIntervalTicks 250 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
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

Part of the Institution suite — Core · Contraband · Justice · Gangs, alongside Foul Play and Ward. Each stands alone; together they interlock. Developed with substantial assistance from Claude (Anthropic).