diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp index c662f0cf230..82656fd4c3e 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp @@ -1592,36 +1592,7 @@ winName.format("ScoreScreen.wnd:StaticTextScore%d", pos); if (TheNetwork->sawCRCMismatch()) { ++stats.desyncs[ptIdx]; - } - else if (gameEndedInDisconnect) - { - ++stats.discons[ptIdx]; - } - else if (TheVictoryConditions->isLocalAlliedDefeat() || !TheVictoryConditions->getEndFrame()) - { - ++stats.losses[ptIdx]; - } - else - { - ++stats.wins[ptIdx]; - } - - ScoreKeeper *s = player->getScoreKeeper(); - stats.buildingsBuilt[ptIdx] += s->getTotalBuildingsBuilt(); - stats.buildingsKilled[ptIdx] += s->getTotalBuildingsDestroyed(); - stats.buildingsLost[ptIdx] += s->getTotalBuildingsLost(); - - if (TheGameSpyGame->isQMGame()) - { - stats.QMGames[ptIdx]++; - } - else - { - stats.customGames[ptIdx]++; - } - if (TheNetwork->sawCRCMismatch()) - { stats.lossesInARow = 0; stats.desyncsInARow++; stats.disconsInARow = 0; @@ -1630,6 +1601,8 @@ winName.format("ScoreScreen.wnd:StaticTextScore%d", pos); } else if (gameEndedInDisconnect) { + ++stats.discons[ptIdx]; + stats.lossesInARow = 0; stats.desyncsInARow = 0; stats.disconsInARow++; @@ -1638,6 +1611,8 @@ winName.format("ScoreScreen.wnd:StaticTextScore%d", pos); } else if (TheVictoryConditions->isLocalAlliedVictory()) { + ++stats.wins[ptIdx]; + stats.lossesInARow = 0; stats.desyncsInARow = 0; stats.disconsInARow = 0; @@ -1646,6 +1621,8 @@ winName.format("ScoreScreen.wnd:StaticTextScore%d", pos); } else { + ++stats.losses[ptIdx]; + stats.lossesInARow++; stats.desyncsInARow = 0; stats.disconsInARow = 0; @@ -1653,6 +1630,20 @@ winName.format("ScoreScreen.wnd:StaticTextScore%d", pos); stats.maxLossesInARow = max(stats.lossesInARow, stats.maxLossesInARow); } + ScoreKeeper *s = player->getScoreKeeper(); + stats.buildingsBuilt[ptIdx] += s->getTotalBuildingsBuilt(); + stats.buildingsKilled[ptIdx] += s->getTotalBuildingsDestroyed(); + stats.buildingsLost[ptIdx] += s->getTotalBuildingsLost(); + + if (TheGameSpyGame->isQMGame()) + { + stats.QMGames[ptIdx]++; + } + else + { + stats.customGames[ptIdx]++; + } + stats.earnings[ptIdx] += s->getTotalMoneyEarned(); stats.duration[ptIdx] += TheGameLogic->getFrame() / LOGICFRAMES_PER_SECOND / 60; // in minutes stats.games[ptIdx]++; diff --git a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/VictoryConditions.cpp b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/VictoryConditions.cpp index 7779e98180e..bfef969f06f 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/VictoryConditions.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/VictoryConditions.cpp @@ -92,10 +92,15 @@ class VictoryConditions : public VictoryConditionsInterface Bool amIObserver( void ) { return m_isObserver;} ///< Am I an observer?( need this for scripts ) virtual UnsignedInt getEndFrame( void ) { return m_endFrame; } ///< on which frame was the game effectively over? private: + Player* findFirstVictoriousPlayer(); ///< Find the first player that has achieved victory. + void markAllianceVictorious(Player* victoriousPlayer); ///< Mark the victorious player and his allies as victorious. + Bool multipleAlliancesExist(void); ///< Are there multiple alliances still alive? + Player* m_players[MAX_PLAYER_COUNT]; Int m_localSlotNum; UnsignedInt m_endFrame; Bool m_isDefeated[MAX_PLAYER_COUNT]; + Bool m_isVictorious[MAX_PLAYER_COUNT]; Bool m_localPlayerDefeated; ///< prevents condition from being signaled each frame Bool m_singleAllianceRemaining; ///< prevents condition from being signaled each frame Bool m_isObserver; @@ -127,6 +132,7 @@ void VictoryConditions::reset( void ) { m_players[i] = nullptr; m_isDefeated[i] = false; + m_isVictorious[i] = false; } m_localSlotNum = -1; @@ -139,42 +145,54 @@ void VictoryConditions::reset( void ) } //------------------------------------------------------------------------------------------------- -void VictoryConditions::update( void ) +Bool VictoryConditions::multipleAlliancesExist() { - if (!TheRecorder->isMultiplayer() || (m_localSlotNum < 0 && !m_isObserver)) - return; + Player* alive = nullptr; - // Check for a single winning alliance - if (!m_singleAllianceRemaining) + for (Int i = 0; i < MAX_PLAYER_COUNT; ++i) { - Bool multipleAlliances = false; - Player *alive = nullptr; - Player *player; - for (Int i=0; iisMultiplayer() || (m_localSlotNum < 0 && !m_isObserver)) + return; + + // Check for a single winning alliance + if (!m_singleAllianceRemaining) + { + if (!multipleAlliancesExist()) { m_singleAllianceRemaining = true; // don't check again m_endFrame = TheGameLogic->getFrame(); + + // TheSuperHackers @bugfix Stubbjax 11/02/2026 Cache victory status so that premature exits don't void the victory. + + Player* victoriousPlayer = findFirstVictoriousPlayer(); + + if (victoriousPlayer) + markAllianceVictorious(victoriousPlayer); } } @@ -233,19 +251,51 @@ void VictoryConditions::update( void ) } } +//------------------------------------------------------------------------------------------------- +Player* VictoryConditions::findFirstVictoriousPlayer() +{ + for (Int i = 0; i < MAX_PLAYER_COUNT; ++i) + { + Player* player = m_players[i]; + if (player && !hasSinglePlayerBeenDefeated(player)) + return player; + } + + return nullptr; +} + +//------------------------------------------------------------------------------------------------- +void VictoryConditions::markAllianceVictorious(Player* victoriousPlayer) +{ + // This marks the player and any allies as victorious, including defeated allies. + // This also ensures players retain their victorious status if their assets are destroyed after + // the victory conditions are met (e.g. when quitting the game prior to the victory screen). + + for (Int i = 0; i < MAX_PLAYER_COUNT; ++i) + { + Player* player = m_players[i]; + if (player == victoriousPlayer || (player && areAllies(player, victoriousPlayer))) + m_isVictorious[i] = true; + } +} + //------------------------------------------------------------------------------------------------- Bool VictoryConditions::hasAchievedVictory(Player *player) { if (!player) return false; - if (m_singleAllianceRemaining) + if (!m_singleAllianceRemaining) + return false; + + for (Int i = 0; i < MAX_PLAYER_COUNT; ++i) { - for (Int i=0; isawCRCMismatch()) { ++stats.desyncs[ptIdx]; - } - else if (gameEndedInDisconnect) - { - ++stats.discons[ptIdx]; - } - else if (TheVictoryConditions->isLocalAlliedDefeat() || !TheVictoryConditions->getEndFrame()) - { - ++stats.losses[ptIdx]; - } - else - { - ++stats.wins[ptIdx]; - } - - ScoreKeeper *s = player->getScoreKeeper(); - stats.buildingsBuilt[ptIdx] += s->getTotalBuildingsBuilt(); - stats.buildingsKilled[ptIdx] += s->getTotalBuildingsDestroyed(); - stats.buildingsLost[ptIdx] += s->getTotalBuildingsLost(); - - if (TheGameSpyGame->isQMGame()) - { - stats.QMGames[ptIdx]++; - } - else - { - stats.customGames[ptIdx]++; - } - if (TheNetwork->sawCRCMismatch()) - { stats.lossesInARow = 0; stats.desyncsInARow++; stats.disconsInARow = 0; @@ -1897,6 +1868,8 @@ winName.format("ScoreScreen.wnd:StaticTextScore%d", pos); } else if (gameEndedInDisconnect) { + ++stats.discons[ptIdx]; + stats.lossesInARow = 0; stats.desyncsInARow = 0; stats.disconsInARow++; @@ -1905,6 +1878,8 @@ winName.format("ScoreScreen.wnd:StaticTextScore%d", pos); } else if (TheVictoryConditions->isLocalAlliedVictory()) { + ++stats.wins[ptIdx]; + stats.lossesInARow = 0; stats.desyncsInARow = 0; stats.disconsInARow = 0; @@ -1913,6 +1888,8 @@ winName.format("ScoreScreen.wnd:StaticTextScore%d", pos); } else { + ++stats.losses[ptIdx]; + stats.lossesInARow++; stats.desyncsInARow = 0; stats.disconsInARow = 0; @@ -1920,6 +1897,20 @@ winName.format("ScoreScreen.wnd:StaticTextScore%d", pos); stats.maxLossesInARow = max(stats.lossesInARow, stats.maxLossesInARow); } + ScoreKeeper *s = player->getScoreKeeper(); + stats.buildingsBuilt[ptIdx] += s->getTotalBuildingsBuilt(); + stats.buildingsKilled[ptIdx] += s->getTotalBuildingsDestroyed(); + stats.buildingsLost[ptIdx] += s->getTotalBuildingsLost(); + + if (TheGameSpyGame->isQMGame()) + { + stats.QMGames[ptIdx]++; + } + else + { + stats.customGames[ptIdx]++; + } + stats.earnings[ptIdx] += s->getTotalMoneyEarned(); stats.duration[ptIdx] += TheGameLogic->getFrame() / LOGICFRAMES_PER_SECOND / 60; // in minutes stats.games[ptIdx]++; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/VictoryConditions.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/VictoryConditions.cpp index ae5a84d361e..2f7b27f6902 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/VictoryConditions.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/VictoryConditions.cpp @@ -93,10 +93,15 @@ class VictoryConditions : public VictoryConditionsInterface Bool amIObserver( void ) { return m_isObserver;} ///< Am I an observer?( need this for scripts ) virtual UnsignedInt getEndFrame( void ) { return m_endFrame; } ///< on which frame was the game effectively over? private: + Player* findFirstVictoriousPlayer(); ///< Find the first player that has achieved victory. + void markAllianceVictorious(Player* victoriousPlayer); ///< Mark the victorious player and his allies as victorious. + Bool multipleAlliancesExist(void); ///< Are there multiple alliances still alive? + Player* m_players[MAX_PLAYER_COUNT]; Int m_localSlotNum; UnsignedInt m_endFrame; Bool m_isDefeated[MAX_PLAYER_COUNT]; + Bool m_isVictorious[MAX_PLAYER_COUNT]; Bool m_localPlayerDefeated; ///< prevents condition from being signaled each frame Bool m_singleAllianceRemaining; ///< prevents condition from being signaled each frame Bool m_isObserver; @@ -128,6 +133,7 @@ void VictoryConditions::reset( void ) { m_players[i] = nullptr; m_isDefeated[i] = false; + m_isVictorious[i] = false; } m_localSlotNum = -1; @@ -140,42 +146,54 @@ void VictoryConditions::reset( void ) } //------------------------------------------------------------------------------------------------- -void VictoryConditions::update( void ) +Bool VictoryConditions::multipleAlliancesExist() { - if (!TheRecorder->isMultiplayer() || (m_localSlotNum < 0 && !m_isObserver)) - return; + Player* alive = nullptr; - // Check for a single winning alliance - if (!m_singleAllianceRemaining) + for (Int i = 0; i < MAX_PLAYER_COUNT; ++i) { - Bool multipleAlliances = false; - Player *alive = nullptr; - Player *player; - for (Int i=0; iisMultiplayer() || (m_localSlotNum < 0 && !m_isObserver)) + return; + + // Check for a single winning alliance + if (!m_singleAllianceRemaining) + { + if (!multipleAlliancesExist()) { m_singleAllianceRemaining = true; // don't check again m_endFrame = TheGameLogic->getFrame(); + + // TheSuperHackers @bugfix Stubbjax 11/02/2026 Cache victory status so that premature exits don't void the victory. + + Player* victoriousPlayer = findFirstVictoriousPlayer(); + + if (victoriousPlayer) + markAllianceVictorious(victoriousPlayer); } } @@ -235,19 +253,51 @@ void VictoryConditions::update( void ) } } +//------------------------------------------------------------------------------------------------- +Player* VictoryConditions::findFirstVictoriousPlayer() +{ + for (Int i = 0; i < MAX_PLAYER_COUNT; ++i) + { + Player* player = m_players[i]; + if (player && !hasSinglePlayerBeenDefeated(player)) + return player; + } + + return nullptr; +} + +//------------------------------------------------------------------------------------------------- +void VictoryConditions::markAllianceVictorious(Player* victoriousPlayer) +{ + // This marks the player and any allies as victorious, including defeated allies. + // This also ensures players retain their victorious status if their assets are destroyed after + // the victory conditions are met (e.g. when quitting the game prior to the victory screen). + + for (Int i = 0; i < MAX_PLAYER_COUNT; ++i) + { + Player* player = m_players[i]; + if (player == victoriousPlayer || (player && areAllies(player, victoriousPlayer))) + m_isVictorious[i] = true; + } +} + //------------------------------------------------------------------------------------------------- Bool VictoryConditions::hasAchievedVictory(Player *player) { if (!player) return false; - if (m_singleAllianceRemaining) + if (!m_singleAllianceRemaining) + return false; + + for (Int i = 0; i < MAX_PLAYER_COUNT; ++i) { - for (Int i=0; i