Advertisement
partyboy1a

Plok5.lua

Sep 2nd, 2011
91
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 18.09 KB | None | 0 0
  1. --[[
  2. ********************************
  3. * Plok helper script version 5 *
  4. ********************************
  5.  
  6. IMPORTANT NOTE: Lua can't handle any input while
  7. the emulator is paused. Therefore, whenever you see
  8. an instruction like "press [whateverkey]", you either
  9. need to unpause the emulator beforehand, or you need
  10. to press [whateverkey]+frameadvance.
  11. All the Lua text displays are delayed by one frame.
  12.  
  13. "recording mode" keeps track of all the input you give
  14. while being in this mode. It also records some important
  15. RAM values, which can easily be adjusted to work for
  16. different games.
  17. You can save everything that was recorded in this mode
  18. by pressing F11 on the keyboard. Lua will give you a
  19. warning message when you do this, because it takes some time.
  20. You can load such a record by pressing F12. Lua will give you a
  21. warning message when you do this, because it takes some time.
  22. You can switch to "replay mode" by pressing R on the keyboard.
  23.  
  24. "replay mode" works the following way:
  25. If the script is allowed to do the input for you,
  26. then it will input exactly what you recorded during
  27. "recording mode" previously. You can adjust when the input is
  28. applied by setting the offset.
  29. Example: You pressed A on frame 2000, and B on frame 2100 in recording
  30. mode. Let's assume you could improve your movie before this sequence by
  31. exactly 30 frames. Now you want to use all of the recorded input again,
  32. but 30 frames earlier than before.
  33. Then you would do the following:
  34. - Playback your old movie at least until frame 2100.
  35. - Set the offset (see below) to 30
  36. - Switch to replay mode.
  37. - Load your new movie, and let it playback until some point
  38.     before frame 1970, and create a savestate right there.
  39. - Pause the emulator, switch to read+write mode, load the state,
  40.     and press numpad* + frameadvance (see below)
  41. Now the script will input A on frame 1970, and B on frame 2070
  42. for you.
  43. Now let's say that you had 5 less lag frames in between A and B.
  44. (This script will help you detecting lag, see below).
  45. Then you can still use the recorded input:
  46. - load the state you created above.
  47. - let the script replay the game for you up right after the
  48.     "lost" lag, and create another savestate.
  49. - set the offset to 30 - 5 = 25.
  50. - load the state.
  51. Now let's say you want to press right on frame 2050, and leave
  52. everything else as before. Then you would do the following:
  53. - load the state you created above, and let the new movie
  54.     play up to frame 2049. The script should show you that
  55.     RECORDED input was used.
  56. - press right + frameadvance. The script should show you that
  57.     MANUAL input was used.
  58. - press numpad* + frameadvance. (see below). The script should
  59.     show you that RECORDED input was used.
  60. - unpause the emulator. Everything will work as above from now on.
  61.  
  62. The offset can be adjusted by pressing numpad+ + frameadvance,
  63. numpad- + frameadvance, or numpad0 + frameadvance. In each case,
  64. the game will advance one frame. numpad+ will increase the offset
  65. by one, numpad- will decrease the offset by one, and numpad0
  66. will reset it. If you adjust the offset while (re)recording,
  67. you most likely have to load a savestate before you adjusted the
  68. offset.
  69.  
  70. When you're in replay mode, you will see a message telling you
  71. if MANUAL input was used, or if RECORDED input was used.
  72. When you're playing back a movie, this will be counted as
  73. MANUAL input. As long as the script displays that manual
  74. input was used, it will not try to apply the recorded input.
  75. When you press numpad*, you allow the script to use recorded
  76. input again. When you're playing back a movie file write-protected
  77. (you see "Playing"), this won't work, and it will show you that
  78. MANUAL input was used.
  79. If you press any buttons on your controller while the script
  80. shows you that RECORDED input was used, it will now start
  81. to display that MANUAL input was used until you press
  82. numpad* + frameadvance.
  83.  
  84. You will see two rather large tables during "replay" mode.
  85. The upper table shows you the difference between the "recorded"
  86. session, and what you're doing right now.
  87. Let's say you went right all the time, stopped for 10 frames,
  88. and then you jumped and pressed right. Let's say you completed
  89. the whole level afterwards, and forgot that you stopped. You
  90. have created a savestate before all this action, and you were
  91. in "recording mode" all the time. Now you want to remove this
  92. mistake. This works the following way:
  93. - switch to replay mode
  94. - load the savestate (the emulator should tell you now that
  95.     you're rerecording)
  96. - let the script play the game for you up to somewhere right
  97.     before the mistake.
  98. - create a savestate, and now fix the mistake.
  99. - create another savestate, set the offset accordingly. The lower
  100.     table will help you to find the right offset (see below).
  101.     Load this later state again
  102. - press numpad* + frameadvance to let the script play
  103.     the rest of the level again.
  104. Now you have effectively done the same as if you fixed the
  105. mistake with the help of a hex-editor...
  106. The lower table is effectively the same as a "desync indicator".
  107. It shows you the differences between what you do now
  108. and what happened (frameoffset) frames later during the
  109. previously recorded session.
  110. If you have improved an older section by 40 frames, and used
  111. the instructions above, and it syncs perfectly, the lower table
  112. will show either 0, or "somevalue"||"samevalue" all the time.
  113.  
  114. Let's say your improvement caused a visible desync, because
  115. two consecutive lag frames didn't appear. The table at the bottom will stop displaying
  116. all-zero exactly at the frame the desync really occured. Let's say you're
  117. moving 3 pixels per frame. Because you're now one frame more ahead than
  118. you should, it will now display -6 for the x value. If you create a savestate,
  119. lower the offset by 2, and load the state again, it should sync again
  120. (given that these changes didn't mess up some luck manipulation...).
  121.  
  122. *******************************************************
  123. * Functions implemented especially for Plok
  124. *******************************************************
  125. "Special slide mode":
  126. Sometimes you can see a slide, and you want to reach a certain location
  127. as fast as possible. If you don't lose all the extra speed from sliding
  128. before the level exit, it is very difficult to say how long you should
  129. actually slide, and when you should begin to jump and press down+right
  130. or down+left, because the jump will be 2 pixels per frame faster right
  131. at the beginning, but it will slow down by 7/256 of a pixel per frame.
  132. Sliding instead will increase the extra speed even further, but you don't
  133. gain the extra speed from pressing right.
  134.  
  135. How it works:
  136. Suppose you're at x=100 on a slope with speed=0.
  137. The slope goes on until x=300
  138. You want to reach x=400 as fast as possible. It doesn't matter for
  139. you to get to any place x > 400 at the shortest amount of time.
  140. Then you do the following:
  141. - create a savestate when standing at x=100
  142. - go to x=400, press "U".
  143. - load the savestate, press "I"+frameadvance
  144. - when you got to the end of the slope, press I+frameadvance again.
  145. - the script will show you the best location for starting the jump.
  146.  
  147. --]]
  148. dofile("tablehandling.lua")
  149.  
  150. -- game dependant
  151. xpos = {"xpos", 0x7e0426,2,"u"}
  152. xpossub = {"xpos(sub)", 0x7e083A,1,"u"}
  153. ypos = {"ypos", 0x7e0428,2,"u"}
  154. xposcam = {"xposcam", 0x7e006A,2,"u"}
  155. yposcam = {"yposcam", 0x7e006C,2,"u"}
  156. shells = {"shells", 0x7e08F2,1,"u"}
  157. acceleration = {"acceleration", 0x7e0838, 2, "s"}
  158. inair = {"in air",0x7E0812,1,"s", mode="showboth"}
  159. -- the functions drawcurrentpos and drawoldpos
  160. -- currently rely on the fact that
  161. -- RAM_to_track = { xpos, ypos, xposcam, yposcam, [rest can come afterwards ...] }
  162. RAM_to_track = { xpos, ypos, xposcam, yposcam, shells, acceleration, inair, xpossub }
  163. drawposition = true
  164. drawparams = {xoffset = 0, yoffset = 0, width = 32, height = 32}
  165.  
  166. -- this script should be modifiable
  167. -- so that here starts the game-independant part
  168. function read_ram(t)
  169.     if t[3] == 1 then if t[4]=="u" then return memory.readbyte(t[2])  else return memory.readbytesigned(t[2])  end end
  170.     if t[3] == 2 then
  171.         if t[4]=="u" then
  172.             return memory.readbyte(t[2]) + memory.readbyte(t[2] + 1) * 0x100
  173.         else
  174.             return memory.readwordsigned(t[2])
  175.         end
  176.     end
  177.     if t[3] == 4 then if t[4]=="u" then return memory.readdword(t[2]) else return memory.readdwordsigned(t[2]) end end
  178. end
  179.  
  180. oldrecord = {}
  181. newrecord = {}
  182.  
  183. function drawcurrentpos(size)
  184.     cf = emu.framecount()
  185.     if oldrecord[cf] == nil then return end
  186.     x = newrecord[cf][1] - newrecord[cf][3]
  187.     y = newrecord[cf][2] - newrecord[cf][4]
  188.     gui.box(x, y, x + size, y + size, nil, "yellow")
  189. end
  190. function drawoldpos(size)
  191.     cf = emu.framecount()
  192.     if oldrecord[cf] == nil then return end
  193.     x = oldrecord[cf][1] - newrecord[cf][3]
  194.     y = oldrecord[cf][2] - newrecord[cf][4]
  195.     gui.box(x, y, x + size, y + size, nil, "red")
  196. end
  197.  
  198. function nothingpressed()
  199.     local k = joypad.get(1)
  200.     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)
  201. end
  202.  
  203. --compressing input reduces the file size significantly (about 85%)
  204. --and it even becomes more readable this way
  205. function compressinput(joypadkeys)
  206.     local result = ""
  207.     local convertertable = {
  208.         [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"},
  209.         [false] = {up = ".", left=".", down=".", right = ".", A=".", B=".", X=".", Y=".", L=".", R=".", start=".", select="."}
  210.     }
  211.     for i,s in ipairs({"up","left","down","right","A","B","X","Y","L","R","start","select"}) do
  212.         -- "or false" is required because joypadkeys[s] == nil is possible, and nil or false := false
  213.         result = result .. convertertable[joypadkeys[s] or false][s]
  214.     end
  215.     return result
  216. end
  217. function uncompressinput(joypadstring)
  218.     local result = {}
  219.     local convertertable = {
  220.         [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"},
  221.         [false] = {up = ".", left=".", down=".", right = ".", A=".", B=".", X=".", Y=".", L=".", R=".", start=".", select="."}
  222.     }
  223.     local t1, t2
  224.     for a, b in pairs(convertertable[true]) do
  225.         t1, t2 = string.find(joypadstring,b,1)
  226.         --there is a strange lua-specific bug:
  227.         --If you use string.find for the character ^
  228.         --(which I wanted to use for "up") then
  229.         --t1 == 1, t2 == 0 if it was not found...
  230.         result[a] = (t1 ~= nil and t2 ~= nil and t1 >= t2)
  231.     end
  232.     return result
  233. end
  234.  
  235. mode = "recording"
  236. allowrecordedinput = true
  237. offset = 0
  238. while(true) do
  239.     snes9x.frameadvance()
  240.     keys = input.get()
  241.     if keys["R"] == true then
  242.         mode = "replay"
  243.     end
  244.     if keys["F11"] then
  245.         table.save(oldrecord,"recorded.txt")
  246.         gui.text(0,100,"current record saved to recorded.txt")
  247.     end
  248.     if keys["F12"] then
  249.         oldrecord = table.load("recorded.txt")
  250.         gui.text(0,105,"loaded record from recorded.txt")
  251.         mode = "replay"
  252.     end
  253.     if keys["numpad*"] then
  254.         allowrecordedinput = true
  255.     end
  256.    
  257.     --note: you will see the offset change one
  258.     --frame later than you expect.
  259.     if keys["numpad+"] then
  260.         offset = offset + 1
  261.     end
  262.     if keys["numpad-"] then
  263.         offset = offset - 1
  264.     end
  265.     if keys["numpad0"] then
  266.         offset = 0
  267.     end
  268.    
  269.     -- slide destination point
  270.     if keys["U"] then
  271.         slide_destx = read_ram(xpos)
  272.         slide_desty = read_ram(ypos)
  273.         gui.text(0,100,"slide destination point set to "..tostring(slide_destx).."//"..tostring(slide_desty))
  274.         emu.pause()
  275.     end
  276.    
  277.     -- slide test mode
  278.     if keys["I"] then
  279.         assert(slide_destx ~= nil, "setup destination first!")
  280.         slidestartstate = savestate.create()
  281.         savestate.save(slidestartstate)
  282.        
  283.         gui.text(0,100,"entering special slide mode")
  284.         walkspeed = 2
  285.         best = {startframe = emu.framecount(), finishframe = emu.framecount() + math.abs(slide_destx - read_ram(xpos)) / walkspeed}
  286.        
  287.         function calculatetime(currentspeed, currentx, destx)
  288.             -- speed is measured as 1/256th of a pixel, and gives the additional speed
  289.             -- against walking (which is 2 pixels per frame). This means
  290.             -- that sliding with a speed of 512 is exactly as fast as
  291.             -- walking, and if you got a speed of 1024 and start to jump,
  292.             -- you will move forward 1024/256 + 2 = 6 pixels per frame
  293.             -- when starting the jump.
  294.             -- When you have your "normal" movement abilities (you're normal
  295.             -- Plok, grandpappy Plok, "firethrowing" Plok, ...), this extra
  296.             -- speed decreases by 7 on each frame you don't touch the ground,
  297.             -- and a lot more if you do touch the ground.
  298.             -- The calculation assumes that you can avoid touching the ground
  299.             -- until you reach the final spot.
  300.             speeddecrease = 7
  301.             walkspeed = 2
  302.             local speed = math.abs(currentspeed)
  303.             distance = math.abs(destx - currentx)
  304.             gui.text(0,200,tostring(speed).."/"..tostring(distance))
  305.             maxboosttime = math.ceil(math.abs((speed / speeddecrease)))
  306.             -- note: position(t + 1) = position(t) + speed(t)/256 + walkspeed
  307.             -- and speed(t) = math.max(speed(0) - speeddecrease(t), 0)
  308.             -- => position(t) = (\sum_{k=0}^{t-1} ( speed(t)/256 + walkspeed )) + position(0) -- assume position(0) = 0
  309.             --                = \sum_{k=0}^{t-1} ( (speed(0) - speeddecrease*k)/256 + walkspeed )
  310.             --                = walkspeed*t + (speeddecrease/256) * \sum_{k=0}^{t-1} (t - k)
  311.             --                = walkspeed*t + (speeddecrease/256) * t * (t + 1) / 2 --if t is less or equal to speed / speeddecrease
  312.             -- This is exact only for positive integer t (=> speed must be a multiple of speeddecrease),
  313.             -- otherwise there will be a small error. Since the error is less than one pixel, it's acceptable.
  314.             maxboost = (speed / speeddecrease + 1) * (speed / speeddecrease) / 2 * speeddecrease / 256 + 2 * maxboosttime
  315.             if maxboost > distance then
  316.                 -- we need to solve t^2 + p*t + q = 0
  317.                 -- for the given parameters. This calculation is like
  318.                 -- using the full boost, and then going backwards the appropriate
  319.                 -- amount of time until we exactly hit the desired position
  320.                 local p = (512 / speeddecrease) + 1 --more readable than p=74,142857142857142857142857142857
  321.                 local q = (256 / speeddecrease) * (distance - maxboost)
  322.                 local result = maxboosttime - (-p/2 + math.sqrt(p*p/4 - q))
  323.                 gui.text(0,210,tostring(p).."/"..tostring(q).."/"..tostring(result))
  324.                 return result
  325.             else
  326.                 -- if t is large, then position(t) = maxboost + (t - maxboosttime)*walkspeed
  327.                 local result = maxboosttime + (distance - maxboost) / walkspeed
  328.                 gui.text(0,210,tostring(maxboost).."\\"..tostring(maxboosttime).."\\"..tostring(result))
  329.                 return result
  330.             end
  331.         end    
  332.        
  333.         -- there is no way to tell a priori
  334.         -- how much speed you gain by sliding (it depends
  335.         -- on the angle of the slope, and how long you're
  336.         -- sliding already), so all versions must be tested.
  337.         repeat
  338.             joypad.set(1,{down=true})
  339.             snes9x.frameadvance()
  340.             finishframe = emu.framecount() + calculatetime(read_ram(acceleration), read_ram(xpos), slide_destx)
  341.             if finishframe < best.finishframe then
  342.                 best.finishframe = finishframe
  343.                 best.startframe = emu.framecount()
  344.             end
  345.             gui.text(0,100,"last result: " .. tostring(finishframe))
  346.             gui.text(0,110,"best result: " .. tostring(best.finishframe) .. ", jumping at frame " .. best.startframe )
  347.             keys = input.get()
  348.         until keys["I"]
  349.        
  350.         savestate.load(slidestartstate)
  351.         while(emu.framecount() < best.startframe - 1) do
  352.             joypad.set(1,{down=true})
  353.             snes9x.frameadvance()
  354.         end
  355.        
  356.         gui.text(0,120," begin the jump now!")
  357.         emu.pause()
  358.     end
  359.     -- "recording" mode records all RAM values specified,
  360.     -- and controller data (for first controller)
  361.     if mode == "recording" then
  362.         gui.text(90,10,"offset: " .. tostring(offset))
  363.         gui.text(180,0,"recording, press R to switch")
  364.         cf = emu.framecount()
  365.         for i, r in ipairs(RAM_to_track) do
  366.             if oldrecord[cf] == nil then
  367.                 oldrecord[cf] = {}
  368.             end
  369.             temp = read_ram(r)
  370.             if temp == nil then
  371.                 gui.text(180,i*10, "FAILURE at reading " .. r[1])
  372.             else
  373.                 gui.text(180,i*10,r[1] .. " : " .. tostring(temp))
  374.             end
  375.             oldrecord[cf][i] = temp
  376.         end
  377.         oldrecord[cf][0] = compressinput(joypad.get(1))
  378.     end
  379.     -- "replay" mode does the following:
  380.     -- _show differnces to previous record
  381.     -- _if the emulator is in "rerecording" mode, it will input
  382.     --   the previously recorded keys
  383.     -- _you can specify an offset for that by pressing
  384.     --   numpad+, numpad-, or numpad0_
  385.     --   or you can override it by entering some new input.
  386.     -- _if you want no input in the next frame,
  387.     --   use numpad*
  388.     if mode == "replay" then
  389.         gui.text(180,0,"replay mode (old minus new)")
  390.         cf = emu.framecount()
  391.         newrecord[cf] = {}
  392.         gui.text(180,10,"offset: " .. tostring(offset))
  393.         for i, r in ipairs(RAM_to_track) do
  394.             newrecord[cf][i] = read_ram(r)
  395.             if oldrecord[cf] ~= nil and oldrecord[cf][i] ~= nil then
  396.                 if r.mode == "showboth" then
  397.                     gui.text(180,(i+1)*10,r[1] .. " : " .. tostring(oldrecord[cf][i]) .. "||" ..tostring(newrecord[cf][i]))
  398.                     if oldrecord[cf+offset] ~= nil and oldrecord[cf+offset][i] ~= nil then
  399.                         -- if the inserted part syncs perfectly, you should always see "somevalue"||"thesamevalue"
  400.                         gui.text(180, 128 + (i+1)*10,r[1] .. " : " .. tostring(oldrecord[cf+offset][i]) .. "||" ..tostring(newrecord[cf][i]))
  401.                     end
  402.                 else   
  403.                     gui.text(180, (i+1)*10,r[1] .. " : " .. tostring(oldrecord[cf][i] - newrecord[cf][i]))
  404.                     if oldrecord[cf+offset] ~= nil and oldrecord[cf+offset][i] ~= nil then
  405.                         -- if the inserted part syncs perfectly, you should always see the value 0 here
  406.                         gui.text(180, 128 + (i+1)*10,r[1] .. " : " .. tostring(oldrecord[cf+offset][i] - newrecord[cf][i]))
  407.                     end
  408.                 end
  409.             else
  410.                 if i==1 then gui.text(180,(i+1)*10, "no record found for " .. r[1]) end
  411.             end
  412.         end
  413.         drawoldpos(32)
  414.         drawcurrentpos(32)
  415.        
  416.         if oldrecord[cf+offset] ~= nil and oldrecord[cf+offset][0] ~= nil then
  417.             if not nothingpressed() or not allowrecordedinput then
  418.                 gui.text(0, 0, "MANUAL input used; numpad* for recorded input.")
  419.                 allowrecordedinput = false
  420.             else
  421.                 joypad.set(1,uncompressinput(oldrecord[cf+offset][0]))
  422.                 gui.text(0, 0, "RECORDED input used from frame " .. tostring(cf+offset))
  423.             end
  424.         else
  425.             gui.text(0,0,"no controller data available")
  426.         end
  427.        
  428.         if keys["end"] then mode = "recording" end
  429.         if keys["numpad/"] then oldrecord = newrecord end
  430.     end
  431.    
  432. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement