expense handling from gamelogic:

Problem: HandleRecordThrow ignorierte throwResult.Triggers aus ProcessThrow - speziell die ExpensePoint-Trigger vom Scheißspiel für Verlierer.

  Lösung:

  1. HandleRecordThrow (Zeile 358, 370, 427-436):
    - Neue Variable gameLogicTriggers
    - Zuweisung aus throwResult.Triggers
    - Aufruf von FireGameLogicTriggersAsync
  2. HandleExecuteGameAction (Zeile 457, 497-506):
    - Auf async geändert
    - Verarbeitet result.Triggers für Game-Actions wie "Passen"
  3. Neue Methode FireGameLogicTriggersAsync (Zeile 985-1058):
    - Parst TriggerType als ExpenseTriggerType
    - Ruft IGameEventService.RegisterExpensePointsAsync für ExpensePoint-Trigger (mit Multiplier)
    - Ruft IGameEventService.RegisterEliminatedAsync für Eliminated-Trigger
    - Dispatcht TriggerExpensesCreatedAction für UI-Update
This commit is contained in:
beo3000 2025-12-28 17:41:50 +01:00
parent b8bd1b0939
commit 83b0e6a91b
1 changed files with 103 additions and 6 deletions

View File

@ -355,6 +355,7 @@ public class GameEffects
bool isGameOver = false; bool isGameOver = false;
Guid? winnerId = null; Guid? winnerId = null;
object? updatedGameModel = state.GameModel; object? updatedGameModel = state.GameModel;
IReadOnlyList<TriggerEvent> gameLogicTriggers = [];
// Call ProcessThrow if game logic service is available // Call ProcessThrow if game logic service is available
if (gameLogicService != null && state.GameModel != null) if (gameLogicService != null && state.GameModel != null)
@ -366,6 +367,7 @@ public class GameEffects
shouldRotatePlayer = throwResult.ShouldRotatePlayer; shouldRotatePlayer = throwResult.ShouldRotatePlayer;
isGameOver = throwResult.IsGameOver; isGameOver = throwResult.IsGameOver;
winnerId = throwResult.WinnerId; winnerId = throwResult.WinnerId;
gameLogicTriggers = throwResult.Triggers;
// Check for overrides from game logic // Check for overrides from game logic
if (throwResult.Overrides != null) if (throwResult.Overrides != null)
@ -422,6 +424,17 @@ public class GameEffects
// Fire expense triggers for special throw events // Fire expense triggers for special throw events
await FireThrowTriggersAsync(state, currentPlayerId.Value, action, afterThrowState, dispatcher); await FireThrowTriggersAsync(state, currentPlayerId.Value, action, afterThrowState, dispatcher);
// Fire game-specific triggers (e.g., ExpensePoint from Scheiss-Spiel)
if (gameLogicTriggers.Count > 0 && state.DayId.HasValue && state.ActiveGameId.HasValue)
{
await FireGameLogicTriggersAsync(
state.DayId.Value,
state.ActiveGameId.Value,
state.Participants.PlayerIds.ToList(),
gameLogicTriggers,
dispatcher);
}
// If game is over, skip save (user must confirm end via UI) // If game is over, skip save (user must confirm end via UI)
if (isGameOver) if (isGameOver)
{ {
@ -441,7 +454,7 @@ public class GameEffects
/// Handles ExecuteGameActionAction - executes game-specific action via IGameLogicService. /// Handles ExecuteGameActionAction - executes game-specific action via IGameLogicService.
/// </summary> /// </summary>
[EffectMethod] [EffectMethod]
public Task HandleExecuteGameAction(ExecuteGameActionAction action, IDispatcher dispatcher) public async Task HandleExecuteGameAction(ExecuteGameActionAction action, IDispatcher dispatcher)
{ {
var state = _gameState.Value; var state = _gameState.Value;
var gameTypeName = state.GameTypeName; var gameTypeName = state.GameTypeName;
@ -451,13 +464,13 @@ public class GameEffects
if (state.IsGameOver) if (state.IsGameOver)
{ {
dispatcher.Dispatch(new ExecuteGameActionFailureAction("Cannot execute action: game is already over")); dispatcher.Dispatch(new ExecuteGameActionFailureAction("Cannot execute action: game is already over"));
return Task.CompletedTask; return;
} }
if (string.IsNullOrEmpty(gameTypeName) || !currentPlayerId.HasValue || state.GameModel == null) if (string.IsNullOrEmpty(gameTypeName) || !currentPlayerId.HasValue || state.GameModel == null)
{ {
dispatcher.Dispatch(new ExecuteGameActionFailureAction("Cannot execute action: missing game state")); dispatcher.Dispatch(new ExecuteGameActionFailureAction("Cannot execute action: missing game state"));
return Task.CompletedTask; return;
} }
try try
@ -481,10 +494,21 @@ public class GameEffects
"Game action executed: {ActionId}, rotate={Rotate}, gameOver={GameOver}", "Game action executed: {ActionId}, rotate={Rotate}, gameOver={GameOver}",
action.ActionId, result.ShouldRotatePlayer, result.IsGameOver); action.ActionId, result.ShouldRotatePlayer, result.IsGameOver);
// Fire game-specific triggers from action result
if (result.Triggers.Count > 0 && state.DayId.HasValue && state.ActiveGameId.HasValue)
{
await FireGameLogicTriggersAsync(
state.DayId.Value,
state.ActiveGameId.Value,
state.Participants.PlayerIds.ToList(),
result.Triggers,
dispatcher);
}
// If game is over, skip save (user must confirm end via UI) // If game is over, skip save (user must confirm end via UI)
if (result.IsGameOver) if (result.IsGameOver)
{ {
return Task.CompletedTask; return;
} }
// Debounce save operations // Debounce save operations
@ -505,8 +529,6 @@ public class GameEffects
_logger.LogError(ex, "Error executing game action: {ActionId}", action.ActionId); _logger.LogError(ex, "Error executing game action: {ActionId}", action.ActionId);
dispatcher.Dispatch(new ExecuteGameActionFailureAction($"Error: {ex.Message}")); dispatcher.Dispatch(new ExecuteGameActionFailureAction($"Error: {ex.Message}"));
} }
return Task.CompletedTask;
} }
private static ThrowPanelSnapshot CreateThrowPanelSnapshot(ThrowPanelState state) private static ThrowPanelSnapshot CreateThrowPanelSnapshot(ThrowPanelState state)
@ -968,4 +990,79 @@ public class GameEffects
// Don't fail the throw recording if trigger fails // Don't fail the throw recording if trigger fails
} }
} }
/// <summary>
/// Fires game-specific triggers returned by IGameLogicService.ProcessThrow.
/// These are separate from standard throw triggers (Gutter, Strike, etc.).
/// </summary>
private async Task FireGameLogicTriggersAsync(
Guid dayId,
Guid gameId,
List<Guid> allParticipantIds,
IReadOnlyList<TriggerEvent> triggers,
IDispatcher dispatcher)
{
var allCreatedExpenses = new List<PersonExpenseDto>();
try
{
foreach (var trigger in triggers)
{
// Parse trigger type
if (!Enum.TryParse<ExpenseTriggerType>(trigger.TriggerType, out var triggerType))
{
_logger.LogWarning("Unknown trigger type: {TriggerType}", trigger.TriggerType);
continue;
}
List<PersonExpenseDto> expenses = [];
switch (triggerType)
{
case ExpenseTriggerType.ExpensePoint:
expenses = await _gameEventService.RegisterExpensePointsAsync(
trigger.PersonId,
dayId,
gameId,
allParticipantIds,
trigger.Multiplier);
break;
case ExpenseTriggerType.Eliminated:
expenses = await _gameEventService.RegisterEliminatedAsync(
trigger.PersonId,
dayId,
gameId,
allParticipantIds);
break;
default:
_logger.LogDebug(
"Trigger type {TriggerType} not handled in game logic triggers",
triggerType);
break;
}
allCreatedExpenses.AddRange(expenses);
if (expenses.Count > 0)
{
_logger.LogInformation(
"Game logic trigger fired: Type={TriggerType}, Person={PersonId}, Multiplier={Multiplier}, Expenses={Count}",
triggerType, trigger.PersonId, trigger.Multiplier, expenses.Count);
}
}
// Dispatch action to update UI with all created expenses
if (allCreatedExpenses.Count > 0)
{
dispatcher.Dispatch(new DayState.TriggerExpensesCreatedAction(allCreatedExpenses));
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error firing game logic triggers");
// Don't fail the throw recording if trigger fails
}
}
} }