|
91 | 91 | #include "GameLogic/Module/CreateModule.h" |
92 | 92 | #include "GameLogic/Module/DestroyModule.h" |
93 | 93 | #include "GameLogic/Module/OpenContain.h" |
| 94 | +#include "GameLogic/Module/PhysicsUpdate.h" |
94 | 95 | #include "GameLogic/PartitionManager.h" |
95 | 96 | #include "GameLogic/PolygonTrigger.h" |
96 | 97 | #include "GameLogic/ScriptActions.h" |
@@ -3662,6 +3663,15 @@ void GameLogic::update( void ) |
3662 | 3663 | UnsignedInt now = getFrame(); |
3663 | 3664 | TheGameClient->setFrame(now); |
3664 | 3665 |
|
| 3666 | + // TheSuperHackers @info bobtista 20/01/2026 |
| 3667 | + // Restore RNG state immediately at start of first logic update after checkpoint load. |
| 3668 | + // This must happen before any scripts, terrain, or object updates run, since they may call random. |
| 3669 | + if ( m_pendingRngRestore ) |
| 3670 | + { |
| 3671 | + SetGameLogicRandomState( m_pendingRngState, m_pendingRngBaseSeed ); |
| 3672 | + m_pendingRngRestore = FALSE; |
| 3673 | + } |
| 3674 | + |
3665 | 3675 | // update (execute) scripts |
3666 | 3676 | { |
3667 | 3677 | TheScriptEngine->UPDATE(); |
@@ -3692,21 +3702,10 @@ void GameLogic::update( void ) |
3692 | 3702 | // doesn't perfectly match what CRC calculation expects due to timing differences in the |
3693 | 3703 | // frame lifecycle. Skip multiple checks to allow state to stabilize. |
3694 | 3704 | // NOTE: Don't decrement here - it's decremented in handleCRCMessage() after validation skipped. |
| 3705 | + // NOTE: RNG state is now restored at start of update(), so no need to check here. |
3695 | 3706 | if ( m_skipCRCCheckCount > 0 ) |
3696 | 3707 | { |
3697 | | - // Still restore RNG and reset transient state if pending |
3698 | | - if ( m_pendingRngRestore ) |
3699 | | - { |
3700 | | - SetGameLogicRandomState( m_pendingRngState, m_pendingRngBaseSeed ); |
3701 | | - m_pendingRngRestore = FALSE; |
3702 | | - |
3703 | | - // TheSuperHackers @info bobtista 19/01/2026 |
3704 | | - // Reset transient AI state that affects CRC but isn't properly serialized. |
3705 | | - if ( TheAI ) |
3706 | | - { |
3707 | | - TheAI->resetTransientStateForCheckpoint(); |
3708 | | - } |
3709 | | - } |
| 3708 | + // Nothing to do here - skip CRC generation |
3710 | 3709 | } |
3711 | 3710 | else |
3712 | 3711 | { |
@@ -3865,6 +3864,36 @@ void GameLogic::update( void ) |
3865 | 3864 | m_frame++; |
3866 | 3865 | m_hasUpdated = TRUE; |
3867 | 3866 | } |
| 3867 | + |
| 3868 | + // TheSuperHackers @feature bobtista 20/01/2026 Auto-save checkpoint at specified frame during replay playback |
| 3869 | + // Save AFTER m_frame is incremented so the checkpoint correctly represents |
| 3870 | + // "ready to play frame N+1" when saved after frame N completes. |
| 3871 | + // We check for m_frame == saveAtFrame + 1 since m_frame was just incremented. |
| 3872 | + if (TheGlobalData->m_replaySaveAtFrame > 0 && m_frame == TheGlobalData->m_replaySaveAtFrame + 1) |
| 3873 | + { |
| 3874 | + AsciiString saveName = TheGlobalData->m_replaySaveTo; |
| 3875 | + if (saveName.isEmpty()) |
| 3876 | + { |
| 3877 | + saveName.format("checkpoint_%u.sav", TheGlobalData->m_replaySaveAtFrame); |
| 3878 | + } |
| 3879 | + UnicodeString desc; |
| 3880 | + desc.format(L"Replay checkpoint after frame %u", TheGlobalData->m_replaySaveAtFrame); |
| 3881 | + DEBUG_LOG(("Auto-saving checkpoint after frame %u (current frame %u) to %s", |
| 3882 | + TheGlobalData->m_replaySaveAtFrame, m_frame, saveName.str())); |
| 3883 | + SaveCode result = TheGameState->saveGame(saveName, desc, SAVE_FILE_TYPE_NORMAL, SNAPSHOT_SAVELOAD); |
| 3884 | + if (result != SC_OK) |
| 3885 | + { |
| 3886 | + DEBUG_LOG(("WARNING: Failed to save checkpoint, error code %d", result)); |
| 3887 | + } |
| 3888 | + else |
| 3889 | + { |
| 3890 | + DEBUG_LOG(("Checkpoint saved successfully, exiting replay simulation")); |
| 3891 | + // Exit the game after saving the checkpoint |
| 3892 | + TheGameEngine->setQuitting(TRUE); |
| 3893 | + } |
| 3894 | + // Clear the save frame so we don't try to save again |
| 3895 | + TheWritableGlobalData->m_replaySaveAtFrame = 0; |
| 3896 | + } |
3868 | 3897 | } |
3869 | 3898 |
|
3870 | 3899 | // ------------------------------------------------------------------------------------------------ |
@@ -4074,23 +4103,9 @@ UnsignedInt GameLogic::getCRC( Int mode, AsciiString deepCRCFileName ) |
4074 | 4103 |
|
4075 | 4104 | setFPMode(); |
4076 | 4105 |
|
4077 | | - // TheSuperHackers @info bobtista 19/01/2026 |
4078 | | - // Restore the RNG state right before CRC calculation if we just loaded from a checkpoint. |
4079 | | - // This ensures the RNG state is exactly what it was when the checkpoint was saved, |
4080 | | - // even if something called random between loadPostProcess() and now. |
4081 | | - if ( m_pendingRngRestore ) |
4082 | | - { |
4083 | | - SetGameLogicRandomState( m_pendingRngState, m_pendingRngBaseSeed ); |
4084 | | - m_pendingRngRestore = FALSE; |
4085 | | - |
4086 | | - // TheSuperHackers @info bobtista 19/01/2026 |
4087 | | - // Reset transient AI state that affects CRC but isn't properly serialized. |
4088 | | - // This must happen after checkpoint load but before CRC calculation. |
4089 | | - if ( TheAI ) |
4090 | | - { |
4091 | | - TheAI->resetTransientStateForCheckpoint(); |
4092 | | - } |
4093 | | - } |
| 4106 | + // TheSuperHackers @info bobtista 20/01/2026 |
| 4107 | + // RNG state is now restored at start of update() instead of here. |
| 4108 | + // This ensures it happens before any game logic runs, not just before CRC check. |
4094 | 4109 |
|
4095 | 4110 | LatchRestore<Bool> latch(inCRCGen, !isInGameLogicUpdate()); |
4096 | 4111 |
|
@@ -5315,8 +5330,8 @@ void GameLogic::loadPostProcess( void ) |
5315 | 5330 | // re-sort the priority queue all at once now that all modules are on it |
5316 | 5331 | remakeSleepyUpdate(); |
5317 | 5332 |
|
5318 | | - // TheSuperHackers @info bobtista 19/01/2026 |
5319 | | - // Note: RNG state restoration is deferred to getCRC() to ensure it happens right before |
5320 | | - // CRC calculation. This is necessary because code that runs between loadPostProcess() and |
5321 | | - // getCRC() may call random functions (e.g., updateHeadless(), ScriptEngine, TerrainLogic). |
| 5333 | + // TheSuperHackers @info bobtista 20/01/2026 |
| 5334 | + // Note: RNG state restoration is handled in update() at the start of the first logic update |
| 5335 | + // after checkpoint load. This ensures the RNG is restored before any scripts or game logic |
| 5336 | + // that might call random functions. The m_pendingRngRestore flag signals when restoration is needed. |
5322 | 5337 | } |
0 commit comments