diff --git a/LobbyActor.cs b/LobbyActor.cs index b8abbd2..99cb5c2 100644 --- a/LobbyActor.cs +++ b/LobbyActor.cs @@ -479,6 +479,10 @@ public class LobbyActor : IDisposable ProcessCastVote(player, m, ref ack); break; + case TaskStart m: + ProcessTaskStart(player, m, ref ack); + break; + case TaskComplete m: ProcessTaskComplete(player, m, ref ack); break; @@ -1083,6 +1087,59 @@ public class LobbyActor : IDisposable CheckWinConditions(); } + private void ProcessTaskStart(Player player, TaskStart message, ref Ack ack) + { + // Same gating as TaskComplete: must be Playing, no meeting, must be crew. + if (_phase != GamePhase.Playing || _currentMeeting != null) + { + ack.Success = false; + ack.Error = "Nelze začít task"; + return; + } + + if (player.State != PlayerState.Alive) + { + ack.Success = false; + ack.Error = "Mrtví hráči nemohou provádět tasky"; + return; + } + + if (player.Role == PlayerRole.Impostor) + { + ack.Success = false; + ack.Error = "Impostoři nemohou dělat tasky"; + return; + } + + // Validate the task belongs to this player and isn't already done. + var task = player.Tasks.FirstOrDefault(t => t.TaskId == message.TaskId); + if (task == null || player.CompletedTaskIds.Contains(message.TaskId)) + { + ack.Success = false; + ack.Error = "Neznámý nebo již dokončený task"; + return; + } + + // Distance check (same threshold as TryCompleteTask uses). + var distance = player.Position.DistanceTo(task.Location); + if (distance > _config.TaskStartDistanceM) + { + ack.Success = false; + ack.Error = $"Příliš daleko od tasku ({distance:F1}m)"; + return; + } + + player.CurrentTaskId = message.TaskId; + + var evt = CreateEvent("TaskStarted", player.ClientUuid, new TaskStartedPayload + { + ClientUuid = player.ClientUuid, + TaskId = message.TaskId + }); + + PersistAndBroadcast(evt); + } + private void ProcessTaskComplete(Player player, TaskComplete message, ref Ack ack) { if (_phase != GamePhase.Playing || _currentMeeting != null) @@ -1551,9 +1608,21 @@ public class LobbyActor : IDisposable station.IsRepaired = true; station.RepairingPlayerId = player.ClientUuid; station.RepairStartTime = DateTime.UtcNow; - + _logger.LogInformation("Stanice opravena: {Station} by {Player}", station.Name, player.DisplayName); - + + // Broadcast that this station's repair started so other clients can + // surface "1/2 stations active" coaching during multi-station + // (meltdown) sabotage. Without this, only the meltdown countdown + // is visible - players don't know whether anyone else is helping. + var startedEvt = CreateEvent("RepairStarted", player.ClientUuid, new RepairStartedPayload + { + SabotageId = _currentSabotage.SabotageId, + StationId = station.StationId, + PlayerId = player.ClientUuid + }); + PersistAndBroadcast(startedEvt); + // Zkontroluj zda je sabotáž kompletně opravena CheckSabotageRepairComplete(player.ClientUuid, station.StationId); } diff --git a/Protocol.cs b/Protocol.cs index 2793135..fbd5f27 100644 --- a/Protocol.cs +++ b/Protocol.cs @@ -733,6 +733,12 @@ public class PlayerEjectedPayload public PlayerRole Role { get; set; } } +public class TaskStartedPayload +{ + public required string ClientUuid { get; set; } + public required string TaskId { get; set; } +} + public class TaskCompletedPayload { public required string ClientUuid { get; set; } @@ -793,6 +799,13 @@ public class RepairStartedPayload public required string PlayerId { get; set; } } +public class RepairStartedPayload +{ + public required string SabotageId { get; set; } + public required string StationId { get; set; } + public required string PlayerId { get; set; } +} + public class RepairStoppedPayload { public required string SabotageId { get; set; }