Arnethegreat

Sprite ID viewer

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