Skip to content

Mod Integration API Reference

artriy edited this page Jul 17, 2026 · 6 revisions

API Reference

Supported namespace: PerfectComms.Api in PerfectComms.dll. Current API version: 1.1.

← Back to Mod Integration

Only the types below are the supported mod-integration surface. Public implementation types in VoiceChatPlugin.VoiceChat are internal architecture and are not part of this API contract.


PerfectCommsApi

public static class PerfectCommsApi
{
    public const string ApiVersion = "1.1";
    public const string PluginId = "com.edgetel.perfectcomms";

    public static void RegisterVoiceRule(
        string modId,
        Func<VoiceRuleContext, VoiceRuleResult> rule);

    public static void RegisterGlobalGate(
        string modId,
        VoicePhaseKind phase,
        Func<bool> isActive,
        string reason);

    public static void RegisterVoiceChannel(
        string modId,
        Func<VoiceRuleContext, VoiceChannelResult?> channel);

    public static void RegisterListenerOrigin(
        string modId,
        Func<PlayerControl, VoiceListenerResult?> origin);

    public static void RegisterListenerFilter(
        string modId,
        Func<PlayerControl, bool> shouldMuffle);

    public static void RegisterHostOption(
        string modId,
        VoiceHostOption option);

    public static void RegisterHostEnumOption(
        string modId,
        VoiceHostEnumOption option);

    public static void RegisterModTab(string modId, string tabLabel);

    public static void RegisterOverlayViewerRule(
        string modId,
        Func<VoiceOverlayViewerContext, VoiceOverlayViewerResult> rule);

    public static void RegisterOverlaySpeakerRule(
        string modId,
        Func<VoiceOverlaySpeakerContext, VoiceOverlaySpeakerResult> rule);

    public static void Unregister(string modId);
}

An empty modId or null callback/option is silently ignored. Other declarations are not validated. Registrations accumulate; only RegisterModTab deduplicates by exact, case-sensitive modId. Unregister(modId) removes every registration and option value for that id; there is no individual unregister method.

ApiVersion is a C# compile-time constant, not a runtime capability probe: its value is embedded into a mod compiled against this DLL. State and test the minimum Perfect Comms version your integration requires, and keep optional API calls behind the soft-dependency boundary described in Mod Integration.


Enums

public enum VoicePhaseKind
{
    Lobby,
    Tasks,
    Meeting,
    Exile,
}

public enum VoiceAudioShape
{
    Proximity,
    Radio,
    Muffle,
}

public enum VoiceVerdict
{
    Pass,
    Mute,
    Muffle,
}

public enum VoiceListenerMode
{
    Replace,
    Additive,
}

public enum VoiceOverlayViewerVerdict
{
    Pass,
    DimAll,
    HideAll,
}

public enum VoiceOverlaySpeakerVerdict
{
    Pass,
    Alias,
    HideSource,
    HideAll,
}

VoiceVerdict.Muffle remains present for binary compatibility but its per-speaker filter is not applied by API 1.1 routing. See Known API 1.1 limitations.


Rule context and result

public sealed record VoiceRuleContext(
    PlayerControl Player,
    VoicePhaseKind Phase,
    bool IsLocal,
    bool IsDead)
{
    public Func<string, bool> GetOption { get; init; }
    public Func<string, int> GetEnumOption { get; init; }
}

public sealed record VoiceRuleResult(VoiceVerdict Verdict, string Reason)
{
    public static readonly VoiceRuleResult Pass;
    public static VoiceRuleResult Mute(string reason);
    public static VoiceRuleResult Muffle(string reason);
}

GetOption("Key") and GetEnumOption("Key") read the value registered as modId.Key, where modId is the id used for this callback. Missing bools return false; missing enums return 0.

Use Pass for no opinion. Mute(null) uses "Muted"; Muffle(null) uses "Muffled". A callback returning null is treated as Pass even though its declared result is non-nullable.

Current gate composition is: the first Mute stops evaluation and wins; if nothing mutes, the first Muffle verdict is retained. Because API 1.1 does not consume that retained per-speaker muffle flag, working integrations should currently return only Pass or Mute.


Channel result

public sealed record VoiceChannelResult(
    string Key,
    bool TwoWay = true,
    VoiceAudioShape Shape = VoiceAudioShape.Radio,
    float Volume = 1f,
    Vector2? Origin = null);
  • Key must be non-empty. It is scoped to the registering modId.
  • Volume is clamped to 0f..1f.
  • Radio is flat audio; Muffle is flat low-pass audio.
  • Proximity is spatial only with an explicit Origin and a task-phase listener position. Without either, it falls back to flat audio; meetings are flat.
  • Keep TwoWay: true. TwoWay: false is not a supported one-way-channel mechanism in API 1.1.

The first accepted channel membership for a player wins. Within one mod, callbacks are checked in registration order. Return null, not an empty-key result, when not applicable. See Channels.


Listener origin and filter

public sealed record VoiceListenerResult(
    Vector2 Origin,
    float LightRadius,
    VoiceListenerMode Mode);

// Present in the assembly, but orphaned and unsupported in API 1.1.
public sealed record VoiceListenerFilterResult(bool Muffle);

Listener origin is local-player and task-phase only. The first non-null result wins, and built-in control hearing takes precedence. Replace hears from the override origin; Additive evaluates body and override origin and keeps the more audible result per speaker.

Supply the desired sight radius explicitly. LightRadius <= 0 disables vision-radius limiting at the override origin and does not inherit the player's radius.

RegisterListenerOrigin and RegisterListenerFilter receive only PlayerControl; they have no host-option accessors. The working listener-filter signature returns bool: any true muffles all audible incoming routes for the local listener. Do not instantiate VoiceListenerFilterResult. See Listener Origin & Filter.


Overlay privacy

public sealed record VoiceOverlayViewerContext(
    PlayerControl Viewer,
    VoicePhaseKind Phase,
    bool IsDead)
{
    public Func<string, bool> GetOption { get; init; }
    public Func<string, int> GetEnumOption { get; init; }
}

public readonly record struct VoiceOverlayViewerResult(
    VoiceOverlayViewerVerdict Verdict)
{
    public static readonly VoiceOverlayViewerResult Pass;
    public static readonly VoiceOverlayViewerResult DimAll;
    public static readonly VoiceOverlayViewerResult HideAll;
}

public sealed record VoiceOverlaySpeakerContext(
    PlayerControl Viewer,
    PlayerControl Speaker,
    VoicePhaseKind Phase,
    bool ViewerIsDead,
    bool SpeakerIsDead)
{
    public Func<string, bool> GetOption { get; init; }
    public Func<string, int> GetEnumOption { get; init; }
}

public readonly record struct VoiceOverlaySpeakerResult(
    VoiceOverlaySpeakerVerdict Verdict,
    byte? AliasPlayerId = null)
{
    public static readonly VoiceOverlaySpeakerResult Pass;
    public static readonly VoiceOverlaySpeakerResult HideSource;
    public static readonly VoiceOverlaySpeakerResult HideAll;
    public static VoiceOverlaySpeakerResult Alias(byte targetPlayerId);
}

Viewer results compose as HideAll > DimAll > Pass. Speaker results compose as HideAll > HideSource > Alias > Pass; conflicting, missing, sentinel, or unsafe aliases fail to HideSource. Viewer exceptions fail to HideAll; speaker exceptions fail to HideSource.

Overlay privacy changes identity-bearing Perfect Comms UI only. It does not mute audio, change routing, or alter transmission. See Overlay Privacy.


Host options and tabs

public sealed record VoiceHostOption(
    string Key,
    string Label,
    bool Default)
{
    public string Description { get; init; } = "";
}

public sealed record VoiceHostEnumOption(
    string Key,
    string Label,
    int Default,
    string[] Choices)
{
    public string Description { get; init; } = "";
}

The caller must provide unique, non-empty, case-sensitive keys; nonempty enum choices; and an in-range enum default. Description supplies the in-game help text. Register one tab per mod id for options to render. Toggles render first, then enums, preserving registration order within each group.

Values are session-local and reset to registered defaults after restart. At most 256 mod options across all installed mods fit in one host-settings snapshot. Values use a 32-bit hash of modId.Key; collisions are possible and not detected. Host-object matching is for lobby compatibility, not hostile-client authentication. See Host Options & Tabs.


Callback cadence, failures, and networking

Do not rely on an exact callback frequency. Rule, channel, and listener-origin callbacks run on voice snapshot cadence, roughly 20 times per second per applicable player. Listener-filter and overlay callbacks can run once per rendered frame. Keep every callback cheap, allocation-light, and throw-free.

Callback Neutral/failure result
Voice rule Pass
Global gate predicate false
Channel resolver null
Listener origin null
Listener filter false
Overlay viewer rule HideAll (fail-private)
Overlay speaker rule HideSource (fail-private)

Perfect Comms synchronizes host-option values only. It does not network your role, effect, channel-membership, alias, or listener state. The integrating mod must already make the state used by callbacks available and consistent on the clients that need it.

Avoid relying on cross-mod evaluation order. Within one mod id, duplicate callback registrations run in registration order and duplicate option registrations produce duplicate rows. Register once and call Unregister(modId) on unload.


Known API 1.1 limitations

  • Per-speaker gate muffle is not wired: VoiceVerdict.Muffle and VoiceRuleResult.Muffle(...) are public but do not currently apply a speaker filter. Use VoiceAudioShape.Muffle for channel audio or RegisterListenerFilter for listener-wide incoming muffle.
  • Gate mute has a phase/life-state boundary: API 1.1 enforces VoiceRuleResult.Mute(...) for living speakers in Tasks, Meeting, and Exile. Do not rely on it to mute Lobby voice or dead speakers.
  • One-way channels are unsupported: VoiceChannelResult.TwoWay remains public, but TwoWay: false does not reliably create a directional route. Keep it true.
  • Proximity channels need both spatial inputs: VoiceAudioShape.Proximity spatializes only with an explicit Origin and a task-phase listener position. Without either input, including in meetings, the channel is heard at flat volume.
  • VoiceListenerFilterResult is orphaned: the type is public but no API 1.1 registration consumes it. Use RegisterListenerFilter(string, Func<PlayerControl, bool>).
  • Listener radius is explicit: LightRadius <= 0 disables radius limiting; it does not inherit a player radius.

These members remain visible in the signatures for compatibility; the working alternatives above are the supported API 1.1 paths.

Clone this wiki locally