mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-29 05:15:44 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77a071afcf |
@@ -18,6 +18,16 @@ namespace eagle {
|
||||
bool IsProcessingCommand { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shardok-side counterpart to <see cref="IGameStateProvider"/>. The two protos and providers
|
||||
/// are kept separate on purpose so the strategic and tactical layers can evolve independently.
|
||||
/// </summary>
|
||||
public interface IShardokGameStateProvider {
|
||||
/// <summary>Server-reported Shardok battle status. Null if no status received
|
||||
/// yet.</summary>
|
||||
ShardokServerStatus ShardokServerStatus { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple UI component to display connection status and reconnection countdown.
|
||||
/// Attach to a TextMeshProUGUI component to display status.
|
||||
@@ -28,6 +38,7 @@ namespace eagle {
|
||||
|
||||
private PersistentClientConnection _connection;
|
||||
private IGameStateProvider _gameStateProvider;
|
||||
private IShardokGameStateProvider _shardokGameStateProvider;
|
||||
private string _environmentName;
|
||||
|
||||
[Tooltip("Optional text component to display connected environment (prod/qa)")]
|
||||
@@ -68,6 +79,15 @@ namespace eagle {
|
||||
_gameStateProvider = provider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the Shardok game state provider for showing battle-specific status.
|
||||
/// Call this when entering a Shardok battle, pass null when leaving so the Eagle
|
||||
/// strategic status takes over again.
|
||||
/// </summary>
|
||||
public void SetShardokGameStateProvider(IShardokGameStateProvider provider) {
|
||||
_shardokGameStateProvider = provider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the environment name to display (e.g., "prod." or "qa.").
|
||||
/// Pass null to clear the indicator.
|
||||
@@ -131,6 +151,10 @@ namespace eagle {
|
||||
}
|
||||
|
||||
private string GetConnectedStatusText() {
|
||||
// While a Shardok battle is on screen, the tactical status takes precedence over the
|
||||
// strategic one.
|
||||
if (_shardokGameStateProvider != null) { return GetShardokConnectedStatusText(); }
|
||||
|
||||
// If no game state provider, just show "Connected"
|
||||
if (_gameStateProvider == null) { return "<color=green>●</color> Connected"; }
|
||||
|
||||
@@ -160,6 +184,21 @@ namespace eagle {
|
||||
};
|
||||
}
|
||||
|
||||
private string GetShardokConnectedStatusText() {
|
||||
var status = _shardokGameStateProvider.ShardokServerStatus;
|
||||
if (status == null) { return "<color=yellow>●</color> Waiting for server..."; }
|
||||
|
||||
return status.Status switch { ShardokServerStatus.Types.Status.YourTurn =>
|
||||
"<color=green>●</color> Your turn",
|
||||
ShardokServerStatus.Types.Status.WaitingForAi =>
|
||||
"<color=green>●</color> Waiting for AI",
|
||||
ShardokServerStatus.Types.Status.WaitingForHumanPlayer =>
|
||||
"<color=green>●</color> Waiting for other player",
|
||||
ShardokServerStatus.Types.Status.Processing =>
|
||||
"<color=green>●</color> Processing...",
|
||||
_ => "<color=green>●</color> Connected" };
|
||||
}
|
||||
|
||||
private string GetReconnectingText(DateTime? nextAttempt) {
|
||||
if (!nextAttempt.HasValue) { return "<color=yellow>●</color> Reconnecting..."; }
|
||||
|
||||
|
||||
@@ -846,6 +846,8 @@ namespace eagle {
|
||||
"GoToBattle: shardokContainer missing ShardokGameController component");
|
||||
}
|
||||
|
||||
PersistentUIManager.Instance?.connectionStatusUI?.SetShardokGameStateProvider(
|
||||
shardokModel);
|
||||
shardokController.SetUpGame(shardokModel);
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
@@ -869,6 +871,8 @@ namespace eagle {
|
||||
"GoToBattle: shardokContainer missing ShardokGameController component");
|
||||
}
|
||||
|
||||
PersistentUIManager.Instance?.connectionStatusUI?.SetShardokGameStateProvider(
|
||||
shardokModel);
|
||||
shardokController.SetUpGame(shardokModel);
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
@@ -441,6 +441,10 @@ namespace eagle {
|
||||
|
||||
shardokGameModel.HandleAvailableCommands(oneResponse.AvailableCommands);
|
||||
|
||||
if (oneResponse.ShardokServerStatus != null) {
|
||||
shardokGameModel.HandleServerStatus(oneResponse.ShardokServerStatus);
|
||||
}
|
||||
|
||||
// Clear resync flags after successfully receiving updates
|
||||
ClearShardokResyncFlag(oneResponse.ShardokGameId);
|
||||
shardokGameModel.SkipAutoPlacement = false;
|
||||
|
||||
+2
@@ -313,6 +313,8 @@ namespace Shardok {
|
||||
endTurnButton.interactable = false;
|
||||
var gameOver = Model.CanExitBattle;
|
||||
if (gameOver) {
|
||||
eagle.PersistentUIManager.Instance?.connectionStatusUI?.SetShardokGameStateProvider(
|
||||
null);
|
||||
Model = null;
|
||||
eagleCanvas.gameObject.SetActive(true);
|
||||
hexGrid.ClearCellLabels();
|
||||
|
||||
@@ -16,7 +16,7 @@ using ShardokGameId = System.String;
|
||||
using static Net.Eagle0.Eagle.Api.ShardokPlacementCommands.Types;
|
||||
using Hostility = Net.Eagle0.Common.Hostility;
|
||||
|
||||
public class ShardokGameModel {
|
||||
public class ShardokGameModel : eagle.IShardokGameStateProvider {
|
||||
private class HeroNameListener : IClientTextListener {
|
||||
private readonly string _textId;
|
||||
private readonly ShardokGameModel _model;
|
||||
@@ -119,6 +119,7 @@ public class ShardokGameModel {
|
||||
private List<PlayerTotals> PlayerTotals { get; set; }
|
||||
|
||||
public List<CommandDescriptor> AvailableCommands { get; private set; } = new();
|
||||
public Net.Eagle0.Eagle.Api.ShardokServerStatus ShardokServerStatus { get; private set; }
|
||||
public Action UpdateAction { get; set; }
|
||||
public bool BattleResetPending { get; set; }
|
||||
public bool CanExitBattle { get; set; }
|
||||
@@ -280,6 +281,11 @@ public class ShardokGameModel {
|
||||
UpdateAction?.Invoke();
|
||||
}
|
||||
|
||||
public void HandleServerStatus(Net.Eagle0.Eagle.Api.ShardokServerStatus status) {
|
||||
ShardokServerStatus = status;
|
||||
UpdateAction?.Invoke();
|
||||
}
|
||||
|
||||
public bool HasTargetedCommand(Coords start, Coords target, List<CommandType> possibleTypes) {
|
||||
if (InSetUp) {
|
||||
return false;
|
||||
|
||||
@@ -328,11 +328,29 @@ message ShardokActionResultResponse {
|
||||
repeated .net.eagle0.shardok.api.ActionResultView action_result_views = 2;
|
||||
int32 new_result_view_count = 3;
|
||||
.net.eagle0.shardok.api.AvailableCommands available_commands = 4;
|
||||
// Server-reported status for this Shardok battle, used by the connection-status UI to
|
||||
// show whether the player is up, waiting on AI, or waiting on another human.
|
||||
ShardokServerStatus shardok_server_status = 5;
|
||||
}
|
||||
|
||||
repeated SingleShardokGameResultResponse shardok_game_responses = 1;
|
||||
}
|
||||
|
||||
// Server-reported status for a single Shardok battle. Distinct from ServerGameStatus on purpose:
|
||||
// the strategic and tactical layers evolve independently and shouldn't share a status enum.
|
||||
message ShardokServerStatus {
|
||||
enum Status {
|
||||
UNKNOWN = 0;
|
||||
YOUR_TURN = 1; // The receiving player has commands available
|
||||
WAITING_FOR_AI = 2; // An AI-controlled faction has commands and we're awaiting its move
|
||||
WAITING_FOR_HUMAN_PLAYER = 3; // Another human faction has commands and we're awaiting their move
|
||||
PROCESSING = 4; // Server is between turns / resolving action
|
||||
}
|
||||
Status status = 1;
|
||||
// For WAITING_FOR_HUMAN_PLAYER and WAITING_FOR_AI: which faction(s) we're waiting for
|
||||
repeated int32 waiting_for_faction_ids = 2;
|
||||
}
|
||||
|
||||
// Signal to the client that a tutorial battle has reset (defender lost, battle restarting).
|
||||
// The client should clear all Shardok battle state for this game ID and prepare for fresh updates.
|
||||
message ShardokBattleResetResponse {
|
||||
|
||||
@@ -11,7 +11,7 @@ import net.eagle0.common.{RandomState, SeededRandom, SimpleTimedLogger}
|
||||
import net.eagle0.eagle.{FactionId, GameId, ProvinceId, ShardokGameId, UserId}
|
||||
import net.eagle0.eagle.ai.AIClient
|
||||
import net.eagle0.eagle.api.command.AvailableCommands
|
||||
import net.eagle0.eagle.api.eagle.ServerGameStatus
|
||||
import net.eagle0.eagle.api.eagle.{ServerGameStatus, ShardokServerStatus}
|
||||
import net.eagle0.eagle.api.eagle.ShardokActionResultResponse.SingleShardokGameResultResponse
|
||||
import net.eagle0.eagle.api.eagle.UpdateStreamRequest.StreamGameRequest.{ShardokViewStatus, StreamingTextStatus}
|
||||
import net.eagle0.eagle.api.selected_command.SelectedCommand
|
||||
@@ -94,6 +94,42 @@ object GameController {
|
||||
else None
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the Shardok-side status for one battle from the perspective of a single human player. Used by the
|
||||
* connection-status indicator while a battle is on screen so the player can see whether they're up, waiting on AI, or
|
||||
* waiting on another human.
|
||||
*/
|
||||
private[controller] def computeShardokServerStatus(
|
||||
shardokGameId: ShardokGameId,
|
||||
myFactionId: FactionId,
|
||||
fullHistory: FullGameHistory,
|
||||
allFactionIds: Iterable[FactionId],
|
||||
aiFactionIds: Set[FactionId]
|
||||
): ShardokServerStatus = {
|
||||
def hasCommands(fid: FactionId): Boolean =
|
||||
fullHistory.shardokPlayerAvailableCommands(shardokGameId, fid).exists(_.currentCommand.nonEmpty)
|
||||
|
||||
if hasCommands(myFactionId) then ShardokServerStatus(status = ShardokServerStatus.Status.YOUR_TURN)
|
||||
else {
|
||||
val factionsToAct = allFactionIds.filter(_ != myFactionId).filter(hasCommands).toVector
|
||||
val aiToAct = factionsToAct.filter(aiFactionIds.contains)
|
||||
val humansToAct = factionsToAct.filterNot(aiFactionIds.contains)
|
||||
|
||||
if aiToAct.nonEmpty then
|
||||
ShardokServerStatus(
|
||||
status = ShardokServerStatus.Status.WAITING_FOR_AI,
|
||||
waitingForFactionIds = aiToAct
|
||||
)
|
||||
else if humansToAct.nonEmpty then
|
||||
ShardokServerStatus(
|
||||
status = ShardokServerStatus.Status.WAITING_FOR_HUMAN_PLAYER,
|
||||
waitingForFactionIds = humansToAct
|
||||
)
|
||||
else ShardokServerStatus(status = ShardokServerStatus.Status.PROCESSING)
|
||||
}
|
||||
end if
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if all human factions are defeated (can't issue commands). A faction is defeated if:
|
||||
* - It no longer exists in the game state, OR
|
||||
@@ -223,6 +259,7 @@ object GameController {
|
||||
visibilityExtensions: Vector[ClientTextVisibilityExtensionT],
|
||||
engine: Engine,
|
||||
fullHistory: FullGameHistory,
|
||||
aiFactionIds: Set[FactionId],
|
||||
battleProgress: Map[ShardokGameId, BattleProgressTracker],
|
||||
lastRevealedRound: Int
|
||||
): Vector[HumanPlayerClientConnectionState] = {
|
||||
@@ -231,7 +268,8 @@ object GameController {
|
||||
clientTextStore,
|
||||
visibilityExtensions,
|
||||
engine,
|
||||
fullHistory
|
||||
fullHistory,
|
||||
aiFactionIds
|
||||
)
|
||||
// Send battle progress updates if there is any progress to report
|
||||
if battleProgress.isEmpty then afterResults
|
||||
@@ -260,7 +298,8 @@ object GameController {
|
||||
clientTextStore: ClientTextStore,
|
||||
visibilityExtensions: Vector[ClientTextVisibilityExtensionT],
|
||||
engine: Engine,
|
||||
fullHistory: FullGameHistory
|
||||
fullHistory: FullGameHistory,
|
||||
aiFactionIds: Set[FactionId]
|
||||
): Vector[HumanPlayerClientConnectionState] =
|
||||
humanClients.flatMap { client =>
|
||||
val clientTextUpdates = visibilityExtensions
|
||||
@@ -302,11 +341,19 @@ object GameController {
|
||||
shardokGameId = shardokGameId,
|
||||
factionId = client.factionId
|
||||
)
|
||||
val shardokStatus = GameController.computeShardokServerStatus(
|
||||
shardokGameId = shardokGameId,
|
||||
myFactionId = client.factionId,
|
||||
fullHistory = fullHistory,
|
||||
allFactionIds = engine.factionIds,
|
||||
aiFactionIds = aiFactionIds
|
||||
)
|
||||
SingleShardokGameResultResponse(
|
||||
shardokGameId = shardokGameId,
|
||||
actionResultViews = arvs,
|
||||
newResultViewCount = startingCount + arvs.length,
|
||||
availableCommands = acs
|
||||
availableCommands = acs,
|
||||
shardokServerStatus = Some(shardokStatus)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -670,6 +717,7 @@ final case class GameController(
|
||||
visibilityExtensions = Vector.empty,
|
||||
engine = newEngine,
|
||||
fullHistory = newHistory,
|
||||
aiFactionIds = aiClientFactionIds.toSet,
|
||||
battleProgress = newBattleProgress,
|
||||
lastRevealedRound = newLastRevealedRound
|
||||
)
|
||||
@@ -679,7 +727,8 @@ final case class GameController(
|
||||
clientTextStore = clientTextStore,
|
||||
visibilityExtensions = Vector.empty,
|
||||
engine = newEngine,
|
||||
fullHistory = newHistory
|
||||
fullHistory = newHistory,
|
||||
aiFactionIds = aiClientFactionIds.toSet
|
||||
)
|
||||
|
||||
GameControllerWithPostResults(
|
||||
@@ -874,6 +923,7 @@ final case class GameController(
|
||||
visibilityExtensions = clientVisibilityExtensions,
|
||||
engine = finalEngineAndResults.engine,
|
||||
fullHistory = newFullHistory,
|
||||
aiFactionIds = aiClientFactionIds.toSet,
|
||||
battleProgress = battleProgress,
|
||||
lastRevealedRound = lastRevealedRound
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user