daily pastebin goal
70%
SHARE
TWEET

Sprite ID viewer

Arnethegreat Jul 20th, 2013 131 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  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
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top