Skip to content

Client API Tour

Anomaly.Client.Api.dll is the public client contract. The release SDK includes its XML documentation and the matching Anomaly.Shared contract. Do not reference Anomaly.Client.dll.

using System.Collections.Generic;
using Anomaly.Client.Api.Commands;
public sealed class HelloCommand : ICommand
{
public string Name => "hello";
public IReadOnlyList<string> Aliases => new[] { "hi" };
public string Description => "Print a greeting.";
public void Execute(CommandContext context)
{
string name = context.ArgOr(0, "world");
context.Reply($"Hello, {name}!");
}
}
public static class HelloFeature
{
// Registers mymod.hello and the collision-checked alias hi.
public static void Enable() =>
CommandRegistry.Register("mymod", new HelloCommand());
public static void Disable() =>
CommandRegistry.Unregister("mymod", "hello");
}
using System;
using Anomaly.Client.Api.Input;
using UnityEngine;
// During initialization: retain this handle for the mod's lifetime.
IDisposable binding = InputRegistry.Register(new InputBindingDefinition(
"mymod.action",
"mymod.input.action",
new InputKey(KeyCode.F, InputModifiers.Shift)));
Action<string> onPressed = id =>
{
if (id == "mymod.action")
RunAction();
};
InputRegistry.Pressed += onPressed;
// During deinitialization:
InputRegistry.Pressed -= onPressed;
binding.Dispose();

Bindings are snapshotted before polling and edge state is updated before callbacks. Disposal removes only the exact registration, silently clears its held state, and stops stale callbacks, including later subscribers to the same in-flight event. It does not synthesize Released or delete the saved override in UserData/Anomaly/bindings.json. Duplicate registration warns and returns an inert handle. Re-registering while a key is held produces a fresh Pressed on the next poll. Use the API on the game thread.

Use your own lowercase dotted namespace. Public command, binding, virtual-input, cooldown, config, and persistence registrations reject anomaly and anomaly.*.

ClientConfig.For(“mymod”) exposes a MelonPreferences-backed configuration store. ClientPersistence.For(“mymod”) stores non-user-facing state and supports Flush for explicit durability. Runtime shutdown, disconnect, and scene transitions flush scheduled persistence before clearing work.

For visible settings, create typed MelonPreferences entries. Use a raw category ID without dots, such as example_mod, separately from an Anomaly namespace such as example.mod: the supported loader treats dots as TOML paths, which can lose saved entries. Anomaly automatically renders common types in its Mods tab; ModSettings metadata customizes labels, ranges, choices, grouping, and refresh behavior. See Mod Settings.

var task = Scheduler.Every(TimeSpan.FromSeconds(1), Tick);
// Peer mods own the returned handle.
task.Cancel();

Peer mods cancel individual tasks through their retained handles. Global scheduler control and owner-wide cancellation belong to the runtime. Managed modules use IModContext scheduling so disposal performs owner-scoped cancellation.

Key handlers include ClientEvents, PlayerEvents, RoleEvents, StatsEvents, EffectEvents, InventoryEvents, PickupEvents, ProjectileEvents, FirearmEvents, AdminToyEvents, RoundEvents, VoiceEvents, HudEvents, NetworkMessageEvents, FileTransferEvents, AnnouncerEvents, WarheadEvents, GeneratorEvents, TeslaEvents, ElevatorEvents, FacilityCameraEvents, and RagdollEvents.

StatsEvents exposes Healed, StaminaChanged, and HumeShieldChanged. Read these as client-observed state; use native LabAPI events for authoritative damage and death handling in a server companion.

Hint interception is exposed through HudEvents.HintDisplaying and targets the client’s ShowLocal(HintMessage) path. Handlers can replace a text hint’s Text, change its finite non-negative DurationSeconds, or set IsAllowed = false to cancel the local render. Event dispatch isolates subscribers so one failure does not stop later handlers or internal routing.

Client exposes connection state, EnrollmentId for the current authenticated server, LocalPlayer, Players, Rooms, Doors, Pickups, Projectiles, and AdminToys. Wrapper caches evict deterministically when underlying objects or lifecycle scopes end; do not retain wrappers across disconnect or scene transitions.

ClientEvents.Ready is raised from the network manager’s OnClientSceneChanged callback, and Client.IsClientReady records that signal. Neither guarantees a local player hub, populated world collections, or enrollment disclosure. Null-check Client.LocalPlayer and related objects when using them; Client.EnrollmentId can remain null until the authenticated server publishes it. Disconnected may also report failed handshake/retry attempts, so cancel-and-clear cleanup must be idempotent and cannot assume a prior Ready or one event per completed gameplay session.

Wait for ClientEvents.CanvasReady before creating UI. UiCanvas orders layers by their enum value independently of the order mods request them. Subscribe during initialization; lifecycle events are not replayed to late subscribers.

Register translations with Tr.RegisterMod(“mymod”). Locale resolution uses the SCP:SL registry language first, then the Anomaly MelonPreferences override, then system culture, then English. Locale files are flat YAML mappings under UserData/i18n/<namespace>/. See Assets and Localization.

ClientCapabilities.Register takes a CapabilityDescriptor from Anomaly.Shared.Capabilities and returns an IDisposable. Use a unique feature ID, such as example.portal, and a major/minor/patch version. IDs contain at most 64 lowercase ASCII letters, digits, dots, hyphens, underscores, or colons. Duplicate IDs and more than 32 total registrations are rejected. A server requirement matches the same ID and major version with a sufficient minor/patch version.

Retain the registration while your feature is available and dispose it when the feature disables. Registrations persist across disconnects; authenticated session state does not. Anomaly sends complete snapshots after a server challenge establishes a nonzero CurrentRoundEpoch, and when registrations change within that epoch. TryGet queries local registrations. The framework owns snapshot sequencing and transport; capability advertising is a feature declaration, not a grant of gameplay authority.

LocalPlayerMotion.TryApply(ownerId, motion, out failure) applies a MotionDiscontinuity to the local first-person movement module on the game thread. The request contains position, velocity, forward direction, a nonzero sequence, and an optional grounded flag. Use your own stable owner ID, at most 128 characters, and strictly increasing sequences for that owner. Vectors must be finite and forward must be nonzero; the current runtime also rejects speeds above 100 units per second.

A successful call records the sequence and raises LocalPlayerMotion.Applied. A failed call leaves that sequence available for a later attempt and reports MotionFailure: RuntimeUnavailable, NoLocalPlayer, NonFpcRole, ModuleNotReady, InvalidInput, StaleSequence, ApplyFailed, or OwnerBusy. OwnerBusy means another owner has an active short movement handoff; do not reset another feature’s state to bypass it. Success describes local application, so coordinate authoritative movement with a server companion.

Use ResetSequence(ownerId) to clear your sequence and cancel your active handoff when the feature disables. The parameterless overload clears all owners, and the overload of TryApply without an owner shares DefaultOwnerId; use explicit ownership for independent mods. Anomaly resets motion state on disconnect, single-scene transitions, runtime reset, and a new capability round epoch. Re-establish feature state at the appropriate lifecycle boundary.

Every IAnomalyMessage declares direction, authenticated phase, and maximum payload policy through an authenticated directional base. See Custom Networking.

Use AnomalyResources.TryGetTexture and TryGetAudioClip for authorized resources, and treat returned objects as borrowed for the current connection. Disk cache maintenance, shared asset unloading, global config disposal, and global persistence flushing are framework internals. Use Tr for localization and your own persistence store’s Flush for durability.

Remote v1 content is items-only. Stock item templates supply model and weight; an item may add text and an optional PNG icon. See Custom Items.

Continue to Server Companion Quickstart.