Advertisement
Arnethegreat

Untitled

Jul 19th, 2013
89
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 28.35 KB | None | 0 0
  1. --------------------  ----------------  ----------------------  ---------------
  2. -- yoshi's island --  -- lua script --  -- work in progress --  -- (nitsuja) --
  3. --------------------  ----------------  ----------------------  ---------------
  4.  
  5. -- configuration
  6. -- these describe (and determine) the basic capabilities of this script:
  7. drawobjectnames        = true -- draws name if known, or object ID if not
  8. drawobjecthitboxes     = false -- note: hitboxes are not 100% accurate yet
  9. drawobjectcenterpoints = true -- draws a little dot at objects' reported centers
  10. drawplayerhitbox       = false -- currently only correct if player is yoshi
  11. drawsavestatehitboxes  = false -- player hitbox for other savestates (GHOST DISPLAY)
  12. drawsavestatenumbers   = false -- draws the slot number next to them
  13. drawplayertrail        = true -- shows 90 frames or so of speed/position info
  14. drawsavestatetrails    = false-- show player trail for other savestates
  15. drawspeedometer        = true -- draws xvel, yvel numbers constantly
  16.  
  17. -- if you're comparing against another movie,
  18. -- save before the end of that movie onto this slot number
  19. -- (you might have to rename the .luasav file to match your new movie)
  20. specialslot = 10
  21.  
  22. -- as you get ahead in each room or level,
  23. -- increase this by the number of frames you're ahead of the other movie
  24. -- to resynchronize ongoing comparisons with the special slot
  25. adjustframeaheadby = 0
  26.  
  27. -- I recommend leaving these on
  28. smartmatchguiobjects   = true -- takes drawing time into account so GUI matches
  29. smartmatchguicamera    = true -- takes camera update time into account for GUI
  30. smartsyncframeupdate   = true -- sync to YI's frame counter update, things go a bit wacky otherwise (especially trails)
  31.  
  32. --------------------------------------------------------------------------------
  33. --------------------------------------------------------------------------------
  34.  
  35.  
  36.  
  37. -- TODO: save this to a file so it doesn't reset to 0 every time the script restarts
  38. currentstatenumber = 0
  39.  
  40. emu = emu or snes9x or gens gens = emu or snes9x or gens snes9x = emu or snes9x or gens
  41.  
  42. -- prevent text from clipping off the screen edges
  43. local oldguitext = gui.text
  44. gui.text = function(x,y,text)
  45.     if type(text) ~= 'string' then text = tostring(text) end
  46.     local width = 4 * #text
  47.     if x+width > 256 then x=256-width end
  48.     if x < 0 then x = 0 end
  49.     if y > 216 then y = 216 end
  50.     if y < 0 then y = 0 end
  51.     oldguitext(x,y,text)
  52. end
  53.  
  54.  
  55. --memory.writeword(0x70008C, memory.readword(0x70008C) + 100)
  56.  
  57. --  gui.text(30,30,memory.readword(0x7001b4)) -- standing on object flag!
  58.  
  59.  
  60. -- because snes9x require numbered savestates to be 'created' to use them
  61. local saveslot = {}
  62. for n = 1,12 do
  63.     saveslot[n] = savestate.create(n)
  64. end
  65.  
  66.  
  67. local copytable = function(t)
  68.     if t == nil then return nil end
  69.     local c = {}
  70.     for k,v in pairs(t) do
  71.         c[k] = v
  72.     end
  73. --  setmetatable(c,debug.getmetatable(t))
  74.     return c
  75. end
  76.  
  77. -- for copying a table in frameinforecords while discarding non-recent entries
  78. local copytablepartial = function(t)
  79.     if t == nil then return nil end
  80.     local abs = math.abs
  81.     local now = getinstantid()
  82.     local c = {}
  83.     for k,v in pairs(t) do
  84.         if type(k) ~= 'number' or abs(k-now) < 3600 then
  85.             c[k] = v
  86.         end
  87.     end
  88.     return c
  89. end
  90.  
  91.  
  92.  
  93.  
  94.  
  95.  
  96. -- a record of frame information
  97. -- looks like: {savenumber1={time1={x1,y1},time2={x2,y2},...},savenumber2=...}
  98. frameinforecords = {} -- initially it's an empty table
  99.  
  100. -- a helper function to get the info we want to store per in-game frame
  101. -- (for now this is only the player position)
  102. function getperframeinfo()
  103.     local x = memory.readword(0x70008C) + (memory.readbyte(0x70008A) / 256)
  104.     local y = memory.readword(0x700090) + (memory.readbyte(0x70008E) / 256)
  105. --  local y2 = memory.readword(0x700011E)
  106. --  local yext = memory.readword(0x7000112)
  107.     local info = {x,y}
  108.     return info
  109. end
  110.  
  111. -- time function, might want to use in-game time here in some games
  112. function getinstantid()
  113. --  return memory.readword(0x7E0030)
  114.     return emu.framecount()
  115. end
  116.  
  117.  
  118. -- if we draw with the current camera position things will look a little off
  119. -- when the camera is moving, so use the camera positon slightly in the past
  120. -- (do this by keeping with a circular buffer of x and y camera positions)
  121.  
  122. xcamhist = {0,0,0,0} -- don't need so many but whatever...
  123. ycamhist = {0,0,0,0}
  124. local function tickcam()
  125.     xcamhist[1 + (getinstantid() % 4)] = memory.readword(0x7E0039)
  126.     ycamhist[1 + (getinstantid() % 4)] = memory.readword(0x7E003B)
  127. end
  128. local function resetcam()
  129.     local xcam = memory.readword(0x7E0039)
  130.     local ycam = memory.readword(0x7E003B)
  131.     for k,v in pairs(xcamhist) do xcamhist[k] = xcam end
  132.     for k,v in pairs(ycamhist) do ycamhist[k] = ycam end
  133. end
  134. resetcam()
  135.  
  136. local getxcam
  137. local getycam
  138.  
  139. if smartmatchguicamera then
  140.  
  141.     getxcam = function()
  142.         return xcamhist[1 + ((4-1 + getinstantid()) % 4)]
  143.     end
  144.     getycam = function()
  145.         return ycamhist[1 + ((4-1 + getinstantid()) % 4)]
  146.     end
  147.  
  148. else
  149.     getxcam = function()
  150.         return memory.readword(0x7E0039)
  151.     end
  152.     getycam = function()
  153.         return memory.readword(0x7E003B)
  154.     end
  155. end
  156.  
  157.  
  158.  
  159.  
  160.  
  161.  
  162.  
  163.  
  164.  
  165. -- the game takes 2 frames to render the current game state,
  166. -- so we have to buffer our rendering by 2 frames to make it match up.
  167. -- it's a little more complicated than that though.
  168. --
  169. -- to make a long story short,
  170. -- what you have to do is NOT call the functions like "gui.box",
  171. -- and instead call the functions named like "drawboxinworld"
  172. -- (using x,y coordinates in WORLD coordinates, don't subtract camera pos),
  173. -- then also call drawdeferred at the start of each gui render.
  174. -- that way your gui drawing should match up perfectly with the game.
  175.  
  176.  
  177. local function clearoldhist(hist,now)
  178.     for k,v in pairs(hist) do
  179.         if k > now or k < now-4 then
  180.             hist[k] = nil
  181.         end
  182.     end
  183. end
  184.  
  185. local function gethistitem(hist,now)
  186.     for i=-3,0 do
  187.         local histindex = 1 + (now + i)
  188.         items = hist[histindex]
  189.         if items then return items end
  190.     end
  191. end
  192.  
  193. local boxhist = {}
  194. local texthist = {}
  195. local linehist = {}
  196. local pixhist = {}
  197.  
  198. local function clearalloldhist(now)
  199.     clearoldhist(boxhist,now)
  200.     clearoldhist(texthist,now)
  201.     clearoldhist(linehist,now)
  202.     clearoldhist(pixhist,now)
  203. end
  204.  
  205. function drawdeferred()
  206.     local now = getinstantid()
  207.    
  208.     -- clear out old entries
  209.     clearalloldhist(now)
  210.  
  211.     local camx = getxcam()
  212.     local camy = getycam()
  213.  
  214.     local lineitems = gethistitem(linehist,now)
  215.     if lineitems then
  216.         local drawline = gui.drawline
  217.         for i,args in ipairs(lineitems) do
  218.             local x1,y1,x2,y2,color = unpack(args)
  219.             drawline(x1-camx,y1-camy,x2-camx,y2-camy,color)
  220.         end
  221.     end
  222.  
  223.     local boxitems = gethistitem(boxhist,now)
  224.     if boxitems then
  225.         local drawbox = gui.drawbox
  226.         for i,args in ipairs(boxitems) do
  227.             local x1,y1,x2,y2,color = unpack(args)
  228.             drawbox(x1-camx,y1-camy,x2-camx,y2-camy,color)
  229.         end
  230.     end
  231.  
  232.     local textitems = gethistitem(texthist,now)
  233.     if textitems then
  234.         local drawtext = gui.text
  235.         for i,args in ipairs(textitems) do
  236.             local x,y,msg = unpack(args) -- TODO: support passing through color
  237.             drawtext(x-camx,y-camy,msg)
  238.         end
  239.     end
  240.  
  241.     local pixitems = gethistitem(pixhist,now)
  242.     if pixitems then
  243.         local drawpixel = gui.drawpixel
  244.         for i,args in ipairs(pixitems) do
  245.             local x,y,color = unpack(args)
  246.             drawpixel(x-camx,y-camy,color)
  247.         end
  248.     end
  249. end
  250.  
  251. local drawboxinworld
  252. local drawtextinworld
  253. local drawlineinworld
  254. local drawpixelinworld
  255.  
  256. if smartmatchguiobjects then
  257.     drawboxinworld = function (...)
  258.         local now = getinstantid()
  259.         local items = boxhist[now]
  260.         if not items then items = {} boxhist[now] = items end
  261.         items[#items+1] = {...}
  262.     end
  263.     drawtextinworld = function(...)
  264.         local now = getinstantid()
  265.         local items = texthist[now]
  266.         if not items then items = {} texthist[now] = items end
  267.         items[#items+1] = {...}
  268.     end
  269.     drawlineinworld = function(...)
  270.         local now = getinstantid()
  271.         local items = linehist[now]
  272.         if not items then items = {} linehist[now] = items end
  273.         items[#items+1] = {...}
  274.     end
  275.     drawpixelinworld = function(...)
  276.         local now = getinstantid()
  277.         local items = pixhist[now]
  278.         if not items then items = {} pixhist[now] = items end
  279.         items[#items+1] = {...}
  280.     end
  281. else
  282.     drawlineinworld = function (x1,y1,x2,y2,color)
  283.         local camx = getxcam()
  284.         local camy = getycam()
  285.         gui.drawline(x1-camx,y1-camy,x2-camx,y2-camy,color)
  286.     end
  287.     drawtextinworld = function (x,y,msg)
  288.         gui.text(x-getxcam(),y-getycam(),msg) -- TODO: support passing through color
  289.     end
  290.     drawboxinworld = function (x1,y1,x2,y2,color)
  291.         local camx = getxcam()
  292.         local camy = getycam()
  293.         gui.drawbox(x1-camx,y1-camy,x2-camx,y2-camy,color)
  294.     end
  295.     drawpixelinworld = function (x,y,color)
  296.         gui.drawpixel(x-getxcam(),y-getycam(),color)
  297.     end
  298. end
  299.  
  300.  
  301.  
  302.  
  303.  
  304.  
  305.  
  306.  
  307.  
  308.  
  309.  
  310.  
  311.  
  312.  
  313. local function renderpasttrajectory(records, id, tracknumber)
  314.  
  315.     if (tracknumber == currentstatenumber and not drawplayertrail) or (tracknumber ~= currentstatenumber and not drawsavestatetrails) then
  316.         return
  317.     end
  318.  
  319.     local points = {}
  320.     local ids = {}
  321.  
  322.     local lastx
  323.     local j = 1
  324.     for i=id,id-90,-1 do
  325.         local info = records[i]
  326.         if info then
  327.             points[j] = info
  328.             ids[j] = i
  329.             j = j + 1
  330.         end
  331.     end
  332.  
  333.     local actualcamx = getxcam()
  334.     local actualcamy = getycam()
  335.  
  336.     local camx = 0 --getxcam()
  337.     local camy = 0 --getycam()
  338.    
  339.     local width = 16
  340.     local height = 32
  341.  
  342.     -- adjust the player hitbox to match up better with the world... (HACK)
  343.     camy = camy - 3  -- y = y + 3
  344.     height = height - 5
  345.     camx = camx - 1  -- x = x + 1
  346.     width = width - 2
  347.    
  348.    
  349.     local outlinecolor
  350.     if tracknumber == -1 then
  351.         outlinecolor = 0xFFFFFFFF
  352.     elseif tracknumber == 0 then
  353.         outlinecolor = 0x0000FFFF
  354.     else
  355.         outlinecolor = 0x7F7FFFFF
  356.     end
  357.    
  358.     local alt = math.floor(id/4)%2==0
  359.    
  360.     local skipfirst = false
  361.    
  362.     local floor = math.floor
  363.    
  364.     for i=1,j-2 do
  365.    
  366.         local a = points[i]
  367.         local b = points[i+1]
  368.         local ax = a[1] - camx
  369.         local ay = a[2] - camy
  370.         local bx = b[1] - camx
  371.         local by = b[2] - camy
  372.        
  373. --      local transp = 255 * (j-i) / j
  374. --      local color = OR(AND(outlinecolor, 0xFFFFFF00), transp)
  375.  
  376.         local color = '#FFFFFF'
  377.  
  378.         local dx = bx - ax
  379.         local dy = by - ay
  380.  
  381.        
  382.         if math.abs(dx) < 256 and math.abs(dy) < 208
  383.         and math.max(ax,bx) - actualcamx < 256
  384.         and math.min(ax,bx) - actualcamx >= 0
  385.         and math.max(ay,by) - actualcamy < 200
  386.         and math.min(ay,by) - actualcamy >= 0
  387.         then
  388.  
  389.             local di = ids[i] - ids[i+1]
  390. --      local speed = math.max(math.abs(dx),math.abs(dy)) / di
  391.             local xspeed = math.abs(dx / di)
  392.    
  393.             local xspeeddiff = xspeed - 2.9375
  394.             if xspeeddiff > 0 then
  395.                 local good = math.min(255,math.max(0, xspeeddiff * 255 / 2))
  396.                 local good2 = math.min(255,math.max(0, xspeeddiff * 255 / 2 - 255))
  397.                 color = string.format('#%02XFF%02X', 255-good, 255-(good-good2))
  398.             else
  399.                 local bad = math.min(255,math.max(0, -xspeeddiff * 512))
  400.                 if tracknumber == currentstatenumber then
  401.                     color = string.format('#FF%02X%02X', 255-bad, 255-bad)
  402.                 else
  403.                     color = string.format('#%02X%02XFF', 255-bad, 255-bad)
  404.                 end
  405.             end
  406.        
  407.             ax = floor(ax)
  408.             bx = floor(bx)
  409.             ay = floor(ay)
  410.             by = floor(by)
  411.        
  412.             if dx < 0 or dy < 0 then
  413.                 drawlineinworld(ax,ay,bx,by, color)
  414.             end
  415.             if dx < 0 or dy > 0 then
  416.                 drawlineinworld(ax,ay+height,bx,by+height, color)
  417.             end
  418.             if dx > 0 or dy < 0 then
  419.                 drawlineinworld(ax+width,ay,bx+width,by, color)
  420.             end
  421.             if dx > 0 or dy > 0 then
  422.                 drawlineinworld(ax+width,ay+height,bx+width,by+height, color)
  423.             end
  424.  
  425.             skipfirst = true
  426.         end    
  427.  
  428.     end
  429.  
  430. end
  431.  
  432.  
  433. -- a helper function to draw the info of the given frame
  434. function renderframeinfo(records, id, tracknumber)
  435.  
  436.     if records == nil then return end
  437.  
  438.     local info = records[id]
  439.     local notcurrent = tracknumber ~= currentstatenumber
  440.  
  441.     -- don't draw anything if there's no info for this frame or it's after the last frame
  442.     if info == nil then return end
  443.     if notcurrent and records.endid and (id > records.endid) then return end
  444.  
  445.  
  446.     renderpasttrajectory(records, id, tracknumber)
  447.  
  448.  
  449.     -- draw a box where the player is
  450. --  local camx = getxcam()
  451. --  local camy = getycam()
  452.     local x = math.floor(info[1]) -- - camx
  453.     local y = math.floor(info[2]) -- - camy
  454.     local width = 16
  455.     local height = 32
  456.  
  457.     -- adjust the player hitbox to match up better with the world... (HACK)
  458.     y = y + 3
  459.     height = height - 5
  460.     x = x + 1
  461.     width = width - 2
  462.    
  463.     if (drawsavestatehitboxes and notcurrent) or (drawplayerhitbox and not notcurrent) then
  464.        
  465.         --local color        = notcurrent and "#7F7FFF" or "#FF7FFF"
  466.         local outlinecolor = notcurrent and "#0000FF" or "#FF0000"
  467.  
  468.         drawboxinworld(x, y, x+width, y+height, outlinecolor)
  469.  
  470.     end
  471.  
  472.     if drawsavestatenumbers then
  473.  
  474.         -- draw the savestate number somewhere in the box
  475.         if notcurrent then
  476.             x = x + (tracknumber%6) * 4 - 10
  477.             y = y + (tracknumber/6) * 6 - 15
  478.             local message = string.format("%d", tracknumber)
  479.             drawtextinworld(x, y, message)
  480.         end
  481.  
  482.     end
  483.  
  484. end
  485.  
  486.  
  487.  
  488.  
  489. local objectinfotable =
  490. {
  491.     -- note: default width and height is 16
  492.     [0x061] = {name='babymario', color='#FF7F7F', width=function(i) return memory.readbyte(0x700F00+4*(i-1))==0xA and 16 or 32 end, height=function(i) return memory.readbyte(0x700F00+4*(i-1))==0x10 and 32 or 16 end, showname=false},
  493.     [0x08C] = {name='yoshi', height=32, color='#00FF00'},
  494.     [0x042] = {name='entrance', width=32, textyoff=8, color='#BFFF00'}, -- pipe
  495.     [0x0D1] = {name='entrance', width=32, textyoff=8},
  496.     [0x084] = {name='warp', width=40, height=28},
  497.     [0x0D0] = {name='entrance', width=22, height=40},
  498.     [0x147] = {name='entrance', width=22, height=40},
  499.     [0x04E] = {name='door', width=11, height=32, xoff=1, yoff=8},
  500.     [0x131] = {name='door', width=11, height=32, xoff=1, yoff=8},
  501.     [0x093] = {name='door', width=11, height=32, xoff=1, yoff=8},
  502.     [0x012] = {name='bossdoor', width=12, height=32, yoff=8},
  503.     [0x001] = {name='shut', width=11, height=32, xoff=1, yoff=8},
  504.     [0x00D] = {name='goal', height=64, yoff=-64, xoff=35, width=8},
  505.     [0x00E] = {name='goal!', width=8, height=8},
  506.     [0x00F] = {name='bonus', width=8, height=8},
  507.     [0x01E] = {name='shyguy'},
  508.     [0x0F3] = {name='jumpingshyguy'},
  509.     [0x192] = {name='flowershyguy'},
  510.     [0x0F2] = {name='stiltshyguy', height=48, yoff=-16},
  511.     [0x066] = {name='pirahnaplant', width=8, height=0, guessscaling=true},
  512.     [0x054] = {name='pirahnaplant', width=8, height=0, guessscaling=true, upsidedown=true},
  513.     [0x09E] = {name='chomprock', width=22, height=24},
  514.     [0x065] = {name='redcoin', color='#FF0000'},
  515.     [0x1AF] = {name='coin', color='#FFFF00'},
  516.     [0x115] = {name='coin'},
  517.     [0x025] = {name='egg', color='#7FFF7F'},
  518.     [0x024] = {name='yellowegg', color='#FFFF7F'},
  519.     [0x023] = {name='redegg', color='#FF7F7F'},
  520.     [0x0A0] = {name='starflower', width=32},
  521.     [0x0AD] = {name='helpbox', scaled=true},
  522.     [0x12C] = {name='redcoin'}, -- shyguy
  523.     [0x08D] = {name='flyguy'}, -- stars or 1up
  524.     [0x178] = {name='1upballoon'},
  525.     [0x17A] = {name='yellowcoinballoon'},
  526.     [0x1A2] = {name='star'},
  527.     [0x100] = {name='1up'},
  528.     [0x0BE] = {name='1up'},
  529.     [0x0B7] = {name='1up'},
  530.     [0x0BF] = {name='key'},
  531.     [0x0B6] = {name='coins'},
  532.     [0x0BC] = {name='bandit'},
  533.     [0x0BD] = {name='coin'},
  534.     [0x0CB] = {name='coin'},
  535.     [0x0CC] = {name='switch'},
  536.     [0x09D] = {name='switch', width=26, xoff=1, height=20, scaled=true, yscaled=true},
  537.     [0x0C2] = {name='door'},
  538.     [0x0B0] = {name='tank'},
  539.     [0x0B1] = {name='copter'},
  540.     [0x098] = {name='yoshiblock', width=32,height=32,yoff=-8},
  541.     [0x06A] = {name='eggblock'}, -- yellow
  542.     [0x06B] = {name='eggblock'}, -- green
  543.     [0x0FA] = {name='flower', width=24, height=24},
  544.     [0x110] = {name='flower', width=24, height=24},
  545.     [0x064] = {name='pivot', width=8, height=8},
  546.     [0x055] = {name='pivot', width=8, height=8},
  547.     [0x03D] = {name='pivot', width=8, height=8},
  548.     [0x0BA] = {name='stairs'}, -- wingedcloud
  549.     [0x0BB] = {name='bridge'}, -- wingedcloud
  550.     [0x0C7] = {name='beanstalk'}, -- wingedcloud
  551.     [0x0C8] = {name='beanstalk'}, -- wingedcloud
  552.     [0x0C0] = {name='stars'}, -- wingedcloud
  553.     [0x0C1] = {name='stars'}, -- wingedcloud
  554.     [0x0B7] = {name='1up'}, -- wingedcloud
  555.     [0x0B8] = {name='flower'}, -- wingedcloud
  556.     [0x0B5] = {name='secret'},
  557.     [0x067] = {name='chomprocksecret', textyoff=-8},
  558.     [0x161] = {name='bonus', width=8, height=8},
  559.     [0x181] = {name='crazydaisy', height=24, yoff=-5},
  560.     [0x094] = {name='growblock', width=14, height=14, scaled=true, yoff=function(i) return (memory.readword(0x701A36+4*(i-1))-256)/-32 end},
  561.     [0x0D8] = {name='chompsign', width=24, height=24},
  562.     [0x0A6] = {name='chomp', width=32, height=32, scaled=true},
  563.     [0x0A9] = {name='chompshadow', width=32, height=8, scaled=true},
  564.     [0x144] = {name='flippergate', width=16, height=64},
  565.     [0x13C] = {name='flippergate', width=64, height=16},
  566.     [0x04F] = {name='midring', width=16, height=44, yoff=2, xoff=function(i) return memory.readbyte(0x701400+4*(i-1)) == 2 and -14 or 16 end},
  567.     [0x06C] = {name='bouncyarrow', width=10, xoff=1, yoff=-7, height=function(i) return 24 * 256 / memory.readword(0x701A36+4*(i-1)) end},
  568.     [0x148] = {name='bouncyarrow', width=10, xoff=1, yoff=-7, height=function(i) return 24 * 256 / memory.readword(0x701A36+4*(i-1)) end},
  569.     [0x06F] = {name='bouncyarrow', width=10, xoff=1, yoff=0, height=function(i) return 16 * 256 / memory.readword(0x701A36+4*(i-1)) end},
  570.     [0x197] = {name='sign'},
  571.     [0x198] = {name='sign'},
  572.     [0x0EA] = {name='gusty'}, -- updown
  573.     [0x0EB] = {name='gusty'}, -- leftright
  574.     [0x10E] = {name='stars', width=20, height=24, scaled=true, yoff=function(i) return (memory.readword(0x701A36+4*(i-1))-256)/-6-8 end}, -- crate
  575.     [0x17E] = {name='stars', width=20, height=20}, -- crate, balloons
  576.     [0x177] = {name='bouncyarrow', width=20, height=20, yoff=2}, -- balloon
  577.     [0x164] = {name='nipper'},
  578.     [0x165] = {name='nipper'}, -- from seed
  579.     [0x0F4] = {name='eggplant', height=24, yoff=-5, color='#7FFF7F'},
  580.     [0x133] = {name='lanternghost'},
  581.     [0x179] = {name='key'},
  582.     [0x027] = {name='key'},
  583.     [0x011] = {name='1up'},
  584.     [0x022] = {name='egg'},
  585.     [0x123] = {name='bucket', yoff=4},
  586.     [0x231] = {name='babymario', width=32, height=32, color='#FF7F7F'},
  587.     [0x230] = {name='toady', width=32, height=32},
  588.     [0x22F] = {name='toady', width=32, height=32},
  589.     [0x091] = {name='toadies'},
  590.     [0x036] = {name='crusher', width=96},
  591.     [0x051] = {name='roller', width=96, height=32, yoff=-16},
  592.     [0x101] = {name='spikepivot'},
  593.     [0x102] = {name='spikepivot'},
  594.     [0x08A] = {name='platform', width=40, height=12},
  595.     [0x089] = {name='platform', width=40, height=12},
  596.     [0x185] = {name='platform', width=40, height=12},
  597.     [0x186] = {name='platform', width=40, height=12},
  598.     [0x187] = {name='platform', width=40, height=12},
  599.     [0x188] = {name='platform', width=40, height=12},
  600.     [0x189] = {name='platform', width=40, height=12},
  601.     [0x18A] = {name='platform', width=40, height=12},
  602.     [0x18B] = {name='platform', width=40, height=12},
  603.     [0x18C] = {name='platform', width=40, height=12},
  604.     [0x18D] = {name='platform', width=40, height=12},
  605.     [0x18E] = {name='platform', width=40, height=12},
  606.     [0x103] = {name='spikewidget', width=32},
  607.     [0x0E7] = {name='burt', width=24, height=24, yoff=-4, scaled=true},
  608.     [0x080] = {name='fireball'},
  609.     [0x018] = {name='fire'},
  610.     [0x194] = {name='blargg', width=32, height=32, yoff=-8},
  611.     [0x0A5] = {name='giantblargg', width=96, height=96, yoff=-96},
  612.     [0x076] = {name='sparky', scaled=true},
  613.     [0x077] = {name='sparky', scaled=true},
  614.     [0x0DA] = {name='vase', width=14, height=20, yoff=-2},
  615.     [0x048] = {name='kamek', width=32, height=24, yoff=-4},
  616.     [0x046] = {name='burt', width=24, height=24, xoff=-8, yoff=-18, width=128, height=function(i) return 128 - memory.readword(0x701900+4*(i-1)) end, yoff=function(i) return -72 + memory.readword(0x701900+4*(i-1))/2 end},
  617.     [0x013] = {name='supernova', width=8, height=8},
  618.     [0x117] = {name='donutblock'},
  619.     [0x13D] = {name='bat'},
  620.     [0x13E] = {name='bat'},
  621.     [0x08B] = {name='1upbomb'},
  622.     [0x092] = {name='rollbug'},
  623.     [0x109] = {name='taptap'},
  624.     [0x10A] = {name='taptap'},
  625.     [0x10B] = {name='taptap'},
  626.     [0x007] = {name='watermelon'},
  627.     [0x107] = {name='seed'},
  628.     [0x1B1] = {name='cannon', width=32, height=32, xoff=8, yoff=8},
  629.     [0x1B2] = {name='coin'},
  630.     [0x1B3] = {name='bandit', height=24, yoff=-6},
  631.     [0x129] = {name='fuzzy'},
  632.     [0x0B3] = {name='fart'},
  633.     [0x108] = {name='milde'},
  634.     [0x03E] = {name='weakplatform', width=64, xoff=-8},
  635.     [0x182] = {name='dragonfly', width=8, height=8},
  636.     [0x183] = {name='butterfly', width=8, height=8},
  637.     [0x0F0] = {name='petal'},
  638.     [0x0EE] = {name='petalshooter'},
  639.     [0x143] = {name='fish', width=32},
  640.     [0x057] = {name='platformghost', width=32},
  641.     [0x15C] = {name='flipperswitch'},
  642.     [0x15D] = {name='flipperswitch'},
  643.     [0x15F] = {name='spikeplatform', width=48, height=12},
  644.     [0x160] = {name='spikeplatform', width=48, height=12},
  645.     [0x020] = {name='bandit', height=24, yoff=-4},
  646.     [0x12D] = {name='yoshi', height=32, yoff=8, color='#00FF00'},
  647.     [0x11E] = {name='spinarrow', width=28,height=28},
  648.     [0x02D] = {name='salvo'},
  649.     [0x02E] = {name='eyes'},
  650.     [0x132] = {name='unshaven'},
  651.     [0x014] = {name='bigkey', width=24, height=24},
  652.     [0x400] = {showname=false, width=0, height=0, color='#00000000'},
  653.     -- TODO: play through more of the game to assign the rest of these...
  654.     -- (or discover a way to auto-calculate them)
  655. }
  656.  
  657.  
  658. local function evalnumber(x, ...)
  659.     if type(x) == 'number' then return x
  660.     elseif type(x) == 'function' then return x(...)
  661.     end
  662. end
  663.  
  664.  
  665.  
  666. local function drawobject(i)
  667.     local off = 4 * (i-1)
  668.  
  669.     local status = memory.readword(0x700F00 + off)
  670.  
  671.     if status == 0 then return end -- has "doesn't exist" status flag
  672.  
  673.     local objecttype = memory.readword(0x701360 + off)
  674.     local objectinfo = objectinfotable[objecttype] or {}
  675.  
  676.     local color = objectinfo.color or '#FFFF00'
  677.  
  678.     if status == 0x0A then --color = '#00FF00' -- has "saddled" status flag
  679.     elseif status == 0x0E then color = '#7F7F7F' -- has "dead" status flag
  680.     elseif status == 0x08 then color = '#007FFF' -- has "in mouth" status flag
  681. --  else emu.message(string.format("%X %d",status,i)) color = '#000000'
  682.     end
  683.  
  684.  
  685.  
  686.     local x = memory.readword(0x7010E2 + off) + (memory.readbyte(0x7010E1 + off) / 256)
  687.     local y = memory.readword(0x701182 + off) + (memory.readbyte(0x701181 + off) / 256)
  688.  
  689.     if objectinfo.xoff then x = x + evalnumber(objectinfo.xoff, i) end
  690.     if objectinfo.yoff then y = y + evalnumber(objectinfo.yoff, i) end
  691.  
  692.     local width = evalnumber(objectinfo.width, i) or 16
  693.     local height = evalnumber(objectinfo.height, i) or 16
  694.  
  695.     if objectinfo.scaled then
  696.         local scaling = memory.readwordsigned(0x701A36 + off)
  697.         local yscaling = objectinfo.yscaled and memory.readwordsigned(0x701A38 + off) or scaling
  698.         width = width * scaling / 256
  699.         height = height * yscaling / 256
  700.     end
  701.  
  702.     local origwidth = width
  703.     local origheight = height
  704.    
  705.     local xcam = getxcam()
  706.     local ycam = getycam()
  707.  
  708.     local y1 = y
  709.     local y2 = memory.readword(0x701CD8 + off) -- y center
  710.    
  711. --  x = x - xcam
  712. --  y = y - ycam
  713.  
  714.     local xcenter = memory.readword(0x701CD6 + off) -- - xcam
  715.     local ycenter = memory.readword(0x701CD8 + off) -- - ycam
  716.  
  717.     if objectinfo.guessscaling then
  718.         -- I don't know how to find the size of object...
  719.         -- but this hack method gets pretty close for dynamic objects like the pirahna plants
  720.         local xcenterguess = x+8
  721.         local ycenterguess = y+8
  722.         local xguessoffby = xcenterguess - xcenter
  723.         local yguessoffby = ycenterguess - ycenter
  724.         width = width + math.abs(xguessoffby)
  725.         height = height + math.abs(yguessoffby)
  726.         if xguessoffby > 0 then x = x - xguessoffby end
  727.         if yguessoffby > 0 then y = y - yguessoffby end
  728.     end
  729.  
  730.     -- adjust for the objectinfo width
  731.     x = x - (origwidth - 16)/2
  732.     y = y - (origheight - 16)/2
  733.  
  734.  
  735.  
  736.     if x-xcam > 256 + 32 or y-ycam > 208 + 32 or x-xcam+width < -32 or y-ycam+height < -32 then
  737.         -- too far offscreen to draw
  738.         return
  739.     end
  740.  
  741.  
  742.    
  743.  
  744.    
  745.     if height < 8 then
  746.         if not objectinfo.upsidedown then
  747.             y = y+height-8
  748.         end
  749.         height = 8
  750.     end
  751.     if width < 8 then
  752.         x = x+width-8
  753.         width = 8
  754.     end
  755.  
  756.     if height > 511 then height = 511 end
  757.     if width > 511 then width = 511 end
  758.  
  759.     width = math.floor(width)
  760.     height = math.floor(height)
  761.  
  762.  
  763.     -- hmm, this one is in screen-space
  764. --  local x2 = memory.readword(0x701680 + off)
  765. --  local y2 = memory.readword(0x701682 + off)
  766.  
  767. --  local y2 = memory.readword(0x700158 + (i-1)*2)
  768.    
  769.  
  770.     if drawobjectcenterpoints then
  771.         drawboxinworld(xcenter-1,ycenter-1,xcenter+1,ycenter+1,'#000000')
  772.         drawpixelinworld(xcenter, ycenter, color)
  773.     end
  774.    
  775.     if drawobjecthitboxes then
  776.         drawboxinworld(x,y,x+width-1,y+height-1,color)
  777.     end
  778.  
  779.  
  780.  
  781.     -- only draw text after this point
  782.     if objectinfo.textyoff then y = y + objectinfo.textyoff end
  783.     if objectinfo.textxoff then x = x + objectinfo.textxoff end
  784.  
  785.     if drawobjectnames then
  786.         if objectinfo.showname ~= false then -- here we want nil -> true
  787.             local name = objectinfo.name
  788.             SpriteID = off/(-4.0)+24
  789.             if name then
  790.                 drawtextinworld(x,y-10,SpriteID)
  791.             else
  792.                 drawtextinworld(x,y-10,SpriteID)
  793.             end
  794.         end
  795.     end
  796.    
  797. --  drawtextinworld(x,y-40,'y='..string.format("%X",memory.readword(0x701182 + off)))
  798. --  drawtextinworld(x,y-50,string.format("%08X", memory.readdword(0x701360 + 4 * (i-1))))
  799. end
  800.  
  801.  
  802.  
  803.  
  804.  
  805.  
  806. -- register a function to run when snes9x draws the screen
  807. gui.register( function ()
  808.  
  809.     drawdeferred()
  810.  
  811.     for i = 1,24 do
  812.         drawobject(i)
  813.     end
  814.  
  815.  
  816.     -- draw the information for this frame FOR ALL STATES
  817.     local id = lastidvalid and lastid or getinstantid()
  818.     for statenumber = 1,12 do
  819.         if statenumber ~= currentstatenumber then
  820.             local iddraw = statenumber==specialslot and id+adjustframeaheadby or id
  821.             renderframeinfo(frameinforecords[statenumber], iddraw, statenumber)
  822.         end
  823.     end
  824.     -- render the current state last so it draws on top
  825.     renderframeinfo(frameinforecords[currentstatenumber], id, currentstatenumber)
  826.  
  827.  
  828.     if drawspeedometer then
  829.  
  830.         local xvel = memory.readwordsigned(0x7000A8)
  831.         local yvel = memory.readwordsigned(0x7000AA)
  832.         local slope = memory.readbyte(0x7000B6) * 360 / 255
  833.         if slope > 180 then slope = slope - 360 end
  834.         gui.text(5,40,"xvel: "..xvel)
  835.         gui.text(5,50,"yvel: "..yvel)
  836.         if slope ~= 0 then
  837.             gui.text(5,60,"angle: "..string.format("%d",math.floor(slope<0 and slope-1 or slope)))
  838.         end
  839.  
  840.     end
  841.  
  842. end)
  843.  
  844.  
  845. -- a function to run after the emulation of every frame
  846. registeredafter = ( function ()
  847.  
  848.     local id = getinstantid()
  849.  
  850.     -- sort of a hack...
  851.     if id == lastid then
  852.         lastidvalid = false
  853.     else
  854.         lastid = id
  855.         lastidvalid = true
  856.     end
  857.  
  858.     -- just in case...
  859.     if frameinforecords[currentstatenumber] == nil then frameinforecords[currentstatenumber] = {} end
  860.  
  861.     -- keep track of the last frame reached that's been saved
  862.     frameinforecords[currentstatenumber].endid = id
  863.  
  864.     -- store the new information for this frame
  865.     local nowinfo = getperframeinfo()
  866.     frameinforecords[currentstatenumber][id] = nowinfo
  867.  
  868. end)
  869.  
  870.  
  871.     -- run some startup code: retrieve the ghost info stored in each savestate
  872. --  sound.clear()
  873.     frameinforecords = {}
  874.     for n = 1,12 do
  875.         frameinforecords[n] = savestate.loadscriptdata(saveslot[n])
  876. --      emu.wait()
  877.     end
  878.  
  879.  
  880. -- register a function that gets called when a numbered savestate is saved.
  881. -- the return value(s) becomes the savestate's scriptdata saved alongside it.
  882. savestate.registersave( function (statenumber)
  883.  
  884.     -- check for invalid save slot
  885.     if not frameinforecords[currentstatenumber] then return end
  886.  
  887.     -- if the user switched which state they're saving to,
  888.     -- replace that state's info table with the current one
  889.     if statenumber ~= currentstatenumber then
  890.         if statenumber == specialslot then
  891.             -- F10 state is special, keep all info for it
  892.             frameinforecords[statenumber] = copytable(frameinforecords[currentstatenumber])
  893.         else
  894.             -- for other states, only keep the last 1 minute of perframeinfo
  895.             frameinforecords[statenumber] = copytablepartial(frameinforecords[currentstatenumber])
  896.         end
  897.         currentstatenumber = statenumber
  898.     end
  899.  
  900.     -- save all the perframeinfo records for this state
  901.     return frameinforecords[currentstatenumber]
  902.  
  903. end)
  904.  
  905.  
  906. -- register a function that gets called when a numbered savestate is loaded.
  907. savestate.registerload( function (statenumber)
  908.  
  909.     -- check for invalid save slot
  910.     if not frameinforecords[currentstatenumber] then return end
  911.  
  912.     -- just switch the info record track to that state's table
  913.     currentstatenumber = statenumber
  914.  
  915.     -- also clear rendering buffers since the time (or state) has suddenly changed
  916.     clearalloldhist(-1)
  917.     resetcam()
  918.     lastidvalid = false
  919.  
  920.     -- maybe this helps
  921.     collectgarbage()
  922.  
  923. end)
  924.  
  925. if smartsyncframeupdate then
  926.     memory.register(0x7E0030, function()
  927.         registeredafter()
  928.         tickcam()
  929.     end)
  930. end
  931.  
  932. -- finally, the main loop
  933. while true do
  934.     emu.frameadvance()
  935.  
  936.     if not smartsyncframeupdate then
  937.         registeredafter()
  938.         tickcam()
  939.     end
  940. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement