THIS IS NOT MADE BY ME.
This was from a pastebin that someone made, but they are unknown, but this is made for clocking Sonic's speed in Sonic 2.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
------ Sonic 2 Speedometer v1.1 ------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- This script shows several interesting values read directly out of the S2 game
-- engine. All of these, except for the speedometer, will autohide themselves.
-- Use Gens Rerecording http://code.google.com/p/gens-rerecording/
-- Items marked with a + will appear when you press Up + C; tap C multiple times
-- to get them to stay onscreen longer. Or, use Up + B to toggle them.
-- + Checkpoint: Shows most recent checkpoint.
-- + Remaining: Shows how many rings are left to collect until you get a
-- Perfect Bonus.
-- + Circular thing with a number: This shows you the distance and bearing to
-- the nearest ring. In other words, the needle in the circle shows you in
-- what direction the nearest ring lies, and the number next to it shows how
-- far away the ring is.
-- Yes, I know this won't always give a correct distance/bearing for
-- Metropolis Zone due to the wrapping.
-- + UP D/L/R: These change color to show which collision collision plane is
-- active: yellow for the primary, cyan for the secondary.
-- - LOCK: Shows up when a control lock engages.
-- - Air: Shows up under water. Shows how how many seconds of air are left.
-- - Invincible: This timer shows up when you get an invincibility monitor.
-- - Speed Shoe: When this timer show up, it shows what you'd think it shows.
-- - Unlabeled red number: Shows how many FRAMES of temporary invunerability
-- you have left.
-- - Continues: Reminds you how many continues you have got.
-- - Speed: This shows your speed. The dial also features a digital readout
-- of your speed below. The gray needle bounces up and down to show your
-- most recent maximum speed; the red number above shows what this speed is.
-- You can change the range of the speedometer
-- - Boost: Shows up only when you do a spin dash. Freezes to show how
-- powerful the dash was.
--
-- Lua is supposed to be easy to understand, even for non-coders. You shouldn't
-- have trouble changing what gets shown on screen.
--
-- To Do:
-- - Fix ring finder for Metropolis Zone
-- - Implement non-sucky arc-drawing and fill algorithms. I'll probably never
-- actually do this, but feel free to contribute one for me.
--
-- Consider this script to be given out under the GPL. --- DrDnar, 1 May 2012
--
-- Change log:
-- - 2 May 2012: Made the code look a little cleaner
-- - mid-April through 1 May 2012: Initial version
--------------------------------------------------------------------------------
------ Meter Object ------------------------------------------------------------
--------------------------------------------------------------------------------
-- This object lets you draw a generic meter or dial, with multiple needles.
-- To declare a meter, do
-- - Meter:Create(baseX, baseY, size, thetaMin, thetaMax, scaleInc, fgColor, bgColor, fill)
-- - baseX, baseY: X&Y location for the meter
-- - size: Radius of the meter
-- - thetaMin, thetaMax: These variables control the arc that the meter's
-- range will sweep. You could, for example, have the meter sweep a range
-- less than the semicircle used in this example.
-- - scaleInc: If not nil, this controls how often tic marks appear on the
-- meter's scale. Specified as an angular incrememt.
-- - fgColor, bgColor: Controls the color of the main outer arc and the
-- shadowing of that arc.
-- - fill: Controls the fill color of the arc.
-- * The color values tend to look funny if you specify an opacity that isn't
-- fully opaque or transparent.
-- This function will return a new Meter object. Don't forget to assign it to a
-- variable:
-- - aMeter = Meter:Create(...)
-- To draw the meter on the screen, first do
-- - aMeter:DrawMeter()
-- to draw the meter's basic graphic. Then, for each needle value you want to
-- show, do
-- - aMeter:DrawNeedle(percentage, color, saturate, size)
-- - percentage: A number between 0 and 1 that represents the fraction of the
-- arc between thetaMin and thetaMax that the needle should point to.
-- - color: The color of the needle line. At the moment, there is no support
-- for shadowing the needle color.
-- - saturate: If true, then any percentage value less than 0 will be treated
-- as zero, and any value greater than 1 will be treated as one.
-- - size: Controls the length (radius) of the needle.
-- The parameters after color may be omitted. They will default to
-- - color: fgColor passed to Create() method
-- - saturate: false
-- - size: size passed to Create() method.
Meter = {}
Meter.__index = Meter
function Meter:Create(baseX, baseY, size, thetaMin, thetaMax, scaleInc, fgColor, bgColor, fill)
local newItem = {}
setmetatable(newItem, Meter) -- Bind object methods to new object
newItem.baseX = baseX -- x location
newItem.baseY = baseY -- y location
newItem.thetaMin = thetaMin
newItem.thetaMax = thetaMax
newItem.thetaDelta = thetaMax - thetaMin
newItem.size = size -- size
newItem.scaleInc = scaleInc
newItem.bg = bgColor
newItem.fg = fgColor
newItem.fill = fill
return newItem
end
function Meter:DrawMeter()
local inc = math.asin(1/self.size)/(self.size/2)
local start, stop = self.thetaMin, self.thetaMax
if start > stop then
start, stop = stop, start
end
local theta = start
local size, basex, basey = self.size, self.baseX, self.baseY
local cos, sin
while theta < stop do
cos = math.cos(theta)
sin = math.sin(theta)
gui.pixel(sin*size + basex, cos*size + basey, self.fg)
gui.pixel(sin*(size+0.5) + basex, cos*(size+0.5) + basey, self.fg)
--gui.pixel(sin*(size-1) + basex, cos*(size-1) + basey, self.fg)
if self.bg ~= nil then
gui.pixel(sin*(size-1) + basex, cos*(size-1) + basey, self.bg)
gui.pixel(sin*(size-1.5) + basex, cos*(size-1.5) + basey, self.bg)
end
if self.bg ~= nil then
gui.line(basex, basey, sin*(size-2) + basex, cos*(size-2) + basey, self.fill)
end
theta = theta + inc
end
theta = start
while theta < stop do
cos = math.cos(theta)
sin = math.sin(theta)
if self.scaleInc ~= nil and ((theta-start)/self.scaleInc)%1 * self.scaleInc > (self.scaleInc-(2*inc)) then
gui.pixel(sin*(size-1) + basex, cos*(size-1) + basey, self.fg)
gui.pixel(sin*(size-1.5) + basex, cos*(size-1.5) + basey, self.fg)
gui.pixel(sin*(size-2) + basex, cos*(size-2) + basey, self.fg)
gui.pixel(sin*(size-2.5) + basex, cos*(size-2.5) + basey, self.fg)
-- gui.line(sin*(size-1) + basex, cos*(size-1) + basey, sin*(size-3) + basex, cos*(size-3) + basey, self.fg)
end
theta = theta + inc
end
end
function Meter:DrawNeedle(percent, fgColor, saturate, size)
if saturate and percent > 1 then
percent = 1
end
if saturate and percent < 0 then
percent = 0
end
if fgColor == nil then
fgColor = self.fgColor
end
if size == nil then
size = self.size - 3
end
local theta = percent * self.thetaDelta + self.thetaMin
local sin, cos = math.cos(theta), math.sin(theta)
gui.line(self.baseX, self.baseY, size*cos + self.baseX, size*sin + self.baseY, fgColor)
end
--------------------------------------------------------------------------------
------ Globals -----------------------------------------------------------------
--------------------------------------------------------------------------------
lastSpindashValue = 0
boostMeter = Meter:Create(231, 214, 20, 3*math.pi/2, math.pi/2, math.pi/8, 0xFFFF00FF, 0x000000FF, 0x202020FF)
boostTimer = 0
speedometer = Meter:Create(287, 214, 20, 3*math.pi/2, math.pi/2, math.pi/8, 0xFFFF00FF, 0x000000FF, 0x202020FF)
maxSpeed = 0
maxMaxSpeed = 0
maximumSpeed = 4096
ringFinder = Meter:Create(27, 72, 10, math.pi*2+math.pi/2, math.pi/2, math.pi/4, 0xFFFF00FF, 0x000000FF, 0x202020FF)
lastRingBearing = 0
showThingsTimer = 0
showThingsFlag = false
fps = 60 -- frames per second, change if needed
--------------------------------------------------------------------------------
------ Show Stuff on-Screen ----------------------------------------------------
--------------------------------------------------------------------------------
gui.register( function ()
-- Hide the additional HUD elements if we're in the normal game loop.
if memory.readbyte(0xFFF711) ~= 0 then
-- Autohide various things
isPaused = memory.readbyte(0xFFF63B) ~= 0
currentButtons = joypad.get(1)
if currentButtons.up and AND(memory.readbyte(0xFFF605), 0x20) == 0x20 then
showThingsTimer = showThingsTimer + 120
showThingsFlag = false
else
if not isPaused and showThingsTimer ~= 0 then
showThingsTimer = showThingsTimer - 1
end
end
if currentButtons.up and AND(memory.readbyte(0xFFF605), 0x10) == 0x10 then
if showThingsFlag then
showThingsFlag = false
else
showThingsFlag = true
showThingsTimer = 1
end
end
if showThingsFlag then
showThingsTimer = 1
end
if showThingsTimer == 0 then
showThingsFlag = false
end
-- Ring finder
if memory.readwordunsigned(0xFFFF40) ~= 0 then -- addr = Perfect bonus counter
-- Cache Sonic's location
xloc = memory.readwordunsigned(0xFFB008)
yloc = memory.readwordunsigned(0xFFB00C)
-- Initalize scanning loop
keepGoing = true
address = 0xFFE806
nearestDistance = 65535
nearestBearing = 0
-- Scan the ring table in RAM, keeping track of the smallest distance found.
-- The ring table has 6-byte entries. The first word is the
-- destruction animation counter. The next two are simply the x and y
-- location. 0xFFFFFFFF marks the end of the table.
while keepGoing and address < 0xFFEDFF do
value = memory.readwordunsigned(address)
if value == 0 then
deltax = memory.readwordunsigned(address+2) - xloc
deltay = memory.readwordunsigned(address+4) - yloc
value = math.sqrt(deltax*deltax + deltay*deltay)
if value < nearestDistance then
nearestDistance = value
nearestBearing = math.atan2(deltay, deltax)
end
else
if value == 0xFFFF and memory.readwordunsigned(address+2) == 0xFFFF then
keepGoing = false
end
end
address = address + 6
end
-- Make 0 < nearestBearing < 2*pi
if nearestBearing < 0 then nearestBearing = nearestBearing + 2*math.pi end
-- Now remap that to be between 0 and 1 (input for the DrawNeedle() method)
nearestBearing = nearestBearing/(2*math.pi)
-- Here's where we get to the fancy animation. This finds the
-- direction the needle needs to swing in to make the shortest arc to
-- the current bearing.
smallestDelta = 1
for n = -1, 1 do
delta = math.abs(nearestBearing - lastRingBearing - n)
if smallestDelta > delta then
smallestDelta = delta
value = nearestBearing - lastRingBearing - n
end
end
-- Then, by dividing by a number, we make it swing to the location,
-- instead of jumping there instantly.
lastRingBearing = value/fps/3 + lastRingBearing
lastRingBearing = lastRingBearing - math.floor(lastRingBearing)
-- Now show the results, if wanted. Note that the needle still
-- swings when it isn't visible.
if showThingsTimer ~= 0 then
ringFinder:DrawMeter()
ringFinder:DrawNeedle(lastRingBearing, 0xFFFF00FF)
gui.text(40, 64, string.format("%d", nearestDistance), 0xFFFF00FF, 0x000000FF)
end
end
-- Speed
xspeed = memory.readwordsigned(0xFFB010)
yspeed = memory.readwordsigned(0xFFB012)
value = math.sqrt(xspeed*xspeed + yspeed*yspeed)
speedometer:DrawMeter()
if value > maxSpeed then
maxSpeed = value
maxMaxSpeed = value
else
if not isPaused then
maxSpeed = maxSpeed - 16
if maxSpeed < 0 then
maxSpeed = 0
maxMaxSpeed = 0
end
end
end
if maxMaxSpeed ~= 0 then
gui.text(290, 184, string.format("%5d", maxMaxSpeed), 0xFF0000FF, "black")
end
gui.text(266, 216, string.format("Speed:%5d", value), 0xFFFF00FF, "black")
speedometer:DrawNeedle(maxSpeed/maximumSpeed, 0xFF0000FF, false, speedometer.size-4)
speedometer:DrawNeedle(value/maximumSpeed, 0xFFFF00FF)
-- Perfect counter
if showThingsTimer ~= 0 then
color = 0xFFFF00FF
bgColor = 0x000000FF
value = memory.readwordunsigned(0xFFFF40)
if value == 0 then
color = 0xFFFFFF60
bgColor = 0x00000060
end
gui.text(17, 54, string.format("Remaining: %3d", value), color, bgColor)
end
-- Spin-dash "Boost meter"
color = 0xFFFF00FF--0xFFFF00A0
bgColor = 0x000000FF--0x000000A0
boostColor = 0x808080FF
if memory.readbyte(0xFFB039) ~= 0 then -- addr = Spin dash flag
lastSpindashValue = memory.readword(0xFFB03A)
boostColor = 0xFF2020FF
boostTimer = 90
else
if not isPaused then
if boostTimer > 0 then boostTimer = boostTimer - 1 end
end
end
if boostTimer > 0 then
boostMeter:DrawMeter()
boostMeter:DrawNeedle(lastSpindashValue/0x800, boostColor)
message = string.format("Boost: %4d", lastSpindashValue)
gui.text(210, 216, message, color, bgColor)
end
-- Collisions selection
if showThingsTimer ~= 0 then
color = 0xFFFF00FF
bgColor = 0x000000FF
if memory.readbyte(0xFFB03E) ~= 0x0C then
color = 0x00FFFFFF
end
gui.text(276, 0, "UP", color, bgColor)
color = 0xFFFF00FF
bgColor = 0x000000FF
if memory.readbyte(0xFFB03F) ~= 0x0D then
color = 0x00FFFFFF
end
gui.text(290, 0, "D/L/R", color, bgColor)
end
-- Horizontal control lock
value = memory.readwordunsigned(0xFFB02E)
if value ~= 0 then
gui.text(300, 18, "LOCK", 0xFF0000FF, 0x000000FF)
end
-- Continues
if showThingsTimer ~= 0 then
color = 0xFFFF0080
bgColor = 0x00000080
gui.text(17, 216, string.format("Continues: %d", memory.readbyte(0xFFFE18)), color, bgColor)
end
-- Checkpoint
if showThingsTimer ~= 0 then
color = 0xFFFFFF80
bgColor = 0x00000080
value = memory.readbyteunsigned(0xFFFE30)
if value ~= 0 then
color = 0xFFFF00FF
bgColor = 0x000000FF
end
gui.text(17, 0, string.format("Checkpoint: %d", value), color, bgColor)
-- memory.writebyte(0xFFFE30,0)--Disables checkpoints
end
-- Timers
-- Air
value = memory.readbyte(0xFFB028)
if value ~= 0x1E then
gui.text(80, 0, string.format("Air: %2d", value), 0xFFFFFFFF, 0x000000FF)
end
-- Temporary invunerability
value = memory.readwordunsigned(0xFFB030)
if value ~= 0 then
gui.text(246, 9, string.format("%4d", value), 0xFF0000FF, 0x000000FF)
end
-- Invincibility
value = memory.readwordunsigned(0xFFB032)
if value ~= 0 then
gui.text(120, 0, string.format("Invincible: %3d", value/fps), 0xFFFFFFFF, 0x000000FF)
end
-- Speed shoe
value = memory.readwordunsigned(0xFFB034)
if value ~= 0 then
gui.text(192, 0, string.format("Speed Shoe: %3d", value/fps), 0xFFFFFFFF, 0x000000FF)
end
else
showThingsFlag = false
end
end)