Vessels and Substances
Foul Play splits every throwable into two axes that never touch:
- The vessel (
CompVessel) is the container — how it is delivered. - The substance (
SubstanceDef) is the fluid — what it does when it lands.
A "piss jar" is the intersection: a jar (vessel) of urine (substance). This page is about how the two combine at runtime, and the single most important fact about the whole framework: a vessel holds a weighted blend, and every consequence reads that blend, not the def.
A vessel is two numbers
RimWorld has no carryable containers of its own, so Foul Play ships a capability instead of a family.
Any ThingDef becomes a throwable vessel just by carrying CompProperties_Vessel, which declares two
things and lets everything else derive:
<li Class="FoulPlay.CompProperties_Vessel">
<volumeMl>500</volumeMl> <!-- capacity in millilitres -->
<sealed_>true</sealed_> <!-- corked/waxed, or open like a bucket -->
<substance>PN_Urine</substance> <!-- the default fill -->
</li>
Everything physical about the weapon is a function of those two numbers, derived once in VesselProfile
so the derivation is consistent across our vessels and any modded one. The reference point is a 5 L
carboy (RefVolumeMl = 5000); VolumeFraction = Clamp(volumeMl / 5000, 0.05, 4).
| Derived property | Formula | Sealed → | Open → | Consumed by |
|---|---|---|---|---|
| Blast radius | 4.5 × VolumeFraction^(1/3) |
×1.0 | ×1.35 (slops wider) | the splash |
| Dose density | constant | 1.0 | 0.55 (lost some in transit) | splash damage + infection dose |
| Filth count | max(2, round(4 × √VolumeFraction)) |
— | — | how much mess it leaves |
| Spill per cell | constant | 0.0 | 0.06 | (design model; see below) |
| Throw range factor | Lerp(1.3, 0.6, InverseLerp(0.1, 1.6, VF)) |
×1.0 | ×0.65 | (design model; see below) |
| Fill ticks | round((volumeMl / mlPerDay) × 60000) |
— | — | how long a body takes to fill it |
Blast is a cube-root of volume because a splash is a volume spreading over an area — doubling the litres does not double the radius. A sealed vessel bursts concentrated; an open one has already lost liquid down the carrier's leg and spreads the rest thin.
Honesty note. Blast radius, dose density and filth count are read live by the splash. Spill-per-cell and throw-range-factor are part of the same single-source derivation model but are not yet wired into a live per-cell spill or a range multiplier; the open vessel's "wears it on the way" character is currently expressed at throw time as a tripled self-splash chance (0.18 → 0.54). The model is the design contract; the splash is where it currently bites.
The four starter vessels, derived
Concrete numbers for the Piss Nuke family, all falling straight out of the two dials:
| Vessel | Vol | Sealed | Blast radius | Dose density | Filth | Fill time (urine, 1750 ml/day) |
|---|---|---|---|---|---|---|
| Piss Jar | 500 ml | yes | ~2.1 | 1.0 | 2 | ~7 in-game hours |
| Piss Bottle | 750 ml | yes | ~2.4 | 1.0 | 2 | ~10 in-game hours |
| Piss Bucket | 8000 ml | no | ~7.1 | 0.55 | 5 | ~4.6 days |
| Piss Nuke | 5000 ml | yes | ~4.5 | 1.0 | 4 | ~2.9 days |
The bucket holds sixteen times the jar but throws shorter, lands wider, and arrives thinner — the whole personality of an open container, none of it hand-authored.
A vessel holds a blend, not a substance
Here is the part that turns four items into a system. CompVessel does not store "the substance." It
stores a weighted blend — a dictionary of how much of each substance is present, by volume in ml:
amounts = { FP_Urine: 250, FP_Feces: 250 } // a half-and-half cocktail in a 500 ml jar
A single substance is just a blend of one. A never-touched vessel reports null and behaves as a full
vessel of its def's substance until you pour something in — at which point the amounts materialise and
can be mutated.
The four measurements
Every downstream system asks the blend the same handful of questions:
| Property | Meaning |
|---|---|
Capacity |
how much it holds full, in ml (= volumeMl) |
TotalVolume |
how much liquid is in it right now (a never-touched vessel = full of its def) |
Headroom |
Capacity − TotalVolume — room left to pour more in |
FractionOf(sub) |
that substance's share of the blend, amount / total, in 0..1 |
IsFull is Headroom ≤ 0.001. Headroom is the gatekeeper of mixing: you cannot pour into a vessel
with no headroom. (See Mixing and Reactions.)
The dominant substance
Substance (singular) returns the biggest share — the substance a mostly-piss cocktail "is." It is used
for the label, for self-splash on a throw ("whatever they mostly slopped on themselves"), and anywhere a
single answer is needed. Setting it replaces the whole blend with a full vessel of that one substance.
Everything scales by share
This is the payoff, and it is uniform across the entire mod. No consequence looks at the def's declared substance; each looks at the runtime blend and scales by each substance's fraction. A drop of hooch in a jar of piss barely warms you; a jar that is mostly hooch is a firebomb. Ratios decide the dose.
| System | How the share is applied |
|---|---|
| Throwing / splash | the projectile carries the whole blend; each constituent lands its own splash at potency × fraction. A thrown cocktail applies every substance in its ratios. |
| Drinking | PostIngested walks the blend and applies each substance's drink effect (hediff, thought, nutrition, addiction) scaled by its fraction. A memory below a 0.2 share is not even worth remembering. |
| Addiction | addictionRarity × fraction × dispositionMultiplier — a splash of luciferium in a jar rolls at a fraction of a full jar's odds. |
| Reactions | water douses fire volume-for-volume; a splash of water in a carboy of chemfuel barely dents it, a cup of hooch in a bucket of water is simply gone. |
| Label | a real cocktail relabels itself with the shares: piss jar (urine 60%, hooch 40%). |
Contents-aware labels
Because contents are runtime, a "piss jar" the def shipped may actually hold a vile cocktail. CompVessel
tells the truth in the label:
- A cocktail shows the shares, dominant first:
piss jar (feces 50%, urine 50%). - A single substance that is not the def's own:
piss jar (hooch). - A fermenting or spoiled vessel appends its state:
piss nuke, fermenting 42%/piss nuke, spoiled. Only a full-strength (≥95% potency), undiluted, pure vessel gets to wear its plain name — which is the mechanical root of "a carboy is only a piss nuke when it is 100% ripe pure urine." (See The Piss Nuke.)
For modders
Add a throwable container to any mod in three lines of XML: give a ThingDef a CompProperties_Vessel
with volumeMl, sealed_, and a default substance. You inherit correct fill time, blast, spill,
splash shape, mixing, fermentation-readiness, drink behaviour and contents labels for free — no
per-container code. Attach a new SubstanceDef (see Substance Catalogue) and any vessel can now hold
it. The two axes are orthogonal by construction: N vessels × M substances is N×M weapons from
N+M defs.
The contents blend is exposed as a flat string via SerializeContents() / DeserializeContents() so a
content-agnostic layer (like Contraband's concealment) can store a cocktail blind and hand it back whole.
Foul Play is part of the Institution suite — one install with a checkbox per layer (Core, Contraband, Policing, Corrections, Gangs, Ward) about what an institution does to the people inside it. It bridges to the Contraband layer when Institution is installed.