User File #638323042734073463

Upload All User Files

#638323042734073463 - Sonic 2 Speedometer

speed.lua
Game: Sonic the Hedgehog 2 ( Genesis, see all files )
117 downloads
Uploaded 10/7/2023 7:37 PM by YakkoYT (see all 8)
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)