Skip to content

Client API Tour

Anomaly.Client.Api is the public surface other mods link against. This chapter is a guided tour: what each subsystem is, the minimal usage snippet, and the cross-references you’ll want when you need more depth.

Every public type in Anomaly.Client.Api.* has XML documentation, so IntelliSense / hover in your IDE is the fastest reference once you know what to look for.

In-game console commands for the modded client.

using Anomaly.Client.Api.Commands;
public class HelloCommand : ICommand
{
public string Description => "Print a greeting.";
public string Usage => "hello [name]";
public void Execute(string[] args, ICommandContext ctx)
{
var name = args.Length > 0 ? args[0] : "world";
ctx.Reply($"Hello, {name}!");
}
}
// In OnInitializeMelon:
CommandRegistry.Register("mymod", new HelloCommand());

The canonical invocation is <modId>.<commandname> — users type mymod.hello at the console. hello alone works as an alias unless another mod has registered the same alias, in which case Anomaly picks the namespaced form to avoid ambiguity.

Named key bindings with user-rebindable overrides persisted to bindings.json.

using Anomaly.Client.Api.Input;
using UnityEngine;
InputRegistry.Register(new InputBindingDefinition
{
Id = "mymod.jump",
DisplayName = "Jump",
DefaultKey = new InputKey { PrimaryKey = KeyCode.F }
});
// Poll from an Update loop or event handler
if (InputRegistry.WasPressedThisFrame("mymod.jump"))
DoJump();
// Or subscribe
InputRegistry.Get("mymod.jump").Pressed += DoJump;
InputRegistry.Get("mymod.jump").Released += StopJumping;

The user rebinds in a settings UI Anomaly provides; rebinds land in bindings.json and are reapplied automatically on next launch.

ClientConfig.For(modId) is a MelonPreferences-backed compatibility facade for typed per-mod settings. New mods should prefer direct MelonPreferences categories for user-facing options; use ClientConfig when you want the small Anomaly typed key/value shape or are maintaining older code.

using Anomaly.Client.Api.Config;
var cfg = ClientConfig.For("mymod");
// Get with default
float volume = cfg.Get("volume", 0.75f);
// Set
cfg.Set("volume", 0.5f);
// Subscribe to changes made through this facade
cfg.KeyChanged += key =>
{
if (key == "volume") ApplyVolume(cfg.Get("volume", 0.75f));
};

Values are stored as JSON strings inside MelonPreferences entries so existing typed Get<T> / Set<T> consumers keep working. Keys are strings; values are JSON-serializable types (primitives, strings, arrays, nested objects via POCOs).

Call cfg.Save() if you need MelonPreferences written to disk immediately. Call cfg.Reload() if you intentionally want to reload the category from disk and fire KeyChanged for entries whose values changed.

Same shape as Config, but for mod-owned state that is not user-facing. It is also MelonPreferences-backed, and writes are debounced.

using Anomaly.Client.Api.Persistence;
var store = ClientPersistence.For("mymod");
store.Save("lastLoadout", myLoadoutObject);
var loadout = store.Load<Loadout>("lastLoadout");
// Force an immediate flush (otherwise the debouncer batches)
store.Flush();

Use this for “the user played this” state; use direct MelonPreferences categories or ClientConfig for “the user set this preference.”

Delayed and recurring callbacks plus named cooldowns. Tick-accurate, based on Time.unscaledDeltaTime so paused time doesn’t skip.

using Anomaly.Client.Api.Scheduling;
// One-shot delay
Scheduler.In(TimeSpan.FromSeconds(3), () => ShowHint(), ownerId: "mymod");
// Recurring
var handle = Scheduler.Every(TimeSpan.FromSeconds(1), DoTick, ownerId: "mymod.ticker");
// Cooldowns
Scheduler.StartCooldown("mymod.ability", TimeSpan.FromSeconds(5));
if (Scheduler.GetCooldownRemaining("mymod.ability") == TimeSpan.Zero)
FireAbility();
// Clear everything owned by your mod (automatic on disconnect; manual if you need it)
Scheduler.ClearAll(ownerId: "mymod");

ClearAll(ownerId) runs automatically on client disconnect, so your scheduled tasks don’t leak into the next session. If you want tasks across sessions, re-register them in the next ClientEvents.Ready.

All live under Anomaly.Client.Api.Events.Handlers. Each handler class is static; subscribe with += during OnInitializeMelon.

HandlerEvents
ClientEventsSceneLoaded, Connected, Disconnected, Ready, CanvasReady, CreditHook
PlayerEventsAdded, Removed
RoleEventsChanged
StatsEventsDamaged, Died, Healed
EffectEventsEnabled, Disabled, IntensityChanged
InventoryEventsItemsChanged, CurrentItemChanged
PickupEventsAdded, Removed
ProjectileEventsSpawned, Removed
FirearmEventsShot
AdminToyEventsAdded, Removed
RoundEventsPhaseChanged + per-phase convenience events
VoiceEventsSending, Sent, Receiving, Received
NetworkMessageEventsAnomalyMessageReceived
FileTransferEventsProgress, Complete
AssetEventsSpawned, Despawned, Reloaded, OverrideApplied, OverrideCleared
AnnouncerEventsLineScheduled, LineStarted
WarheadEventsProgressChanged, Detonated
GeneratorEventsEngaged
TeslaEventsAdded, Removed, Bursted
ElevatorEventsSpawned, Removed, Moved
FacilityCameraEventsCreated, Removed, StateChanged
RagdollEventsSpawned, Removed

StatsEvents also exposes StaminaChanged and HumeShieldChanged alongside the existing Damaged/Died/Healed — see HUD Output and Player State.

All handlers run on the Unity main thread. Exceptions in your handler are caught and logged so they do not break other subscribers.

Read-only feature wrappers around game state:

  • Client.Players, Client.LocalPlayer — list of Player and your local hub.
  • Client.Rooms, Client.Doors, Client.Pickups, Client.Projectiles, Client.AdminToys — world views.
  • Wrapper types: Player, Role, Item, PlayerEffect, PlayerStatsWrapper, etc.

All wrappers are read-only views — writing to .Health on a Player has no effect on the server, just on the local visualization (and even that may be overwritten by the next server update). If you need authoritative mutation, write a server companion (see Server Companion Quickstart).

Shared screen-space canvas with Z-ordered layers.

using Anomaly.Client.Api.UI;
using UnityEngine;
// Inside ClientEvents.CanvasReady (not OnInitializeMelon — canvas isn't ready yet)
var hudLayer = UiCanvas.GetLayer(UiLayer.HUD);
var myHudRoot = Object.Instantiate(MyHudPrefab, hudLayer);

Layers (higher values render above lower):

  • Background = 0
  • World = 100
  • HUD = 200
  • Overlay = 300
  • Modal = 400
  • Debug = 500

Helper utilities live in the same namespace: UiAnchorExtensions, SafeArea, UiParameterBinder, UiPrefabValidator.

Register your mod’s translation directory in OnInitializeMelon, then call Tr.Get:

using Anomaly.Client.Api.Localization;
// Conventional path: <MelonLoader UserData>/i18n/mymod/
Tr.RegisterMod("mymod");
// Later
string greeting = Tr.Get("mymod.greeting", "Player1"); // "Hello, Player1!"

Use Tr.RegisterMod("mymod", customDirectory) only when your YAML files intentionally live outside UserData/i18n/mymod/.

Full details — file format, region fallback, rich text — are in Assets and Localization.

Server-provided asset loading, populated by Anomaly’s asset-transfer layer.

using Anomaly.Client.Api.Assets;
if (AnomalyResources.TryGetTexture(textureHash, out var tex))
{
myMaterial.mainTexture = tex;
}
if (AnomalyResources.TryLoadFromBundle<GameObject>(bundleHash, "Prefabs/MySpawn", out var prefab))
{
Instantiate(prefab);
}

Treat asset identifiers as opaque values supplied by the server or your server companion plugin. See Assets and Localization.

A thin wrapper around MelonLoader’s coroutine support so your code doesn’t accidentally call StartCoroutine (Unity-standard) instead of MelonCoroutines.Start (IL2CPP-safe).

using Anomaly.Client.Api;
Coroutines.Start(MyCoroutine());
Coroutines.CallDelayed(0.5f, () => DoThing());
Coroutines.Stop(handle);

Prefer the Scheduler for time-based logic — Coroutines is for genuinely per-frame patterns that yield on specific Unity events.

VirtualInputs injects button, mouse-button, and axis data into the engine’s UnityEngine.Input pipeline — for controller support, accessibility modes, gesture systems. Different from InputRegistry (rebindable named keybinds): VirtualInputs drives existing engine input from a custom source.

using Anomaly.Client.Api.Input;
VirtualInputs.RegisterAxis("mymod.horizontal", "Horizontal", () => Gamepad.LeftStick.x);

See Virtual Inputs for the merge semantics and worked examples.

Three rendering helpers under Anomaly.Client.Api.Rendering and Anomaly.Client.Api.Features:

  • MaterialTint — per-renderer HDRP_Lit tinting via cached MaterialPropertyBlock.
  • ModelAttachment + PlayerAnchor / PlayerAnchors + ItemModels — attach arbitrary visuals to anchors on a player’s character model.
  • ScreenEffects — full-screen tint and one-shot flash overlays.
  • Subtitles — bottom-anchored text helper layered over LocalHint.
  • CharacterModels + IModelHook — write character-model state in sync with SCP:SL’s manual animator timing.

See Rendering and Visuals.

Helpers for showing feedback on the local player’s HUD plus the player-state readouts mods most often pair with them:

  • LocalHint, LocalBroadcast, CameraShake — show feedback without round-tripping the server.
  • Hud — visibility toggle and push-based stack for cinematic / screenshot / modal mods.
  • Player.MovementState, IsCrouching, IsSprinting, IsGrounded, Zone — convenience readouts over FirstPersonMovementModule and Room.
  • PlayerStatsWrapper.Stamina / MaxStamina — stamina readout alongside health / Hume shield.
  • Round — read-only static facade over the round’s lifecycle phase.
  • CameraManager — cached active camera with role-change auto-invalidation.

See HUD Output and Player State.

Read-only wrappers around facility state and map distributors — Warhead, Scp914, Decontamination, Generator, Tesla, Elevator, FacilityCamera, Ragdoll, Locker. Plus typed item / pickup / door subtypes (FirearmItem, KeycardItem, BreakableDoor, etc.) reachable via item.As<FirearmItem>() or pattern-matching, and serial-based Item.Get(serial) / Pickup.Get(serial) / Projectile.Get(serial) lookup.

See Map Features.

WorldObjects is a local-only manipulation API for arbitrary scene GameObjects — typically non-networked map decoration. Caches the original local pose so ResetTransform always restores it.

See World Objects.

Server Companion Quickstart when your mod needs a server-side component.