headlessclient

Headless Client (.NET tests & bots)

A Crossplay client without Unity, for a game's automated end-to-end tests and console bots: connect over LiteNet UDP (plaintext, or with the server's pre-shared key), log in with an account or as a guest (the identity can be kept in a file so the next run is the same player), resume and sign out, typed requests matched by id exactly like the Unity client's CallAsync, and pushes through OnMessage / NextAsync. It knows no piece: it speaks any piece's wire, or the game's own, through their Contracts types. Many independent clients run in one process. Reference Crossplay.HeadlessClient.Server.dll plus the Contracts DLLs from the server's _crossplay/lib, and the LiteNetLib and MemoryPack packages. Adds nothing to the server.

Category Infra Services 1 Options 1
Depends on
core
Shape
server-only · zero wire

Services you call 1

Crossplay implements these. Resolve them from DI and call them from your own systems.

IHeadlessClient

A Crossplay client without Unity, for a game's automated end-to-end tests and console bots. It speaks any piece's wire - or the game's own - through that piece's Contracts types, so it knows no piece itself.

  • long? AccountId { get; }

    The signed-in account, or null.

  • Task<TReply> CallAsync<TRequest, TReply>(TRequest request, TimeSpan? timeout = null, CancellationToken ct = null)

    Sends a typed request and waits for its reply, matched by request id - the Unity IRequestClient.CallAsync contract. timeout null = the options' request timeout.

  • Task ConnectAsync(CancellationToken ct = null)

    Connects (following shard redirects). Throws TimeoutException or InvalidOperationException when the server cannot be reached or refuses.

  • void Disconnect()

    Closes the link on purpose (no Disconnected). ConnectAsync works again after.

  • event Action<string> Disconnected

    Raised when an established link drops for any reason other than Disconnect or Dispose; the argument is the transport's reason.

  • event Action<uint> FlagsReceived

    Raised with a reply's piggyback flags when they are not 0.

  • GuestIdentity Guest { get; }

    The guest identity the last successful LoginAsGuestAsync used, or null.

  • event Action<Exception> HandlerFailed

    Raised when an OnMessage``1 handler throws (the client keeps running).

  • bool IsConnected { get; }

    True while the link to the server is up.

  • Task<LoginResponse> LoginAsGuestAsync(string identityPath = null, CancellationToken ct = null)

    Logs in as a guest. With identityPath the identity is read from that file (created on first use) and saved after a successful login, so the next run is the same player; without it the identity lives as long as this client. If the server rejects a saved identity's credentials (e.g. a wiped dev database), one fresh identity is tried. Needs Auth:AllowSignUpOnFirstLogin on the server.

  • Task<LoginResponse> LoginAsync(string username, string password, CancellationToken ct = null)

    Logs in with a username and password. Waits through a login queue.

  • Task<T> NextAsync<T>(Func<T, bool> match = null, TimeSpan? timeout = null, CancellationToken ct = null)

    Waits for the next T that satisfies match (null = any). It is subscribed when called, so call it BEFORE sending what triggers the message. Throws TimeoutException when none arrives in time.

  • IDisposable OnMessage<T>(Action<T> handler)

    Runs handler for every T the server sends until the returned subscription is disposed. Any number of handlers may watch one type.

  • Task<ResumeResponse> ResumeAsync(string token = null, CancellationToken ct = null)

    Resumes a session on a fresh connection with token (null = SessionToken).

  • void Send<T>(T message, bool reliable = true)

    Sends any Contracts message (its id comes from its [MessageId]).

  • string SessionToken { get; }

    The token ResumeAsync resumes with, or null.

  • Task<SignOutResponse> SignOutAsync(CancellationToken ct = null)

    Signs out on purpose: the server revokes the resume token, then the client clears its identity and disconnects, whatever the server answered.

Configuration 1

Every tunable lives in an options object — there are no magic numbers to hunt for.

HeadlessClientOptions

Where and how a HeadlessClient connects. Mirror the server's Crossplay:Transport.

  • TimeSpan ConnectTimeout { get; set; }

    How long ConnectAsync waits for the server to accept.

  • string ConnectionKey { get; set; }

    LiteNet connection key; must match the server's Transport:ConnectionKey.

  • TimeSpan DisconnectTimeout { get; set; }

    How long without traffic before LiteNet declares the link dead.

  • string EncryptionKey { get; set; }

    Pre-shared packet key (the server's Transport:EncryptionKey). Empty = plaintext wire, for a dev server that has no key set.

  • string GuestNamePrefix { get; set; }

    Start of a generated guest name (Create).

  • string Host { get; set; }

    Server host name or address.

  • int MaxGuestNameLength { get; set; }

    Longest generated guest name; keep it within the server's name limit.

  • int MaxShardRedirects { get; set; }

    How many shard redirects a connect follows before giving up.

  • int Port { get; set; }

    Server UDP port (the server's Transport:Port).

  • int ProtocolVersion { get; set; }

    Sent with login and resume; the server checks it only when it expects a version.

  • TimeSpan RequestTimeout { get; set; }

    Default wait for a reply (requests, login, resume, sign-out) and for NextAsync``1. A login-queue update restarts the login wait.