Usage - Guantol-Lemat/Isaac.Glowing-Hourglass-Manager GitHub Wiki
Preface
I'm going to be referring to the variables needed to track what is happening in the current frame as a "Game State".
Given that you will need to save copies of these variables in order to remember an older game state in case of a rewind, I suggest inserting all this variables inside of a GamaState table so that you can easily copy the contents without the need to constantly update the code when a new variable needs to be tracked.
NOTE: If you do choose to use this approach you will need to DeepCopy the table rather than using the assignment operator (=), , even though it is used in the code snippets, as that merely creates an Alias for the same table rather than a copy.
Usage
In order to properly synchronize your modded data with the actual game state you should keep track of 3 "Game States":
- RewindState
- PhantomRewindState
- PreviousFloorState
- PreCurseRoomDamageHealthState (Only track this if you need information about the player's health)
(You can technically not track PreCurseRoomDamageHealthState, and maybe even PreviousFloorState, and only track PhantomRewindState, if you want ore info on how to remove them you can refer to the Additional Notes)
I'm also going to be referring to the current variables you are tracking as the CurrentGameState
When any of these variables are supposed to be updated the ON_GLOWING_HOURGLASS_GAME_STATE_UPDATE Custom Callback will fire. So you should register a callback function that handles the GameState changes to this custom Callback by using:
local function OnGHUpdate(_, TransactionID, UpdateType, ShouldOverwriteHealthState, WasPreviousFloorStateNull)
-- Your Code Here
end
YourModReference:AddCallback(GHManager.Callbacks.ON_GLOWING_HOURGLASS_GAME_STATE_UPDATE, OnGHUpdate)
Alternatively, you can also create unique function for each Hourglass Update Type:
local function OnGHUpdate_New_Stage()
-- Your Code Here
end
YourModReference:AddCallback(GHManager.Callbacks.ON_GLOWING_HOURGLASS_GAME_STATE_UPDATE, OnGHUpdate_New_Stage, GHManager.HourglassUpdate.New_Stage)
âšī¸ INFO: The Callback is fired most of the times during MC_POST_NEW_ROOM and only during specific situations (HourglassUpdate is of Type "Save_Pre_Room_Clear_State" and "Save_Pre_Curse_Damage_Health") it will fire during MC_PRE_SPAWN_CLEAN_AWARD and MC_ENTITY_TAKE_DMG (respectively).
The way the data is supposed to be updated depends on the type of HourglassUpdate
New_Session
Occurs either when starting a New run or when Continuing a run in which the CanStartTrueCoop flag is still enabled.
Also occurs after R Key is used
RewindState = CurrentGameState
PreviousFloorState = nil
PhantomRewindState = nil
Continued_Session
Occurs once every play session when Continuing a run in which the CanStartTrueCoop flag is disabled
RewindState = CurrentGameState
PreviousFloorState = CurrentGameState
PhantomRewindState = nil
New_State
If you need to track health remember to read the ShouldOverwriteHealthState function argument
RewindState = CurrentGameState
PreviousFloorState = CurrentGameState
PhantomRewindState = nil
-- add this if you need to track the player's health
if ShouldOverwriteHealthState then
RewindState.Health = PreCurseRoomDamageHealthState
PreviousFloorState.Health = PreCurseRoomDamageHealthState
end
New_State_Warped
If you need to track health remember to read the ShouldOverwriteHealthState function argument
if PhantomRewindState then
RewindState = PhantomRewindState
PreviousFloorState = PhantomRewindState
end
PhantomRewindState = CurrentGameState
Rewind_Previous_Room
Make the callback function read the WasPreviousFloorStateNull function argument
CurrentGameState = RewindState
PhantomRewindState = nil
if WasPreviousFloorStateNull then
PreviousFloorState = nil
else
PreviousFloorState = RewindState -- This is important
end
Rewind_Current_Room
Functionally the same as Rewind_Previous_Room except that it is unnecessary to update the PreviousFloorState
CurrentGameState = RewindState
PhantomRewindState = nil
New_Stage
RewindState = CurrentGameState
PhantomRewindState = nil
New_Absolute_Stage
The only difference between this and New_Stage is that this only occurs when PreviousFloorState is null when performing a stage transition, and the player can therefore never rewind back to the previous floor. Attempting a rewind here will trigger the Failed_Stage_Return HourglassUpdate.
Essentially this is akin to a New_Session in almost every aspect, if not for the way it is achieved
RewindState = CurrentGameState
PreviousFloorState = nil -- Technically unnecessary as for this event to occur the state must already be nil
PhantomRewindState = nil
Previous_Stage_Last_Room
CurrentGameState = PreviousFloorState
RewindState = PreviousFloorState
PhantomRewindState = nil
Previous_Stage_Penultimate_Room
The only difference between this and Previous_Stage_Last_Room is that when this executes you are sent to the Penultimate_Room you were in before you left the floor, regardless of whether the room was cleared or not (if the penultimate room was an uncleared Boss Room you will be sent there).
This specific scenario occurs ONLY if the last room transition before the Stage Transition was from anywhere to a CLEARED and VISITED room.
EXCEPT that this rule is broken if the stage transition is done using Forget Me Now, in which case you get the Previous_Stage_Last_Room update type (What the f**k?)
CurrentGameState = PreviousFloorState
RewindState = PreviousFloorState
PhantomRewindState = nil
Failed_Stage_Return
It's essentially a Reskinned Rewind_Current_Room (This only occurs if PreviousFloorState = nil / when you can still start True Coop).
RewindState = CurrentGameState
PreviousFloorState = nil
PhantomRewindState = nil
Save_Pre_Room_Clear_State
Triggered during MC_PRE_SPAWN_CLEAN_AWARD
PreviousFloorState = CurrentGameState
PhantomRewindState = CurrentGameState
Save_Pre_Curse_Damage_Health
Triggered ONLY during the FIRST MC_ENTITY_TAKE_DMG where the player is damaged by a Curse Door
PreviousFloorState = CurrentGameState
PhantomRewindState = CurrentGameState
PreCurseRoomDamageHealthState = CurrentGameState
Additional Notes
It seems that the game doesn't have 3 tracked RewindStates (and a 4th just for the player's Health), but only 2 (RewindState and PhantomRewindState).
By my understanding, whenever you use the Glowing Hourglass you are always brought back to the Rewind State, and, whilst the Rewind State constantly updates as you move trough the floor, there are very specific situations in which a Temporary "Phantom" Rewind State is saved rather than the regular Rewind State, which can then overwrite the data that is Present or that is being Recorded inside the Rewind State under even more specific situations.
The reason I decided to keep the other 2 "fake" states, is mostly to for simplicity's sake, though it is admittedly VERY Memory Inefficient, based on the amount of data you need to store within the Game State, but the cases in which you Overwrite the Rewind State with the Temporary data can be seen as confusing, at first.
For PreviousFloorState The problem is caused by the fact that Stage Transitions (New_Stage) act differently compared to Warps (New_State_Warped).
If you "chain" multiple consecutive Warps (you only transition between rooms using Warps) you will rewind back to the Penultimate Phantom Rewind State that was created, hence why this piece of code exists within New_State_Warped:
if PhantomRewindState then
RewindState = PhantomRewindState
end
However if you "chain" multiple consecutive Stage Transitions (you never go trough another room, but only trough another stage), you will Rewind Back to the Start of the chain, no matter how long it was. (Unless you trigger a New_Absolute_Stage which essentially creates a New_Session each time).
So you would be inclined to believe that what happens is that, on a New_Stage you create a Phantom Rewind State but you never attempt to modify the Rewind State. But that doesn't accurately represent what would actually happen, because if a Phantom State does exist before you ever start a Stage Transition chain then the Rewind State does indeed overwrite the Rewind State on the first New_Stage.
So you would need to memorize what caused the Phantom Rewind State to be created (if it exists) and then on a New_Stage, if the cause is not a Stage Transition, then the Rewind State should be fully overwritten by the Phantom Rewind State, then create a new Phantom Rewind State (It needs to be created because, in the case that the player performs a Warp right after they trigger a Stage Transition, you need to know what the Game State was when the new Floor was started).
Moving on to PreCurseRoomDamageHealthState, this is easier to get rid of as the condition is that if a regular Stage Transition is triggered (New_State) and you either move TO or FROM a Curse Room then only the Health of the Rewind State is overwritten by the one stored within the Phantom Rewind State
This does not need to be tracked however since the ShouldOverwriteHealthState function argument already details whether or not this overwrite should take place.