Slip- und Beschuldigungslogik überarbeitet
All checks were successful
Build / build (push) Successful in 2m19s
All checks were successful
Build / build (push) Successful in 2m19s
Slips werden jetzt 30 Sekunden vorgehalten und anschließend automatisch abgeschlossen; direkte Spieler-Beschuldigungen, Win-Status, SignalR-Events, MAUI-UI, EF-Mapping/Migrationen sowie Tests wurden an die neue Spiellogik angepasst.
This commit is contained in:
@@ -242,6 +242,35 @@ public class GameHub : Hub
|
||||
Success = result
|
||||
});
|
||||
await Clients.Caller.SendAsync("PlayerHandUpdated", hand);
|
||||
|
||||
// Nach 30 Sekunden Vorhaltezeit automatisch vollenden (falls keine Beschuldigung dazwischen kam)
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Kleiner Puffer, da Task.Delay minimal früher zurückkehren kann als die 30s-Prüfung
|
||||
await Task.Delay(TimeSpan.FromSeconds(30.5));
|
||||
var completed = await _gameService.CompleteSlipAsync(gameId, cardId);
|
||||
if (completed)
|
||||
{
|
||||
var updatedHand = await _gameService.GetPlayerHandAsync(playerId);
|
||||
var updatedGameState = await _gameService.GetGameStateAsync(gameId);
|
||||
|
||||
using var db = _contextFactory.CreateDbContext();
|
||||
var targetP = await db.Players.FindAsync(playerId);
|
||||
if (targetP?.ConnectionId != null)
|
||||
{
|
||||
await Clients.Client(targetP.ConnectionId).SendAsync("PlayerHandUpdated", updatedHand);
|
||||
}
|
||||
|
||||
await Clients.Group(game.LobbyCode).SendAsync("GameStateUpdated", updatedGameState);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error completing slip for card {CardId}", cardId);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
@@ -254,8 +283,59 @@ public class GameHub : Hub
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ChallengePlayer(int gameId, int challengingPlayerId, int targetPlayerId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetAuthenticatedUserId();
|
||||
await ValidatePlayerAccessAsync(challengingPlayerId, userId);
|
||||
|
||||
var challenge = await _gameService.CreateChallengeAsync(gameId, challengingPlayerId, targetPlayerId);
|
||||
var game = await _gameService.GetGameAsync(gameId);
|
||||
|
||||
using var context = _contextFactory.CreateDbContext();
|
||||
var challengingPlayer = await context.Players
|
||||
.Include(p => p.User)
|
||||
.FirstOrDefaultAsync(p => p.Id == challengingPlayerId);
|
||||
|
||||
var targetPlayer = await context.Players
|
||||
.Include(p => p.User)
|
||||
.FirstOrDefaultAsync(p => p.Id == targetPlayerId);
|
||||
|
||||
var challengeDto = new SlipChallengeDto
|
||||
{
|
||||
ChallengeId = challenge.Id,
|
||||
ChallengingPlayerId = challengingPlayerId,
|
||||
ChallengingPlayerName = challengingPlayer?.User.Username ?? string.Empty,
|
||||
TargetPlayerId = challenge.TargetPlayerId,
|
||||
TargetPlayerName = targetPlayer?.User.Username ?? string.Empty,
|
||||
TargetCardId = challenge.TargetCardId,
|
||||
Status = challenge.Status.ToString(),
|
||||
CreatedAt = challenge.CreatedAt
|
||||
};
|
||||
|
||||
await Clients.Group(game.LobbyCode).SendAsync("SlipChallenged", challengeDto);
|
||||
|
||||
// Sende direkte Notification an den beschuldigten Spieler
|
||||
if (targetPlayer?.ConnectionId != null)
|
||||
{
|
||||
await Clients.Client(targetPlayer.ConnectionId).SendAsync("ChallengeReceived", challengeDto);
|
||||
}
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
await Clients.Caller.SendAsync("Error", new { Message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error challenging player");
|
||||
await Clients.Caller.SendAsync("Error", new { Message = "An error occurred while challenging player." });
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ChallengeSlip(int gameId, int challengingPlayerId, int targetCardId)
|
||||
{
|
||||
// Kompatibilitätsmethode
|
||||
try
|
||||
{
|
||||
var userId = GetAuthenticatedUserId();
|
||||
@@ -269,13 +349,18 @@ public class GameHub : Hub
|
||||
.Include(p => p.User)
|
||||
.FirstOrDefaultAsync(p => p.Id == challengingPlayerId);
|
||||
|
||||
var targetPlayer = await context.Players
|
||||
.Include(p => p.User)
|
||||
.FirstOrDefaultAsync(p => p.Id == challenge.TargetPlayerId);
|
||||
|
||||
var challengeDto = new SlipChallengeDto
|
||||
{
|
||||
ChallengeId = challenge.Id,
|
||||
ChallengingPlayerId = challengingPlayerId,
|
||||
ChallengingPlayerName = challengingPlayer?.User.Username ?? string.Empty,
|
||||
TargetPlayerId = challenge.TargetPlayerId,
|
||||
TargetCardId = targetCardId,
|
||||
TargetPlayerName = targetPlayer?.User.Username ?? string.Empty,
|
||||
TargetCardId = challenge.TargetCardId,
|
||||
Status = challenge.Status.ToString(),
|
||||
CreatedAt = challenge.CreatedAt
|
||||
};
|
||||
@@ -283,7 +368,6 @@ public class GameHub : Hub
|
||||
await Clients.Group(game.LobbyCode).SendAsync("SlipChallenged", challengeDto);
|
||||
|
||||
// Sende direkte Notification an den beschuldigten Spieler
|
||||
var targetPlayer = await context.Players.FindAsync(challenge.TargetPlayerId);
|
||||
if (targetPlayer?.ConnectionId != null)
|
||||
{
|
||||
await Clients.Client(targetPlayer.ConnectionId).SendAsync("ChallengeReceived", challengeDto);
|
||||
@@ -300,15 +384,22 @@ public class GameHub : Hub
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ResolveChallenge(int challengeId, bool approved)
|
||||
public async Task ResolveChallenge(int challengeId, bool approved, int? discardCardId = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetAuthenticatedUserId();
|
||||
await ValidateChallengeTargetAccessAsync(challengeId, userId);
|
||||
|
||||
var result = await _gameService.ResolveChallengeAsync(challengeId, approved);
|
||||
var game = await _gameService.GetGameAsync(result.GameRound.GameId);
|
||||
var result = await _gameService.ResolveChallengeAsync(challengeId, approved, discardCardId);
|
||||
int gameId = result.GameRound?.GameId ?? 0;
|
||||
if (gameId == 0)
|
||||
{
|
||||
using var context = _contextFactory.CreateDbContext();
|
||||
var round = await context.GameRounds.FindAsync(result.GameRoundId);
|
||||
gameId = round?.GameId ?? 0;
|
||||
}
|
||||
var game = await _gameService.GetGameAsync(gameId);
|
||||
|
||||
await Clients.Group(game.LobbyCode).SendAsync("ChallengeResolved", new
|
||||
{
|
||||
@@ -319,16 +410,19 @@ public class GameHub : Hub
|
||||
// Aktualisierte Handkarten an betroffene Spieler senden
|
||||
var challengingHand = await _gameService.GetPlayerHandAsync(result.ChallengingPlayerId);
|
||||
var targetHand = await _gameService.GetPlayerHandAsync(result.TargetPlayerId);
|
||||
var gameState = await _gameService.GetGameStateAsync(gameId);
|
||||
|
||||
using var context = _contextFactory.CreateDbContext();
|
||||
var challengingPlayer = await context.Players.FindAsync(result.ChallengingPlayerId);
|
||||
var targetPlayer = await context.Players.FindAsync(result.TargetPlayerId);
|
||||
using var db = _contextFactory.CreateDbContext();
|
||||
var challengingPlayer = await db.Players.FindAsync(result.ChallengingPlayerId);
|
||||
var targetPlayer = await db.Players.FindAsync(result.TargetPlayerId);
|
||||
|
||||
if (challengingPlayer?.ConnectionId != null)
|
||||
await Clients.Client(challengingPlayer.ConnectionId).SendAsync("PlayerHandUpdated", challengingHand);
|
||||
|
||||
if (targetPlayer?.ConnectionId != null)
|
||||
await Clients.Client(targetPlayer.ConnectionId).SendAsync("PlayerHandUpdated", targetHand);
|
||||
|
||||
await Clients.Group(game.LobbyCode).SendAsync("GameStateUpdated", gameState);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
@@ -354,6 +448,17 @@ public class GameHub : Hub
|
||||
|
||||
var gameState = await _gameService.GetGameStateAsync(gameId);
|
||||
await Clients.Caller.SendAsync("GameStateUpdated", gameState);
|
||||
|
||||
// Abgelaufene Slips wurden beim Laden des Zustands abgeschlossen; Hand des Anfragenden mitsenden
|
||||
using var context = _contextFactory.CreateDbContext();
|
||||
var playerId = await context.Players
|
||||
.Where(p => p.GameId == gameId && p.UserId == userId)
|
||||
.Select(p => p.Id)
|
||||
.FirstOrDefaultAsync();
|
||||
if (playerId > 0)
|
||||
{
|
||||
await Clients.Caller.SendAsync("PlayerHandUpdated", await _gameService.GetPlayerHandAsync(playerId));
|
||||
}
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
@@ -389,43 +494,11 @@ public class GameHub : Hub
|
||||
.Select(g => g.LobbyCode)
|
||||
.FirstAsync();
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, lobbyCode);
|
||||
|
||||
// Der Client bekommt nach einem Reconnect keinen vollständigen Zustand
|
||||
// mehr aus der Queue gespielt — er muss RequestGameState aufrufen.
|
||||
}
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
// Nicht authentifiziert — ConnectionId wird nicht gespeichert.
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to register connection for authenticated user");
|
||||
}
|
||||
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
|
||||
public override async Task OnDisconnectedAsync(Exception? exception)
|
||||
{
|
||||
_logger.LogInformation("Client {ConnectionId} disconnected", Context.ConnectionId);
|
||||
|
||||
// ConnectionId zurücksetzen, damit keine Nachrichten an eine tote Verbindung gehen.
|
||||
try
|
||||
{
|
||||
using var context = _contextFactory.CreateDbContext();
|
||||
var players = await context.Players
|
||||
.Where(p => p.ConnectionId == Context.ConnectionId)
|
||||
.ToListAsync();
|
||||
foreach (var player in players)
|
||||
player.ConnectionId = null;
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to clear connection id on disconnect");
|
||||
}
|
||||
|
||||
await base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user