Documentation
DOC-05 · Planned, spec published
Plugin architecture
The callout system is designed, and its interface is published below. It is not implemented. This page exists so the design can be challenged before it is built, not so anyone can build on it today.
The planned interface
In order: identity and presentation, then the conditions under which dispatch may pick the call, then the lifecycle it runs through, and last what it pays out.
GetID() / GetDisplayName() / GetRadioCode()
GetValidDistricts() / GetTimeWindow() / GetMinRank()
GetLocationCandidates() -> array<Vector4>
OnDispatch() / OnPlayerArrived() / OnResolved() / OnFailed() / OnCleanup()
GetPayout() / GetXP()The rule the design must meet
Every dispatch is a plugin. A third party must be able to add a new callout with a single .reds file and zero changes to the core. If the architecture does not allow that, it is wrong.That is the test the implementation will be judged by, and the reason the interface above is deliberately wide: a callout has to be able to say where and when it is valid without the scheduler knowing anything specific about it.
Abstract class, not interface
Despite its name, ICallout will be an abstract class. The vanilla code never uses the interface keyword: CDPR writes its own contracts as abstract or importonly class. Following the engine’s own conventions beats following the ones another language would suggest.
House rules for contributors
They govern the core today, and will govern plugins once the loader exists.
- 01
@wrapMethod always
@replaceMethod is forbidden unless a comment directly above the function explains, in writing, why wrapping is not enough. Replacing a vanilla method is the single biggest cause of incompatibility between mods.
- 02
Everything under module NCPDFR.*
No global identifiers, not one. One submodule per system, and the module path carries the name instead of a text prefix on every type.
- 03
Code and comments in English
Identifiers, inline comments, log messages. User-facing documentation can be in another language; the source cannot.
- 04
TweakDBIDs centralised
Every TweakDBID lives in Core/Constants.reds. No loose t"..." literals scattered across the systems that use them.
- 05
Every cast followed by IsDefined()
No exceptions. Anything spawned is checked again before it is reused: the player may have walked away, and streaming may have unloaded it.
- 06
One file, one system
Once a file passes roughly 300 lines, it gets split. Balancing numbers go into configuration, not into the logic as magic values.
- 07
Never assume an optional dependency
Detect it, and degrade gracefully when it is missing. Phantom Liberty content is the standing example: Dogtown sits behind a check because the expansion may not be installed.
Propose a callout
This interface was published before it was written, so it can be argued with while changing it still costs nothing. If a callout you want does not fit the shape above, say so on Discord. The shape is what is still open for discussion, not the fact that there will be one.