mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 21:55:42 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a780bf2cb |
@@ -0,0 +1,267 @@
|
||||
# Allied Victory: Battle Resolution Flow
|
||||
|
||||
How allied victories work end-to-end, from Shardok tactical resolution through Eagle strategic
|
||||
processing to player-facing aftermath decisions.
|
||||
|
||||
## 1. Battle Setup (Eagle -> Shardok)
|
||||
|
||||
When Eagle sends a battle to Shardok via `RequestBattlesAction`, each player gets specific victory
|
||||
conditions:
|
||||
|
||||
| Player | Victory Conditions |
|
||||
|-------------|-------------------------------------------------------|
|
||||
| Attacker(s) | `HoldsCriticalTiles`, `LastPlayerStanding` |
|
||||
| Defender | `LastPlayerStanding`, `WinAfterMaxRounds` |
|
||||
|
||||
Attackers do **not** receive `LastAllianceStanding`. This is set in `RequestBattlesAction.shardokPlayers`.
|
||||
|
||||
Alliance information is sent separately in `PlayerSetupInfo.allies`. Eagle reads each player's
|
||||
`factionRelationships` and includes all non-HOSTILE factions as allies
|
||||
(`ShardokInterfaceGrpcClient.scala:75-80`).
|
||||
|
||||
## 2. Victory Condition Evaluation (Shardok)
|
||||
|
||||
Victory conditions are checked in `UpdateGameStatusAction.cpp` in priority order:
|
||||
|
||||
### Check 1: LastPlayerStanding (any time)
|
||||
|
||||
If exactly **one** player has surviving units and has `LastPlayerStanding`:
|
||||
- That player is the sole winner.
|
||||
- This applies to solo victories only (1 survivor).
|
||||
|
||||
### Check 2: LastAllianceStanding (any time)
|
||||
|
||||
If **multiple** players survive, check if they form a winning alliance:
|
||||
- ALL survivors must have `LastAllianceStanding` condition
|
||||
- ALL survivors must be **mutually** allied (every pair checks both directions)
|
||||
- If yes: all survivors are winners
|
||||
|
||||
**In practice, this never triggers for AssaultProvince battles** because attackers don't receive
|
||||
`LastAllianceStanding`. It exists for other battle types.
|
||||
|
||||
### Check 3: WinAfterMaxRounds (end-of-round only)
|
||||
|
||||
If the round counter exceeds `max_rounds`:
|
||||
- Find the player with `WinAfterMaxRounds` (always the defender)
|
||||
- That player wins, even if they have no surviving units
|
||||
- If no player has this condition: the Shardok proto produces a Draw, but Eagle rejects draws
|
||||
(the `internalRequire` in `RequestBattlesAction` guarantees exactly 1 player has this condition)
|
||||
|
||||
### Check 4: HoldsCriticalTiles (end-of-round only)
|
||||
|
||||
**Single-player castle control**: If one player with `HoldsCriticalTiles` occupies ALL critical
|
||||
tiles with hero-bearing units, that player wins alone.
|
||||
|
||||
**Allied castle control**: If multiple players collectively occupy all critical tiles with
|
||||
hero-bearing units, AND:
|
||||
- All occupants have `HoldsCriticalTiles`
|
||||
- All occupants are mutually allied
|
||||
|
||||
Then all occupants are winners together.
|
||||
|
||||
## 3. EndGameCondition Assignment Per Player (Shardok -> Eagle)
|
||||
|
||||
After determining winners, `EagleInterfaceGrpcServer.cpp:381-409` assigns each player an
|
||||
`EndGameCondition`:
|
||||
|
||||
| Condition | Criterion |
|
||||
|-----------|-----------|
|
||||
| `Victory(type)` | Player is in `winning_shardok_ids` |
|
||||
| `AllyVictory(type)` | Player is NOT in winners, but has an ally who IS |
|
||||
| `Loss(type)` | Neither of the above |
|
||||
|
||||
The `type` is the VictoryCondition that caused the game to end (e.g., `HoldsCriticalTiles`).
|
||||
|
||||
### When does AllyVictory actually occur?
|
||||
|
||||
Since attackers don't have `LastAllianceStanding`, the only scenarios producing `AllyVictory` in
|
||||
standard AssaultProvince battles are:
|
||||
|
||||
1. **One attacker holds all castles alone** -> that attacker gets `Victory(HoldsCriticalTiles)`,
|
||||
their allied co-attacker gets `AllyVictory(HoldsCriticalTiles)`.
|
||||
|
||||
2. **Defender wins via WinAfterMaxRounds** -> defender gets `Victory(WinAfterMaxRounds)`. If an
|
||||
attacker is allied with the defender (unusual, see note below), they'd get
|
||||
`AllyVictory(WinAfterMaxRounds)`.
|
||||
|
||||
When allied attackers **jointly** hold all castles, both get `Victory(HoldsCriticalTiles)` -- not
|
||||
AllyVictory -- because both are in `winning_shardok_ids`.
|
||||
|
||||
## 4. Eagle Processes Battle Results
|
||||
|
||||
`ResolveBattleAction.scala` receives `BattleResolution` containing each player's
|
||||
`EndGameCondition` and resolved units.
|
||||
|
||||
### Winner/Loser Partition
|
||||
|
||||
Players are split by `isWinOrAllyVictory` (line 383-386):
|
||||
- `winningResolvedPlayers`: Victory OR AllyVictory
|
||||
- `losingResolvedPlayers`: Loss
|
||||
|
||||
### The `unitReturned` Function (line 765-788)
|
||||
|
||||
Determines which units go home vs stay at the battle province:
|
||||
|
||||
| Unit Status | Returned? | Notes |
|
||||
|---------------|-----------|-------|
|
||||
| `Fled` | Always | Sent to their army's flee province |
|
||||
| `NeverEntered`| Conditional | Only if: attacker AND has flee province AND did NOT win (`!isWinOrAllyVictory`) |
|
||||
| `Captured` | Never | |
|
||||
| `Normal` | Never | |
|
||||
| `Retreated` | Never | |
|
||||
| `Outlawed` | Never | Becomes unaffiliated hero |
|
||||
|
||||
Key point: `NeverEntered` units from **winning** factions (including AllyVictory) stay at the
|
||||
battle province rather than being sent home. This is important for bounced co-attackers who are
|
||||
allied with the actual winner.
|
||||
|
||||
### Withdrawn/Fled Unit Routing (line 690-706)
|
||||
|
||||
For units that ARE returned, `armyFromResolvedArmy` builds a returning army:
|
||||
- **Victory players**: `fleeProvinceId = None` (no special routing)
|
||||
- **AllyVictory players**: `fleeProvinceId = Some(destinationProvinceId)` (routed to their
|
||||
original march destination)
|
||||
|
||||
If a returning army has `fleeProvinceId`, it's sent there as an incoming army. If not, the army
|
||||
is "shattered" (units become outlaws in the battle province).
|
||||
|
||||
### Battle Resolution Branching (line 397-552)
|
||||
|
||||
Three branches based on who won:
|
||||
|
||||
#### Branch A: Defender Won
|
||||
|
||||
Triggered when: `winningShardokPlayers.exists(_.isDefender)`
|
||||
|
||||
- Executes `ProvinceHeldAction`
|
||||
- All non-defender, non-returned, non-outlawed units are **captured**
|
||||
- Province stays with the defending faction
|
||||
|
||||
#### Branch B: Single Attacker Won
|
||||
|
||||
Triggered when: exactly 1 attacking winner
|
||||
|
||||
- Executes `ProvinceConqueredAction` immediately
|
||||
- Winning attacker's non-fled units occupy the province
|
||||
- Losing defenders' non-fled units are captured
|
||||
- Province transfers to the attacker
|
||||
|
||||
#### Branch C: Multiple Allied Attackers Won
|
||||
|
||||
Triggered when: 2+ attacking winners
|
||||
|
||||
- Executes `MultiVictorBattleSetupAction`
|
||||
- Creates `PendingConquestInfo` with aftermath claimants
|
||||
- Province enters the **Battle Aftermath** phase
|
||||
|
||||
## 5. Battle Aftermath (Multi-Victor Only)
|
||||
|
||||
When multiple allied attackers win, the province enters an aftermath decision phase where players
|
||||
decide who keeps it.
|
||||
|
||||
### Claimant Setup
|
||||
|
||||
Each attacking winner becomes an `AftermathClaimant` with:
|
||||
- `units`: their non-fled, non-outlawed units still at the battle province
|
||||
- `armySize`: total troop count of those units
|
||||
- `broughtGold` / `broughtFood`: supplies their armies carried
|
||||
|
||||
Claimants are **sorted by army size descending** (largest army decides first).
|
||||
|
||||
### Decision Phase
|
||||
|
||||
Claimants are presented with a choice **one at a time**, in order:
|
||||
|
||||
**If no one has chosen "Keep" yet:**
|
||||
- **Keep Province**: claim the province for your faction
|
||||
- **Withdraw To**: pick an adjacent province to march your army to, optionally carrying up to
|
||||
the gold/food you brought
|
||||
|
||||
**If someone already chose "Keep":**
|
||||
- **Withdraw To** is the only option (can't have two keepers)
|
||||
|
||||
**Last undecided claimant:**
|
||||
- If no one has chosen "Keep" yet, the last claimant **automatically keeps** (no command shown,
|
||||
resolved by `AutoResolveBattleAftermathAction`)
|
||||
- This guarantees someone always claims the province
|
||||
|
||||
### Finalization
|
||||
|
||||
Once all claimants have decided (`FinalizeAftermathAction`):
|
||||
|
||||
1. The keeper's units run through `ProvinceConqueredAction` -- province transfers to their faction
|
||||
2. Withdrawing claimants' units become incoming armies to their chosen adjacent province,
|
||||
arriving next round, carrying the specified supplies
|
||||
3. `PendingConquestInfo` is cleared from the province
|
||||
|
||||
## 6. Summary: Who Gets What
|
||||
|
||||
### Two allied attackers jointly hold all castles
|
||||
- Shardok: Both get `Victory(HoldsCriticalTiles)` (both in `winning_shardok_ids`)
|
||||
- Eagle: Both are attacking winners -> MultiVictorBattleSetupAction
|
||||
- Players: Largest army picks first: Keep or Withdraw. Last player auto-keeps if no one chose Keep.
|
||||
|
||||
### One attacker holds all castles, allied attacker doesn't
|
||||
- Shardok: Castle-holder gets `Victory(HoldsCriticalTiles)`, ally gets
|
||||
`AllyVictory(HoldsCriticalTiles)`
|
||||
- Eagle: Both are attacking winners -> MultiVictorBattleSetupAction
|
||||
- Players: Same aftermath decision as above.
|
||||
|
||||
### Allied attackers eliminate all defenders (LastPlayerStanding impossible with 2 survivors)
|
||||
- Shardok: LastAllianceStanding check fails (attackers don't have this condition).
|
||||
Game continues until max rounds -> defender wins via `WinAfterMaxRounds` (even if dead).
|
||||
- Eagle: Defender won -> ProvinceHeldAction. Province stays with defender.
|
||||
- Players: The attack effectively fails. Attacker units are captured.
|
||||
|
||||
### Single attacker wins (any condition)
|
||||
- No alliance considerations. ProvinceConqueredAction immediately.
|
||||
|
||||
### Defender wins (WinAfterMaxRounds or LastPlayerStanding)
|
||||
- ProvinceHeldAction. All non-returned attacker units are captured.
|
||||
|
||||
## 7. Potential Issues
|
||||
|
||||
### Allied attackers can't win by elimination alone
|
||||
|
||||
Since attackers receive `LastPlayerStanding` but not `LastAllianceStanding`, two allied attackers
|
||||
who destroy the entire defending army **cannot win**. The game continues until max rounds, at which
|
||||
point the (dead) defender wins via `WinAfterMaxRounds`. The attackers must capture all critical
|
||||
tiles to actually win.
|
||||
|
||||
This is probably intentional -- it forces allied attackers to achieve a concrete tactical objective
|
||||
(castle control) rather than simply killing troops. But it means that on maps with castles, even
|
||||
total elimination of the enemy doesn't count as victory for allied attackers.
|
||||
|
||||
### Defender-won branch doesn't filter allied winners
|
||||
|
||||
In `ResolveBattleAction`, the "Defender won" branch (line 403) computes `capturedUnits` as all
|
||||
resolved players except the winning defender. If an attacker had `AllyVictory` (meaning they're
|
||||
allied with the defender), their non-returned units would still be "captured". This is technically
|
||||
a bug, but it cannot occur in practice because Eagle prevents attacking an allied faction's
|
||||
province.
|
||||
|
||||
### AllyVictory fled-unit routing
|
||||
|
||||
When an AllyVictory player has fled units, `armyFromResolvedArmy` assigns
|
||||
`fleeProvinceId = Some(destinationProvinceId)`. This routes the fled army to the original march
|
||||
destination. For Victory players, `fleeProvinceId = None`, which causes the returning army to be
|
||||
"shattered" (units become outlaws). This asymmetry between Victory and AllyVictory in fled-unit
|
||||
routing is potentially surprising -- a solo victor's fled units are shattered, but an
|
||||
ally-victor's fled units arrive safely at their destination.
|
||||
|
||||
## Key File References
|
||||
|
||||
| Component | File |
|
||||
|-----------|------|
|
||||
| Victory condition checking | `src/main/cpp/net/eagle0/shardok/library/actions/UpdateGameStatusAction.cpp` |
|
||||
| Per-player EndGameCondition | `src/main/cpp/net/eagle0/shardok/server/EagleInterfaceGrpcServer.cpp:381-409` |
|
||||
| Battle setup (victory conditions) | `src/main/scala/.../library/actions/impl/action/RequestBattlesAction.scala` |
|
||||
| Alliance communication | `src/main/scala/.../shardok_interface/ShardokInterfaceGrpcClient.scala:75-80` |
|
||||
| Battle result processing | `src/main/scala/.../library/actions/impl/action/ResolveBattleAction.scala` |
|
||||
| Unit return logic | `ResolveBattleAction.scala:765-788` (`unitReturned`) |
|
||||
| Multi-victor setup | `src/main/scala/.../library/actions/impl/action/MultiVictorBattleSetupAction.scala` |
|
||||
| Aftermath availability | `src/main/scala/.../library/actions/availability/AvailableBattleAftermathDecisionCommandFactory.scala` |
|
||||
| Aftermath command | `src/main/scala/.../library/actions/impl/command/BattleAftermathDecisionCommand.scala` |
|
||||
| Auto-resolve last claimant | `src/main/scala/.../library/actions/impl/action/AutoResolveBattleAftermathAction.scala` |
|
||||
| Finalize aftermath | `src/main/scala/.../library/actions/impl/action/FinalizeAftermathAction.scala` |
|
||||
| EndGameCondition enum | `src/main/scala/.../model/state/shardok_battle/EndGameCondition.scala` |
|
||||
Reference in New Issue
Block a user