V2/Client/ClientController.cs

Client controller for the Hexagon V2 game. It defines an IClientCommandTransport and IHexClientController and implements HexClientController which translates high level client operations (character list, create/load/delete character, inventory item moves, item actions, chat, interaction commands, schema commands) into ClientCommand instances and sends them via the transport.

Networking
#nullable enable

using System;
using System.Threading;
using System.Threading.Tasks;
using Hexagon.V2.Domain;
using Hexagon.V2.Kernel;
using Hexagon.V2.Networking;

namespace Hexagon.V2.Client;

public interface IClientCommandTransport
{
	ValueTask<OperationResult> SendAsync(
		ClientCommand command,
		CancellationToken cancellationToken = default);
}

public interface IHexClientController
{
	ValueTask<OperationResult> RequestCharactersAsync(CancellationToken cancellationToken = default);
	ValueTask<OperationResult> CreateCharacterAsync(CharacterCreationInput input, CancellationToken cancellationToken = default);
	ValueTask<OperationResult> LoadCharacterAsync(CharacterId characterId, CancellationToken cancellationToken = default);
	ValueTask<OperationResult> DeleteCharacterAsync(CharacterId characterId, CancellationToken cancellationToken = default);
	ValueTask<OperationResult> UnloadCharacterAsync(CancellationToken cancellationToken = default);
	ValueTask<OperationResult> MoveItemAsync(InventoryId sourceId, InventoryId targetId, ItemId itemId, int x, int y,
		CancellationToken cancellationToken = default);
	ValueTask<OperationResult> MoveItemAsync(
		InventoryId sourceId,
		InventoryId targetId,
		ItemId itemId,
		InventoryGridPosition position,
		CancellationToken cancellationToken = default);
	ValueTask<OperationResult> RunItemActionAsync(InventoryId inventoryId, ItemId itemId, ActionId actionId,
		CancellationToken cancellationToken = default);
	ValueTask<OperationResult> RunItemActionAsync(InventoryId inventoryId, ItemId itemId, ActionId actionId,
		IReadOnlyDictionary<string, SnapshotValue> arguments,
		CancellationToken cancellationToken = default);
	ValueTask<OperationResult> DropItemAsync(InventoryId sourceId, ItemId itemId, CancellationToken cancellationToken = default);
	ValueTask<OperationResult> PickUpItemAsync(ItemId itemId, InventoryId destinationId,
		CancellationToken cancellationToken = default);
	ValueTask<OperationResult> SendChatAsync(string channelId, string text, CancellationToken cancellationToken = default);
	ValueTask<OperationResult> CancelActionAsync(Guid instanceId, CancellationToken cancellationToken = default);
	ValueTask<OperationResult> BeginInteractionAsync(InteractionTargetInput target, CancellationToken cancellationToken = default);
	ValueTask<OperationResult> ContinueInteractionAsync(InteractionSessionId sessionId, InteractionTargetInput target,
		CancellationToken cancellationToken = default);
	ValueTask<OperationResult> CloseInteractionAsync(InteractionSessionId sessionId, CancellationToken cancellationToken = default);
	ValueTask<OperationResult> RunSchemaCommandAsync(string commandId,
		IReadOnlyDictionary<string, SnapshotValue>? arguments = null,
		CancellationToken cancellationToken = default);
}

public sealed class HexClientController : IHexClientController, IDisposable
{
	private readonly IClientCommandTransport _transport;

	public HexClientController(IClientCommandTransport transport) =>
		_transport = transport ?? throw new ArgumentNullException(nameof(transport));

	public ValueTask<OperationResult> RequestCharactersAsync(CancellationToken cancellationToken = default) =>
		Send(new RequestCharacterListCommand(), cancellationToken);

	public ValueTask<OperationResult> CreateCharacterAsync(
		CharacterCreationInput input,
		CancellationToken cancellationToken = default) =>
		Send(new CreateCharacterCommand(input ?? throw new ArgumentNullException(nameof(input))), cancellationToken);

	public ValueTask<OperationResult> LoadCharacterAsync(
		CharacterId characterId,
		CancellationToken cancellationToken = default) =>
		Send(new LoadCharacterCommand(characterId), cancellationToken);

	public ValueTask<OperationResult> DeleteCharacterAsync(
		CharacterId characterId,
		CancellationToken cancellationToken = default) =>
		Send(new DeleteCharacterCommand(characterId), cancellationToken);

	public ValueTask<OperationResult> UnloadCharacterAsync(CancellationToken cancellationToken = default) =>
		Send(new UnloadCharacterCommand(), cancellationToken);

	public ValueTask<OperationResult> MoveItemAsync(
		InventoryId sourceId,
		InventoryId targetId,
		ItemId itemId,
		int x,
		int y,
		CancellationToken cancellationToken = default) =>
		MoveItemAsync( sourceId, targetId, itemId, new InventoryGridPosition( x, y ), cancellationToken );

	public ValueTask<OperationResult> MoveItemAsync(
		InventoryId sourceId,
		InventoryId targetId,
		ItemId itemId,
		InventoryGridPosition position,
		CancellationToken cancellationToken = default) =>
		Send(new MoveInventoryItemCommand(sourceId, targetId, itemId, position), cancellationToken);

	public ValueTask<OperationResult> RunItemActionAsync(
		InventoryId inventoryId,
		ItemId itemId,
		ActionId actionId,
		CancellationToken cancellationToken = default) =>
		Send(new RunItemActionCommand(inventoryId, itemId, actionId), cancellationToken);

	public ValueTask<OperationResult> RunItemActionAsync(
		InventoryId inventoryId,
		ItemId itemId,
		ActionId actionId,
		IReadOnlyDictionary<string, SnapshotValue> arguments,
		CancellationToken cancellationToken = default) =>
		Send(new RunItemActionCommand(inventoryId, itemId, actionId,
			arguments ?? throw new ArgumentNullException(nameof(arguments))), cancellationToken);

	public ValueTask<OperationResult> DropItemAsync(
		InventoryId sourceId,
		ItemId itemId,
		CancellationToken cancellationToken = default) =>
		Send(new DropItemCommand(sourceId, itemId), cancellationToken);

	public ValueTask<OperationResult> PickUpItemAsync(
		ItemId itemId,
		InventoryId destinationId,
		CancellationToken cancellationToken = default) =>
		Send(new PickUpItemCommand(itemId, destinationId), cancellationToken);

	public ValueTask<OperationResult> SendChatAsync(
		string channelId,
		string text,
		CancellationToken cancellationToken = default) =>
		Send(new SendChatCommand(channelId ?? string.Empty, text ?? string.Empty), cancellationToken);

	public ValueTask<OperationResult> CancelActionAsync(
		Guid instanceId,
		CancellationToken cancellationToken = default) =>
		Send(new CancelActionCommand(instanceId), cancellationToken);

	public ValueTask<OperationResult> BeginInteractionAsync(
		InteractionTargetInput target,
		CancellationToken cancellationToken = default) =>
		Send(new BeginInteractionCommand(target), cancellationToken);

	public ValueTask<OperationResult> ContinueInteractionAsync(
		InteractionSessionId sessionId,
		InteractionTargetInput target,
		CancellationToken cancellationToken = default) =>
		Send(new ContinueInteractionCommand(sessionId, target), cancellationToken);

	public ValueTask<OperationResult> CloseInteractionAsync(
		InteractionSessionId sessionId,
		CancellationToken cancellationToken = default) =>
		Send(new CloseInteractionCommand(sessionId), cancellationToken);

	public ValueTask<OperationResult> RunSchemaCommandAsync(
		string commandId,
		IReadOnlyDictionary<string, SnapshotValue>? arguments = null,
		CancellationToken cancellationToken = default) =>
		Send(new RunSchemaCommandCommand(commandId, arguments), cancellationToken);

	private ValueTask<OperationResult> Send(ClientCommand command, CancellationToken cancellationToken) =>
		_transport.SendAsync(command, cancellationToken);

	public void Dispose()
	{
		if ( _transport is IDisposable disposable ) disposable.Dispose();
	}
}