Advertisement
Guest User

Sonic 2 Speedometer v1.1

a guest
May 2nd, 2012
481
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 15.03 KB | None | 0 0
  1. --------------------------------------------------------------------------------
  2. --------------------------------------------------------------------------------
  3. ------ Sonic 2 Speedometer v1.1 ------------------------------------------------
  4. --------------------------------------------------------------------------------
  5. --------------------------------------------------------------------------------
  6.  
  7. -- This script shows several interesting values read directly out of the S2 game
  8. -- engine.  All of these, except for the speedometer, will autohide themselves.
  9. -- Use Gens Rerecording http://code.google.com/p/gens-rerecording/
  10. -- Items marked with a + will appear when you press Up + C; tap C multiple times
  11. -- to get them to stay onscreen longer.  Or, use Up + B to toggle them.
  12. --  + Checkpoint: Shows most recent checkpoint.
  13. --  + Remaining: Shows how many rings are left to collect until you get a
  14. --    Perfect Bonus.
  15. --  + Circular thing with a number: This shows you the distance and bearing to
  16. --    the nearest ring.  In other words, the needle in the circle shows you in
  17. --    what direction the nearest ring lies, and the number next to it shows how
  18. --    far away the ring is.
  19. --    Yes, I know this won't always give a correct distance/bearing for
  20. --    Metropolis Zone due to the wrapping.
  21. --  + UP D/L/R: These change color to show which collision collision plane is
  22. --    active: yellow for the primary, cyan for the secondary.
  23. --  - LOCK: Shows up when a control lock engages.
  24. --  - Air: Shows up under water.  Shows how how many seconds of air are left.
  25. --  - Invincible: This timer shows up when you get an invincibility monitor.
  26. --  - Speed Shoe: When this timer show up, it shows what you'd think it shows.
  27. --  - Unlabeled red number: Shows how many FRAMES of temporary invunerability
  28. --    you have left.
  29. --  - Continues: Reminds you how many continues you have got.
  30. --  - Speed: This shows your speed.  The dial also features a digital readout
  31. --    of your speed below.  The gray needle bounces up and down to show your
  32. --    most recent maximum speed; the red number above shows what this speed is.
  33. --    You can change the range of the speedometer
  34. --  - Boost: Shows up only when you do a spin dash.  Freezes to show how
  35. --    powerful the dash was.
  36. --
  37. -- Lua is supposed to be easy to understand, even for non-coders.  You shouldn't
  38. -- have trouble changing what gets shown on screen.
  39. --
  40. -- To Do:
  41. --  - Fix ring finder for Metropolis Zone
  42. --  - Implement non-sucky arc-drawing and fill algorithms.  I'll probably never
  43. --    actually do this, but feel free to contribute one for me.
  44. --
  45. -- Consider this script to be given out under the GPL. --- DrDnar, 1 May 2012
  46. --
  47. -- Change log:
  48. --  - 2 May 2012: Made the code look a little cleaner
  49. --  - mid-April through 1 May 2012: Initial version
  50.  
  51.  
  52.  
  53. --------------------------------------------------------------------------------
  54. ------ Meter Object ------------------------------------------------------------
  55. --------------------------------------------------------------------------------
  56. -- This object lets you draw a generic meter or dial, with multiple needles.
  57. -- To declare a meter, do
  58. --  - Meter:Create(baseX, baseY, size, thetaMin, thetaMax, scaleInc, fgColor, bgColor, fill)
  59. --    - baseX, baseY: X&Y location for the meter
  60. --    - size: Radius of the meter
  61. --    - thetaMin, thetaMax: These variables control the arc that the meter's
  62. --      range will sweep.  You could, for example, have the meter sweep a range
  63. --      less than the semicircle used in this example.
  64. --    - scaleInc: If not nil, this controls how often tic marks appear on the
  65. --      meter's scale.  Specified as an angular incrememt.
  66. --    - fgColor, bgColor: Controls the color of the main outer arc and the
  67. --      shadowing of that arc.
  68. --    - fill: Controls the fill color of the arc.
  69. --    * The color values tend to look funny if you specify an opacity that isn't
  70. --      fully opaque or transparent.
  71. -- This function will return a new Meter object.  Don't forget to assign it to a
  72. -- variable:
  73. --  - aMeter = Meter:Create(...)
  74. -- To draw the meter on the screen, first do
  75. --  - aMeter:DrawMeter()
  76. -- to draw the meter's basic graphic.  Then, for each needle value you want to
  77. -- show, do
  78. --  - aMeter:DrawNeedle(percentage, color, saturate, size)
  79. --    - percentage: A number between 0 and 1 that represents the fraction of the
  80. --      arc between thetaMin and thetaMax that the needle should point to.
  81. --    - color: The color of the needle line.  At the moment, there is no support
  82. --      for shadowing the needle color.
  83. --    - saturate: If true, then any percentage value less than 0 will be treated
  84. --      as zero, and any value greater than 1 will be treated as one.
  85. --    - size: Controls the length (radius) of the needle.
  86. -- The parameters after color may be omitted.  They will default to
  87. --    - color: fgColor passed to Create() method
  88. --    - saturate: false
  89. --    - size: size passed to Create() method.
  90.  
  91. Meter = {}
  92. Meter.__index = Meter
  93.  
  94. function Meter:Create(baseX, baseY, size, thetaMin, thetaMax, scaleInc, fgColor, bgColor, fill)
  95.     local newItem = {}
  96.     setmetatable(newItem, Meter)  -- Bind object methods to new object
  97.     newItem.baseX = baseX -- x location
  98.     newItem.baseY = baseY -- y location
  99.     newItem.thetaMin = thetaMin
  100.     newItem.thetaMax = thetaMax
  101.     newItem.thetaDelta = thetaMax - thetaMin
  102.     newItem.size = size -- size
  103.     newItem.scaleInc = scaleInc
  104.     newItem.bg = bgColor
  105.     newItem.fg = fgColor
  106.     newItem.fill = fill
  107.     return newItem
  108. end
  109.  
  110. function Meter:DrawMeter()
  111.     local inc = math.asin(1/self.size)/(self.size/2)
  112.     local start, stop = self.thetaMin, self.thetaMax
  113.     if start > stop then
  114.         start, stop = stop, start
  115.     end
  116.     local theta = start
  117.     local size, basex, basey = self.size, self.baseX, self.baseY
  118.     local cos, sin
  119.    
  120.     while theta < stop do
  121.         cos = math.cos(theta)
  122.         sin = math.sin(theta)
  123.         gui.pixel(sin*size + basex, cos*size + basey, self.fg)
  124.         gui.pixel(sin*(size+0.5) + basex, cos*(size+0.5) + basey, self.fg)
  125.         --gui.pixel(sin*(size-1) + basex, cos*(size-1) + basey, self.fg)
  126.         if self.bg ~= nil then
  127.             gui.pixel(sin*(size-1) + basex, cos*(size-1) + basey, self.bg)
  128.             gui.pixel(sin*(size-1.5) + basex, cos*(size-1.5) + basey, self.bg)
  129.         end
  130.         if self.bg ~= nil then
  131.             gui.line(basex, basey, sin*(size-2) + basex, cos*(size-2) + basey, self.fill)
  132.         end
  133.         theta = theta + inc
  134.     end
  135.     theta = start
  136.     while theta < stop do
  137.         cos = math.cos(theta)
  138.         sin = math.sin(theta)
  139.         if self.scaleInc ~= nil and ((theta-start)/self.scaleInc)%1 * self.scaleInc > (self.scaleInc-(2*inc)) then
  140.             gui.pixel(sin*(size-1) + basex, cos*(size-1) + basey, self.fg)
  141.             gui.pixel(sin*(size-1.5) + basex, cos*(size-1.5) + basey, self.fg)
  142.             gui.pixel(sin*(size-2) + basex, cos*(size-2) + basey, self.fg)
  143.             gui.pixel(sin*(size-2.5) + basex, cos*(size-2.5) + basey, self.fg)
  144. --          gui.line(sin*(size-1) + basex, cos*(size-1) + basey, sin*(size-3) + basex, cos*(size-3) + basey, self.fg)
  145.         end
  146.         theta = theta + inc
  147.     end
  148.  
  149. end
  150.  
  151. function Meter:DrawNeedle(percent, fgColor, saturate, size)
  152.     if saturate and percent > 1 then
  153.         percent = 1
  154.     end
  155.     if saturate and percent < 0 then
  156.         percent = 0
  157.     end
  158.     if fgColor == nil then
  159.         fgColor = self.fgColor
  160.     end
  161.     if size == nil then
  162.         size = self.size - 3
  163.     end
  164.     local theta = percent * self.thetaDelta + self.thetaMin
  165.     local sin, cos = math.cos(theta), math.sin(theta)
  166.    
  167.     gui.line(self.baseX, self.baseY, size*cos + self.baseX, size*sin + self.baseY, fgColor)
  168. end
  169.  
  170.  
  171.  
  172. --------------------------------------------------------------------------------
  173. ------ Globals -----------------------------------------------------------------
  174. --------------------------------------------------------------------------------
  175.  
  176. lastSpindashValue = 0
  177. boostMeter = Meter:Create(231, 214, 20, 3*math.pi/2, math.pi/2, math.pi/8, 0xFFFF00FF, 0x000000FF, 0x202020FF)
  178. boostTimer = 0
  179. speedometer = Meter:Create(287, 214, 20, 3*math.pi/2, math.pi/2, math.pi/8, 0xFFFF00FF, 0x000000FF, 0x202020FF)
  180. maxSpeed = 0
  181. maxMaxSpeed = 0
  182. maximumSpeed = 4096
  183. ringFinder = Meter:Create(27, 72, 10, math.pi*2+math.pi/2, math.pi/2, math.pi/4, 0xFFFF00FF, 0x000000FF, 0x202020FF)
  184. lastRingBearing = 0
  185. showThingsTimer = 0
  186. showThingsFlag = false
  187. fps = 60 -- frames per second, change if needed
  188.  
  189.  
  190. --------------------------------------------------------------------------------
  191. ------ Show Stuff on-Screen ----------------------------------------------------
  192. --------------------------------------------------------------------------------
  193.  
  194. gui.register( function ()
  195. -- Hide the additional HUD elements if we're in the normal game loop.
  196. if memory.readbyte(0xFFF711) ~= 0 then
  197.  
  198.  
  199. -- Autohide various things
  200.  
  201.     isPaused = memory.readbyte(0xFFF63B) ~= 0
  202.     currentButtons = joypad.get(1)
  203.     if currentButtons.up and AND(memory.readbyte(0xFFF605), 0x20) == 0x20 then
  204.         showThingsTimer = showThingsTimer + 120
  205.         showThingsFlag = false
  206.     else
  207.         if not isPaused and showThingsTimer ~= 0 then
  208.             showThingsTimer = showThingsTimer - 1
  209.         end
  210.     end
  211.     if currentButtons.up and AND(memory.readbyte(0xFFF605), 0x10) == 0x10 then
  212.         if showThingsFlag then
  213.             showThingsFlag = false
  214.         else
  215.             showThingsFlag = true
  216.             showThingsTimer = 1
  217.         end
  218.     end
  219.     if showThingsFlag then
  220.         showThingsTimer = 1
  221.     end
  222.     if showThingsTimer == 0 then
  223.         showThingsFlag = false
  224.     end
  225.  
  226.  
  227. -- Ring finder
  228.    
  229.     if memory.readwordunsigned(0xFFFF40) ~= 0 then -- addr = Perfect bonus counter
  230.         -- Cache Sonic's location
  231.         xloc = memory.readwordunsigned(0xFFB008)
  232.         yloc = memory.readwordunsigned(0xFFB00C)
  233.        
  234.         -- Initalize scanning loop
  235.         keepGoing = true
  236.         address = 0xFFE806
  237.         nearestDistance = 65535
  238.         nearestBearing = 0
  239.        
  240.         -- Scan the ring table in RAM, keeping track of the smallest distance found.
  241.         -- The ring table has 6-byte entries.  The first word is the
  242.         -- destruction animation counter.  The next two are simply the x and y
  243.         -- location.  0xFFFFFFFF marks the end of the table.
  244.         while keepGoing and address < 0xFFEDFF do
  245.             value = memory.readwordunsigned(address)
  246.             if value == 0 then
  247.                 deltax = memory.readwordunsigned(address+2) - xloc
  248.                 deltay = memory.readwordunsigned(address+4) - yloc
  249.                 value = math.sqrt(deltax*deltax + deltay*deltay)
  250.                 if value < nearestDistance then
  251.                     nearestDistance = value
  252.                     nearestBearing = math.atan2(deltay, deltax)
  253.                 end
  254.             else
  255.                 if value == 0xFFFF and memory.readwordunsigned(address+2) == 0xFFFF then
  256.                     keepGoing = false
  257.                 end
  258.             end
  259.             address = address + 6
  260.         end
  261.        
  262.         -- Make 0 < nearestBearing < 2*pi
  263.         if nearestBearing < 0 then nearestBearing = nearestBearing + 2*math.pi end
  264.         -- Now remap that to be between 0 and 1 (input for the DrawNeedle() method)
  265.         nearestBearing = nearestBearing/(2*math.pi)
  266.         -- Here's where we get to the fancy animation.  This finds the
  267.         -- direction the needle needs to swing in to make the shortest arc to
  268.         -- the current bearing.
  269.         smallestDelta = 1
  270.         for n = -1, 1 do
  271.             delta = math.abs(nearestBearing - lastRingBearing - n)
  272.             if smallestDelta > delta then
  273.                 smallestDelta = delta
  274.                 value = nearestBearing - lastRingBearing - n
  275.             end
  276.         end
  277.         -- Then, by dividing by a number, we make it swing to the location,
  278.         -- instead of jumping there instantly.
  279.         lastRingBearing = value/fps/3 + lastRingBearing
  280.         lastRingBearing = lastRingBearing - math.floor(lastRingBearing)
  281.         -- Now show the results, if wanted.  Note that the needle still
  282.         -- swings when it isn't visible.
  283.         if showThingsTimer ~= 0 then
  284.             ringFinder:DrawMeter()
  285.             ringFinder:DrawNeedle(lastRingBearing, 0xFFFF00FF)
  286.             gui.text(40, 64, string.format("%d", nearestDistance), 0xFFFF00FF, 0x000000FF)
  287.         end
  288.     end
  289.  
  290.  
  291. -- Speed
  292.  
  293.     xspeed = memory.readwordsigned(0xFFB010)
  294.     yspeed = memory.readwordsigned(0xFFB012)
  295.     value = math.sqrt(xspeed*xspeed + yspeed*yspeed)
  296.    
  297.     speedometer:DrawMeter()
  298.     if value > maxSpeed then
  299.         maxSpeed = value
  300.         maxMaxSpeed = value
  301.     else
  302.         if not isPaused then
  303.             maxSpeed = maxSpeed - 16
  304.             if maxSpeed < 0 then
  305.                 maxSpeed = 0
  306.                 maxMaxSpeed = 0
  307.             end
  308.         end
  309.     end
  310.     if maxMaxSpeed ~= 0 then
  311.         gui.text(290, 184, string.format("%5d", maxMaxSpeed), 0xFF0000FF, "black")
  312.     end
  313.     gui.text(266, 216, string.format("Speed:%5d", value), 0xFFFF00FF, "black")
  314.     speedometer:DrawNeedle(maxSpeed/maximumSpeed, 0xFF0000FF, false, speedometer.size-4)
  315.     speedometer:DrawNeedle(value/maximumSpeed, 0xFFFF00FF)
  316.  
  317.  
  318. -- Perfect counter
  319.    
  320.     if showThingsTimer ~= 0 then
  321.     color = 0xFFFF00FF
  322.     bgColor = 0x000000FF
  323.     value = memory.readwordunsigned(0xFFFF40)
  324.     if value == 0 then
  325.         color = 0xFFFFFF60
  326.         bgColor = 0x00000060
  327.     end
  328.     gui.text(17, 54, string.format("Remaining: %3d", value), color, bgColor)
  329.     end
  330.  
  331.  
  332. -- Spin-dash "Boost meter"
  333.  
  334.     color = 0xFFFF00FF--0xFFFF00A0
  335.     bgColor = 0x000000FF--0x000000A0
  336.     boostColor = 0x808080FF
  337.     if memory.readbyte(0xFFB039) ~= 0 then -- addr = Spin dash flag
  338.         lastSpindashValue = memory.readword(0xFFB03A)
  339.         boostColor = 0xFF2020FF
  340.         boostTimer = 90
  341.     else
  342.         if not isPaused then
  343.             if boostTimer > 0 then boostTimer = boostTimer - 1 end
  344.         end
  345.     end
  346.     if boostTimer > 0 then
  347.         boostMeter:DrawMeter()
  348.         boostMeter:DrawNeedle(lastSpindashValue/0x800, boostColor)
  349.         message = string.format("Boost: %4d", lastSpindashValue)
  350.         gui.text(210, 216, message, color, bgColor)
  351.     end
  352.  
  353.  
  354. -- Collisions selection
  355.  
  356.     if showThingsTimer ~= 0 then
  357.     color = 0xFFFF00FF
  358.     bgColor = 0x000000FF
  359.     if memory.readbyte(0xFFB03E) ~= 0x0C then
  360.         color = 0x00FFFFFF
  361.     end
  362.     gui.text(276, 0, "UP", color, bgColor)
  363.     color = 0xFFFF00FF
  364.     bgColor = 0x000000FF
  365.     if memory.readbyte(0xFFB03F) ~= 0x0D then
  366.         color = 0x00FFFFFF
  367.     end
  368.     gui.text(290, 0, "D/L/R", color, bgColor)
  369.     end
  370.  
  371.  
  372. -- Horizontal control lock
  373.    
  374.     value = memory.readwordunsigned(0xFFB02E)
  375.     if value ~= 0 then
  376.         gui.text(300, 18, "LOCK", 0xFF0000FF, 0x000000FF)
  377.     end
  378.  
  379.    
  380. -- Continues
  381.  
  382.     if showThingsTimer ~= 0 then
  383.     color = 0xFFFF0080
  384.     bgColor = 0x00000080
  385.     gui.text(17, 216, string.format("Continues: %d", memory.readbyte(0xFFFE18)), color, bgColor)
  386.     end
  387.  
  388.  
  389. -- Checkpoint
  390.  
  391.     if showThingsTimer ~= 0 then
  392.     color = 0xFFFFFF80
  393.     bgColor = 0x00000080
  394.     value = memory.readbyteunsigned(0xFFFE30)
  395.     if value ~= 0 then
  396.         color = 0xFFFF00FF
  397.         bgColor = 0x000000FF
  398.     end
  399.     gui.text(17, 0, string.format("Checkpoint: %d", value), color, bgColor)
  400. --  memory.writebyte(0xFFFE30,0)--Disables checkpoints
  401.     end
  402.  
  403.  
  404. -- Timers
  405.  
  406.     -- Air
  407.     value = memory.readbyte(0xFFB028)
  408.     if value ~= 0x1E then
  409.         gui.text(80, 0, string.format("Air: %2d", value), 0xFFFFFFFF, 0x000000FF)
  410.     end
  411.  
  412.     -- Temporary invunerability
  413.     value = memory.readwordunsigned(0xFFB030)
  414.     if value ~= 0 then
  415.         gui.text(246, 9, string.format("%4d", value), 0xFF0000FF, 0x000000FF)
  416.     end
  417.  
  418.     -- Invincibility
  419.     value = memory.readwordunsigned(0xFFB032)
  420.     if value ~= 0 then
  421.         gui.text(120, 0, string.format("Invincible: %3d", value/fps), 0xFFFFFFFF, 0x000000FF)
  422.     end
  423.  
  424.     -- Speed shoe
  425.     value = memory.readwordunsigned(0xFFB034)
  426.     if value ~= 0 then
  427.         gui.text(192, 0, string.format("Speed Shoe: %3d", value/fps), 0xFFFFFFFF, 0x000000FF)
  428.     end
  429.  
  430.  
  431. else
  432.     showThingsFlag = false
  433. end
  434. end)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement