Skip to content

Crime and Police

Applejack's justice system. Note throughout how consistently it prefers detention over death.

Sources: metatables/sv_player.lua (warrants, arrest), hooks/sv_player.lua (the gating hooks), plugins/officials/sv_init.lua (400 lines — the policy), libraries/sh_laws.lua, plugins/prisonpoints/, sv_commands.lua, entities/weapons/cider_lockpick/, entities/weapons/cider_tranq/.


1. The separation of mechanism and policy

This is the best architectural decision in the entire legacy codebase and it must survive.

  • metatables/sv_player.lua provides the mechanisms: Warrant, Arrest, KnockOut, TieUp, Incapacitate, TakeWeapons.
  • hooks/sv_player.lua declares the gates: PlayerCanArrest, PlayerCanUnarrest, PlayerCanStun, PlayerCanKnockOut, PlayerCanWakeUp, PlayerCanWarrant, PlayerCanUnwarrant, PlayerCanDemote, PlayerCanChangeLaws.
  • plugins/officials/ supplies the policy — who is police, what they may do, whether warrants are required.

Remove the officials plugin and the mechanisms still exist; there is simply nobody authorised to use them. A server can replace the entire concept of law enforcement by replacing one plugin.

Design note: the new architecture reproduces this split as service interfaces plus cancellable events, not as hooks returning truthy values (Architecture/04_EVENT_BUS.md).


2. Warrants

meta:Warrant(class, time) / meta:UnWarrant().

Two classes: "search" and "arrest".

Setting Default
Search Warrant Expire Time 300 s (5 minutes)
Arrest Warrant Expire Time 3600 s (1 hour)

State is published as NWString "Warrant", plus a _WarrantExpireTime CSVar so the client can count down. Expiry is a timer.Create keyed on the player's UniqueID, which on firing calls the PlayerWarrantExpired hook and then UnWarrant.

Because expiry is an in-memory timer and warrants are not persisted, all warrants are cleared by a server restart. Almost certainly unintended. The new design persists warrants with an absolute expiry timestamp.

Warrants are issued through /warrant (sv_commands.lua:1145) and revoked with /unwarrant (:1189); admins have /awarrant (:134).

The design intent: a search warrant licenses a police officer to search a specific player, and an arrest warrant licenses arrest. Both are visible to the target — you know you are wanted. The short search-warrant expiry forces the police to act on information promptly.


3. Arrest

meta:Arrest(time). Default Arrest Time = 300 s (5 minutes).

The sequence, in order:

  1. Fire PlayerArrested.
  2. Set cider._Arrested = truethis is persistent — and NWBool "Arrested".
  3. Start the release timer and publish _UnarrestTime for the client countdown.
  4. Incapacitate() — see §5.
  5. TakeWeapons(true) — confiscate weapons, excluding items (see below).
  6. StripAmmo().
  7. Turn the flashlight off.
  8. UnWarrant() — the warrant is spent.
  9. UnTie(true) — you cannot be both tied and arrested.

On expiry: UnArrest(true), notify, and Spawn() the player — releasing them from jail. UnArrest(reset) without reset also Recapacitate()s and ReturnWeapons().

Confiscation

TakeWeapons(noitems) stores every held weapon class in _StoredWeapons and strips them. The noitems flag — passed as true by Arrest — means weapons that correspond to inventory items are not stored for return. Arrest therefore destroys your item-backed weapons rather than holding them: you lose the gun permanently.

Compare TieUp(), which calls TakeWeapons() with no flag — a tied player gets everything back when untied. Arrest confiscates; tying merely disarms.

Jail

plugins/prisonpoints/ stores per-map jail positions in prisonpoints/<map>.txt and teleports the player there on PlayerArrested.

You cannot log out of jail

_Arrested persists, and player.loadFunctions._Arrested calls ply:Arrest() on load (02). Disconnecting does not shorten a sentence.

There is a subtlety worth noting for the reimplementation: the remaining time is not saved, only the boolean. Reconnecting therefore restarts the full 5 minutes. Preserve the inescapability; fix the arithmetic.


4. Non-lethal force

The original's arsenal of ways to remove someone from a fight without killing them:

Knock out — meta:KnockOut(time, velocity)

Default Knock Out Time = 30 s.

The implementation is careful and worth describing:

  • Refuses if already knocked out; exits any vehicle first ("This shit goes crazy if you ragdoll in a car").
  • Captures all 71 bone matrices from the player and applies them to the spawned ragdoll, so the body is a natural continuation of the pose rather than a collapse.
  • Uses prop_ragdoll, falling back to prop_physics for models without a valid ragdoll.
  • Sets COLLISION_GROUP_WEAPON so bodies cannot be used to crush or shove players.
  • Gives the ragdoll to the world via prop protection so nobody can pick it up and wave it around.
  • The ragdoll carries its own health (ply.ragdoll = {entity, health}) — an unconscious body can be killed.

And metatables/sh_player.lua overrides Player:GetPos() to return the ragdoll's position while one exists, so an unconscious player is genuinely located at their body.

meta:WakeUp(reset) requires the player's own input to stand up.

Tie up — meta:TieUp() / meta:UnTie(reset)

Setting Default
Tying Timeout 5 s to apply
UnTying Timeout 5 s to remove
Tying Struggles 1 struggle to escape
Tying Struggles Timeout 30 s for the struggle meter to refill
Rope struggles 5 (marked unused)

Tying incapacitates, disarms (recoverably), and kills the flashlight.

Incapacitate — meta:Incapacitate() / Recapacitate()

The shared primitive underneath arrest, tying and being carried:

  • walk and run speed both → Incapacitated Speed (100, against 150 walk / 275 run)
  • jump power → 0
  • NWBool "Incapacitated" → true

Recapacitate() is gated by the PlayerCanBeRecapacitated hook — which is how the stamina plugin prevents you standing up while exhausted. A clean example of a plugin modifying a core mechanic through a declared gate rather than by patching it.

Bleeding — meta:Bleed(time) / StopBleeding()

Default Bleed Time = 5 s. Traces down from the player every 0.25 s and stamps a blood decal on the floor. A wounded player leaves a followable trail. Cosmetic in implementation, tactical in effect — and one of the few genuinely emergent mechanics in the original.

Note the bug: meta:Bleed(time) computes its repeat count from seconds, an undefined global, not from its time parameter. (seconds or 0) * 4 is therefore always 0, which in the legacy timer library means repeat forever. Bleeding never stops on its own.

Tranquilliser

entities/weapons/cider_tranq/ with items/ammo/ammo_dart.lua — a non-lethal takedown weapon feeding the knockout system. Its presence signals the design intent clearly.


5. Lockpicking and breaking in

entities/weapons/cider_lockpick/:

Setting Default
Maximum Lockpick Hits 30 hits to open a lock
Lockpick Break Chance 0.01 added per lock successfully picked

The break chance is cumulative across the lockpick's life: each successful pick makes the tool 1% more likely to snap on any subsequent hit. A veteran burglar's lockpick is a liability. Lovely mechanic; preserve it.

Also present: cider_breach (breaching a door, which jams it open for 60 s — see 04) and cider_padlock as a physical, attackable lock.

Known issue, recorded in the developer chat log preserved at the top of sh_init.lua: "You can only lockpick the guy once, and if he moves the lockpick is unable to pick the lock… And unable to pick the lock on the same thing again."


6. Contraband

Contraband entities (cider_money_printer, cider_drug_lab, cider_contraband) pay out every Earning Interval = 300 s (init.lua:1412, hooks/sv_player.lua:308).

items/base/contraband.lua enforces per-player counts against GM.Config.Contraband[uid].maximum via meta:AddCount / TakeCount.

Police destroy contraband for a reward, governed by two settings:

Setting Default Meaning
Officials Contraband true City officials also receive contraband payments
Need Warrant false Officials need a warrant to destroy contraband
Police Kill Drop true Weapons drop when a player is killed by police

Officials Contraband = true is striking: the police can run printers too. The framework does not assume the state is honest.

The Applejack fork adds tiered contrabandcider_money_printer_2/3/4 and cider_drug_lab_2/3/4 — an upgrade path the canonical copy lacks. Good input to the crafting design.


7. Laws

libraries/sh_laws.lua (63 lines).

Ten laws. Free text. Editable by the Mayor. A 120 second cooldown between changes. Synced to clients over datastream plus a legacy usermessage; displayed by derma/cl_laws.lua. Changing them is gated by PlayerCanChangeLaws.

The laws are not enforced by code at all. Nothing reads them. They exist so that police can point at them and players can argue about them. This is the purest expression of the design philosophy in the codebase: the framework provides a noticeboard, and the players provide the legal system.


8. Commands

Command Line Access
/warrant 1145 Police
/unwarrant 1189 Police
/arrest 114 Admin
/awarrant 134 Admin
/mutiny 1261 Gang members

Plus the officials plugin's SayRequest / SayBroadcast for police radio, and radio recipient filtering.


9. What is missing

The master plan's Phase 12 lists several things the original does not have:

  • Evidence. No concept. A search finds items; it does not record findings.
  • Fines. No mechanism — punishment is detention only.
  • Searching as a distinct action. A search warrant authorises it socially; there is no search interaction separate from just taking things from an incapacitated player.
  • Persistent criminal records. Warrants are transient and die with the server.
  • Bail, trials, sentencing. Arrest time is a constant.

All new design. The mechanisms that exist to build on: warrants with classes and expiry, the gate hooks, jail points, confiscation, and the laws noticeboard.


10. What survives

Survives: the mechanism/gate/policy split; warrant classes with different expiries; arrest as detention with confiscation; inescapable sentences; jail points per map; the incapacitate primitive shared by arrest/tying/carrying; knockout with a posed, damageable, world-owned ragdoll; position following the ragdoll; blood trails; cumulative lockpick wear; breaching with a timed jam; contraband caps and interval payouts; police optionally corruptible; ten player-authored laws that the code never reads.

Does not survive: warrants dying on restart; arrest restarting its full duration on reconnect; the bleed timer bug; hooks that gate by truthy return; NWBool/NWString as the state model.