Don't like ads? PRO users don't see any ads ;-)
Guest

S2 & S3&K Speedometer

By: a guest on May 6th, 2012  |  syntax: Lua  |  size: 22.63 KB  |  hits: 86  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. --------------------------------------------------------------------------------
  2. --------------------------------------------------------------------------------
  3. ------ Sonic Speedometer v1.0 --------------------------------------------------
  4. ------ For multiple versions ---------------------------------------------------
  5. --------------------------------------------------------------------------------
  6. --------------------------------------------------------------------------------
  7.  
  8. -- This script shows several interesting values read directly out of the game
  9. -- engine.  All of these, except for the speedometer, will autohide themselves.
  10. -- This currently works for Sonic 2 and Sonic 3&K.
  11. -- Use Gens Rerecording http://code.google.com/p/gens-rerecording/
  12. -- Items marked with a + will appear when you press Up + C; tap C multiple times
  13. -- to get them to stay onscreen longer.  Or, use Up + B to toggle them.
  14. --  + Checkpoint: Shows most recent checkpoint.
  15. --  + Remaining: Shows how many rings are left to collect until you get a
  16. --    Perfect Bonus.
  17. --  + Circular thing with a number: This shows you the distance and bearing to
  18. --    the nearest ring.  In other words, the needle in the circle shows you in
  19. --    what direction the nearest ring lies, and the number next to it shows how
  20. --    far away the ring is.
  21. --    Yes, I know this won't always give a correct distance/bearing for
  22. --    Metropolis Zone due to the wrapping.
  23. --  + UP D/L/R: These change color to show which collision collision plane is
  24. --    active: yellow for the primary, cyan for the secondary.
  25. --  - LOCK: Shows up when a control lock engages.
  26. --  - Air: Shows up under water.  Shows how how many seconds of air are left.
  27. --  - Invincible: This timer shows up when you get an invincibility monitor.
  28. --  - Speed Shoe: When this timer show up, it shows what you'd think it shows.
  29. --  - Unlabeled red number: Shows how many FRAMES of temporary invunerability
  30. --    you have left.
  31. --  - Flying: Shows up when Tails is flying
  32. --  - Continues: Reminds you how many continues you have got.
  33. --  - Speed: This shows your speed.  The dial also features a digital readout
  34. --    of your speed below.  The gray needle bounces up and down to show your
  35. --    most recent maximum speed; the red number above shows what this speed is.
  36. --    You can change the range of the speedometer
  37. --  - Boost: Shows up only when you do a spin dash.  Freezes to show how
  38. --    powerful the dash was.
  39. --
  40. -- Lua is supposed to be easy to understand, even for non-coders.  You shouldn't
  41. -- have trouble changing what gets shown on screen.
  42. --
  43. -- To Do:
  44. --  - Fix ring finder for Metropolis Zone and other zones that loop
  45. --  - Implement non-sucky arc-drawing and fill algorithms.  I'll probably never
  46. --    actually do this, but feel free to contribute one for me.
  47. --
  48. -- Consider this script to be given out under the GPL. --- DrDnar, 1 May 2012
  49. --
  50. -- Change log:
  51.  
  52.  
  53.  
  54. --------------------------------------------------------------------------------
  55. ------ Game-specific Data ------------------------------------------------------
  56. --------------------------------------------------------------------------------
  57.  
  58. isPaused = {}
  59. gameActive = {}
  60. buttonsChanged = {}
  61. perfectCounter = {}
  62. xLocation = {}
  63. yLocation = {}
  64. xSpeed = {}
  65. ySpeed = {}
  66. continues = {}
  67. checkpoint = {}
  68. airRemaining = {}
  69. hLockTimer = {}
  70. invulnTimer = {}
  71. invincTimer = {}
  72. speedShoeTimer = {}
  73. spinDashFlag = {}
  74. spinDashCharge = {}
  75. collisionsUpFlag = {}
  76. collisionsDLRFlag = {}
  77. ringTable = {}
  78. checkpoint = {}
  79. spriteTable = {}
  80. currentCharacter = {}
  81.  
  82. function ReadROM(start, length)
  83.         array = {}
  84.         for n = 0, length-1 do
  85.                 array[n+1] = memory.readbyte(start + n)
  86.         end
  87.         return array
  88. end
  89.  
  90. function ArrayEquals(one, two)
  91.         local n = 1
  92.         while one[n] ~= nil and two[n] ~= nil do
  93. --              print(string.format("%2X %2X | ", one[n], two[n]))
  94.                 if one[n] ~= two[n] then return false end
  95.                 n = n + 1
  96.         end
  97.         if one[n] ~= two[n] then return false end
  98.         return true
  99. end
  100.  
  101. romIDLocation = 0x180
  102. romIDLength = 14
  103. s1id = {0x47, 0x4D, 0x20, 0x30, 0x30, 0x30, 0x30, 0x34, 0x30, 0x34, 0x39, 0x2D, 0x30, 0x31}
  104. --        G     M           0     0     0     0     4     0     4     9     -     0     1
  105. sonic1 = 1
  106.  
  107. s2id = {0x47, 0x4D, 0x20, 0x30, 0x30, 0x30, 0x30, 0x31, 0x30, 0x35, 0x31, 0x2D, 0x30, 0x31}
  108. --        G     M           0     0     0     0     1     0     5     1     -     0     1
  109. sonic2 = 2
  110.  
  111. s3kid = {0x47, 0x4D, 0x20, 0x4D, 0x4B, 0x2D, 0x31, 0x35, 0x36, 0x33, 0x20, 0x2D, 0x30, 0x30}
  112. --         G     M           M     K     -     1     5     6     3           -     0     0
  113. sonic3k = 3
  114.  
  115. --if ArrayEquals(s1id, ReadROM(romIDLocation, romIDLength)) then
  116. --      isS1 = true
  117. --      currentGame = sonic1
  118. --end
  119.  
  120. if ArrayEquals(s2id, ReadROM(romIDLocation, romIDLength)) then
  121.         print("ROM type: S2")
  122.         isS2 = true
  123.         currentGame = sonic2
  124.         isPaused.location = 0xFFF63B
  125.         gameActive.location = 0xFFF711
  126.         buttonsChanged.location = 0xFFF605
  127.         perfectCounter.location = 0xFFFF40
  128.         xLocation.location = 0xFFB008
  129.         yLocation.location = 0xFFB00C
  130.         xSpeed.location = 0xFFB010
  131.         ySpeed.location = 0xFFB012
  132.         continues.location = 0xFFFE18
  133.         checkpoint.location = 0xFFFE30
  134.         airRemaining.location = 0xFFB028
  135.         hLockTimer.location = 0xFFB02E
  136.         function invulnTimer.get() return memory.readwordunsigned(0xFFB030) end
  137.         function invincTimer.get() return memory.readwordunsigned(0xFFB032) end
  138.         function speedShoeTimer.get() return memory.readwordunsigned(0xFFB034) end
  139.         spinDashFlag.location = 0xFFB039
  140.         spinDashFlag.exists = true
  141.         spinDashCharge.location = 0xFFB03A
  142.         collisionsUpFlag.location = 0xFFB03E
  143.         collisionsUpFlag.exists = true
  144.         collisionsDLRFlag.location = 0xFFB03F
  145.         ringTable.location = 0xFFE806
  146.         ringTable.length = 0x600
  147.         ringTable.type = sonic2
  148.         checkpoint.location = 0xFFFE30
  149.         spriteTable.itemSize = 0x40
  150.         spriteTable.location = 0xFFB000
  151. end
  152.  
  153. if ArrayEquals(s3kid, ReadROM(romIDLocation, romIDLength)) then
  154.         print("ROM type: S3&K")
  155.         isS3K = true
  156.         currentGame = sonic3k
  157.         isPaused.location = 0xFFF63B
  158.         gameActive.location = 0xFFF711
  159.         buttonsChanged.location = 0xFFF605
  160.         perfectCounter.location = 0xFFFF04
  161.         xLocation.location = 0xFFB010
  162.         yLocation.location = 0xFFB014
  163.         xSpeed.location = 0xFFB018
  164.         ySpeed.location = 0xFFB01A
  165.         continues.location = 0xFFFE18
  166.         checkpoint.location = 0xFFFE2A
  167.         airRemaining.location = 0xFFB02C
  168.         hLockTimer.location = 0xFFB032
  169.         function invulnTimer.get() return memory.readbyteunsigned(0xFFB034) end
  170.         function invincTimer.get() return memory.readbyteunsigned(0xFFB035)*8 end
  171.         function speedShoeTimer.get() return memory.readbyteunsigned(0xFFB036)*8 end
  172.         spinDashFlag.location = 0xFFB03D
  173.         spinDashFlag.exists = true
  174.         spinDashCharge.location = 0xFFB03E
  175.         collisionsUpFlag.location = 0xFFB046
  176.         collisionsDLRFlag.location = 0xFFB047
  177.         ringTable.location = 0xFFE806
  178.         ringTable.length = 0x600
  179.         ringTable.type = sonic3k
  180.         checkpoint.location = 0xFFFE30
  181.         spriteTable.itemSize = 0x4A
  182.         spriteTable.location = 0xFFB000
  183.         currentCharacter.location = 0xFFFF08
  184. end
  185.  
  186. if currentGame == nil then
  187.         error("Possibily unsupported game. Script editing is required.")
  188. end
  189.  
  190.  
  191.  
  192. --------------------------------------------------------------------------------
  193. ------ Meter Object ------------------------------------------------------------
  194. --------------------------------------------------------------------------------
  195. -- This object lets you draw a generic meter or dial, with multiple needles.
  196. -- To declare a meter, do
  197. --  - Meter:Create(baseX, baseY, size, thetaMin, thetaMax, scaleInc, fgColor, bgColor, fill)
  198. --    - baseX, baseY: X&Y location for the meter
  199. --    - size: Radius of the meter
  200. --    - thetaMin, thetaMax: These variables control the arc that the meter's
  201. --      range will sweep.  You could, for example, have the meter sweep a range
  202. --      less than the semicircle used in this example.
  203. --    - scaleInc: If not nil, this controls how often tic marks appear on the
  204. --      meter's scale.  Specified as an angular incrememt.
  205. --    - fgColor, bgColor: Controls the color of the main outer arc and the
  206. --      shadowing of that arc.
  207. --    - fill: Controls the fill color of the arc.
  208. --    * The color values tend to look funny if you specify an opacity that isn't
  209. --      fully opaque or transparent.
  210. -- This function will return a new Meter object.  Don't forget to assign it to a
  211. -- variable:
  212. --  - aMeter = Meter:Create(...)
  213. -- To draw the meter on the screen, first do
  214. --  - aMeter:DrawMeter()
  215. -- to draw the meter's basic graphic.  Then, for each needle value you want to
  216. -- show, do
  217. --  - aMeter:DrawNeedle(percentage, color, saturate, size)
  218. --    - percentage: A number between 0 and 1 that represents the fraction of the
  219. --      arc between thetaMin and thetaMax that the needle should point to.
  220. --    - color: The color of the needle line.  At the moment, there is no support
  221. --      for shadowing the needle color.
  222. --    - saturate: If true, then any percentage value less than 0 will be treated
  223. --      as zero, and any value greater than 1 will be treated as one.
  224. --    - size: Controls the length (radius) of the needle.
  225. -- The parameters after color may be omitted.  They will default to
  226. --    - color: fgColor passed to Create() method
  227. --    - saturate: false
  228. --    - size: size passed to Create() method.
  229.  
  230. Meter = {}
  231. Meter.__index = Meter
  232.  
  233. function Meter:Create(baseX, baseY, size, thetaMin, thetaMax, scaleInc, fgColor, bgColor, fill)
  234.         local newItem = {}
  235.         setmetatable(newItem, Meter)  -- Bind object methods to new object
  236.         newItem.baseX = baseX -- x location
  237.         newItem.baseY = baseY -- y location
  238.         newItem.thetaMin = thetaMin
  239.         newItem.thetaMax = thetaMax
  240.         newItem.thetaDelta = thetaMax - thetaMin
  241.         newItem.size = size -- size
  242.         newItem.scaleInc = scaleInc
  243.         newItem.bg = bgColor
  244.         newItem.fg = fgColor
  245.         newItem.fill = fill
  246.         return newItem
  247. end
  248.  
  249. function Meter:DrawMeter()
  250.         local inc = math.asin(1/self.size)/(self.size/2)
  251.         local start, stop = self.thetaMin, self.thetaMax
  252.         if start > stop then
  253.                 start, stop = stop, start
  254.         end
  255.         local theta = start
  256.         local size, basex, basey = self.size, self.baseX, self.baseY
  257.         local cos, sin
  258.        
  259.         while theta < stop do
  260.                 cos = math.cos(theta)
  261.                 sin = math.sin(theta)
  262.                 gui.pixel(sin*size + basex, cos*size + basey, self.fg)
  263.                 gui.pixel(sin*(size+0.5) + basex, cos*(size+0.5) + basey, self.fg)
  264.                 --gui.pixel(sin*(size-1) + basex, cos*(size-1) + basey, self.fg)
  265.                 if self.bg ~= nil then
  266.                         gui.pixel(sin*(size-1) + basex, cos*(size-1) + basey, self.bg)
  267.                         gui.pixel(sin*(size-1.5) + basex, cos*(size-1.5) + basey, self.bg)
  268.                 end
  269.                 if self.bg ~= nil then
  270.                         gui.line(basex, basey, sin*(size-2) + basex, cos*(size-2) + basey, self.fill)
  271.                 end
  272.                 theta = theta + inc
  273.         end
  274.         theta = start
  275.         while theta < stop do
  276.                 cos = math.cos(theta)
  277.                 sin = math.sin(theta)
  278.                 if self.scaleInc ~= nil and ((theta-start)/self.scaleInc)%1 * self.scaleInc > (self.scaleInc-(2*inc)) then
  279.                         gui.pixel(sin*(size-1) + basex, cos*(size-1) + basey, self.fg)
  280.                         gui.pixel(sin*(size-1.5) + basex, cos*(size-1.5) + basey, self.fg)
  281.                         gui.pixel(sin*(size-2) + basex, cos*(size-2) + basey, self.fg)
  282.                         gui.pixel(sin*(size-2.5) + basex, cos*(size-2.5) + basey, self.fg)
  283. --                      gui.line(sin*(size-1) + basex, cos*(size-1) + basey, sin*(size-3) + basex, cos*(size-3) + basey, self.fg)
  284.                 end
  285.                 theta = theta + inc
  286.         end
  287.  
  288. end
  289.  
  290. function Meter:DrawNeedle(percent, fgColor, saturate, size)
  291.         if saturate and percent > 1 then
  292.                 percent = 1
  293.         end
  294.         if saturate and percent < 0 then
  295.                 percent = 0
  296.         end
  297.         if fgColor == nil then
  298.                 fgColor = self.fgColor
  299.         end
  300.         if size == nil then
  301.                 size = self.size - 3
  302.         end
  303.         local theta = percent * self.thetaDelta + self.thetaMin
  304.         local sin, cos = math.cos(theta), math.sin(theta)
  305.        
  306.         gui.line(self.baseX, self.baseY, size*cos + self.baseX, size*sin + self.baseY, fgColor)
  307. end
  308.  
  309.  
  310.  
  311. --------------------------------------------------------------------------------
  312. ------ Globals -----------------------------------------------------------------
  313. --------------------------------------------------------------------------------
  314.  
  315. lastSpindashValue = 0
  316. boostMeter = Meter:Create(231, 214, 20, 3*math.pi/2, math.pi/2, math.pi/8, 0xFFFF00FF, 0x000000FF, 0x202020FF)
  317. boostTimer = 0
  318. speedometer = Meter:Create(287, 214, 20, 3*math.pi/2, math.pi/2, math.pi/8, 0xFFFF00FF, 0x000000FF, 0x202020FF)
  319. maxSpeed = 0
  320. maxMaxSpeed = 0
  321. maximumSpeed = 4096
  322. ringFinder = Meter:Create(27, 72, 10, math.pi*2+math.pi/2, math.pi/2, math.pi/4, 0xFFFF00FF, 0x000000FF, 0x202020FF)
  323. lastRingBearing = 0
  324. showThingsTimer = 0
  325. showThingsFlag = false
  326. fps = 60 -- frames per second, change if needed
  327.  
  328. nearestDistance = 0 -- needs init, not really global though
  329.  
  330.  
  331. --------------------------------------------------------------------------------
  332. ------ Show Stuff on-Screen ----------------------------------------------------
  333. --------------------------------------------------------------------------------
  334.  
  335. gui.register( function ()
  336. -- Hide the additional HUD elements if we're not in the normal game loop.
  337. if memory.readbyte(gameActive.location) ~= 0 then
  338.  
  339.  
  340.         perfectCounter.value = memory.readwordunsigned(perfectCounter.location)
  341.         -- Cache Sonic's location and other things
  342.         xLocation.value = memory.readwordunsigned(xLocation.location)
  343.         yLocation.value = memory.readwordunsigned(yLocation.location)
  344.         xSpeed.value = memory.readwordsigned(xSpeed.location)
  345.         ySpeed.value = memory.readwordsigned(ySpeed.location)
  346.         perfectCounter.value = memory.readwordunsigned(perfectCounter.location)
  347.         if currentGame == sonic3k then
  348.                 currentCharacter.value = memory.readwordunsigned(currentCharacter.location)
  349.         end
  350.        
  351.  
  352. -- Autohide various things
  353.  
  354.         isPaused.value = memory.readbyte(isPaused.location) ~= 0
  355.         currentButtons = joypad.get(1)
  356.         if currentButtons.up and AND(memory.readbyte(buttonsChanged.location), 0x20) == 0x20 then
  357.                 showThingsTimer = showThingsTimer + 120
  358.                 showThingsFlag = false
  359.         else
  360.                 if not isPaused.value and showThingsTimer ~= 0 then
  361.                         showThingsTimer = showThingsTimer - 1
  362.                 end
  363.         end
  364.         if currentButtons.up and AND(memory.readbyte(buttonsChanged.location), 0x10) == 0x10 then
  365.                 if showThingsFlag then
  366.                         showThingsFlag = false
  367.                 else
  368.                         showThingsFlag = true
  369.                         showThingsTimer = 1
  370.                 end
  371.         end
  372.         if showThingsFlag then
  373.                 showThingsTimer = 1
  374.         end
  375.         if showThingsTimer == 0 then
  376.                 showThingsFlag = false
  377.         end
  378.  
  379.  
  380. -- Ring finder
  381.  
  382.         haveBearing = false
  383.         -- Sonic 2
  384.         if perfectCounter.value ~= 0 and not isPaused.value and currentGame == sonic2 then
  385.                 -- Initalize scanning loop
  386.                 keepGoing = true
  387.                 address = 0xFFE806
  388.                 nearestDistance = 65535
  389.                 nearestBearing = 0
  390.                
  391.                 -- Scan the ring table in RAM, keeping track of the smallest distance found.
  392.                 -- The ring table has 6-byte entries.  The first word is the
  393.                 -- destruction animation counter.  The next two are simply the x and y
  394.                 -- location.  0xFFFFFFFF marks the end of the table.
  395.                 while keepGoing and address < 0xFFEDFF do
  396.                         value = memory.readwordunsigned(address)
  397.                         if value == 0 then
  398.                                 deltax = memory.readwordunsigned(address+2) - xLocation.value
  399.                                 deltay = memory.readwordunsigned(address+4) - yLocation.value
  400.                                 value = math.sqrt(deltax*deltax + deltay*deltay)
  401.                                 if value < nearestDistance then
  402.                                         nearestDistance = value
  403.                                         nearestBearing = math.atan2(deltay, deltax)
  404.                                 end
  405.                                 haveBearing = true
  406.                         else
  407.                                 if value == 0xFFFF and memory.readwordunsigned(address+2) == 0xFFFF then
  408.                                         keepGoing = false
  409.                                 end
  410.                         end
  411.                         address = address + 6
  412.                 end
  413.         end
  414.         -- S3&K
  415.         if perfectCounter.value ~= 0 and (currentGame == sonic3k) then
  416.                 haveBearing = true
  417.                 -- Scan backwards
  418.                 keepGoing = true
  419.                 address = memory.readwordunsigned(0xFFEE42)
  420.                 address = address * 65536 + memory.readwordunsigned(0xFFEE44)
  421.                 status = 0xFF0000 + memory.readwordunsigned(0xFFEE4A)
  422.                 nearestDistance = 65535
  423.                 nearestBearing = 0
  424.                 while keepGoing do
  425.                         value = memory.readwordunsigned(address)
  426.                         if value ~= 0 then
  427.                                 value = memory.readwordunsigned(status)
  428.                                 if memory.readwordunsigned(status) == 0 then
  429.                                         deltax = memory.readwordunsigned(address) - xLocation.value
  430.                                         deltay = memory.readwordunsigned(address+2) - yLocation.value
  431.                                         value = math.sqrt(deltax*deltax + deltay*deltay)
  432.                                         if value < nearestDistance then
  433.                                                 nearestDistance = value
  434.                                                 nearestBearing = math.atan2(deltay, deltax)
  435.                                                 haveBearing = true
  436.                                         end
  437.                                 end
  438.                         else
  439.                                 keepGoing = false
  440.                         end
  441.                         address = address - 4
  442.                         status = status - 2
  443.                 end
  444.                 -- Scan forwards
  445.                 keepGoing = true
  446.                 address = memory.readwordunsigned(0xFFEE42)
  447.                 address = address * 65536 + memory.readwordunsigned(0xFFEE44)
  448.                 status = 0xFF0000 + memory.readwordunsigned(0xFFEE4A)
  449.                 while keepGoing do
  450.                         value = memory.readwordunsigned(address)
  451.                         if value ~= 0xFFFF then
  452.                                 if memory.readwordunsigned(status) == 0 then
  453.                                         deltax = memory.readwordunsigned(address) - xLocation.value
  454.                                         deltay = memory.readwordunsigned(address+2) - yLocation.value
  455.                                         value = math.sqrt(deltax*deltax + deltay*deltay)
  456.                                         if value < nearestDistance then
  457.                                                 nearestDistance = value
  458.                                                 nearestBearing = math.atan2(deltay, deltax)
  459.                                                 haveBearing = true
  460.                                         end
  461.                                 end
  462.                         else
  463.                                 keepGoing = false
  464.                         end
  465.                         address = address + 4
  466.                         status = status + 2
  467.                 end
  468.         end
  469.         if haveBearing then
  470.                 if not isPaused.value then
  471.                         -- Make 0 < nearestBearing < 2*pi
  472.                         if nearestBearing < 0 then nearestBearing = nearestBearing + 2*math.pi end
  473.                         -- Now remap that to be between 0 and 1 (input for the DrawNeedle() method)
  474.                         nearestBearing = nearestBearing/(2*math.pi)
  475.                         -- Here's where we get to the fancy animation.  This finds the
  476.                         -- direction the needle needs to swing in to make the shortest arc to
  477.                         -- the current bearing.
  478.                         smallestDelta = 1
  479.                         for n = -1, 1 do
  480.                                 delta = math.abs(nearestBearing - lastRingBearing - n)
  481.                                 if smallestDelta > delta then
  482.                                         smallestDelta = delta
  483.                                         value = nearestBearing - lastRingBearing - n
  484.                                 end
  485.                         end
  486.                         -- Then, by dividing by a number, we make it swing to the location,
  487.                         -- instead of jumping there instantly.
  488.                         if math.abs(value) > 0.006 then -- Implement minimum speed
  489.                                 value = (value/math.abs(value)) * (value*value / 3 + 0.002)
  490.                                 if math.abs(value) > 0.03 then -- Implement maximum speed
  491.                                         value = 0.03 * value/math.abs(value)
  492.                                 end
  493.                         end
  494.                         lastRingBearing = value + lastRingBearing
  495.                         lastRingBearing = lastRingBearing - math.floor(lastRingBearing)
  496.                         nearestDistance = nearestDistance - 14
  497.                         if nearestDistance < 0 then nearestDistance = 0 end
  498.                 end
  499.  
  500.                 -- Now show the results, if wanted.  Note that the needle still
  501.                 -- swings when it isn't visible.
  502.                 if showThingsTimer ~= 0 then
  503.                         ringFinder:DrawMeter()
  504.                         ringFinder:DrawNeedle(lastRingBearing, 0xFFFF00FF)
  505.                         gui.text(40, 64, string.format("%d", nearestDistance), 0xFFFF00FF, 0x000000FF)
  506.                 end
  507.         end
  508.  
  509.  
  510. -- Speed
  511.  
  512.         value = math.sqrt(xSpeed.value*xSpeed.value + ySpeed.value*ySpeed.value)
  513.        
  514.         speedometer:DrawMeter()
  515.         if value > maxSpeed then
  516.                 maxSpeed = value
  517.                 maxMaxSpeed = value
  518.         else
  519.                 if not isPaused.value then
  520.                         maxSpeed = maxSpeed - 16
  521.                         if maxSpeed < 0 then
  522.                                 maxSpeed = 0
  523.                                 maxMaxSpeed = 0
  524.                         end
  525.                 end
  526.         end
  527.         if maxMaxSpeed ~= 0 then
  528.                 gui.text(290, 184, string.format("%5d", maxMaxSpeed), 0xFF0000FF, "black")
  529.         end
  530.         gui.text(266, 216, string.format("Speed:%5d", value), 0xFFFF00FF, "black")
  531.         speedometer:DrawNeedle(maxSpeed/maximumSpeed, 0xFF0000FF, false, speedometer.size-4)
  532.         speedometer:DrawNeedle(value/maximumSpeed, 0xFFFF00FF)
  533.  
  534.  
  535. -- Perfect counter
  536.        
  537.         if perfectCounter then
  538.         if showThingsTimer ~= 0 then
  539.         color = 0xFFFF00FF
  540.         bgColor = 0x000000FF
  541.         value = perfectCounter.value
  542.         if value == 0 then
  543.                 color = 0xFFFFFF60
  544.                 bgColor = 0x00000060
  545.         end
  546.         gui.text(17, 54, string.format("Remaining: %3d", value), color, bgColor)
  547.         end
  548.         end
  549.  
  550.  
  551. -- Spin-dash "Boost meter"
  552.  
  553.         if spinDashFlag then
  554.         color = 0xFFFF00FF--0xFFFF00A0
  555.         bgColor = 0x000000FF--0x000000A0
  556.         boostColor = 0x808080FF
  557.         if memory.readbyte(spinDashFlag.location) ~= 0 then
  558.                 lastSpindashValue = memory.readword(spinDashCharge.location)
  559.                 boostColor = 0xFF2020FF
  560.                 boostTimer = 90
  561.         else
  562.                 if not isPaused.value then
  563.                         if boostTimer > 0 then boostTimer = boostTimer - 1 end
  564.                 end
  565.         end
  566.         if boostTimer > 0 then
  567.                 boostMeter:DrawMeter()
  568.                 boostMeter:DrawNeedle(lastSpindashValue/0x800, boostColor)
  569.                 message = string.format("Boost: %4d", lastSpindashValue)
  570.                 gui.text(210, 216, message, color, bgColor)
  571.         end
  572.         end
  573.  
  574.  
  575. -- Collisions selection
  576.  
  577.         if showThingsTimer ~= 0 then
  578.         color = 0xFFFF00FF
  579.         bgColor = 0x000000FF
  580.         if memory.readbyte(collisionsUpFlag.location) ~= 0x0C then
  581.                 color = 0x00FFFFFF
  582.         end
  583.         gui.text(276, 0, "UP", color, bgColor)
  584.         color = 0xFFFF00FF
  585.         bgColor = 0x000000FF
  586.         if memory.readbyte(collisionsDLRFlag.location) ~= 0x0D then
  587.                 color = 0x00FFFFFF
  588.         end
  589.         gui.text(290, 0, "D/L/R", color, bgColor)
  590.         end
  591.  
  592.  
  593. -- Horizontal control lock
  594.        
  595.         value = memory.readwordunsigned(hLockTimer.location)
  596.         if value ~= 0 then
  597.                 gui.text(300, 18, "LOCK", 0xFF0000FF, 0x000000FF)
  598.         end
  599.  
  600.        
  601. -- Continues
  602.  
  603.         if showThingsTimer ~= 0 then
  604.         color = 0xFFFF0080
  605.         bgColor = 0x00000080
  606.         gui.text(17, 216, string.format("Continues: %d", memory.readbyte(continues.location)), color, bgColor)
  607.         end
  608.  
  609.  
  610. -- Checkpoint
  611.  
  612.         if showThingsTimer ~= 0 then
  613.         color = 0xFFFFFF80
  614.         bgColor = 0x00000080
  615.         value = memory.readbyteunsigned(checkpoint.location)
  616.         if value ~= 0 then
  617.                 color = 0xFFFF00FF
  618.                 bgColor = 0x000000FF
  619.         end
  620.         gui.text(17, 0, string.format("Checkpoint: %d", value), color, bgColor)
  621. --      memory.writebyte(0xFFFE30,0)--Disables checkpoints
  622.         end
  623.  
  624.  
  625. -- Timers
  626.  
  627.         -- Air
  628.         value = memory.readbyte(airRemaining.location)
  629.         if value ~= 0x1E then
  630.                 gui.text(80, 0, string.format("Air: %2d", value), 0xFFFFFFFF, 0x000000FF)
  631.         end
  632.  
  633.         -- Temporary invunerability
  634.         value = invulnTimer.get()
  635.         if value ~= 0 then
  636.                 gui.text(246, 9, string.format("%4d", value), 0xFF0000FF, 0x000000FF)
  637.         end
  638.  
  639.         -- Invincibility
  640.         value = invincTimer.get()
  641.         if value ~= 0 then
  642.                 gui.text(120, 0, string.format("Invincible: %2d", value/fps), 0xFFFFFFFF, 0x000000FF)
  643.         end
  644.  
  645.         -- Speed shoe
  646.         value = speedShoeTimer.get()
  647.         if value ~= 0 then
  648.                 gui.text(192, 0, string.format("Speed Shoe: %2d", value/fps), 0xFFFFFFFF, 0x000000FF)
  649.         end
  650.  
  651.         -- Tails flying time
  652.         if currentGame == sonic3k then
  653.                 if currentCharacter.value == 0 then
  654.                         value = memory.readbyteunsigned(0xFFB06F)*2
  655.                         if memory.readbyteunsigned(0xFFB079) ~= 0 then
  656.                                 gui.text(192, 9, string.format("Flying: %2d", value/fps), 0xFFFFFFFF, 0x000000FF)
  657.                         end
  658.                 end
  659.                 if currentCharacter.value == 02 then
  660.                         value = memory.readbyteunsigned(0xFFB025)*2
  661.                         if memory.readbyteunsigned(0xFFB02F) ~= 0 then
  662.                                 gui.text(192, 9, string.format("Flying: %2d", value/fps), 0xFFFFFFFF, 0x000000FF)
  663.                         end
  664.                 end
  665.         end
  666.  
  667. else
  668.         showThingsFlag = false
  669. end
  670. end)