bugfix(victory): Save victory status to prevent early exits from resulting in defeat in network matches#2292
Conversation
|
| Filename | Overview |
|---|---|
| Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/VictoryConditions.cpp | Added m_isVictorious array and markAllianceVictorious() to save victory status early; refactored alliance checking into multipleAlliancesExist(); critical edge case when all players are defeated simultaneously |
| GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/VictoryConditions.cpp | Identical changes to Generals version; same critical edge case exists |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[VictoryConditions::update called] --> B{Is multiplayer?}
B -->|No| Z[Return]
B -->|Yes| C{m_singleAllianceRemaining?}
C -->|Yes| D[Check player eliminations]
C -->|No| E[Call multipleAlliancesExist]
E --> F{Multiple alliances?}
F -->|Yes| D
F -->|No| G[Set m_singleAllianceRemaining = true]
G --> H[Set m_endFrame]
H --> I[Call findFirstUndefeatedPlayer]
I --> J{Player found?}
J -->|Yes| K[Call markAllianceVictorious]
J -->|No| L[BUG: No one marked victorious]
K --> D
L --> D
D --> M[Loop through all players]
M --> N{Player defeated?}
N -->|Yes| O[Set m_isDefeated and notify]
N -->|No| P[Continue]
O --> P
Last reviewed commit: 36f8103
GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/VictoryConditions.cpp
Outdated
Show resolved
Hide resolved
Additional Comments (2)
The old logic returned true for any player when Add fallback logic: Bool VictoryConditions::hasBeenDefeated(Player *player)
{
if (!player)
return false;
if (!m_singleAllianceRemaining)
return false;
for (Int i = 0; i < MAX_PLAYER_COUNT; ++i)
{
if (player == m_players[i])
{
if (m_isDefeated[i])
return true;
if (!m_isVictorious[i])
return true;
return false;
}
}
return false;
}Prompt To Fix With AIThis is a comment left during a code review.
Path: Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/VictoryConditions.cpp
Line: 287:302
Comment:
logic issue: losing players who quit early without asset destruction won't be marked as defeated
The old logic returned true for any player when `m_singleAllianceRemaining && !hasAchievedVictory(player)`. The new logic only checks `m_isDefeated[i]`, which is only set when `hasSinglePlayerBeenDefeated()` returns true (assets destroyed). If a losing player quits early and their assets are maintained by allies, they won't be marked defeated.
Add fallback logic:
```cpp
Bool VictoryConditions::hasBeenDefeated(Player *player)
{
if (!player)
return false;
if (!m_singleAllianceRemaining)
return false;
for (Int i = 0; i < MAX_PLAYER_COUNT; ++i)
{
if (player == m_players[i])
{
if (m_isDefeated[i])
return true;
if (!m_isVictorious[i])
return true;
return false;
}
}
return false;
}
```
How can I resolve this? If you propose a fix, please make it concise.
The old logic returned true for any player when Add fallback logic: Bool VictoryConditions::hasBeenDefeated(Player *player)
{
if (!player)
return false;
if (!m_singleAllianceRemaining)
return false;
for (Int i = 0; i < MAX_PLAYER_COUNT; ++i)
{
if (player == m_players[i])
{
if (m_isDefeated[i])
return true;
if (!m_isVictorious[i])
return true;
return false;
}
}
return false;
}Prompt To Fix With AIThis is a comment left during a code review.
Path: GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/VictoryConditions.cpp
Line: 289:304
Comment:
logic issue: losing players who quit early without asset destruction won't be marked as defeated
The old logic returned true for any player when `m_singleAllianceRemaining && !hasAchievedVictory(player)`. The new logic only checks `m_isDefeated[i]`, which is only set when `hasSinglePlayerBeenDefeated()` returns true (assets destroyed). If a losing player quits early and their assets are maintained by allies, they won't be marked defeated.
Add fallback logic:
```cpp
Bool VictoryConditions::hasBeenDefeated(Player *player)
{
if (!player)
return false;
if (!m_singleAllianceRemaining)
return false;
for (Int i = 0; i < MAX_PLAYER_COUNT; ++i)
{
if (player == m_players[i])
{
if (m_isDefeated[i])
return true;
if (!m_isVictorious[i])
return true;
return false;
}
}
return false;
}
```
How can I resolve this? If you propose a fix, please make it concise. |
…defeat in network matches
e688101 to
87a75a4
Compare
Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/VictoryConditions.cpp
Show resolved
Hide resolved
GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/VictoryConditions.cpp
Outdated
Show resolved
Hide resolved
GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/VictoryConditions.cpp
Outdated
Show resolved
Hide resolved
GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/VictoryConditions.cpp
Outdated
Show resolved
Hide resolved
GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/VictoryConditions.cpp
Outdated
Show resolved
Hide resolved
GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/VictoryConditions.cpp
Outdated
Show resolved
Hide resolved
Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/VictoryConditions.cpp
Outdated
Show resolved
Hide resolved
| } | ||
|
|
||
| //------------------------------------------------------------------------------------------------- | ||
| Player* VictoryConditions::findFirstVictoriousPlayer() |
There was a problem hiding this comment.
Conceptually, bool multipleAlliancesExist + Player* findFirstNotDefeatedPlayer + markAllianceVictorious(Player*) is a bit cumbersome logic.
A better logic would be Player* getSingleAllianceSurvived + markAllianceVictorious(Player*)
.
There was a problem hiding this comment.
Checking for a single surviving alliance does not conceptually work either - we still want the condition to be satisfied if there are no alliances remaining.
GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/VictoryConditions.cpp
Outdated
Show resolved
Hide resolved
db4f4c7 to
06feb14
Compare
GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/VictoryConditions.cpp
Outdated
Show resolved
Hide resolved
06feb14 to
36f8103
Compare
Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/VictoryConditions.cpp
Show resolved
Hide resolved
GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/VictoryConditions.cpp
Show resolved
Hide resolved
…lting in defeat in network matches (TheSuperHackers#2292)
This change fixes an issue where a player's victory is not reliably recorded at the end of a network match.
If a player quits the game at any point before the score screen, and it results in the destruction of their assets (i.e. no allies exist), then a loss is recorded, regardless of the actual outcome. This is because the victory condition is processed at the time the game ends and the score screen is shown. To resolve this, the player's victory is now saved as soon as one team remains and the match is considered over, and so any subsequent defeat + asset destruction has no effect on the recorded outcome.
The change includes a minor refactor by abstracting some of the update logic into a
multipleAlliancesExistfunction, and consolidating some stat conditions. There are further refactors that can be done in follow-up changes, such as readingm_isDefeatedinstead of repeatedly callinghasSinglePlayerBeenDefeatedin multiple places, and splitting several blocks in the update method into functions.Tests
Below is a series of network match test results to verify the fix.
Test 1
Knife surrenders at the start of a 1v1 match. Fork then quits the game.
Test 2
Knife surrenders at the start of a 1v1v1 match. Fork then surrenders. Spoon then quits the game.
Test 3
Knife surrenders at the start of a 2v1 match. Fork then surrenders. Spoon then quits the game.
Test 4
Fork surrenders at the start of a 2v1 match. Spoon then quits the game. Knife then quits the game.
Test 5
Knife surrenders at the start of a 2v1 match. Spoon then surrenders. Fork then quits the game.