Post subject: LUA Script triggers on savestate load?
Joined: 3/22/2008
Posts: 84
I want to create a practice tool that will allow me to load a savestate while also randomizing the RNG. I already have the RNG address for my game and I've created some simple LUA scripts to display my current RNG value (and speed and position and such), but I have no idea if it's possible to trigger a LUA script on savestate. Has anyone attempted to do something similar?
RetroEdit
Any
Editor, Reviewer, Player (169)
Joined: 8/8/2019
Posts: 152
One potential way I can think off the top of my head: Each frame, compare the current frame number to the previous (and then store the current frame number as the previous). If the current frame isn't one greater than the previous, then you have loaded a savestate. There may be a better way, but there's currently no built in callbacks for savestate loading if I recall, and I don't see any obvious flaws with this method. Edit: My recollection was wrong, as Amaraticando points out, event.onloadstate exists and should be preferred as a more robust solution than the hacky solution of comparing to the previous frame. My apologies for being too lazy to even check the documentation (I am a bit averse to BizHawk's Lua docs, since I find them to be poorly formatted and difficult to use, but that is no excuse.) As for flaws with the previous method, I'm writing a reply currently, but I concede my method is not perfect for all cases. Code example with a callback:
Language: lua

event.onloadstate(randomize_rng)
<h2>My original, less robust method</h2> Code-wise, it would be something like:
Language: lua

local prev_frame = emu.framecount() - 1 while true do local curr_frame = emu.framecount() if curr_frame ~= prev_frame + 1 then randomize_rng() end prev_frame = curr_frame emu.frameadvance() end
Joined: 3/22/2008
Posts: 84
cool, I'll try this out, thanks
Amaraticando
It/Its
Editor, Player (159)
Joined: 1/10/2012
Posts: 673
Location: Brazil
Retroedit's answer does not substitute a proper onLoadState event. What if you load a state whose framecount is the current plus 1? Or if you are executing a function in the middle of the frame? Use BizHawk's event library: event.onloadstate string event.onloadstate(nluafunc luaf, [string name = null]) Fires after a state is loaded. Receives a lua function name, and registers it to the event immediately following a successful savestate event Reference: http://tasvideos.org/Bizhawk/LuaFunctions.html
RetroEdit
Any
Editor, Reviewer, Player (169)
Joined: 8/8/2019
Posts: 152
I will note, I was aware that this solution would not work in cases where the frame loaded happens to be one frame greater than the current frame. However, in this particular example of practicing a particular section, I assume they would not go back in time and even if that was the case, the only negative effect is not re-seeding the RNG for that particular attempt. Regardless, I should have mentioned this drawback. I'm not certain I follow what you mean when you mention "what if you are executing a function in the middle of the frame"? How would a frame number-comparison method break down in that instance?
Amaraticando
It/Its
Editor, Player (159)
Joined: 1/10/2012
Posts: 673
Location: Brazil
RetroEdit wrote:
I'm not certain I follow what you mean when you mention "what if you are executing a function in the middle of the frame"? How would a frame number-comparison method break down in that instance?
Some emulators, not sure about BizHawk, will increase the framecount in the 1st instruction after the frame breakpoint. BTW, being unaware of a certain API function, and using the other APIs to hack a solution, is not uncommon.
Joined: 3/22/2008
Posts: 84
just to close the loop on this, that solution worked completely, thanks
Joined: 3/22/2008
Posts: 84
Decided to experiment with the onloadstate() event and the results are a little weird. I want this to be toggle-able, which the simple one-liner doesn't accomplish. Once I load the script, the event seems to be registered until I close the instance of the emulator. I attempted to address this by combining the script methods, and it MOSTLY works but has one key flaw. First, here's the code I have so far:
local function_name = "randomize_rng_on_load_state"

function randomize_rng()
	local new_rng1 = math.random(0, 255)
	local new_rng2 = math.random(0, 15)
	memory.usememorydomain("Combined WRAM")
	memory.writebyte(0x040FB4, new_rng1)
	memory.writebyte(0x040FB5, new_rng2)
end

function clean_load_state_event()
	event.unregisterbyname(function_name)
end

event.onloadstate(randomize_rng, function_name)
event.onexit(clean_load_state_event)

while true do
    emu.frameadvance()
end
The flaw is that approximately half the time when toggling the script off, it throws a scary looking error message. It still toggles off properly and you can close the error without the instance crashing, but I'd love to stop that from happening at all if possible
Amaraticando
It/Its
Editor, Player (159)
Joined: 1/10/2012
Posts: 673
Location: Brazil
I cannot test as I don't use Windows. Try to use GUIDs (= UUIDs) instead of names.
Language: lua

local function randomize_rng() local new_rng1 = math.random(0, 255) local new_rng2 = math.random(0, 15) memory.usememorydomain("Combined WRAM") memory.writebyte(0x040FB4, new_rng1) memory.writebyte(0x040FB5, new_rng2) end local handle = event.onloadstate(randomize_rng) console.log('handle', handle) -- for debugging purposes TODO: erase event.onexit(function() local foo = event.unregisterbyid(handle) -- not by name console.log('foo', foo) -- test whether it worked. TODO: erase end) while true do emu.frameadvance() end