Server Companion Quickstart
Use a normal LabAPI Plugin<Config>. The supported author surface is Anomaly.Server.Api.* in Anomaly.Server.dll; game events, commands, configuration, permissions, players, and plugin lifecycle remain LabAPI responsibilities.
Load the Anomaly core at server startup, before facility generation. Loading or replacing it after a map exists is refused; restart the server for core updates. Call game-facing Anomaly APIs on the server game thread.
Build the packaged starter
Section titled “Build the packaged starter”Extract the matching developer SDK and open samples/HelloAnomalyServer. Set AnomalyServerReferenceDir to the matching dedicated server’s managed assemblies and AnomalyLabApiDir to its LabAPI assembly folder:
dotnet build -c Release -p:AnomalyServerReferenceDir="X:\Server\SCPSL_Data\Managed" -p:AnomalyLabApiDir="X:\Server\LabAPI" -p:DeployLocal=falseThe starter finds the enclosing SDK automatically. If copied elsewhere, retain both Anomaly.Server.Sample.props and HelloMessages.cs in the parent samples directory, and also set AnomalySdkRoot. Game and LabAPI references are supplied by your server installation.
The packaged starter also includes an authenticated request/reply example with HelloAnomalyMod. Both projects compile the matching samples/HelloMessages.cs contract. Install both companions, connect, and run example.hello.server on the client. The server reports content readiness even if the player declined optional content. Keep this shared source when copying either starter; register the same contracts in both runtimes. The example below focuses on item registration and LabAPI events.
Custom item and native events
Section titled “Custom item and native events”using System;using Anomaly.Server.Api.Content;using Anomaly.Server.Api.Events.Arguments;using Anomaly.Server.Api.Events.Handlers;using LabApi.Events.Arguments.PlayerEvents;using LabApi.Events.Handlers;using LabApi.Features.Console;using LabApi.Features;using LabApi.Loader.Features.Plugins;
namespace HelloAnomalyServer;
/// <summary>An ordinary LabAPI consumer with no Anomaly plugin base class.</summary>public sealed class Plugin : Plugin<Config>{ private const string ItemKey = "example.server:medkit"; public override string Name => "Hello Anomaly Server"; public override string Description => "Minimal server-owned custom item using native LabAPI events."; public override string Author => "AnomalyLabs"; public override Version Version => new Version(1, 0, 0); public override Version RequiredApiVersion => new Version(LabApiProperties.CompiledVersion);
public override void Enable() { CustomItems.Register(new CustomItemDefinition { Key = ItemKey, BaseTemplate = ItemType.Medkit, DisplayName = "Example medical kit", Description = "A custom presentation with ordinary medkit behavior.", }); ClientEvents.ContentReady += OnContentReady; PlayerEvents.UsedItem += OnUsedItem; }
public override void Disable() { ClientEvents.ContentReady -= OnContentReady; PlayerEvents.UsedItem -= OnUsedItem; }
private void OnContentReady(ClientEventArgs ev) { if (Config.GiveOnContentReady && CustomItems.TryGiveItem(ev.Player, ItemKey, out var item)) Logger.Info($"Granted {ItemKey}, serial {item.Serial}, to {ev.Player.Nickname}."); }
private static void OnUsedItem(PlayerUsedItemEventArgs ev) { if (CustomItems.TryGetItemKey(ev.UsableItem.Type, out string key) && key == ItemKey) Logger.Info($"{ev.Player.Nickname} used {key} through LabAPI's normal UsedItem event."); }}
public sealed class Config{ public bool GiveOnContentReady { get; set; } = true;}Register definitions during Enable, before the first catalog freezes. Subscribe to Anomaly client notifications and LabAPI gameplay events during Enable; remove the same handlers in Disable. Notifications use LabAPI event arguments and are not replayed to late subscribers.
ClientEvents.Connected reports an authenticated, initialized client session. ContentReady is separate and fires once after that player instance activates the exact offered catalog. Native round restart replaces the player instance even when it keeps the authenticated connection, so the new player activates again and receives its own ContentReady event. CustomItems.TryGiveItem returns a LabAPI Item only after activation. Handle ordinary item behavior through LabAPI PlayerEvents.
The custom item keeps its stock model, pickup, weight, and use behavior. Definitions are process-lifetime declarations once frozen; restart to change or remove them. Invalid registration arguments throw before changing the catalog. Freezing and materializing the accepted definitions remains transactional; a materialization failure rejects the complete catalog.
Deployment
Section titled “Deployment”Put only your companion plugin DLL in LabAPI plugins/global/; place its non-plugin shared dependencies in dependencies/global/. Keep the matching Anomaly server runtime installed and do not copy SDK/game references into your companion package. Builds never deploy automatically.
Optional IconPath values refer to PNG files beneath the operator’s configured AssetDirectory. A player’s UserId is server-local and can differ on another Anomaly server.
Client capabilities and motion
Section titled “Client capabilities and motion”Anomaly.Server.Api.Features.ClientCapabilities exposes TryGet, Supports, and GetSnapshot for a native LabAPI Player. Capability descriptors describe the client’s current-round support. Observe ClientEvents.CapabilitiesChanged and query the player again when capabilities arrive or change. GetSnapshot returns detached descriptors sorted by ID; Supports checks the requested capability version. Capability advertisement does not replace your feature’s authorization rules.
Anomaly.Server.Api.Features.PlayerMotion.TryApply applies a MotionDiscontinuity containing position, velocity, forward direction, a sequence number, and optional grounded state. Call it on the server game thread with a stable owner ID and a strictly increasing nonzero sequence for each owner/player pair. Check the returned MotionFailure when it returns false; an active handoff owned by another feature reports OwnerBusy.
ClientEvents.MotionApplied exposes Player, OwnerId, and Motion. Call PlayerMotion.Reset(player, ownerId) when releasing your feature’s motion state. Reset only the owner ID your plugin controls.
Public server surface
Section titled “Public server surface”| Namespace | Author entry points |
|---|---|
Anomaly.Server.Api.Content | CustomItems, CustomItemDefinition, ContentReadiness |
Anomaly.Server.Api.Networking | ClientMessaging |
Anomaly.Server.Api.Events.Handlers | ClientEvents, MapEvents |
Anomaly.Server.Api.Events.Arguments | Player, message, motion, and layout event arguments |
Anomaly.Server.Api.Features | ClientCapabilities, PlayerMotion, Announcer |
Anomaly.Server.Api.Assets | AssetRegistry, AssetOverrides |
Anomaly.Server.Api.Map | FacilityLayout, FacilityRoomPlacement, FacilityGlyphs, GlyphPairInfo |
Use Anomaly.Server.Plugin.Instance?.IsOperational == true for a startup dependency check. Anomaly owns its runtime initialization and shutdown; companions register their definitions and subscriptions through the APIs above.
For procedural placement, see Server Facility Authoring. See Custom Networking and the API Index for the remaining author surface.