mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 22:35:42 +00:00
Add ShardokServerStatus proto and server-side population (#6685)
Introduces a new ShardokServerStatus message on SingleShardokGameResultResponse so the client can render a tactical-side connection-status indicator (Your Turn / Waiting for AI / Waiting for Other Player / Processing). ShardokServerStatus is intentionally separate from ServerGameStatus so the strategic and tactical layers don't have to evolve in lockstep. GameController.computeShardokServerStatus picks the status from the receiving player's perspective by checking shardokPlayerAvailableCommands across factions and classifying them via aiClientFactionIds. The two humanClientsAfterPostingResults overloads now thread an aiFactionIds set through and populate the new field on every per-battle response. This is the server half of a two-PR split. The client wiring lands separately; until then the new field is emitted but unread, which is safe. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -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