Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --------------------------------------------------------------------------------
- --------------------------------------------------------------------------------
- ------ 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)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement