SHARE
TWEET

info and ghost display script for TASing in DeSmuME

a guest Feb 4th, 2011 435 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. -- info and ghost display script for TASing in DeSmuME
  2. -- supported games: Sonic Rush, Sonic Rush Adventure, Sonic Colo[u]rs, and New Super Mario Bros.
  3. -- original script author: nitsuja
  4.  
  5. -- script input configuration
  6. hide_key = 'capslock'
  7. protect_ghost_key = 'leftclick'
  8. delete_ghost_key = 'rightclick'
  9. adjust_ghost_backward_key = 'leftbracket'
  10. adjust_ghost_forward_key = 'rightbracket'
  11. -- note: press forward and backward keys together -> reset adjustment
  12.  
  13. -- script feature set settings
  14. draw_info_text = true
  15. draw_velocity_trails = true
  16. draw_ghosts = true
  17. max_ghosts = 16
  18. color_coded_speed = true
  19. use_in_game_time = true
  20. keep_history_across_sessions = true
  21.  
  22.  
  23.  
  24.  
  25.  
  26.  
  27.  
  28. if keep_history_across_sessions then
  29.   -- define which variable live across multiple runs of the script
  30.   emu.persistglobalvariables{
  31.     currenttracknumber = 1,
  32.  
  33.     -- a record of frame information...
  34.     -- looks like: {savenumber1={time1=x1y1,time2=x2y2,...},savenumber2=...}
  35.     frameinforecords = {}, -- initially it's an empty table
  36.   }
  37. else
  38.   currenttracknumber = 1
  39.   frameinforecords = {}
  40. end
  41.  
  42.  
  43.  
  44. local drawinfo_y = 30
  45. local drawinfo = function(msg,val,xoff,xoff2,color)
  46.   gui.text(180+(xoff or 0),drawinfo_y,msg,0xffffff3f)
  47.   gui.text(220+(xoff2 or 0),drawinfo_y,val or _G[msg],color)
  48.   drawinfo_y = drawinfo_y + 10
  49. end
  50.  
  51.  
  52.  
  53. -- a helper function to convert from unpacked x,y position to combined xy
  54. local function packxy(x,y)
  55.   return OR(x, SHIFT(y, -16))
  56. end
  57. -- a helper function to convert from packed xy position to separate x,y
  58. local function unpackxy(xy)
  59.   local x = AND(xy, 0xFFFF)
  60.   local y = SHIFT(xy, 16)
  61.   if y > 32767 then y = y - 65536 end
  62.   return x,y
  63. end
  64.  
  65.  
  66.  
  67. -- hack fix for desmume's readlongunsigned returning signed values
  68. local origreadlongunsigned = memory.readlongunsigned
  69. memory.readlongunsigned = function(...)
  70.   rv = origreadlongunsigned(...)
  71.   if rv < 0 then rv = 1 + (rv + 0xFFFFFFFF) end
  72.   return rv
  73. end
  74.  
  75.  
  76. -- hack fix for new bug in desmume 0.9.7 with integers > 0x7FFFFFFF getting mangled when read in by the emulator
  77. local function fixcolor(c) return type(c)=='number' and OR(c,0) or c end
  78. local origdrawtext,origdrawline,origdrawbox = gui.text,gui.drawline,gui.drawbox
  79. gui.text = function(x,y,s,c1,c2) return origdrawtext(x,y,s,fixcolor(c1),fixcolor(c2)) end
  80. gui.drawline = function(x1,y1,x2,y2,c1,s) return origdrawline(x1,y1,x2,y2,fixcolor(c1),s) end
  81. gui.drawbox = function(x1,y1,x2,y2,c1,c2) return origdrawbox(x1,y1,x2,y2,fixcolor(c1),fixcolor(c2)) end
  82.  
  83.  
  84.  
  85.  
  86. -- stub out game-specific functions until we know which game this is
  87. function getcamx() return 0 end
  88. function getcamy() return 0 end
  89. function istopontop() return true end
  90. function isnotinlevel() return false end
  91. function getinstantid() return 0 end
  92. function getperframeinfo() return 0 end
  93.  
  94. function initgamespecificfunctions()
  95.  
  96.   if whichgame == 'sc' then
  97.  
  98.     function getcamx()
  99.       -- estimates the camera position of 2 frames ago, since that matches up best visually
  100.       local x = memory.readlong(0x021203BC+8+ramshift)*2 - memory.readlong(0x021203BC+ramshift)
  101.       return math.floor((x / 4096) + .5)
  102.     end
  103.     function getcamy()
  104.       -- each screen has its own set of camera values, which will be different for the y component.
  105.       -- this function returns the y value of whichever screen is on top.
  106.       local y1 = memory.readlong(0x021203C0+8+ramshift)*2 - memory.readlong(0x021203C0+ramshift)
  107.       local y2 = memory.readlong(0x02120448+8+ramshift)*2 - memory.readlong(0x02120448+ramshift)
  108.       return math.floor((math.min(y1,y2) / 4096) + .5)
  109.     end
  110.     function istopontop()
  111.       return memory.readlong(0x021203C0+ramshift) < memory.readlong(0x02120448+ramshift)
  112.     end
  113.     function isnotinlevel()
  114.       return memory.readlong(0x02163EE4+ramshift) == 0 or memory.readword(0x0212089C+ramshift) == 0 -- hack
  115.     end
  116.     function getinstantid()
  117.       -- this function should return whatever metric we want to minimize,
  118.       -- although it should be scaled such that it most often increases by 1 per frame.
  119.       -- for example, we could return (framecount + estimated_future_delays) or (framecount - score*n).
  120.       -- in this case, let's simply use in-game level time and nothing else.
  121.       return memory.readword(0x0212089C+ramshift) -- time =
  122.     end
  123.     function getperframeinfo()
  124.       -- a helper function to get current player position info
  125.       -- (packed into a single number, since we might store a LOT of this data...)
  126.  
  127.       --local baseaddr = memory.readlong(0x02120850+ramshift) -- often 0x0219B220
  128.       --local rawx = memory.readlong(baseaddr + 0x44)
  129.       --local x = rawx/4096
  130.       --local y = memory.readlong(baseaddr + 0x48)/4096
  131.       --local lagx = memory.readlong(0x27E36EC+ramshift)
  132.       --if lagx < 32768 and lagx ~= rawx then -- hack to smooth out near-lag frames
  133.       --  -- (a better way is sync the script to memory.registerwrite of the level timer,
  134.       --  -- but non-dev desmume doesn't support that function)
  135.       --  x = x + memory.readlong(baseaddr + 0xC0)/4096
  136.       --  y = y + memory.readlong(baseaddr + 0xC4)/4096
  137.       --end
  138.        
  139.       -- more 'correct' values. the other way makes for obviously screwy-looking trails.
  140.       -- unfortunately this way is 1 frame behind, but that might be unavoidable because
  141.       -- this could be the earliest frame that the game is actually sure about the position.
  142.       local x = memory.readlong(0x0212A340+ramshift)/4096
  143.       local y = memory.readlong(0x0212A344+ramshift)/4096
  144.       return packxy(x,y)
  145.     end
  146.  
  147.   elseif whichgame == 'sra' then
  148.  
  149.     function getcamx()
  150.       local x = memory.readlong(0x02133ACC+8+ramshift3)*2 - memory.readlong(0x02133ACC+ramshift3)
  151.       return math.floor((x / 4096) + .5)
  152.     end
  153.     function getcamy()
  154.       local y1 = memory.readlong(0x02133AD0+8+ramshift4)*2 - memory.readlong(0x02133AD0+ramshift4)
  155.       local y2 = memory.readlong(0x02133B40+8+ramshift4)*2 - memory.readlong(0x02133B40+ramshift4)
  156.       return math.floor((math.min(y1,y2) / 4096) + .5)
  157.     end
  158.     function istopontop()
  159.       return memory.readlong(0x02133AD0+ramshift4) < memory.readlong(0x02133B40+ramshift4)
  160.     end
  161.     function isnotinlevel()
  162.       return memory.readword(0x213399C+ramshift) == 0
  163.     end
  164.     function getinstantid()
  165.       return memory.readword(0x021338C8+ramshift2)
  166.     end
  167.     function getperframeinfo()
  168.       --local baseaddr = memory.readlong(0x02133884+ramshift?)
  169.       --local rawx = memory.readlong(baseaddr + 0x44)
  170.       --local x = rawx/4096
  171.       --local y = memory.readlong(baseaddr + 0x48)/4096
  172.       local x = memory.readlong(0x0214389C+ramshift5)/4096
  173.       local y = memory.readlong(0x021438A0+ramshift5)/4096
  174.       return packxy(x,y)
  175.     end
  176.  
  177.   elseif whichgame == 'sr' then
  178.  
  179.     function getcamx()
  180.       local x = memory.readlong(0x0236AADC+8+ramshift)*2 - memory.readlong(0x0236AADC+ramshift)
  181.       return math.floor((x / 256) + .5)
  182.     end
  183.     function getcamy()
  184.       local y1 = memory.readlong(0x0236AAE0+8+ramshift)*2 - memory.readlong(0x0236AAE0+ramshift)
  185.       local y2 = memory.readlong(0x0236AB44+8+ramshift)*2 - memory.readlong(0x0236AB44+ramshift)
  186.       return math.floor((math.min(y1,y2) / 256) + .5)
  187.     end
  188.     function istopontop()
  189.       return memory.readlong(0x02090050+ramshift) < memory.readlong(0x02090058+ramshift)
  190.     end
  191.     function isnotinlevel()
  192.       return false -- nyi
  193.     end
  194.     function getinstantid()
  195.       return memory.readword(0x02091EBC+ramshift)
  196.     end
  197.     function getperframeinfo()
  198.       --local x = memory.readlong(0x020907DC+ramshift)/256
  199.       --local y = memory.readlong(0x020907E0+ramshift)/256
  200.       local x = memory.readlong(0x02090B54+ramshift)/256
  201.       local y = memory.readlong(0x02090B58+ramshift)/256
  202.       return packxy(x,y)
  203.     end
  204.  
  205.   elseif whichgame == 'nsmb' then
  206.  
  207.     function getcamx()
  208.       local x = memory.readlong(0x020CAF20) -- or 0x020CADCC (1 ahead)
  209.       return math.floor((x / 4096) + .5)
  210.     end
  211.     function getcamy()
  212.       return math.floor((memory.readlong(0x020CADDC) / -4096) + .5) - (istopontop() and 240 or 512) -- or 0x02085B78 (lower)
  213.     end
  214.     function istopontop()
  215.       return AND(memory.readbyte(0x020CAD0C),8) ~= 0
  216.     end
  217.     function isnotinlevel()
  218.       return memory.readwordsigned(0x021C5A62) == -1 -- or 0x021C5B5E or 0x021C5B82 -- not very good, only detects map screen
  219.     end
  220.     function getinstantid()
  221.       return memory.readword(0x020CAB14) -- or 0x2088BD8
  222.     end
  223.     function getperframeinfo()
  224.       local x = memory.readlong(0x0212AFC4)/4096 -- or 0x020CAEBC (1 ahead)
  225.       local y = memory.readlong(0x0212AFC8)/-4096 -- or 0x020CAEBC (1 ahead)
  226.       return packxy(x,y)
  227.     end
  228.  
  229.   end
  230.  
  231.   if not use_in_game_time then
  232.     getinstantid = function() return emu.framecount() end
  233.   end
  234.  
  235. end
  236.  
  237.  
  238. local speedtocolor, speedtocolornb
  239. if color_coded_speed then
  240.   speedtocolor = function (speed)
  241.     speed = math.abs(speed)
  242.     if speed < 512 then return 0x000000FF end
  243.     if speed < 2560 then return 0xFF0000FF end
  244.     if speed == 2560 then return 0xFF4000FF end
  245.     if speed < 3072 then return 0xFF7F00FF end
  246.     if speed == 3072 then return 0xFFAF00FF end
  247.     if speed < 3648 then return 0xFFFF00FF end
  248.     if speed <= 3840 then return 0xFFFFFFFF end
  249.     if speed < 4096 then return 0x7FFF7FFF end
  250.     if speed == 4096 then return 0x7FFFFFFF end
  251.     if speed < 8192 then return 0x00FFFFFF end
  252.     return 0xBF7FFFFF
  253.   end
  254.   speedtocolornb = function (speed)
  255.     local color = speedtocolor(speed)
  256.     if AND(color,0xFFFFFF00) == 0 then color = OR(color,0x7F7F7F00) end
  257.     return color
  258.   end
  259. else
  260.   speedtocolor = function (speed) return 0xFFFFFFFF end
  261.   speedtocolornb = function (speed) return 0xFFFFFFFF end
  262. end
  263.  
  264. function drawinfodisplay()
  265.   if input.get()[hide_key] then return end
  266.   if not draw_info_text then return end
  267.  
  268.   local topontop = istopontop()
  269.   drawinfo_y = not topontop and 20 or 20-192
  270.  
  271.   if isnotinlevel() then
  272.     drawinfo('...', '')
  273.     return
  274.   end
  275.  
  276.   local metery, timex, timey
  277.   if whichgame == 'sc' then
  278.     local baseaddr = memory.readlong(0x02120850+ramshift) -- (or 0x0212085C or 0x02120860)
  279.     xvel  = math.floor(memory.readlongsigned(baseaddr + 0xC0) / 16 + 0.5)
  280.     yvel  = math.floor(memory.readlongsigned(baseaddr + 0xC4) / 16 + 0.5)
  281.     speed = math.floor(memory.readlongsigned(baseaddr + 0xCC) / 16 + 0.5)
  282.     meter = (memory.readwordsigned(baseaddr + 0x654) * 300 / 4800)
  283.     power = (memory.readwordsigned(baseaddr + 0x820))
  284.     if memory.readbyte(0x021201EC+ramshift) == 2 then power = 0 end
  285.     time = memory.readword(0x0212089C+ramshift) -- level timer only
  286.     xpos = memory.readlong(baseaddr + 0x44) / 4096
  287.     ypos = memory.readlong(baseaddr + 0x48) / 4096
  288.     --local angval = memory.readshort(0x0212FF94+ramshift)
  289.     --if angval == 0 then angval = memory.readlong(baseaddr + 0x1AC) end
  290.     --angle = math.floor(-math.asin(angval / 4096) * (180 / math.pi) + 0.5)
  291.     --if angval > 4096 or angval < -4096 then angle = 0 end
  292.     metery = 160
  293.     timex = 160
  294.     timey = 11
  295.     if power > 0 then gui.text(0,not topontop and 48 or 48-192,power) end
  296.   elseif whichgame == 'sra' then
  297.     local baseaddr = memory.readlong(0x02133884+ramshift)
  298.     xvel  = math.floor(memory.readlongsigned(baseaddr + 0xBC) / 16 + 0.5)
  299.     yvel  = math.floor(memory.readlongsigned(baseaddr + 0xC0) / 16 + 0.5)
  300.     speed = math.floor(memory.readlongsigned(baseaddr + 0xC8) / 16 + 0.5)
  301.     meter = memory.readwordsigned(baseaddr + 0x5f8) * 300 / 4800
  302.     time = memory.readword(0x021338C8+ramshift2)
  303.     xpos = memory.readlong(baseaddr + 0x44) / 4096
  304.     ypos = memory.readlong(baseaddr + 0x48) / 4096
  305.     --angle = memory.readlongsigned(baseaddr + 0x34) * 180 / 32768 -- todo
  306.     --angle = angle<180 and -angle or 360-angle
  307.     --if -angle == 0 then angle = 0 end
  308.     metery = 46
  309.     timex = 152
  310.     timey = 17
  311.   elseif whichgame == 'sr' then
  312.     xvel  = memory.readwordsigned(0x020907E8+ramshift)
  313.     yvel  = memory.readwordsigned(0x020907EA+ramshift)
  314.     speed = memory.readwordsigned(0x020907F4+ramshift)
  315.     meter = memory.readwordsigned(0x02090B10+ramshift) * 300 / 4800
  316.     time = memory.readword(0x02091EBC+ramshift)
  317.     xpos = memory.readlong(0x020907DC+ramshift) / 256
  318.     ypos = memory.readlong(0x020907E0+ramshift) / 256
  319.     --local angval = memory.readshort(0x02090934+ramshift) -- todo
  320.     --angle = math.floor(-math.asin(angval / 4096) * (180 / math.pi) + 0.5)
  321.     --if angval > 4096 or angval < -4096 then angle = 0 end
  322.     metery = 41
  323.     timex = 82
  324.     timey = 17
  325.   elseif whichgame == 'nsmb' then
  326.     xvel  = math.floor(memory.readwordsigned(0x021B7104) / 4 + 0.5)
  327.     yvel  = math.floor(memory.readwordsigned(0x021B7108) / 4 + 0.5)
  328.     speed = math.floor(memory.readwordsigned(0x021B6A90) / 4 + 0.5)
  329.     meter = 0
  330.     time = memory.readword(0x020CAB14)
  331.     xpos = memory.readlong(0x020CAEBC) / 4096
  332.     ypos = memory.readlong(0x020CAEC0) / -4096
  333.     timex = 180
  334.     timey = 1
  335.   else
  336.     speed = 0
  337.     xvel = 0
  338.     meter = 0
  339.     metery = 0
  340.     timex = 0
  341.     timey = 0
  342.   end
  343.  
  344.   drawinfo('speed',speed,0,0,speedtocolornb(speed))
  345.   drawinfo('xvel',xvel,0,0,speedtocolornb(xvel))
  346.   drawinfo('yvel')
  347.   if meter > 0 then gui.text(0,not topontop and metery or metery-192,string.format('%.1f%%',meter)) end
  348.   gui.text(timex,not topontop and timey or timey-192,time,0x7FFFFFFF)
  349.   drawinfo('x', xpos, 0,-30)
  350.   drawinfo('y', ypos, 0,-30)
  351.   --drawinfo('angle', angle)
  352.  
  353. end
  354.  
  355.  
  356.  
  357.  
  358.  
  359.  
  360. -- draws the box of the given frame of a track
  361. function renderframeinfo(records, id,camx,camy, tracknumber)
  362.  
  363.   if not records then return end
  364.  
  365.   local packedinfo = records[id]
  366.   if not packedinfo then return end
  367.  
  368.   local xpos,ypos = unpackxy(packedinfo)
  369.  
  370.   local notcurrent = (tracknumber ~= currenttracknumber)
  371.  
  372.   -- don't draw anything if it's after the last frame
  373.   if notcurrent and (id > (records.endid or id)) then return end
  374.  
  375.   -- draw a box where the player is (in this track)
  376.   local hwidth = 10
  377.   local hheight = 16
  378.   local x = xpos - camx
  379.   local y = ypos - camy - 192
  380.   if y > 0 then
  381.     y = y - 80
  382.     if y <= 16 then return end
  383.   end
  384.  
  385.   -- force offscreen boxes to draw partially onscreen so we can know where they are
  386.   local offscreen
  387.   if x<0    then x=0    offscreen = true end
  388.   if x>256  then x=256  offscreen = true end
  389.   if y<-192 then y=-192 offscreen = true end
  390.   if y>192  then y=192  offscreen = true end
  391.  
  392.   local color        = records.important and 0xFF7F7F7F or 0x7F7FFF7F
  393.   local outlinecolor = notcurrent and 0x000000FF or speedtocolor(math.abs(speed or 0))
  394.   if not notcurrent then -- make filling more transparent if is current state
  395.     color = OR(AND(color,0xFFFFFF00),AND(color,0xFF)*0.5)
  396.   elseif offscreen then -- make more transparent if offscreen
  397.     color = OR(AND(color,0xFFFFFF00),AND(color,0xFF)*0.75)
  398.     outlinecolor = OR(AND(outlinecolor,0xFFFFFF00),AND(outlinecolor,0xFF)*0.75)
  399.   end
  400.   gui.drawbox(x-hwidth, y-hheight, x+hwidth, y+hheight, color, outlinecolor)
  401.  
  402. --  -- draw the track number somewhere in the box
  403. --  if notcurrent then
  404. --    x = x + (tracknumber%6) * 4 - 10
  405. --    y = y + (tracknumber/6) * 6 - 15
  406. --    local message = string.format("%d", tracknumber)
  407. --    gui.text(x, y, message, 0xFFFFFFFF)
  408. --  end
  409.  
  410.   -- draw the track's offset amount somewhere in the box
  411.   if notcurrent and records.offset and records.offset ~= 0 then
  412.     --x = x + (tracknumber%6) * 4 - 13
  413.     x = x - 8
  414.     y = y + (tracknumber/6) * 6 - 15
  415.     local message = string.format("%s%d", records.offset > 0 and '+' or '', records.offset)
  416.     gui.text(x, y, message, outlinecolor,color)
  417.   end
  418.  
  419.   if not offscreen then
  420.     records.seenago = 0
  421.   end
  422.  
  423. end
  424.  
  425.  
  426. function ispointwithintrackbox(records, id,camx,camy, tracknumber, xcheck,ycheck)
  427.  
  428.   if not records then return end
  429.   local packedinfo = records[id]
  430.   if not packedinfo then return end
  431.  
  432.   local xpos,ypos = unpackxy(packedinfo)
  433.  
  434.   local notcurrent = (tracknumber ~= currenttracknumber)
  435.  
  436.   -- don't check if it's after the last frame
  437.   if notcurrent and (id > (records.endid or id)) then return end
  438.  
  439.   -- check a box where the player is (in this track)
  440.   local hwidth = 10
  441.   local x = xpos - camx
  442.  
  443.   -- offscreen boxes draw partially onscreen so you can interact with them
  444.   if x<0    then x=0   end
  445.   if x>256  then x=256 end
  446.  
  447.   if xcheck < x-hwidth or xcheck > x+hwidth then return end
  448.  
  449.   local hheight = 16
  450.   local y = ypos - camy - 192
  451.   if y > 0 then
  452.     y = y - 80
  453.     if y <= 16 then return end
  454.   end
  455.  
  456.   -- offscreen boxes draw partially onscreen so you can interact with them
  457.   if y<-192 then y=-192 end
  458.   if y>192  then y=192  end
  459.  
  460.   if ycheck < y-hheight or ycheck > y+hheight then return end
  461.  
  462.   return true
  463.  
  464. end
  465.  
  466.  
  467. local points = {}
  468. local ids = {}
  469. function renderpasttrajectory(records, id, tracknumber)
  470.  
  471.   if not records or not draw_velocity_trails then return end
  472.  
  473.   local lastx
  474.   local j = 1
  475.   for i=id,id-64,-1 do
  476.     local info = records[i]
  477.     if info then
  478.       points[j] = info
  479.       ids[j] = i
  480.       j = j + 1
  481.     end
  482.   end
  483.   for k=j,#points do
  484.     points[k] = nil
  485.     ids[k] = nil
  486.   end
  487.  
  488.   local camx = getcamx()
  489.   local camy = getcamy() + 192
  490.  
  491.   if #points < 2 then return end
  492.  
  493.   local ax,ay = unpackxy(points[1])
  494.   local bx,by = unpackxy(points[2])
  495.   ax = ax - camx
  496.   ay = ay - camy
  497.   bx = bx - camx
  498.   by = by - camy
  499.  
  500.   local speedfactor = (whichgame == 'nsmb' and 4 or 1)
  501.  
  502.   local skipfirst = false
  503.   for i=3,j-1 do
  504.    
  505.     local aym = ay-16
  506.     local ayp = ay+16
  507.     local bym = by-16
  508.     local byp = by+16
  509.    
  510.     if aym > 0 then aym = math.max(0,aym - 80) end
  511.     if ayp > 0 then ayp = math.max(0,ayp - 80) end
  512.     if bym > 0 then bym = math.max(0,bym - 80) end
  513.     if byp > 0 then byp = math.max(0,byp - 80) end
  514.    
  515.     local transp = 255 * (j+3-i) / j
  516.  
  517.     local dx = bx - ax
  518.     local dy = by - ay
  519.     local di = ids[i-2] - ids[i-1]
  520.     local xspeed = math.abs(dx) * speedfactor / di
  521.    
  522.     if math.abs(dx) < 256 and math.abs(dy) < 192 then
  523.       local color = OR(AND(speedtocolor(xspeed*256),0xFFFFFF00), transp)
  524.       --local color = OR(AND(speedtocolor((i%2)*2560),0xFFFFFF00), transp)
  525.    
  526.       if (dx < 0 or dy < 0) and (aym ~= 0 or bym ~= 0) then
  527.         gui.drawline(ax-10,aym,bx-10,bym, color, skipfirst)
  528.       end
  529.       if (dx < 0 or dy > 0) and (ayp ~= 0 or byp ~= 0) then
  530.         gui.drawline(ax-10,ayp,bx-10,byp, color, skipfirst)
  531.       end
  532.       if (dx > 0 or dy < 0) and (aym ~= 0 or bym ~= 0) then
  533.         gui.drawline(ax+10,aym,bx+10,bym, color, skipfirst)
  534.       end
  535.       if (dx > 0 or dy > 0) and (ayp ~= 0 or byp ~= 0) then
  536.         gui.drawline(ax+10,ayp,bx+10,byp, color, skipfirst)
  537.       end
  538.  
  539.       skipfirst = true
  540.     end  
  541.  
  542.     ax=bx
  543.     ay=by
  544.     bx,by = unpackxy(points[i])
  545.     bx = bx - camx
  546.     by = by - camy
  547.  
  548.   end
  549.  
  550. end
  551.  
  552.  
  553.  
  554.  
  555. function drawghostdisplay()
  556.  
  557.   local inp = input.get()
  558.  
  559.   if inp[hide_key] then return end
  560.  
  561.   if isnotinlevel() then return end
  562.  
  563.   -- calculate/retrieve the frame info
  564.   local id = getinstantid()
  565.   if id < 2 then return end
  566.   local camx = getcamx()
  567.   local camy = getcamy()
  568.  
  569.   if draw_ghosts then
  570.  
  571.     -- draw hitboxes for this frame for all tracks except the current one
  572.     for tracknumber,records in ipairs(frameinforecords) do
  573.       if tracknumber ~= currenttracknumber then
  574.         local tempid = id
  575.         if records and records.offset then
  576.           tempid = tempid + records.offset
  577.         end
  578.         renderframeinfo(records, tempid,camx,camy, tracknumber)
  579.       end
  580.     end
  581.  
  582.   end
  583.  
  584.   -- draw velocity trails for the current track
  585.   renderpasttrajectory(frameinforecords[currenttracknumber], id, currenttracknumber)
  586.  
  587.   -- draw the information for this frame for the current track
  588.   renderframeinfo(frameinforecords[currenttracknumber], id,camx,camy, currenttracknumber)
  589.  
  590. end
  591.  
  592.  
  593. function figureoutwhichgame()
  594.   if emu.framecount() < 10 then return end
  595.   local gameid = memory.readlongunsigned(0x200000C)
  596.   if gameid == 0xF509DEFF or gameid == 0x72E9DEFF or gameid == 0x023FDEFF then
  597.     whichgame = 'sc'
  598.     ramshift = (gameid == 0x023FDEFF) and -0x20 or 0
  599.     print('Sonic Colo'..(gameid == 0x72E9DEFF and 'u' or '')..'rs detected')
  600.   elseif gameid == 0xE481DEFF or gameid == 0x33A9DEFF or gameid == 0xB936DEFF or gameid == 0xAD6FDEFF then
  601.     whichgame = 'sra'
  602.     ramshift = (gameid == 0xAD6FDEFF) and -0x800 or 0
  603.     ramshift2 = (gameid == 0xAD6FDEFF) and -0x7F0 or 0
  604.     ramshift3 = (gameid == 0xAD6FDEFF) and -0x794 or 0
  605.     ramshift4 = (gameid == 0xAD6FDEFF) and -0x804 or 0
  606.     ramshift5 = (gameid == 0xAD6FDEFF) and 0x3CC or 0
  607.     print('Sonic Rush Adventure detected')
  608.   elseif gameid == 0x9771DEFF or gameid == 0x97EDDEFF or gameid == 0x187BDEFF then
  609.     whichgame = 'sr'
  610.     ramshift = (gameid == 0x187BDEFF) and 0x20 or 0
  611.     print('Sonic Rush detected')
  612.   elseif gameid == 0xA315DEFF then
  613.     whichgame = 'nsmb'
  614.     print('New Super Mario Bros. detected')
  615.   elseif not checkedwhichgame then
  616.     print(string.format('unknown game %X',gameid))
  617.   end
  618.   if whichgame then initgamespecificfunctions() end
  619.   checkedwhichgame = true
  620. end
  621.  
  622. emu.registerafter( function()
  623.  
  624.   if not whichgame then figureoutwhichgame() end
  625.  
  626.   if isnotinlevel() then return end
  627.  
  628.   id = getinstantid()
  629.  
  630.   if id == 0 then return end
  631.  
  632.   -- just in case...
  633.   if not frameinforecords[currenttracknumber] then frameinforecords[currenttracknumber] = {} end
  634.  
  635.   -- store the new information for this frame
  636.   prevperframeinfo = getperframeinfo()
  637.   local currecords = frameinforecords[currenttracknumber]
  638.   currecords[id] = prevperframeinfo
  639.   currecords.endid = id
  640.   currecords.endinfo = prevperframeinfo
  641.   currecords.seenago = 0
  642.  
  643.   -- update "last seen" counter for each track
  644.   for tracknumber,records in ipairs(frameinforecords) do
  645.     records.seenago = (records.seenago or 0) + 1
  646.   end
  647.  
  648. end)
  649.  
  650.  
  651.  
  652.  
  653. emu_registeridle = (function()
  654.  
  655.   if not whichgame and not checkedwhichgame then figureoutwhichgame() end
  656.  
  657.   -- hack to detect when a savestate got loaded, because desmume currently lacks savestate.registerload
  658.   local curid = getinstantid()
  659.   local curframeinfo = getperframeinfo()
  660.   local removenum
  661.   if id ~= curid or prevperframeinfo ~= curframeinfo then
  662.  
  663.     local prevrecords = frameinforecords[currenttracknumber]
  664.     local previnfo = prevperframeinfo
  665.     local previd = id
  666.  
  667.     id = curid
  668.     prevperframeinfo = curframeinfo
  669.    
  670.     -- savestate got loaded...
  671.    
  672.     -- sort tracks, with higher indices being less important to keep
  673.     table.sort(frameinforecords, function(a,b)
  674.       if not a then return b ~= nil end -- probably not needed, shouldn't be
  675.       if (a==prevrecords) ~= (b==prevrecords) then return a==prevrecords end -- don't discard the track we just recorded
  676.       if a.important ~= b.important then return (a.important and 1 or 0) > (b.important and 1 or 0) end -- avoid discarding protected/important tracks
  677.       if a.seenago ~= b.seenago then return (a.seenago or 9999) < (b.seenago or 9999) end -- discard "least recently seen"
  678.       local ax,_ = unpackxy(a.endinfo or 0)
  679.       local bx,_ = unpackxy(b.endinfo or 0)
  680.       return ax*(b.endid or 0) > bx*(a.endid or 0) -- use average x velocity as tiebreaker
  681.     end)
  682.    
  683.     -- figure out where the previous value of currenttracknumber got moved to by the sorting
  684.     local prevtracknumber
  685.     for tracknumber,records in ipairs(frameinforecords) do
  686.       if records == prevrecords then
  687.         prevtracknumber = tracknumber
  688.         break
  689.       end
  690.     end
  691.    
  692.     -- use next unoccupied track, or replace worst track if out of space
  693.     currenttracknumber = 1+#frameinforecords
  694.     if currenttracknumber > max_ghosts then currenttracknumber = max_ghosts end
  695.    
  696.     -- if the new state matches the middle of another track, copy that track to the new one being selected
  697.     for tracknumber,records in ipairs(frameinforecords) do
  698.       if records[curid] == curframeinfo and tracknumber ~= currenttracknumber then
  699.         frameinforecords[currenttracknumber] = copytable(records)
  700.         frameinforecords[currenttracknumber].endid = curid
  701.         frameinforecords[currenttracknumber].endinfo = curframeinfo
  702.         break
  703.       end
  704.     end
  705.    
  706.     -- if the previous state matches the middle of another track, delete the previous state's track
  707.     if frameinforecords[prevtracknumber] then
  708.       for tracknumber,records in ipairs(frameinforecords) do
  709.         if records[previd] == previnfo and tracknumber ~= prevtracknumber and tracknumber ~= currenttracknumber then
  710.           removenum = prevtracknumber
  711.           break
  712.         end
  713.       end
  714.     end
  715.   end
  716.  
  717.  
  718.   -- if the current state matches the end of another track, delete that track
  719.   if not removenum and frameinforecords[currenttracknumber] and frameinforecords[currenttracknumber].endid == curid and frameinforecords[currenttracknumber].endinfo == curframeinfo then
  720.     for tracknumber,records in ipairs(frameinforecords) do
  721.       if records.endid == curid and records.endinfo == curframeinfo and tracknumber ~= currenttracknumber then
  722.         --removenum = tracknumber
  723.         local important = frameinforecords[tracknumber].important
  724.         frameinforecords[tracknumber] = frameinforecords[currenttracknumber]
  725.         frameinforecords[tracknumber].important = important or frameinforecords[tracknumber].important
  726.         removenum = currenttracknumber
  727.         currenttracknumber = tracknumber
  728.         break
  729.       end
  730.     end
  731.   end
  732.  
  733.   -- actually remove whatever we decided to, if anything
  734.   if removenum then
  735.     table.remove(frameinforecords, removenum)
  736.     if currenttracknumber > removenum then
  737.       currenttracknumber = currenttracknumber - 1
  738.     end
  739.   end
  740.  
  741.  
  742.   -- check for removing a track via right-clicking on it
  743.   local inp = input.get()
  744.   if inp[delete_ghost_key] and not removecheckwasrightclick then
  745.     --while true do
  746.       removenum = nil
  747.      
  748.       local id = getinstantid()
  749.       local camx = getcamx()
  750.       local camy = getcamy()
  751.  
  752.       for tracknumber,records in ipairs(frameinforecords) do
  753.         local tempid = id
  754.         if frameinforecords[tracknumber] and frameinforecords[tracknumber].offset and tracknumber ~= currenttracknumber then
  755.           tempid = tempid + frameinforecords[tracknumber].offset
  756.         end
  757.         if ispointwithintrackbox(records, tempid,camx,camy, tracknumber, inp.xmouse, inp.ymouse) then
  758.           removenum = tracknumber
  759.           break
  760.         end
  761.       end
  762.  
  763.       if removenum then
  764.         if frameinforecords[removenum].important then
  765.           frameinforecords[removenum].important = nil -- strip importance before deleting
  766.         elseif removenum == currenttracknumber then
  767.           frameinforecords[removenum] = {}
  768.           frameinforecords[removenum].seenago = 0
  769.         else
  770.           table.remove(frameinforecords, removenum)
  771.           if currenttracknumber > removenum then
  772.             currenttracknumber = currenttracknumber - 1
  773.           end
  774.         end
  775.         removecheckwasrightclick = inp[delete_ghost_key] -- only remove 1 per click-drag
  776.       end
  777.      
  778.       --if not removenum then break end -- only remove 1 per click
  779.     --end
  780.   end
  781.   if not inp[delete_ghost_key] then removecheckwasrightclick = rightclick end
  782.  
  783.  
  784.   -- check for marking a track as important via left-clicking on it
  785.   if inp[protect_ghost_key] and not markcheckwasleftclick then
  786.     --while true do
  787.       local id = getinstantid()
  788.       local camx = getcamx()
  789.       local camy = getcamy()
  790.  
  791.       for tracknumber,records in ipairs(frameinforecords) do
  792.         local tempid = id
  793.         if frameinforecords[tracknumber] and frameinforecords[tracknumber].offset and tracknumber ~= currenttracknumber then
  794.           tempid = tempid + frameinforecords[tracknumber].offset
  795.         end
  796.         if ispointwithintrackbox(records, tempid,camx,camy, tracknumber, inp.xmouse, inp.ymouse) and not frameinforecords[tracknumber].important then
  797.           frameinforecords[tracknumber].important = true
  798.           markcheckwasleftclick = inp[protect_ghost_key] -- only mark 1 per click-drag
  799.           break
  800.         end
  801.       end
  802.      
  803.       --if not removenum then break end -- only mark 1 per click
  804.     --end
  805.   end
  806.   if not inp[protect_ghost_key] then markcheckwasleftclick = leftclick end
  807.  
  808.  
  809.  
  810.   -- check for adjusting a track's offset via [ and ] while moused over it
  811.   if inp[adjust_ghost_backward_key] or inp[adjust_ghost_forward_key] then
  812.     local id = getinstantid()
  813.     local camx = getcamx()
  814.     local camy = getcamy()
  815.     for tracknumber,records in ipairs(frameinforecords) do
  816.       if tracknumber ~= currenttracknumber then
  817.         local tempid = id
  818.         if frameinforecords[tracknumber] and frameinforecords[tracknumber].offset then
  819.           tempid = tempid + frameinforecords[tracknumber].offset
  820.         end
  821.         if ispointwithintrackbox(records, tempid,camx,camy, tracknumber, inp.xmouse, inp.ymouse) then
  822.           local newoffset = (frameinforecords[tracknumber].offset or 0) + (inp[adjust_ghost_backward_key] and -1 or 1)
  823.           if inp[adjust_ghost_backward_key] and inp[adjust_ghost_forward_key] then newoffset = 0 end
  824.           local newtempid = id + newoffset
  825.           if newtempid == 0 or ((newtempid <= (records.endid or newtempid)) and frameinforecords[tracknumber][newtempid]) then -- don't let it disappear
  826.             if newoffset == 0 then newoffset = nil end
  827.             frameinforecords[tracknumber].offset = newoffset
  828.           end
  829.         end
  830.       end
  831.     end
  832.   end
  833.  
  834. end)
  835.  
  836.  
  837.  
  838. gui.register(function()
  839.   emu_registeridle() -- update some things even while paused, calling this here is a hack and might rely on desmume's dual-core mode
  840.   drawghostdisplay()
  841.   drawinfodisplay()
  842. 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