-
Notifications
You must be signed in to change notification settings - Fork 3
Mod Integration
Perfect Comms API 1.1 exposes a small public surface (PerfectComms.Api) so an Among Us role mod can add voice rules, private channels, relocated hearing, host settings, and overlay privacy without forking Perfect Comms.
Your mod references PerfectComms.dll as a soft dependency, registers only when Perfect Comms is present, and ships its own DLL. Perfect Comms never references your mod.
Before this API, adding a role-specific voice behavior meant editing the Perfect Comms role engine, snapshots, settings RPC, and host panel, then maintaining a fork. The public API keeps that logic in the role mod that owns the state.
Callbacks run locally on each client. That avoids a second network protocol only when the underlying role or modifier state is already synchronized by your mod and every relevant client has a compatible integration. Host-option values are the exception: Perfect Comms includes them in its existing host-settings snapshot. The host-object checks are for lobby compatibility, not hostile-client authentication; see Privacy.
1. Reference the DLL. Add PerfectComms.dll as a compile-time assembly reference. Do not bundle it with your mod; players install Perfect Comms separately.
2. Declare a soft dependency and isolate all API use. Keep Perfect Comms types out of your plugin's base type, fields, method signatures, and static initializers. Check the literal plugin id before entering a helper that touches the API:
using BepInEx;
using BepInEx.Unity.IL2CPP;
[BepInPlugin("com.me.mymod", "My Mod", "1.0.0")]
[BepInDependency(
"com.edgetel.perfectcomms",
BepInDependency.DependencyFlags.SoftDependency)]
public sealed class MyModPlugin : BasePlugin
{
private const string PerfectCommsPluginId = "com.edgetel.perfectcomms";
public override void Load()
{
if (!IL2CPPChainloader.Instance.Plugins.ContainsKey(PerfectCommsPluginId))
return;
PerfectCommsVoiceIntegration.Register();
}
}
internal static class PerfectCommsVoiceIntegration
{
private const string ModId = "com.me.mymod";
internal static void Register()
{
// This method is entered only after the literal-id presence check.
PerfectComms.Api.PerfectCommsApi.RegisterVoiceRule(ModId, ctx =>
ctx.Phase == PerfectComms.Api.VoicePhaseKind.Meeting &&
!ctx.IsDead &&
MyRoles.IsGagged(ctx.Player)
? PerfectComms.Api.VoiceRuleResult.Mute("Gagged")
: PerfectComms.Api.VoiceRuleResult.Pass);
}
// Call this before a dynamic unload or reload, if your mod supports one.
internal static void Unregister()
=> PerfectComms.Api.PerfectCommsApi.Unregister(ModId);
}The isolation matters: keep the presence probe in code that has no Perfect Comms API calls or types, then enter the integration helper only when the plugin is present. A soft dependency controls load ordering; it does not make an API reference in an eagerly resolved method or type safe. Using the literal id also keeps the probe independent of the optional API assembly.
The example targets API 1.1. A presence check does not prove that every method in the version you compiled against exists at runtime, so document and test your minimum supported Perfect Comms version.
| Primitive | What it currently does | Networking |
|---|---|---|
| Gate | Mutes a living player in Tasks, Meeting, or Exile | None |
| Channel | Gives matching players a working two-way private route with a selected audio shape | None |
| Listener Origin | Replaces or augments where the local player hears from during Tasks | None |
| Host Options | Adds declarative host booleans/enums and syncs their values | Automatic |
| Mod Tab | Gives your settings a tab in the host panel | None |
| Overlay Privacy | Dims, hides, or aliases identity-bearing voice indicators | None |
- Callbacks must be cheap and throw-free. Audio-routing callbacks run at snapshot cadence, about 20 times per second per player. Overlay-privacy callbacks run at most once per rendered frame.
-
Audio callback failures are neutral. An exception becomes
Pass,null, orfalse, so the failed callback adds no restriction. Overlay privacy is intentionally different: invalid or throwing privacy callbacks fail private. - Avoid hot-path allocations where practical.
-
Return
Passornullwhen your role does not apply. This lets built-in rules and other integrations decide. - Channel keys are namespaced by mod id internally, preventing cross-mod collisions.
- Keep every key unique within your own mod. This includes host-option keys across boolean and enum options.
-
Unregister before dynamic reload/unload. Re-registering without
PerfectCommsApi.Unregister(ModId)accumulates callbacks and settings. - Local evaluation is not state synchronization. Clients converge only when your mod already gives them the same relevant role state and compatible integration code.
-
Per-speaker
VoiceRuleResult.Muffleis not routed. The verdict is stored, but it does not currently apply an audible low-pass effect. UseRegisterListenerFilterto muffle everything heard by the local listener, or a matching channel withVoiceAudioShape.Muffle. -
TwoWay: falseis not dependable directional routing. UseTwoWay: true; do not publish one-way channel behavior against API 1.1. -
Proximity channels need an explicit origin. Distance falloff requires
Shape: VoiceAudioShape.Proximity, a non-nullOrigin, and a task-phase listener position. Otherwise the route is flat volume. -
LightRadius: -1does not inherit the local player's radius. Any value less than or equal to zero disables the listener-origin vision limit. Pass a positive radius when you need that limit. - Gate mute has phase and life-state boundaries. It is enforced for living speakers in Tasks, Meeting, and Exile. It is not enforced in Lobby or for dead speakers.
- Start with Gate for supported mute behavior.
- Use Examples for complete role-oriented patterns.
- Build private team or pair routes with Channels.
- Relocate task-phase hearing with Listener Origin.
- Add synchronized controls with Host Options & Tabs.
- Protect identity-bearing indicators with Overlay Privacy.
- See every signature in the API Reference.