Skip to content

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.

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

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

  3. 03

    Code and comments in English

    Identifiers, inline comments, log messages. User-facing documentation can be in another language; the source cannot.

  4. 04

    TweakDBIDs centralised

    Every TweakDBID lives in Core/Constants.reds. No loose t"..." literals scattered across the systems that use them.

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

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

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