Files
gtav-src/script/dev_ng/singleplayer/scripts/savegame/autosave_controller.sc
T
2025-09-29 00:52:08 +02:00

482 lines
16 KiB
Python
Executable File

//////////////////////////////////////////////////////////////////////////////////////////
// //
// SCRIPT NAME : autosave_controller.sc //
// AUTHOR : Kenneth Ross //
// DESCRIPTION : Allows for the game to be automatically saved whenever an //
// autosave request has been made (usually at the end of a //
// mission or ambient script). //
// //
//////////////////////////////////////////////////////////////////////////////////////////
//Compile out Title Update changes to header functions.
//Must be before includes.
//CONST_INT USE_TU_CHANGES 0 // Removed by Kenneth R.
USING "rage_builtins.sch"
USING "globals.sch"
USING "script_player.sch"
USING "savegame_public.sch"
USING "savegame_private.sch"
USING "cellphone_public.sch"
USING "flow_help_public.sch"
USING "respawn_location_private.sch"
CONST_INT AUTOSAVE_DELAY_TIME 1000
ENUM SCRIPT_STAGE_ENUM
SCRIPT_STAGE_INITIALISE = 0,
SCRIPT_STAGE_WAIT_FOR_REQUEST,
SCRIPT_STAGE_PERFORM_AUTOSAVE,
SCRIPT_STAGE_CLEANUP
ENDENUM
SCRIPT_STAGE_ENUM eScriptStage = SCRIPT_STAGE_INITIALISE
#IF IS_DEBUG_BUILD
BOOL g_d_bAutoSavePhoneOnScreenSpam = FALSE
#ENDIF
#IF IS_DEBUG_BUILD
BOOL bMakeAutoSaveRequest
PROC SETUP_AUTOSAVE_WIDGETS()
START_WIDGET_GROUP("Autosave Controller")
ADD_WIDGET_BOOL("bMakeAutoSaveRequest", bMakeAutoSaveRequest)
ADD_WIDGET_STRING("Autosave Flags - READ ONLY")
ADD_WIDGET_INT_READ_ONLY("iQueuedRequests", g_sAutosaveData.iQueuedRequests)
ADD_WIDGET_BOOL("bRequest", g_sAutosaveData.bRequest)
ADD_WIDGET_BOOL("bPerforming", g_sAutosaveData.bPerforming)
ADD_WIDGET_BOOL("bInProgress", g_sAutosaveData.bInProgress)
ADD_WIDGET_BOOL("bComplete", g_sAutosaveData.bComplete)
ADD_WIDGET_BOOL("bBeenOffMission", g_sAutosaveData.bBeenOffMission)
ADD_WIDGET_BOOL("bFirstAutosaveComplete", g_sAutosaveData.bFirstAutosaveComplete)
ADD_WIDGET_BOOL("bIgnoreMissionFlag", g_sAutosaveData.bIgnoreOnMissionFlag)
ADD_WIDGET_BOOL("Flush Autosaves", g_sAutosaveData.bFlushAutosaves)
ADD_WIDGET_BOOL("Block Autosaves", g_bDebugBlockAutosaves)
STOP_WIDGET_GROUP()
ENDPROC
PROC MAINTAIN_AUTOSAVE_WIDGETS()
IF bMakeAutoSaveRequest
MAKE_AUTOSAVE_REQUEST()
bMakeAutoSaveRequest = FALSE
ENDIF
ENDPROC
#ENDIF
/// PURPOSE: Cleans up any assets that were created
PROC CLEANUP_SCRIPT()
#IF IS_DEBUG_BUILD
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] AUTOSAVE CONTROLLER SCRIPT TERMINATING")
#ENDIF
TERMINATE_THIS_THREAD()
ENDPROC
/// PURPOSE: Loads the required shop assets and sets up items to browse
PROC INITIALISE_CONTROLLER()
eScriptStage = SCRIPT_STAGE_WAIT_FOR_REQUEST
ENDPROC
/// PURPOSE: Checks for an autosave request being made
PROC CHECK_AUTOSAVE_REQUEST()
// Scripts call MAKE_AUTOSAVE_REQUEST() in autosave_public.sch which will set up
// The appropriate flags in the autosave data struct. Therefore all we need to do in
// this controller is check that the bRequest flag has been set.
IF g_sAutosaveData.iQueuedRequests > 0
// Reset the appropriate autosave flags
g_sAutosaveData.bRequest = TRUE
g_sAutosaveData.bPerforming = FALSE
g_sAutosaveData.bInProgress = FALSE
g_sAutosaveData.bComplete = FALSE
g_sAutosaveData.bBeenOffMission = FALSE
g_sAutosaveData.iTriggerTime = GET_GAME_TIMER()
eScriptStage = SCRIPT_STAGE_PERFORM_AUTOSAVE
ELSE
#IF IS_DEBUG_BUILD
IF NOT g_bDebug_KeepAutoSaveControllerRunning
g_sAutosaveData.bFlushAutosaves = FALSE
eScriptStage = SCRIPT_STAGE_CLEANUP
ENDIF
#ENDIF
ENDIF
ENDPROC
/// PURPOSE: Performs an autosave if safe to do so
PROC PERFORM_AUTOSAVE()
// Reset the flags and exit stage if the process is complete
IF (g_sAutosaveData.bComplete)
g_sAutosaveData.bRequest = FALSE
g_sAutosaveData.bPerforming = FALSE
g_sAutosaveData.bInProgress = FALSE
g_sAutosaveData.bBeenOffMission = FALSE
g_sAutosaveData.bComplete = FALSE
g_sAutosaveData.iQueuedRequests--
// Clearing the block flags for safety.
SET_AUTOSAVE_IGNORES_ON_MISSION_FLAG(FALSE, FALSE)
IF g_sAutosaveData.iQueuedRequests > 0
#IF IS_DEBUG_BUILD
OR g_bDebug_KeepAutoSaveControllerRunning
#ENDIF
eScriptStage = SCRIPT_STAGE_WAIT_FOR_REQUEST
ELSE
g_sAutosaveData.bFlushAutosaves = FALSE
eScriptStage = SCRIPT_STAGE_CLEANUP
ENDIF
EXIT
ENDIF
// Attempt to perform an autosave if we haven't done so already
IF NOT (g_sAutosaveData.bPerforming)
//Check for debug skip.
#IF IS_DEBUG_BUILD
IF g_bDebugBlockAutosaves
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Skipped: Autosaves blocked by debug global.")
g_sAutosaveData.bComplete = TRUE
g_sAutosaveData.bFirstAutosaveComplete = TRUE
EXIT
ENDIF
#ENDIF
// Before we perform an autosave we need to check that the game is in a safe state.
// If we are not in a safe state and just want to delay the process, then bail out
// of the proc.
// If we are not in a safe state and want to cancel the request, set the 'complete'
// flag to TRUE and bail out of the proc.
// IF (GET_GAME_TIMER() - g_sAutosaveData.iTriggerTime) < AUTOSAVE_DELAY_TIME
// CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Delay: Waiting for autosave time delay to complete (")
// PRINTINT(ROUND((TO_FLOAT(GET_GAME_TIMER() - g_sAutosaveData.iTriggerTime)/TO_FLOAT(AUTOSAVE_DELAY_TIME))*100))
// PRINTSTRING("%).")
// EXIT
// ENDIF
IF (g_bInMultiplayer)
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Failed: Autosave not available in multiplayer.")
g_sAutosaveData.bComplete = TRUE
EXIT
ENDIF
IF NOT g_savedGlobals.sFlow.isGameflowActive
OR NOT IS_BIT_SET(g_savedGlobals.sFlow.strandSavedVars[STRAND_PROLOGUE].savedBitflags,SAVED_BITS_STRAND_ACTIVATED)
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Failed: Currently running in debug mode.")
g_sAutosaveData.bComplete = TRUE
EXIT
ENDIF
enumCharacterList eCurrentPed = GET_CURRENT_PLAYER_PED_ENUM()
IF NOT IS_PLAYER_PED_PLAYABLE(eCurrentPed)
IF IS_CURRENTLY_ON_MISSION_OF_TYPE(MISSION_TYPE_ANIMAL)
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Failed: Player is playing as an animal.")
ELIF IS_CURRENTLY_ON_MISSION_OF_TYPE(MISSION_TYPE_DIRECTOR)
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Failed: Player is playing in director mode.")
ELSE
MODEL_NAMES playerModel = GET_ENTITY_MODEL(PLAYER_PED_ID())
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Failed: Player is not a main story character.")
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] ENUM_TO_INT(eCurrentPed) = ",ENUM_TO_INT(eCurrentPed))
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] playerModel = ",playerModel)
//B* - 2308260
IF IS_CURRENTLY_ON_MISSION_OF_TYPE(MISSION_TYPE_SWITCH)
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Delay: Player has not switched back yet.")
EXIT
ENDIF
SCRIPT_ASSERT("Autosave failed: Player is not a main story character. Tell Kenneth R.")
ENDIF
g_sAutosaveData.bComplete = TRUE
EXIT
ENDIF
IF NOT IS_PED_INJURED(PLAYER_PED_ID())
IF GET_CLOSEST_SAVEHOUSE(GET_ENTITY_COORDS(PLAYER_PED_ID()), eCurrentPed) = NUMBER_OF_SAVEHOUSE_LOCATIONS
#IF IS_DEBUG_BUILD
SCRIPT_ASSERT("Autosave failed: No savehouse available for the current player character. Tell Kenneth R.")
#ENDIF
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Failed: No savehouse available for the current player character.")
g_sAutosaveData.bComplete = TRUE
EXIT
ENDIF
ENDIF
IF g_sAutosaveData.bFlushAutosaves
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Failed: Flushing out autosaves.")
g_sAutosaveData.bComplete = TRUE
EXIT
ENDIF
IF NOT g_sAutosaveData.bIgnoreOnMissionFlag
// IF (IS_CURRENTLY_ON_MISSION_TO_TYPE())
// IF (g_sAutosaveData.bBeenOffMission)
// CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Failed: New mission started before autosave could kick in.")
// g_sAutosaveData.bComplete = TRUE
// EXIT
// ENDIF
//
// //CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Delay: Script mission flag still set.")
// //EXIT
// CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Failed: Script mission flag still set.")
// g_sAutosaveData.bComplete = TRUE
// EXIT
// ENDIF
// IF (GET_MISSION_FLAG())
// CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Delay: Code mission flag still set.")
// EXIT
// ENDIF
g_sAutosaveData.bBeenOffMission = TRUE
ENDIF
IF IS_PED_INJURED(PLAYER_PED_ID())
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Delay: Player is dead.")
EXIT
ENDIF
IF IS_AUTO_SAVE_IN_PROGRESS()
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Delay: The game is current saving.")
EXIT
ENDIF
IF IS_MEMORY_CARD_IN_USE()
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Delay: memory card is in use.")
EXIT
ENDIF
IF g_flowUnsaved.bFlowControllerBusy
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Failed: Flow is changing state. Waiting until it is stable.")
//g_sAutosaveData.bComplete = TRUE //commented out this line as it was preventing saves after missions
EXIT
ENDIF
IF NOT (IS_PLAYER_PLAYING(PLAYER_ID()))
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Delay: Player is not playing.")
EXIT
ENDIF
IF IS_PLAYER_BROWSING_ITEMS_IN_ANY_SHOP()
//CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Delay: Player is using shop.")
EXIT
ENDIF
IF IS_PLAYER_SWITCH_IN_PROGRESS()
OR IS_PLAYER_PED_SWITCH_IN_PROGRESS()
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Delay: Waiting for player switch to end")
EXIT
ENDIF
IF (IS_PHONE_ONSCREEN() AND GET_NUMBER_OF_THREADS_RUNNING_THE_SCRIPT_WITH_THIS_HASH(HASH("appTrackify")) = 0)
#IF IS_DEBUG_BUILD
IF NOT g_d_bAutoSavePhoneOnScreenSpam
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Delay: Cellphone is still on screen, holding until it isn't.")
g_d_bAutoSavePhoneOnScreenSpam = TRUE
ENDIF
#ENDIF
EXIT
ENDIF
IF IS_MISSION_STAT_UPLOAD_PENDING()
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Delay: waiting for IS_MISSION_STAT_UPLOAD_PENDING to return false")
EXIT
ENDIF
#IF IS_DEBUG_BUILD
g_d_bAutoSavePhoneOnScreenSpam = FALSE
#ENDIF
IF CHECK_PLAYER_MISSION_BLIPS_on_autosave()
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] CHECK_PLAYER_MISSION_BLIPS_on_autosave returned TRUE")
g_sAutosaveData.bIgnoreScreenFade = TRUE
IF ARE_VECTORS_EQUAL(g_savedGlobals.sRepeatPlayData.mPlayerStruct.vPos, <<0.0,0.0,0.0>>)
g_savedGlobals.sFlowCustom.wasFadedOut_switch = TRUE
ELSE
PRINTLN("[AUTOSAVE] - keep wasFadedOut_switch false for quicksave, inform Alwyn (mission blip)")
ENDIF
ELSE
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] CHECK_PLAYER_MISSION_BLIPS_on_autosave returned false")
ENDIF
IF NOT g_sAutosaveData.bIgnoreScreenFade
IF NOT (IS_SCREEN_FADED_IN())
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Delay: Screen is not faded in.")
EXIT
ENDIF
IF (IS_FRONTEND_FADING())
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Delay: Front end is fading.")
EXIT
ENDIF
SET_FADE_IN_AFTER_LOAD(TRUE)//set this back to default
g_savedGlobals.sFlowCustom.wasFadedOut = FALSE
g_savedGlobals.sFlowCustom.wasFadedOut_switch = FALSE
ELSE
SET_FADE_IN_AFTER_LOAD(FALSE)
g_savedGlobals.sFlowCustom.wasFadedOut = TRUE
IF ARE_VECTORS_EQUAL(g_savedGlobals.sRepeatPlayData.mPlayerStruct.vPos, <<0.0,0.0,0.0>>)
g_savedGlobals.sFlowCustom.wasFadedOut_switch = TRUE
ELSE
PRINTLN("[AUTOSAVE] - keep wasFadedOut_switch false for quicksave, inform Alwyn (bIgnoreScreenFade)")
ENDIF
ENDIF
//Check to see if any strands are sitting on DO_MISSION_NOW flow commands.
//If they are we need to set the wasFadedOut flag to enure the screen doesn't fade in
//on loading until after the mission is running.
INT iStrandIndex
BOOL bMissionAutostartFound = FALSE
REPEAT MAX_STRANDS iStrandIndex
INT iCommandPos = g_savedGlobals.sFlow.strandSavedVars[iStrandIndex].thisCommandPos
IF iCommandPos != ILLEGAL_ARRAY_POSITION
IF g_flowUnsaved.flowCommands[iCommandPos].command = FLOW_DO_MISSION_NOW
PRINTLN("[AUTOSAVE] Considering setting SET_FADE_IN_AFTER_LOAD as strand ", GET_STRAND_DISPLAY_STRING_FROM_STRAND_ID(INT_TO_ENUM(STRANDS, iStrandIndex)), " is on a DO_MISSION_NOW_COMMAND.")
//Only set this flag if this strand doesn't have a save command
//point override set.
BOOL bOverrideSetOnStrand = FALSE
INT iOverrideIndex
REPEAT g_savedGlobals.sFlowCustom.numberStoredOverrides iOverrideIndex
IF ENUM_TO_INT(g_savedGlobals.sFlowCustom.strandToOverride[iOverrideIndex]) = iStrandIndex
IF NOT g_savedGlobals.sFlowCustom.applyOnMPSwitchOnly[iOverrideIndex]
PRINTLN("[AUTOSAVE] Strand ", GET_STRAND_DISPLAY_STRING_FROM_STRAND_ID(INT_TO_ENUM(STRANDS, iStrandIndex)), " had a save override set. Backing out of setting SET_FADE_IN_AFTER_LOAD.")
bOverrideSetOnStrand = TRUE
ENDIF
ENDIF
ENDREPEAT
IF NOT bOverrideSetOnStrand
PRINTLN("[AUTOSAVE] SET_FADE_IN_AFTER_LOAD set for strand ", GET_STRAND_DISPLAY_STRING_FROM_STRAND_ID(INT_TO_ENUM(STRANDS, iStrandIndex)), ".")
SET_FADE_IN_AFTER_LOAD(FALSE)
g_savedGlobals.sFlowCustom.wasFadedOut = TRUE
g_savedGlobals.sFlowCustom.wasFadedOut_switch = FALSE
bMissionAutostartFound = TRUE
ENDIF
ENDIF
ENDIF
ENDREPEAT
// Do the save
PERFORM_PRE_SAVEGAME_ROUTINE(TRUE, FALSE, bMissionAutostartFound)
CLEAR_REPLAY_STATS()//these will have been processed at this point
DO_AUTO_SAVE()
g_sAutosaveData.bPerforming = TRUE
IF NOT (g_sAutosaveData.bFirstAutosaveComplete)
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] This is the first attempt to autosave.")
ENDIF
// If autosave is switched off then the 'autosave in progress' flag should be false immediately
IF NOT (IS_AUTO_SAVE_IN_PROGRESS())
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Failed: Autosave is switched off.")
IF NOT (g_sAutosaveData.bFirstAutosaveComplete)
ADD_HELP_TO_FLOW_QUEUE("SAVE_OFF", FHP_HIGH)
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Letting player know how to turn on autosave.")
ENDIF
EXIT
ENDIF
g_sAutosaveData.bInProgress = TRUE
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Performing autosave.")
ENDIF
// Wait for the save to finish
IF (IS_AUTO_SAVE_IN_PROGRESS())
IF IS_WARNING_MESSAGE_ACTIVE()
DISABLE_CELLPHONE_THIS_FRAME_ONLY()
DISABLE_SELECTOR_THIS_FRAME()
ENDIF
EXIT
ENDIF
// We can tell a save was successful by checking the 'autosave off' flag
IF (g_sAutosaveData.bInProgress)
IF (GET_IS_AUTO_SAVE_OFF())
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Failed: Autosave has just been turned off.")
ELSE
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Success: Autosave process complete.")
ENDIF
ENDIF
// Clear the ignore screen flag as we're ending this autosave.
IF g_sAutosaveData.bIgnoreScreenFade
IF g_sAutosaveData.iQueuedRequests = 0
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] Success:Clearing the ignore screen fade flag.")
g_sAutosaveData.bIgnoreScreenFade = FALSE
ENDIF
ENDIF
// Setting the 'complete' flag allows other flags to be reset when this proc is called again.
g_sAutosaveData.bComplete = TRUE
g_sAutosaveData.bFirstAutosaveComplete = TRUE
ENDPROC
SCRIPT
CPRINTLN(DEBUG_SYSTEM,"[AUTOSAVE] \nStarting autosave controller")
// This script needs to cleanup only when the game moves from SP to MP
IF (HAS_FORCE_CLEANUP_OCCURRED(FORCE_CLEANUP_FLAG_SP_TO_MP))
PRINTSTRING("...autosave_controller.sc has been forced to cleanup (SP to MP)")
CLEANUP_SCRIPT()
ENDIF
#IF IS_DEBUG_BUILD
SETUP_AUTOSAVE_WIDGETS()
#ENDIF
// Main loop
WHILE TRUE
WAIT(0)
#IF IS_DEBUG_BUILD
MAINTAIN_AUTOSAVE_WIDGETS()
#ENDIF
// Perform the main processing
SWITCH eScriptStage
CASE SCRIPT_STAGE_INITIALISE
INITIALISE_CONTROLLER()
BREAK
CASE SCRIPT_STAGE_WAIT_FOR_REQUEST
CHECK_AUTOSAVE_REQUEST()
BREAK
CASE SCRIPT_STAGE_PERFORM_AUTOSAVE
PERFORM_AUTOSAVE()
BREAK
CASE SCRIPT_STAGE_CLEANUP
CLEANUP_SCRIPT()
BREAK
ENDSWITCH
ENDWHILE
ENDSCRIPT