Files
ActionBlock/actionBlocks_server/src/ActionBlocks.Server/Hubs/GameHub.cs
T
2026-10-24 21:16:18 +02:00

135 lines
3.9 KiB
C#

using ActionBlocks.Game;
using ActionBlocks.Server.Dtos;
using ActionBlocks.Server.Lobbies;
using Microsoft.AspNetCore.SignalR;
namespace ActionBlocks.Server.Hubs;
/// <summary>
/// Client-facing API. Server-to-client messages: "LobbyUpdated" (LobbyDto) and "MatchState" (MatchStateDto).
/// </summary>
public sealed class GameHub(LobbyManager lobbies, GameSettings settings) : Hub
{
public const string LobbyUpdated = "LobbyUpdated";
public const string MatchState = "MatchState";
public async Task<JoinResultDto> CreateLobby(string name)
{
var (lobby, player) = Run(() => lobbies.Create(Context.ConnectionId, name));
return await Joined(lobby, player);
}
public async Task<JoinResultDto> JoinLobby(string code, string name)
{
var (lobby, player) = Run(() => lobbies.Join(Context.ConnectionId, code, name));
return await Joined(lobby, player);
}
public async Task LeaveLobby()
{
var lobby = lobbies.FindByConnection(Context.ConnectionId);
if (lobby is null)
{
return;
}
await Groups.RemoveFromGroupAsync(Context.ConnectionId, lobby.Code);
await LeaveAndNotify();
}
public async Task StartGame()
{
var lobby = lobbies.FindByConnection(Context.ConnectionId) ?? throw new HubException("You are not in a lobby.");
LobbyDto dto;
lock (lobby.Sync)
{
var player = lobby.FindByConnection(Context.ConnectionId);
if (player is null || player.Id != lobby.HostId)
{
throw new HubException("Only the host can start the game.");
}
if (lobby.IsPlaying)
{
throw new HubException("A game is already running.");
}
lobby.StartMatch(settings);
dto = lobby.ToDto();
}
await Clients.Group(lobby.Code).SendAsync(LobbyUpdated, dto);
}
public void Input(InputAction action) =>
WithPlayer((lobby, player) =>
{
lobby.Match?.HandleInput(player.Id, action);
lobby.Dirty = true;
});
public void SelectTarget(string targetId) =>
WithPlayer((lobby, player) => lobby.Match?.SelectTarget(player.Id, targetId));
public IReadOnlyList<ItemInfoDto> GetItems() =>
Game.Items.ItemRegistry.Default.All.Select(i => new ItemInfoDto(i.Id, i.DisplayName)).ToList();
public override async Task OnDisconnectedAsync(Exception? exception)
{
await LeaveAndNotify();
await base.OnDisconnectedAsync(exception);
}
private async Task<JoinResultDto> Joined(Lobby lobby, LobbyPlayer player)
{
await Groups.AddToGroupAsync(Context.ConnectionId, lobby.Code);
LobbyDto dto;
lock (lobby.Sync)
{
dto = lobby.ToDto();
}
await Clients.OthersInGroup(lobby.Code).SendAsync(LobbyUpdated, dto);
return new JoinResultDto(player.Id, dto);
}
private async Task LeaveAndNotify()
{
var lobby = lobbies.Leave(Context.ConnectionId);
if (lobby is null)
{
return;
}
LobbyDto dto;
lock (lobby.Sync)
{
lobby.Dirty = true;
dto = lobby.ToDto();
}
await Clients.Group(lobby.Code).SendAsync(LobbyUpdated, dto);
}
private void WithPlayer(Action<Lobby, LobbyPlayer> action)
{
var lobby = lobbies.FindByConnection(Context.ConnectionId);
if (lobby is null)
{
return;
}
lock (lobby.Sync)
{
if (lobby.FindByConnection(Context.ConnectionId) is { } player)
{
action(lobby, player);
}
}
}
private static T Run<T>(Func<T> action)
{
try
{
return action();
}
catch (LobbyException e)
{
throw new HubException(e.Message);
}
}
}