Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[
- ********************************
- * Plok helper script version 5 *
- ********************************
- IMPORTANT NOTE: Lua can't handle any input while
- the emulator is paused. Therefore, whenever you see
- an instruction like "press [whateverkey]", you either
- need to unpause the emulator beforehand, or you need
- to press [whateverkey]+frameadvance.
- All the Lua text displays are delayed by one frame.
- "recording mode" keeps track of all the input you give
- while being in this mode. It also records some important
- RAM values, which can easily be adjusted to work for
- different games.
- You can save everything that was recorded in this mode
- by pressing F11 on the keyboard. Lua will give you a
- warning message when you do this, because it takes some time.
- You can load such a record by pressing F12. Lua will give you a
- warning message when you do this, because it takes some time.
- You can switch to "replay mode" by pressing R on the keyboard.
- "replay mode" works the following way:
- If the script is allowed to do the input for you,
- then it will input exactly what you recorded during
- "recording mode" previously. You can adjust when the input is
- applied by setting the offset.
- Example: You pressed A on frame 2000, and B on frame 2100 in recording
- mode. Let's assume you could improve your movie before this sequence by
- exactly 30 frames. Now you want to use all of the recorded input again,
- but 30 frames earlier than before.
- Then you would do the following:
- - Playback your old movie at least until frame 2100.
- - Set the offset (see below) to 30
- - Switch to replay mode.
- - Load your new movie, and let it playback until some point
- before frame 1970, and create a savestate right there.
- - Pause the emulator, switch to read+write mode, load the state,
- and press numpad* + frameadvance (see below)
- Now the script will input A on frame 1970, and B on frame 2070
- for you.
- Now let's say that you had 5 less lag frames in between A and B.
- (This script will help you detecting lag, see below).
- Then you can still use the recorded input:
- - load the state you created above.
- - let the script replay the game for you up right after the
- "lost" lag, and create another savestate.
- - set the offset to 30 - 5 = 25.
- - load the state.
- Now let's say you want to press right on frame 2050, and leave
- everything else as before. Then you would do the following:
- - load the state you created above, and let the new movie
- play up to frame 2049. The script should show you that
- RECORDED input was used.
- - press right + frameadvance. The script should show you that
- MANUAL input was used.
- - press numpad* + frameadvance. (see below). The script should
- show you that RECORDED input was used.
- - unpause the emulator. Everything will work as above from now on.
- The offset can be adjusted by pressing numpad+ + frameadvance,
- numpad- + frameadvance, or numpad0 + frameadvance. In each case,
- the game will advance one frame. numpad+ will increase the offset
- by one, numpad- will decrease the offset by one, and numpad0
- will reset it. If you adjust the offset while (re)recording,
- you most likely have to load a savestate before you adjusted the
- offset.
- When you're in replay mode, you will see a message telling you
- if MANUAL input was used, or if RECORDED input was used.
- When you're playing back a movie, this will be counted as
- MANUAL input. As long as the script displays that manual
- input was used, it will not try to apply the recorded input.
- When you press numpad*, you allow the script to use recorded
- input again. When you're playing back a movie file write-protected
- (you see "Playing"), this won't work, and it will show you that
- MANUAL input was used.
- If you press any buttons on your controller while the script
- shows you that RECORDED input was used, it will now start
- to display that MANUAL input was used until you press
- numpad* + frameadvance.
- You will see two rather large tables during "replay" mode.
- The upper table shows you the difference between the "recorded"
- session, and what you're doing right now.
- Let's say you went right all the time, stopped for 10 frames,
- and then you jumped and pressed right. Let's say you completed
- the whole level afterwards, and forgot that you stopped. You
- have created a savestate before all this action, and you were
- in "recording mode" all the time. Now you want to remove this
- mistake. This works the following way:
- - switch to replay mode
- - load the savestate (the emulator should tell you now that
- you're rerecording)
- - let the script play the game for you up to somewhere right
- before the mistake.
- - create a savestate, and now fix the mistake.
- - create another savestate, set the offset accordingly. The lower
- table will help you to find the right offset (see below).
- Load this later state again
- - press numpad* + frameadvance to let the script play
- the rest of the level again.
- Now you have effectively done the same as if you fixed the
- mistake with the help of a hex-editor...
- The lower table is effectively the same as a "desync indicator".
- It shows you the differences between what you do now
- and what happened (frameoffset) frames later during the
- previously recorded session.
- If you have improved an older section by 40 frames, and used
- the instructions above, and it syncs perfectly, the lower table
- will show either 0, or "somevalue"||"samevalue" all the time.
- Let's say your improvement caused a visible desync, because
- two consecutive lag frames didn't appear. The table at the bottom will stop displaying
- all-zero exactly at the frame the desync really occured. Let's say you're
- moving 3 pixels per frame. Because you're now one frame more ahead than
- you should, it will now display -6 for the x value. If you create a savestate,
- lower the offset by 2, and load the state again, it should sync again
- (given that these changes didn't mess up some luck manipulation...).
- *******************************************************
- * Functions implemented especially for Plok
- *******************************************************
- "Special slide mode":
- Sometimes you can see a slide, and you want to reach a certain location
- as fast as possible. If you don't lose all the extra speed from sliding
- before the level exit, it is very difficult to say how long you should
- actually slide, and when you should begin to jump and press down+right
- or down+left, because the jump will be 2 pixels per frame faster right
- at the beginning, but it will slow down by 7/256 of a pixel per frame.
- Sliding instead will increase the extra speed even further, but you don't
- gain the extra speed from pressing right.
- How it works:
- Suppose you're at x=100 on a slope with speed=0.
- The slope goes on until x=300
- You want to reach x=400 as fast as possible. It doesn't matter for
- you to get to any place x > 400 at the shortest amount of time.
- Then you do the following:
- - create a savestate when standing at x=100
- - go to x=400, press "U".
- - load the savestate, press "I"+frameadvance
- - when you got to the end of the slope, press I+frameadvance again.
- - the script will show you the best location for starting the jump.
- --]]
- dofile("tablehandling.lua")
- -- game dependant
- xpos = {"xpos", 0x7e0426,2,"u"}
- xpossub = {"xpos(sub)", 0x7e083A,1,"u"}
- ypos = {"ypos", 0x7e0428,2,"u"}
- xposcam = {"xposcam", 0x7e006A,2,"u"}
- yposcam = {"yposcam", 0x7e006C,2,"u"}
- shells = {"shells", 0x7e08F2,1,"u"}
- acceleration = {"acceleration", 0x7e0838, 2, "s"}
- inair = {"in air",0x7E0812,1,"s", mode="showboth"}
- -- the functions drawcurrentpos and drawoldpos
- -- currently rely on the fact that
- -- RAM_to_track = { xpos, ypos, xposcam, yposcam, [rest can come afterwards ...] }
- RAM_to_track = { xpos, ypos, xposcam, yposcam, shells, acceleration, inair, xpossub }
- drawposition = true
- drawparams = {xoffset = 0, yoffset = 0, width = 32, height = 32}
- -- this script should be modifiable
- -- so that here starts the game-independant part
- function read_ram(t)
- if t[3] == 1 then if t[4]=="u" then return memory.readbyte(t[2]) else return memory.readbytesigned(t[2]) end end
- if t[3] == 2 then
- if t[4]=="u" then
- return memory.readbyte(t[2]) + memory.readbyte(t[2] + 1) * 0x100
- else
- return memory.readwordsigned(t[2])
- end
- end
- if t[3] == 4 then if t[4]=="u" then return memory.readdword(t[2]) else return memory.readdwordsigned(t[2]) end end
- end
- oldrecord = {}
- newrecord = {}
- function drawcurrentpos(size)
- cf = emu.framecount()
- if oldrecord[cf] == nil then return end
- x = newrecord[cf][1] - newrecord[cf][3]
- y = newrecord[cf][2] - newrecord[cf][4]
- gui.box(x, y, x + size, y + size, nil, "yellow")
- end
- function drawoldpos(size)
- cf = emu.framecount()
- if oldrecord[cf] == nil then return end
- x = oldrecord[cf][1] - newrecord[cf][3]
- y = oldrecord[cf][2] - newrecord[cf][4]
- gui.box(x, y, x + size, y + size, nil, "red")
- end
- function nothingpressed()
- local k = joypad.get(1)
- return not(k.left or k.right or k.up or k.down or k.A or k.B or k.X or k.Y or k.L or k.R or k.start or k.select)
- end
- --compressing input reduces the file size significantly (about 85%)
- --and it even becomes more readable this way
- function compressinput(joypadkeys)
- local result = ""
- local convertertable = {
- [true] = {up = "u", left="l", down="d", right = "r", A="A", B="B", X="X", Y="Y", L="L", R="R", start="S", select="s"},
- [false] = {up = ".", left=".", down=".", right = ".", A=".", B=".", X=".", Y=".", L=".", R=".", start=".", select="."}
- }
- for i,s in ipairs({"up","left","down","right","A","B","X","Y","L","R","start","select"}) do
- -- "or false" is required because joypadkeys[s] == nil is possible, and nil or false := false
- result = result .. convertertable[joypadkeys[s] or false][s]
- end
- return result
- end
- function uncompressinput(joypadstring)
- local result = {}
- local convertertable = {
- [true] = {up = "u", left="l", down="d", right = "r", A="A", B="B", X="X", Y="Y", L="L", R="R", start="S", select="s"},
- [false] = {up = ".", left=".", down=".", right = ".", A=".", B=".", X=".", Y=".", L=".", R=".", start=".", select="."}
- }
- local t1, t2
- for a, b in pairs(convertertable[true]) do
- t1, t2 = string.find(joypadstring,b,1)
- --there is a strange lua-specific bug:
- --If you use string.find for the character ^
- --(which I wanted to use for "up") then
- --t1 == 1, t2 == 0 if it was not found...
- result[a] = (t1 ~= nil and t2 ~= nil and t1 >= t2)
- end
- return result
- end
- mode = "recording"
- allowrecordedinput = true
- offset = 0
- while(true) do
- snes9x.frameadvance()
- keys = input.get()
- if keys["R"] == true then
- mode = "replay"
- end
- if keys["F11"] then
- table.save(oldrecord,"recorded.txt")
- gui.text(0,100,"current record saved to recorded.txt")
- end
- if keys["F12"] then
- oldrecord = table.load("recorded.txt")
- gui.text(0,105,"loaded record from recorded.txt")
- mode = "replay"
- end
- if keys["numpad*"] then
- allowrecordedinput = true
- end
- --note: you will see the offset change one
- --frame later than you expect.
- if keys["numpad+"] then
- offset = offset + 1
- end
- if keys["numpad-"] then
- offset = offset - 1
- end
- if keys["numpad0"] then
- offset = 0
- end
- -- slide destination point
- if keys["U"] then
- slide_destx = read_ram(xpos)
- slide_desty = read_ram(ypos)
- gui.text(0,100,"slide destination point set to "..tostring(slide_destx).."//"..tostring(slide_desty))
- emu.pause()
- end
- -- slide test mode
- if keys["I"] then
- assert(slide_destx ~= nil, "setup destination first!")
- slidestartstate = savestate.create()
- savestate.save(slidestartstate)
- gui.text(0,100,"entering special slide mode")
- walkspeed = 2
- best = {startframe = emu.framecount(), finishframe = emu.framecount() + math.abs(slide_destx - read_ram(xpos)) / walkspeed}
- function calculatetime(currentspeed, currentx, destx)
- -- speed is measured as 1/256th of a pixel, and gives the additional speed
- -- against walking (which is 2 pixels per frame). This means
- -- that sliding with a speed of 512 is exactly as fast as
- -- walking, and if you got a speed of 1024 and start to jump,
- -- you will move forward 1024/256 + 2 = 6 pixels per frame
- -- when starting the jump.
- -- When you have your "normal" movement abilities (you're normal
- -- Plok, grandpappy Plok, "firethrowing" Plok, ...), this extra
- -- speed decreases by 7 on each frame you don't touch the ground,
- -- and a lot more if you do touch the ground.
- -- The calculation assumes that you can avoid touching the ground
- -- until you reach the final spot.
- speeddecrease = 7
- walkspeed = 2
- local speed = math.abs(currentspeed)
- distance = math.abs(destx - currentx)
- gui.text(0,200,tostring(speed).."/"..tostring(distance))
- maxboosttime = math.ceil(math.abs((speed / speeddecrease)))
- -- note: position(t + 1) = position(t) + speed(t)/256 + walkspeed
- -- and speed(t) = math.max(speed(0) - speeddecrease(t), 0)
- -- => position(t) = (\sum_{k=0}^{t-1} ( speed(t)/256 + walkspeed )) + position(0) -- assume position(0) = 0
- -- = \sum_{k=0}^{t-1} ( (speed(0) - speeddecrease*k)/256 + walkspeed )
- -- = walkspeed*t + (speeddecrease/256) * \sum_{k=0}^{t-1} (t - k)
- -- = walkspeed*t + (speeddecrease/256) * t * (t + 1) / 2 --if t is less or equal to speed / speeddecrease
- -- This is exact only for positive integer t (=> speed must be a multiple of speeddecrease),
- -- otherwise there will be a small error. Since the error is less than one pixel, it's acceptable.
- maxboost = (speed / speeddecrease + 1) * (speed / speeddecrease) / 2 * speeddecrease / 256 + 2 * maxboosttime
- if maxboost > distance then
- -- we need to solve t^2 + p*t + q = 0
- -- for the given parameters. This calculation is like
- -- using the full boost, and then going backwards the appropriate
- -- amount of time until we exactly hit the desired position
- local p = (512 / speeddecrease) + 1 --more readable than p=74,142857142857142857142857142857
- local q = (256 / speeddecrease) * (distance - maxboost)
- local result = maxboosttime - (-p/2 + math.sqrt(p*p/4 - q))
- gui.text(0,210,tostring(p).."/"..tostring(q).."/"..tostring(result))
- return result
- else
- -- if t is large, then position(t) = maxboost + (t - maxboosttime)*walkspeed
- local result = maxboosttime + (distance - maxboost) / walkspeed
- gui.text(0,210,tostring(maxboost).."\\"..tostring(maxboosttime).."\\"..tostring(result))
- return result
- end
- end
- -- there is no way to tell a priori
- -- how much speed you gain by sliding (it depends
- -- on the angle of the slope, and how long you're
- -- sliding already), so all versions must be tested.
- repeat
- joypad.set(1,{down=true})
- snes9x.frameadvance()
- finishframe = emu.framecount() + calculatetime(read_ram(acceleration), read_ram(xpos), slide_destx)
- if finishframe < best.finishframe then
- best.finishframe = finishframe
- best.startframe = emu.framecount()
- end
- gui.text(0,100,"last result: " .. tostring(finishframe))
- gui.text(0,110,"best result: " .. tostring(best.finishframe) .. ", jumping at frame " .. best.startframe )
- keys = input.get()
- until keys["I"]
- savestate.load(slidestartstate)
- while(emu.framecount() < best.startframe - 1) do
- joypad.set(1,{down=true})
- snes9x.frameadvance()
- end
- gui.text(0,120," begin the jump now!")
- emu.pause()
- end
- -- "recording" mode records all RAM values specified,
- -- and controller data (for first controller)
- if mode == "recording" then
- gui.text(90,10,"offset: " .. tostring(offset))
- gui.text(180,0,"recording, press R to switch")
- cf = emu.framecount()
- for i, r in ipairs(RAM_to_track) do
- if oldrecord[cf] == nil then
- oldrecord[cf] = {}
- end
- temp = read_ram(r)
- if temp == nil then
- gui.text(180,i*10, "FAILURE at reading " .. r[1])
- else
- gui.text(180,i*10,r[1] .. " : " .. tostring(temp))
- end
- oldrecord[cf][i] = temp
- end
- oldrecord[cf][0] = compressinput(joypad.get(1))
- end
- -- "replay" mode does the following:
- -- _show differnces to previous record
- -- _if the emulator is in "rerecording" mode, it will input
- -- the previously recorded keys
- -- _you can specify an offset for that by pressing
- -- numpad+, numpad-, or numpad0_
- -- or you can override it by entering some new input.
- -- _if you want no input in the next frame,
- -- use numpad*
- if mode == "replay" then
- gui.text(180,0,"replay mode (old minus new)")
- cf = emu.framecount()
- newrecord[cf] = {}
- gui.text(180,10,"offset: " .. tostring(offset))
- for i, r in ipairs(RAM_to_track) do
- newrecord[cf][i] = read_ram(r)
- if oldrecord[cf] ~= nil and oldrecord[cf][i] ~= nil then
- if r.mode == "showboth" then
- gui.text(180,(i+1)*10,r[1] .. " : " .. tostring(oldrecord[cf][i]) .. "||" ..tostring(newrecord[cf][i]))
- if oldrecord[cf+offset] ~= nil and oldrecord[cf+offset][i] ~= nil then
- -- if the inserted part syncs perfectly, you should always see "somevalue"||"thesamevalue"
- gui.text(180, 128 + (i+1)*10,r[1] .. " : " .. tostring(oldrecord[cf+offset][i]) .. "||" ..tostring(newrecord[cf][i]))
- end
- else
- gui.text(180, (i+1)*10,r[1] .. " : " .. tostring(oldrecord[cf][i] - newrecord[cf][i]))
- if oldrecord[cf+offset] ~= nil and oldrecord[cf+offset][i] ~= nil then
- -- if the inserted part syncs perfectly, you should always see the value 0 here
- gui.text(180, 128 + (i+1)*10,r[1] .. " : " .. tostring(oldrecord[cf+offset][i] - newrecord[cf][i]))
- end
- end
- else
- if i==1 then gui.text(180,(i+1)*10, "no record found for " .. r[1]) end
- end
- end
- drawoldpos(32)
- drawcurrentpos(32)
- if oldrecord[cf+offset] ~= nil and oldrecord[cf+offset][0] ~= nil then
- if not nothingpressed() or not allowrecordedinput then
- gui.text(0, 0, "MANUAL input used; numpad* for recorded input.")
- allowrecordedinput = false
- else
- joypad.set(1,uncompressinput(oldrecord[cf+offset][0]))
- gui.text(0, 0, "RECORDED input used from frame " .. tostring(cf+offset))
- end
- else
- gui.text(0,0,"no controller data available")
- end
- if keys["end"] then mode = "recording" end
- if keys["numpad/"] then oldrecord = newrecord end
- end
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement