Class CameraController
- Namespace
- RustPlusApi.Camera
- Assembly
- RustPlusApi.Camera.dll
Managed session for one subscribed camera: keeps the subscription alive by periodically
re-sending the subscribe request (the server stops streaming rays for stale subscriptions),
forwards ray frames via OnFrameReceived, and exposes the press-and-release
input helpers (ZoomAsync(CancellationToken), ShootAsync(CancellationToken), ReloadAsync(CancellationToken))
used by PTZ cameras and auto-turrets. Mirrors the Camera class of
liamcottle/rustplus.js. Dispose to stop the keep-alive and unsubscribe.
public sealed class CameraController : IAsyncDisposable
- Inheritance
-
CameraController
- Implements
- Inherited Members
Remarks
The server tracks a single camera subscription per connection, so create at most one live controller per IRustPlus client at a time.
Fields
DefaultMoveDuration
How long MoveAsync(CameraButtons, TimeSpan?, CancellationToken) holds the buttons when no duration is supplied. 500 ms moves a live drone roughly 2 m at cruise speed.
public static readonly TimeSpan DefaultMoveDuration
Field Value
DefaultResubscribeInterval
The keep-alive renewal period used when none is supplied (mirrors rustplus.js).
public static readonly TimeSpan DefaultResubscribeInterval
Field Value
Properties
CameraId
The identifier this controller is subscribed to (e.g. CAM01).
public string CameraId { get; }
Property Value
Info
Camera description from the most recent successful subscribe.
public CameraInfo Info { get; }
Property Value
IsAutoTurret
Whether the camera is an auto-turret. Detected via the
Reload flag — only turrets expose a reload input.
(rustplus.js checks Crosshair instead, but a drone
may also render a crosshair, so Reload is the safer discriminator.)
public bool IsAutoTurret { get; }
Property Value
IsDrone
Whether the camera is a drone (advertises the SprintAndDuck flag, which carries the drone's vertical movement controls).
public bool IsDrone { get; }
Property Value
IsPtzCamera
Whether the camera is a PTZ (pan-tilt-zoom) CCTV camera: it supports
Mouse look but is neither an auto-turret nor a drone.
Live-observed PTZ flags are Mouse | Fire — Fire drives
ZoomAsync(CancellationToken).
public bool IsPtzCamera { get; }
Property Value
IsStaticCamera
Whether the camera is a fixed CCTV camera that accepts no input at all (ControlFlags is None).
public bool IsStaticCamera { get; }
Property Value
Methods
DisposeAsync()
Stops the keep-alive loop and unsubscribes from the camera (when still connected).
public ValueTask DisposeAsync()
Returns
LookAsync(float, float, CancellationToken)
Turns the camera by sending a single mouse-delta frame (PTZ cameras, turrets and drones — anything advertising Mouse). Refused with NotSupported when the device does not support mouse look.
public Task<Response> LookAsync(float deltaX, float deltaY, CancellationToken cancellationToken = default)
Parameters
deltaXfloatHorizontal mouse delta (positive looks right).
deltaYfloatVertical mouse delta (positive looks down).
cancellationTokenCancellationTokenA token to observe for cancellation requests.
Returns
- Task<Response>
The acknowledgement Response from the server, or the client-side refusal (nothing sent).
Remarks
Live-observed (2026-06): actuates on PTZ cameras (the frame's
CameraRotation pans) and on drones while airborne — a parked drone ignores the
input (acked with success regardless). Turret behaviour is unverified: the test
server's turret was deactivated and ignored every input.
MoveAsync(CameraButtons, TimeSpan?, CancellationToken)
Moves a drone by streaming the given movement buttons as input frames for
duration (default DefaultMoveDuration), then releasing.
Movement only actuates while a continuous input stream is held — a single
press-and-release is acknowledged by the server but never moves the drone
(live-verified 2026-06). Buttons: Forward/
Backward/Left/
Right need Movement;
Sprint (ascend) and Duck
(descend) need SprintAndDuck — the drone's vertical
controls, hence the flag's name (Jump is accepted but did
nothing on a live drone). Refused with NotSupported
when the device does not advertise the required flags.
public Task<Response> MoveAsync(CameraButtons buttons, TimeSpan? duration = null, CancellationToken cancellationToken = default)
Parameters
buttonsCameraButtonsMovement buttons to hold.
durationTimeSpan?How long to hold the buttons; DefaultMoveDuration when null.
cancellationTokenCancellationTokenA token to observe for cancellation requests.
Returns
- Task<Response>
The release acknowledgement from the server, the first failed send, or the client-side refusal (nothing sent).
Remarks
Live flight 2026-06-12: streaming Sprint for 1 s climbed ~4.7 m,
Forward/Backward moved ~5 m and back, Duck landed the drone on its
starting spot. The drone hovers when the stream stops. A parked drone responds to
Sprint (take-off) only — planar movement and mouse look require being airborne.
Fly gently: collisions with the ground or structures (and incoming fire) cost the drone
HP; a destroyed drone fails subsequent subscribes with
NoPlayer. A carried package (attached by a player) is
dropped with a FirePrimary click via
PressAsync(CameraButtons, CancellationToken).
Exceptions
- ArgumentException
buttonsis empty or contains non-movement buttons (use the action helpers or SendInputAsync(CameraButtons, float, float, CancellationToken) for those).
PressAsync(CameraButtons, CancellationToken)
Performs a discrete action by pressing buttons and then releasing them
(a follow-up input with None), mirroring the press-then-release
pair used for PTZ and auto-turret actions in rustplus.js.
public Task<Response> PressAsync(CameraButtons buttons, CancellationToken cancellationToken = default)
Parameters
buttonsCameraButtonsBitmask of buttons to press and then release.
cancellationTokenCancellationTokenA token to observe for cancellation requests.
Returns
ReloadAsync(CancellationToken)
Reloads an auto-turret, sending the Reload press-and-release used by rustplus.js. Refused with NotSupported unless IsAutoTurret.
public Task<Response> ReloadAsync(CancellationToken cancellationToken = default)
Parameters
cancellationTokenCancellationTokenA token to observe for cancellation requests.
Returns
- Task<Response>
The acknowledgement Response from the server, or the client-side refusal (nothing sent).
SendInputAsync(CameraButtons, float, float, CancellationToken)
Sends a single raw input frame (button bitmask plus mouse deltas) to the subscribed camera.
public Task<Response> SendInputAsync(CameraButtons buttons, float mouseDeltaX = 0, float mouseDeltaY = 0, CancellationToken cancellationToken = default)
Parameters
buttonsCameraButtonsBitmask of buttons to report as pressed.
mouseDeltaXfloatHorizontal mouse delta.
mouseDeltaYfloatVertical mouse delta.
cancellationTokenCancellationTokenA token to observe for cancellation requests.
Returns
ShootAsync(CancellationToken)
Fires an auto-turret once, sending the FirePrimary press-and-release used by rustplus.js. Refused with NotSupported unless IsAutoTurret.
public Task<Response> ShootAsync(CancellationToken cancellationToken = default)
Parameters
cancellationTokenCancellationTokenA token to observe for cancellation requests.
Returns
- Task<Response>
The acknowledgement Response from the server, or the client-side refusal (nothing sent).
SubscribeAsync(IRustPlus, string, TimeSpan?, CancellationToken)
Subscribes to cameraId and returns a controller that keeps the
subscription alive until disposed.
public static Task<Response<CameraController?>> SubscribeAsync(IRustPlus rustPlus, string cameraId, TimeSpan? resubscribeInterval = null, CancellationToken cancellationToken = default)
Parameters
rustPlusIRustPlusA connected client.
cameraIdstringThe camera identifier configured in game (case-sensitive).
resubscribeIntervalTimeSpan?How often to renew the subscription; DefaultResubscribeInterval when null, Zero or negative to disable renewal.
cancellationTokenCancellationTokenA token to observe for cancellation requests.
Returns
- Task<Response<CameraController>>
A Response<T> whose data is the live controller on success.
ZoomAsync(CancellationToken)
Advances a PTZ camera's zoom by one step (it cycles through its zoom levels and wraps), sending the FirePrimary press-and-release used by rustplus.js. Refused with NotSupported unless IsPtzCamera: zoom shares FirePrimary with turret fire, so zooming "on" a turret would shoot it — and the server acks unsupported inputs with success, giving no feedback of its own.
public Task<Response> ZoomAsync(CancellationToken cancellationToken = default)
Parameters
cancellationTokenCancellationTokenA token to observe for cancellation requests.
Returns
- Task<Response>
The acknowledgement Response from the server, or the client-side refusal (nothing sent).
Events
OnFrameReceived
Occurs when a ray frame for the subscribed camera is received.
public event EventHandler<CameraRaysEventArg>? OnFrameReceived
Event Type
OnKeepAliveFailed
Occurs when a keep-alive renewal attempt fails: the server refused the re-subscribe (e.g. NoPlayer after the camera was destroyed in game) or the client was disconnected (reported as Unknown with the exception message). Without a renewal the server stops streaming, so frames going quiet after this event means the subscription is dead — Info keeps its last successful value. The loop keeps retrying, so a later reconnect recovers on its own.
public event EventHandler<ErrorMessage>? OnKeepAliveFailed