Consolidate the suite into one installable Institution mod with toggles

This repo is now the single player-facing mod. It bundles the whole engine and
all three feature layers into one folder -- Core, Contraband, Justice, and Gangs
DLLs plus Contraband's Defs -- with one About.xml and a settings shim that turns
each layer on or off from a checkbox (all on by default). A player installs one
mod (this) and Harmony; no separate Core dependency.

- Source/Institution: the only new code, a settings shim (InstitutionMod +
  InstitutionModSettings) that writes the player's choices into Core's shared
  InstitutionSettings. It owns no gameplay.
- Tools/package.sh: rebuilds the sibling DLLs clean and vendors them + Contraband's
  content in here, so the repo stays a complete drop-in.
- Assemblies/, Defs/, Textures/, Languages/: the vendored, assembled mod.

Still fully modular and splittable: the feature code stays in its own repos and
DLLs (their About.xml files remain), so breaking this back into separate mods is
just shipping them individually -- no code change.
This commit is contained in:
flan
2026-07-15 16:11:56 +00:00
parent 401d34f390
commit 18fc44c728
22 changed files with 340 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
obj/
bin/
*.user
# The assembled drop-in ships from this repo: the vendored Assemblies/ and Defs/ are committed so
# the repo IS a complete, installable mod. Re-sync them with Tools/package.sh after changing a layer.
+38
View File
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<ModMetaData>
<packageId>flan.institution</packageId>
<name>Institution</name>
<author>flan</author>
<supportedVersions>
<li>1.6</li>
</supportedVersions>
<description>Prison Architect in RimWorld: every pawn has a criminal propensity on a spectrum -- nature (traits) x nurture (mood, mistreatment, treatment) -- and the colony can police it.
This is the Institution suite in one install, with a checkbox per layer in the mod settings (all ON by default):
- Contraband -- the physical smuggling loop. Prisoners conceal what they walk in with, whittle shivs from their cell's own furniture, and dig Prison-Architect tunnels under the wall. Wardens search the right prisoner first, by the record -- and a bent warden looks the other way, or smuggles for them.
- Justice -- the corrections layer. Classification grades every held pawn by what they have actually done. A colony has a climate of order that crime erodes and visible justice restores, and it feeds back into everyone's disposition. Discipline hardens or reforms; parole lets the reformed out; regime gives prisoners the recreation vanilla denies them.
- Gangs -- the social layer. Disposed pawns band together along the lines they already share and move contraband among themselves; rival gangs fight, inside the wire or out, and the fight is a crime. A healthy, well-run colony grows few gangs, if any.
Turn any layer off in Options -> Mod Settings and it goes inert immediately.
Requires Harmony (for the recreation feature). The suite's shared Core engine ships inside this mod, so there is no separate dependency to install. Pairs with the standalone Institution: Ward (psychiatric commitment) and Foul Play (the vessel/substance framework, which bridges in).
Part of the Institution suite.
AI disclosure: developed with substantial assistance from Claude (Anthropic).
</description>
<modDependencies>
<li>
<packageId>brrainz.harmony</packageId>
<displayName>Harmony</displayName>
<steamWorkshopUrl>steam://url/CommunityFilePage/2009463077</steamWorkshopUrl>
</li>
</modDependencies>
<loadAfter>
<li>Ludeon.RimWorld</li>
<li>brrainz.harmony</li>
</loadAfter>
</ModMetaData>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+35
View File
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<JobDef>
<defName>Contraband_SearchPawn</defName>
<driverClass>Contraband.JobDriver_SearchPawn</driverClass>
<reportString>searching TargetA.</reportString>
</JobDef>
<!--
A NEW WorkGiver class, not a patch on a vanilla one. That is what keeps this mod out of a
fight with the rest of the prison ecosystem:
- Custom Prisoner Interactions patches the NAMED vanilla warden givers (_Chat, _Convert,
_Enslave, _ReleasePrisoner). It cannot see this one.
- Prison Commons postfixes WorkGiver_Warden.ShouldSkip on the BASE class, which this
inherits and does not override, so searches respect prison-commons areas for free.
Priority sits below feeding: a starving prisoner matters more than a hidden shiv.
-->
<WorkGiverDef>
<defName>Contraband_WardenSearch</defName>
<label>search prisoners for contraband</label>
<giverClass>Contraband.WorkGiver_Warden_Search</giverClass>
<workType>Warden</workType>
<verb>search</verb>
<gerund>searching</gerund>
<priorityInType>40</priorityInType>
<requiredCapacities>
<li>Manipulation</li>
<li>Sight</li>
</requiredCapacities>
</WorkGiverDef>
</Defs>
+41
View File
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<!--
A crude pick, filed the same way a shiv is: off the cell's own furniture, over quiet days, the
damage to the source the visible tell. Where the shiv is a weapon, this is a way OUT: its whole
purpose is the tunnel (escapeWorker), so it carries no weapon stats and is never equipped.
A finished pick starts a Prison-Architect dig: the holder tunnels under the ground and everything
on it toward the nearest wilderness, surfacing beyond the perimeter wall or fence. The dig itself
is a second secret; a warden's cell search can turn it up, and the deeper the shaft the harder it
is to miss. It works for any held pawn (prisoner, ward patient, slave) because the disposition
to dig, not a prison-only mental break, is what drives it.
-->
<ThingDef ParentName="ResourceBase">
<defName>CB_DiggingTool</defName>
<label>crude pick</label>
<description>A stubby digging pick filed off a bed frame, its point hardened in a cell wall. Useless as a weapon and useless above ground -- but pointed downward, patiently, it is a way out that no lock and no guard can see.</description>
<techLevel>Neolithic</techLevel>
<graphicData>
<texPath>Things/Item/Contraband/CrudePick</texPath>
<graphicClass>Graphic_Single</graphicClass>
</graphicData>
<statBases>
<MaxHitPoints>60</MaxHitPoints>
<Mass>1.2</Mass>
<Beauty>-4</Beauty>
<DeteriorationRate>2.0</DeteriorationRate>
</statBases>
<stackLimit>1</stackLimit>
<comps>
<li Class="Contraband.CompProperties_Contraband">
<concealability>0.5</concealability>
<hideIn>InCell</hideIn>
<acquireWorker>Contraband.AcquireWorker_Improvise</acquireWorker>
<escapeWorker>Contraband.EscapeTunnelWorker</escapeWorker>
</li>
</comps>
</ThingDef>
</Defs>
+59
View File
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<!--
A crude shiv, whittled from whatever the cell was furnished with. Stuffable, so its stats come
from the harvested material for free: a wood shiv is feeble, a plasteel one is vicious. This is
the whole point of the harvest-by-damage mechanic: the prison's own construction materials
become the weapon materials, and a luxury block is a worse idea than it looks.
Registered as brewable contraband: a held pawn improvises it (AcquireWorker_Improvise), and the
generic use-during-a-break path equips it (no useWorker; an armed prisoner IS the payload,
and JobGiver_UseContraband only lets the disposed pick it up).
-->
<ThingDef ParentName="BaseMeleeWeapon_Sharp">
<defName>CB_Shiv</defName>
<label>shiv</label>
<description>A blade filed off a bed frame and wrapped in cloth for a grip. Barely a weapon -- but a barely-a-weapon in a cell you thought was empty is a dead guard.</description>
<techLevel>Neolithic</techLevel>
<graphicData>
<texPath>Things/Item/Contraband/Shiv</texPath> <!-- light greys: stuffable, so the material tint reads -->
<graphicClass>Graphic_Single</graphicClass>
</graphicData>
<stuffCategories>
<li>Metallic</li>
<li>Woody</li>
<li>Stony</li>
</stuffCategories>
<costStuffCount>15</costStuffCount>
<statBases>
<WorkToMake>1500</WorkToMake>
<Mass>0.4</Mass>
</statBases>
<weaponClasses>
<li>Melee</li>
</weaponClasses>
<comps>
<li Class="Contraband.CompProperties_Contraband">
<concealability>0.75</concealability>
<hideIn>OnBody, InCell</hideIn>
<acquireWorker>Contraband.AcquireWorker_Improvise</acquireWorker>
</li>
</comps>
<tools>
<li>
<label>point</label>
<capacities><li>Stab</li></capacities>
<power>7</power>
<cooldownTime>1.5</cooldownTime>
</li>
<li>
<label>handle</label>
<capacities><li>Blunt</li></capacities>
<power>5</power>
<cooldownTime>1.6</cooldownTime>
</li>
</tools>
</ThingDef>
</Defs>
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?>
<LanguageData>
<Contraband_Found>{0} searched {1} and found hidden {2}.</Contraband_Found>
<Contraband_TunnelFound>{0} searched {1} and uncovered a tunnel dug with a hidden {2}.</Contraband_TunnelFound>
<Contraband_TunnelBreached>{0} has tunnelled out under the perimeter and escaped.</Contraband_TunnelBreached>
<Contraband_VesselBurst>A {0} has burst under its own pressure.</Contraband_VesselBurst>
<Contraband_Fermentation>Fermentation: {0}%</Contraband_Fermentation>
<Contraband_FermentOverdue>Overdue. The pressure has to go somewhere.</Contraband_FermentOverdue>
</LanguageData>
+29
View File
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<AssemblyName>Institution</AssemblyName>
<RootNamespace>InstitutionSuite</RootNamespace>
<LangVersion>latest</LangVersion>
<Nullable>disable</Nullable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<GenerateDependencyFile>false</GenerateDependencyFile>
<DebugType>none</DebugType>
<OutputPath>..\..\Assemblies\</OutputPath>
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
</PropertyGroup>
<!-- The aggregate mod's only own code: a settings shim that writes the player's checkboxes into
Core's shared InstitutionSettings. The feature DLLs (Contraband/Justice/Gangs) are vendored in
at package time, not referenced here. Core holds the flags, so this references only Core. -->
<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="all" />
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.6.4871" ExcludeAssets="runtime" />
<Reference Include="InstitutionCore">
<HintPath>$(MSBuildThisFileDirectory)..\..\..\rimworld-core\Assemblies\InstitutionCore.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
</Project>
+72
View File
@@ -0,0 +1,72 @@
using UnityEngine;
using Verse;
namespace InstitutionSuite
{
/// <summary>
/// The single player-facing mod. It bundles the Institution feature layers -- Contraband,
/// Justice, Gangs -- into one install with a checkbox per layer, and it owns NO gameplay code of
/// its own. All it does is read the player's choices and write them into Core's shared
/// <see cref="Contraband.InstitutionSettings"/>, which the feature assemblies (shipped alongside
/// in this same mod) read at their entry points.
///
/// This is the whole "merge": a thin settings shim on top of the unchanged, still-separate feature
/// DLLs. Everything defaults ON, so a fresh install is the full experience and the player opts out
/// of pieces they don't want. Pull this mod apart into its layers again and nothing here is missed
/// -- the features default their flags to true on their own.
/// </summary>
public class InstitutionMod : Mod
{
private readonly InstitutionModSettings settings;
public InstitutionMod(ModContentPack content) : base(content)
{
settings = GetSettings<InstitutionModSettings>();
settings.Apply(); // push the loaded choices into Core before the features' static ctors run
}
public override string SettingsCategory() => "Institution";
public override void DoSettingsWindowContents(Rect inRect)
{
Listing_Standard l = new Listing_Standard();
l.Begin(inRect);
l.CheckboxLabeled("Contraband",
ref settings.contraband,
"Concealment and intake, improvised shivs, Prison-Architect tunnels, warden search, and warden corruption.");
l.Gap();
l.CheckboxLabeled("Justice",
ref settings.justice,
"Classification, the deterrence feedback loop, discipline and reform, parole, and prisoner recreation (Regime).");
l.Gap();
l.CheckboxLabeled("Gangs",
ref settings.gangs,
"Gangs as contraband economies: joining by disposition, smuggling networks, rivalry, and fights-as-crime. Leans on Contraband and Justice.");
l.End();
settings.Apply(); // live: a toggle takes effect immediately for the gated behaviour
}
}
public class InstitutionModSettings : ModSettings
{
public bool contraband = true;
public bool justice = true;
public bool gangs = true;
/// <summary>Mirror the choices into Core's shared flags that every feature reads.</summary>
public void Apply()
{
Contraband.InstitutionSettings.contraband = contraband;
Contraband.InstitutionSettings.justice = justice;
Contraband.InstitutionSettings.gangs = gangs;
}
public override void ExposeData()
{
Scribe_Values.Look(ref contraband, "contraband", true);
Scribe_Values.Look(ref justice, "justice", true);
Scribe_Values.Look(ref gangs, "gangs", true);
base.ExposeData();
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/env bash
# Assemble the shippable, drop-in "Institution" mod into this repo.
#
# The Institution suite's code lives in SEPARATE sibling repos (rimworld-core, -contraband, -justice,
# -gangs) as independent projects/DLLs -- that is what keeps it modular and splittable. For DISTRIBUTION
# they are vendored into ONE mod folder (this repo) with one About.xml and a settings shim that toggles
# each layer. This script rebuilds the sibling DLLs clean (no self-test) and copies them + Contraband's
# Defs in here, so the repo is a complete installable mod.
#
# To split back into separate mods later: stop running this, and ship each sibling repo as its own mod
# (their About.xml files still exist). Nothing in the code changes.
#
# Usage: Tools/package.sh (assumes siblings at ../rimworld-<name>)
set -euo pipefail
HERE="$(cd "$(dirname "$0")/.." && pwd)"
SIB="$(dirname "$HERE")"
CORE="$SIB/rimworld-core"
CB="$SIB/rimworld-contraband"
JUST="$SIB/rimworld-justice"
GANGS="$SIB/rimworld-gangs"
command -v dotnet >/dev/null || { echo "dotnet not on PATH" >&2; exit 1; }
echo "==> building the engine + feature DLLs (clean, no self-test)"
dotnet build "$CORE/Source/Core/Core.csproj" -c Release -v q --nologo
dotnet build "$JUST/Source/Justice/Justice.csproj" -c Release -v q --nologo
dotnet build "$CB/Source/Contraband/Contraband.csproj" -c Release -v q --nologo
dotnet build "$GANGS/Source/Gangs/Gangs.csproj" -c Release -v q --nologo
echo "==> vendoring DLLs into Assemblies/"
mkdir -p "$HERE/Assemblies"
rm -f "$HERE/Assemblies/"*.dll # clear old layout
dotnet build "$HERE/Source/Institution/Institution.csproj" -c Release -v q --nologo # (re-)emit Institution.dll here
cp "$CORE/Assemblies/InstitutionCore.dll" "$HERE/Assemblies/"
cp "$CB/Assemblies/Contraband.dll" "$HERE/Assemblies/"
cp "$JUST/Assemblies/InstitutionJustice.dll" "$HERE/Assemblies/"
cp "$GANGS/Assemblies/InstitutionGangs.dll" "$HERE/Assemblies/"
echo "==> vendoring Contraband's content"
for d in Defs Patches Languages Textures; do
rm -rf "$HERE/$d"
[[ -e "$CB/$d" ]] && cp -r "$CB/$d" "$HERE/$d"
done
echo "==> done. Assemblies:"
ls -1 "$HERE/Assemblies/" | sed 's/^/ /'
echo "The repo is now a complete drop-in Institution mod (Core bundled). Requires only Harmony."