This tutorial describes how to remove the artificial delays introduced when saving the game. Saving the game is a reasonably quick process. A full write to SRAM takes less than a quarter of a second. However, for reasons unknown, the game makes it seem like a five-second process, displaying at least two confirmation windows, and forcing the player to wait while saving. Regardless of whether this annoyance was an intentional design choice or an artifact of its time, there is no actual need for the delays, and thus they can easily be removed for a much smoother saving process. Note that most of this tutorial takes place in a single file, [engine/menus/save.asm](../blob/master/engine/menus/save.asm). If nothing else is indicated, assume that the listed functions are found in that file. ## Contents 1. [Understanding the saving process][sec-1] 2. [Removing the slow text printing function][sec-2] 3. [Renaming functions][sec-3] 4. [Removing delays in save function][sec-4] 5. [Removing the extra prompt][sec-5] 6. [Removing the delays on Bill's PC][sec-6] 7. [Removing the remaining delays][sec-7] 8. [Adding a message without delays][sec-8] ## 1. Understanding the saving process Before making any changes to the saving process, it is instructive to look at how it works. The game gets saved on five occasions: * When the player manually initiates a save, via the Start menu; * When the player uses the "move Pokémon without mail" feature; * When the player switches PC boxes; * When the player reaches the Hall of Fame and becomes Champion; * When using link features and/or the Battle Tower. Each of these five situations calls slightly different code, but the underlying saving functions are mostly the same. The most common case, of course, is the case of manually initiated saves. Such a save triggers the following sequence: 1. Display a prompt asking if they want to save. 2. If a previous savefile exists, display another confirmation prompt (with separate prompts for updating the current game's savefile vs. overwriting a previous game's savefile with a completely new one). 3. Block all player inputs after confirming. 4. Override the text speed, setting it to "Medium", and display `SAVING… DON'T TURN OFF THE POWER`. 5. Wait 16 frames. 6. Actually save the game. This is the important step. 7. Wait 32 frames. 8. Override the text speed, setting it to "Medium", and display ` saved the game!`. 9. Play a sound confirming that saving was successful. 10. Wait 30 frames. 11. Close the menus and let the player continue playing. The key takeaway from this list is that saving _only occurs during step 6_. That means that while the game is waiting and displaying the `SAVING…` message, _no actual saving is happening_. Therefore, all of these delays can be removed without interfering at all with the saving feature. The following steps show how to achieve this goal. Note that, with the exception of the immediately following section, all sections are optional. If you want to keep some of the delays, skip the corresponding sections. ## 2. Removing the slow text printing function Much of the slowness in the process comes from the function that prints the `SAVING… DON'T TURN OFF THE POWER` text. This function is adequately called `SavingDontTurnOffThePower`, and handles steps 3 through 5 in [the section above][sec-1]. Since this function serves no useful purpose, it can safely be deleted, along with the two calls to it. Delete the entire function, and remove the two calls as follows: ```diff ChangeBoxSaveGame: ; ... call PauseGameLogic - call SavingDontTurnOffThePower call SaveBox ; ... ; ... _SavingDontTurnOffThePower: - call SavingDontTurnOffThePower SavedTheGame: ; ... ``` The deleted function also references a text element, which in turn makes a far jump to the actual text in [data/text/common_3.asm](../blob/master/data/text/common_3.asm). Both of these elements may be deleted: ```diff -SavingDontTurnOffThePowerText: - text_far _SavingDontTurnOffThePowerText - text_end ``` And in [data/text/common_3.asm](../blob/master/data/text/common_3.asm): ```diff -_SavingDontTurnOffThePowerText:: - text "SAVING… DON'T TURN" - line "OFF THE POWER." - done ``` While most of the delay will be gone with this single step, the rest of the steps in this tutorial will fully remove the delays and polish the experience for the user. ## 3. Renaming functions After the deletion from [the previous section][sec-2], `_SavingDontTurnOffThePower` becomes a simple dummy function that falls through into `SavedTheGame`; the former can thus be removed, and all calls replaced with calls to the latter. Similarly, `SaveGameData` is nothing but a wrapper for `_SaveGameData`; the wrapper can be deleted and the underscore removed. Note that, while this step isn't necessary, it will greatly improve the readability of the code involved and simplify its debugging in case anything goes wrong. While the rest of the tutorial doesn't concern itself with cleaning up existing code, this step is highly recommended. ```diff SaveMenu: ; ... call PauseGameLogic - call _SavingDontTurnOffThePower + call SavedTheGame call ResumeGameLogic ; ... ; ... Link_SaveGame: ; ... call PauseGameLogic - call _SavingDontTurnOffThePower + call SavedTheGame call ResumeGameLogic ; ... ; ... StartMoveMonWOMail_SaveGame: ; ... call PauseGameLogic - call _SavingDontTurnOffThePower + call SavedTheGame call ResumeGameLogic and a ret ; ... -SaveGameData: - call _SaveGameData - ret ; ... -_SavingDontTurnOffThePower: SavedTheGame: - call _SaveGameData + call SaveGameData ; ... -_SaveGameData: +SaveGameData: ld a, TRUE ; ... ``` This renaming also requires editing a single line in [mobile/mobile_5f.asm](../blob/master/mobile/mobile_5f.asm): ```diff IncCrashCheckPointer_SaveGameData: - inc_crash_check_pointer_farcall _SaveGameData + inc_crash_check_pointer_farcall SaveGameData ``` ## 4. Removing delays in save function With the text delays removed, every piece of code that used to print the slow `SAVING… DON'T TURN OFF THE POWER` message now calls `SavedTheGame` instead; other parts of the code call it directly. This function is responsible for calling `SaveGameData` (which is where the actual saving happens) and then informing the player that saving succeeded. Of course, the informing part is done with plenty of delays (namely, steps 7, 8 and 10 in [the first section][sec-1]), which we can remove: ```diff SavedTheGame: call SaveGameData - ; wait 32 frames - ld c, 32 - call DelayFrames ; copy the original text speed setting to the stack ld a, [wOptions] push af - ; set text speed to medium - ld a, TEXT_DELAY_MED + ; set text speed to fast + ld a, TEXT_DELAY_FAST ld [wOptions], a ; saved the game! ld hl, Text_PlayerSavedTheGame call PrintText ; restore the original text speed setting pop af ld [wOptions], a ld de, SFX_SAVE call WaitPlaySFX - call WaitSFX + jp WaitSFX - ; wait 30 frames - ld c, 30 - call DelayFrames - ret ``` ## 5. Removing the extra prompt When the player chooses to save the game, the game only saves immediately if no save data exists. If a (valid) savefile already exists, the game will ask the player to confirm if they want to overwrite that savefile. Of course, this prompt is useful when saving on top of a _different_ game's savefile (which can happen if the player hits New Game while a savefile exists), as it prevents the player from overwriting their previous saved game accidentally. However, saving on top of a previous savefile for the same game is virtually always intentional, as players will update their savefiles as they play through the game. This extra prompt is just inconvenient in this case. Fortunately, since the game already contains separate texts for each situation, removing the extra prompt when saving on top of a previous savefile _for the same game only_ requires a simple change: ```diff AskOverwriteSaveFile: ld a, [wSaveFileExists] and a jr z, .erase call CompareLoadedAndSavedPlayerID - jr z, .yoursavefile + ret z ; pretend the player answered "Yes", but without asking ld hl, AnotherSaveFileText call SaveTheGame_yesorno jr nz, .refused - jr .erase -.yoursavefile - ld hl, AlreadyASaveFileText - call SaveTheGame_yesorno - jr nz, .refused - jr .ok - .erase call ErasePreviousSave - -.ok and a ret .refused scf ret ; ... -AlreadyASaveFileText: - text_far _AlreadyASaveFileText - text_end ``` The corresponding text from [data/text/common_3.asm](../blob/master/data/text/common_3.asm) can be removed as well: ```diff -_AlreadyASaveFileText:: - text "There is already a" - line "save file. Is it" - cont "OK to overwrite?" - done ``` ## 6. Removing the delays on Bill's PC Bill's PC requires a save when changing boxes, and also when using the "move Pokémon without mail" feature (which saves once initially and once per swap). Changing boxes just uses the regular save code (the same we just edited), so that's taken care of. However, swapping Pokémon through the "move Pokémon without mail" feature uses separate code, with smaller delays, simply because the longer delays from the regular function would be intolerable here. Despite these delays being smaller, they have no reason to exist, and thus can safely be removed. First of all, the function in [engine/menus/save.asm](../blob/master/engine/menus/save.asm), the file we've been editing so far: ```diff MoveMonWOMail_InsertMon_SaveGame: ; ... call LoadBox call ResumeGameLogic ld de, SFX_SAVE + jp PlaySFX - call PlaySFX - ld c, 24 - call DelayFrames - ret ``` And secondly, the actual function in Bill's PC's code, in [engine/pokemon/bills_pc.asm](../blob/master/engine/pokemon/bills_pc.asm): ```diff MovePKMNWitoutMail_InsertMon: ; ... ld de, .Saving_LeaveOn call PlaceString - ld c, 20 - call DelayFrames pop af pop bc pop de pop hl ; ... ``` ## 7. Removing the remaining delays At this point, only two sources of delay remain, corresponding to the two most uncommon cases out of the five listed in the [first section][sec-1]. The first one of them is the "quick save" function, used by the link features and the Battle Tower when they require the player to save in the middle of their text prompts. The delay here is very small, hence the "quick" moniker, but it can still be removed. In [engine/link/link.asm](../blob/master/engine/link/link.asm): ```diff TryQuickSave: ; ... ld [wScriptVar], a - ld c, 30 - call DelayFrames pop af ld [wChosenCableClubRoom], a ret ``` Finally, the one delay that remains is the one that appears when the player becomes Champion, before the credits roll. This delay is very large, as it replaces all the smaller delays in other functions; it's nearly two seconds long. Fortunately, it can also be removed. In [engine/events/halloffame.asm](../blob/master/engine/events/halloffame.asm): ```diff HallOfFame_FadeOutMusic: ; ... farcall InitDisplayForHallOfFame - ld c, 100 - jp DelayFrames + ret ``` ## 8. Adding a message without delays At this point, with all sources of delay removed, saving only takes the amount of time that is actually necessary to write data to SRAM. Sadly, since the code isn't as optimized as it could be and the GB is just slow, this takes a visible amount of time; typically around a quarter of a second, but enough to make it seem like the game temporarily froze. A simple solution to this problem is to add again a message like the one we removed. Despite the game wasn't saving while `SAVING… DON'T TURN OFF THE POWER` was being printed, the game _did_ save at some point in time after that (in the middle of the delays) while the message was still visible, so the player knew that the game was doing something. If we are to reinstate this message, we should be careful not to introduce back the delays we spent the whole tutorial removing. Therefore, any message that we introduce must meet the following conditions: * The message must only be printed while the game is actually saving. * Printing the message must not take any significant amount of time. * As soon as saving is done, the message must be removed. This can be achieved by printing the message instantaneously and keeping it short (which eliminates the delays) right before the game begins to save (which ensures that the message is shown while the game actually saves), and letting the regular ` saved the game!` message overwrite it when saving is done (which removes it when no longer saving). Making a final edit to `SavedTheGame` achieves this goal: ```diff SavedTheGame: + ld hl, wOptions + set NO_TEXT_SCROLL, [hl] + push hl + ld hl, .saving_text + call PrintText + pop hl + res NO_TEXT_SCROLL, [hl] call SaveGameData ... jp WaitSFX +.saving_text + text "SAVING…" + done ``` [sec-1]: #1-understanding-the-saving-process [sec-2]: #2-removing-the-slow-text-printing-function [sec-3]: #3-renaming-functions [sec-4]: #4-removing-delays-in-save-function [sec-5]: #5-removing-the-extra-prompt [sec-6]: #6-removing-the-delays-on-bills-pc [sec-7]: #7-removing-the-remaining-delays [sec-8]: #8-adding-a-message-without-delays