-
Notifications
You must be signed in to change notification settings - Fork 3
Mod Integration 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.
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.
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.
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.
public sealed record VoiceChannelResult(
string Key,
bool TwoWay = true,
VoiceAudioShape Shape = VoiceAudioShape.Radio,
float Volume = 1f,
Vector2? Origin = null);-
Keymust be non-empty. It is scoped to the registeringmodId. -
Volumeis clamped to0f..1f. -
Radiois flat audio;Muffleis flat low-pass audio. -
Proximityis spatial only with an explicitOriginand a task-phase listener position. Without either, it falls back to flat audio; meetings are flat. - Keep
TwoWay: true.TwoWay: falseis 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.
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.
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.
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.
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.
-
Per-speaker gate muffle is not wired:
VoiceVerdict.MuffleandVoiceRuleResult.Muffle(...)are public but do not currently apply a speaker filter. UseVoiceAudioShape.Mufflefor channel audio orRegisterListenerFilterfor 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.TwoWayremains public, butTwoWay: falsedoes not reliably create a directional route. Keep ittrue. -
Proximity channels need both spatial inputs:
VoiceAudioShape.Proximityspatializes only with an explicitOriginand a task-phase listener position. Without either input, including in meetings, the channel is heard at flat volume. -
VoiceListenerFilterResultis orphaned: the type is public but no API 1.1 registration consumes it. UseRegisterListenerFilter(string, Func<PlayerControl, bool>). -
Listener radius is explicit:
LightRadius <= 0disables 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.