--GBA F-Zero: Grand Prix Legends - General script
--For use with BizHawk
--FatRatKnight
--Setup
local key_ToggleGhostRecord= "M"
local StaticX,StaticY= 60,220 --Center position
local StaticR= 60 --Radius
local StaticS= 0x0400 --Scale
local RotateX,RotateY= 180,220 --Center position
local RotateR= 60 --Radius
local RotateS= 0x0200 --Scale
local RadarColors= {
[0]=0xFFFFFFFF, --White Note, the player can be any of first four.
0xFF00FF00, --Green Depends on which spot the machine starts in.
0xFFFFFF00, --Yellow
0xFF00FFFF, --Cyan I advise bright colors and distinct hues.
0xFFFF40FF --Purple
}
local GhostColors= { --Try something darker, a'ite?
[0]=0xFFC0C0C0, --White 75% brightness of the main ones.
0xFF00C000, --Green
0xFFC0C000, --Yellow
0xFF00C0C0, --Cyan
0xFFF000F0 --Purple
}
local GhostEqualRadar= 0xFF606060 -- If ghost is identical, a "don't care" color.
local TrackerFile= "FZGPL_PosTracker.txt" -- set nil to disable; no, not "nil".
local flag_RecordGhost= true --Overridden by false if file found.
local GhostTrailFrames= 20 --1/3 second ahead and behind
client.SetGameExtraPadding(0,0,0,120) -- Yay, bottom border
--##############################################################################
--General
--Function renames (I like descriptive names. Renames are for convenience.)
local R4u , R4s= memory.read_u32_le , memory.read_s32_le
local R2u , R2s= memory.read_u16_le , memory.read_s16_le
local R1u , R1s= memory.read_u8 , memory.read_s8
--Constants
local SqrtTwo= math.sqrt(2) -- In case I deal with a square or two.
local CrLf= string.char(0x0D,0x0A) --New line stuff.
--Widely important variables, as opposed to locally important
local InternalFrame= 0 -- What frame is the game itself on right now?
local PlayerSel= 0 -- Machine the player controls.
--mTracker[WhichPlayer].Stat[Frame] Machine Tracker.
--Keeps a record of positions of every machine on every frame.
local mTracker= {}; for i= 0, 4 do mTracker[i]= {x={},y={},Exist={}} end
local function WordToAngle(v) return (v/32768)*math.pi end --cw rev/65536 to cw radians
local function FetchInternalFrame() return R4u(0x17598,"EWRAM") end
local function PlAddr(pl) return 0x14930 + 0xD0*pl end --Machine address
local function FetchPlayerExist(pl) return R1u(PlAddr(pl)+0x9F,"EWRAM") == 4 end
--*****************************************************************************
local function FetchAddrDomainGBA(a)
--*****************************************************************************
--Stand-in for System Bus. Highly desired when you got a full pointer.
--I don't know all regions, though.
if (a >= 0x02000000) and (a < (0x02000000+memory.getmemorydomainsize("EWRAM"))) then
return a-0x02000000, "EWRAM"
elseif (a >= 0x03000000) and (a < (0x03000000+memory.getmemorydomainsize("IWRAM"))) then
return a-0x03000000, "IWRAM"
elseif (a >= 0x08000000) and (a < (0x08000000+memory.getmemorydomainsize("ROM"))) then
return a-0x08000000, "ROM"
else
error(string.format("Unknown address %08X", a),1)
end
end
--*****************************************************************************
local function HexPlusMinus(v,digits)
--*****************************************************************************
--String.format does not prefix a - for hexadecimal values, instead using a
--twos complement of the value. This function is to inject that sign.
local str= "+"
if v < 0 then str= "-"; v= -v end
return string.format(str .. "%" .. digits .. "X",v)
end
--*****************************************************************************
local function InRace() --Stand-in from FZMV, not updated. DO NOT USE
--*****************************************************************************
--Returns nil if not even in a track.
--Returns false if not really doing much racing.
--Returns true if we are racing.
--At least, that's the intent. These memory addresses suggest things.
if R1u(0x0BFA,"IWRAM") == 0 then return nil end --Is track loaded?
return R1u(0x0BFB,"IWRAM") == 5 --Are we racing yet?
end
--*****************************************************************************
local function PartialFillTable(T,x,y,r,s)
--*****************************************************************************
--Exists mainly to relocate or rescale drawing area without re-fetching stats.
T.x= x; T.y= y; T.r= r; T.s= s
T.Left= x-r; T.Top= y-r; T.Right= x+r; T.Bottom= y+r
end
--*****************************************************************************
local function FillTable(T,x,y,r,s,pl)
--*****************************************************************************
--This exists so I only have to do one calculation for multiple uses.
--I pay in table dereferencing, though.
T= T or {} --construct, in case we were fed nil as first parameter
--Most of these won't change frame by frame. Possibly wasteful to retry.
T.x= x; T.y= y; T.r= r; T.s= s; T.pl= pl
T.Left= x-r; T.Top= y-r; T.Right= x+r; T.Bottom= y+r
--Player stats...
local a= PlAddr(pl)
T.Addr= a --Address, in case there are special stats I did not get here.
T.PlX= R4s(a+0x00,"EWRAM") --Player X
T.PlY= R4s(a+0x04,"EWRAM") --Player Y
local Facing= R2u(a+0x78,"EWRAM")
T.Facing= Facing
Facing= WordToAngle(Facing) --Convert to mathematical angle
T.AngleF= Facing
T.Sine= math.sin(Facing); T.Cosine= math.cos(Facing)
return T --If we were fed the table, the caller doesn't need to handle this
end
--*****************************************************************************
local function InBounds(T,x,y)
--*****************************************************************************
--Returns true or false, generally for drawing area.
return (x >= T.Left) and (x <= T.Right) and (y >= T.Top) and (y <= T.Bottom)
end
--*****************************************************************************
local function FetchOldPos(pl,frame)
--*****************************************************************************
--Grabs stored position data. Joy.
pl= pl or PlayerSel --Defaults for unspecified parameters.
frame= frame or InternalFrame
local machine= mTracker[pl]
--nil, for never recorded. False, for recorded not existing.
if not machine.Exist[frame] then return machine.Exist[frame] end
return machine.x[frame], machine.y[frame]
end
--*****************************************************************************
local function RecordPos(frame)
--*****************************************************************************
--For now, it directly reads from memory and stuff it into stored position data
frame= frame or InternalFrame --If unspecified, assume current frame.
--Uh, now that I think about it, we're always going to assume current frame.
--Frankly, we're doing direct memory reads. This will always be current frame.
--I see a redesign in the future.
for pl= 0, 4 do
local a= PlAddr(pl)
local machine= mTracker[pl]
if FetchPlayerExist(pl) then
machine.x[frame],machine.y[frame]= R4s(a+0x00,"EWRAM"),R4s(a+0x04,"EWRAM")
machine.Exist[frame]= true
else
--leave x,y alone. If there's reason to grab stale data, I won't erase.
machine.Exist[frame]= false
end
end
end
--*****************************************************************************
local function LoadTracker(filename)
--*****************************************************************************
--Exists because we can't run script, movie, then another movie.
--Well, we could, but a second movie triggers core reboot. This kills lua.
--Though, a persistent position tracker file might be useful anyway.
local FileIn, err= io.open(filename,"r")
if not FileIn then print("No ghost loaded"); return end
local count= 0
for data in FileIn:lines() do
local frame= tonumber(string.sub(data,1,8)) --Want a decimal value.
if frame then
count= count+1
for i= 0, 4 do
local Pl= mTracker[i]
local x= tonumber(string.sub(data,10+i*18,17+i*18)) --Decimal
local y= tonumber(string.sub(data,19+i*18,26+i*18))
if x and y then
Pl.Exist[frame]= true
Pl.x[frame],Pl.y[frame]= x,y
else
Pl.Exist[frame]= false
end
end -- for each machine
end -- if frame exists (sanity)
end -- Data lines
FileIn:close()
if count == 0 then
print("File opened, but no frames loaded.")
else
flag_RecordGhost= false
print("Ghost loaded from file. Frames: " .. count)
end
end
--*****************************************************************************
local function SaveTracker(filename)
--*****************************************************************************
--Stuffs our tracking data into a persistent tracker file.
--I'm making my own file format on this, which is probably just plain text.
FileOut, err= io.open(filename,"wb")
if not FileOut then print(err); return end --Sorry, didn't save.
--Presumably, you still have the movie file, and can generate a new ghost data.
--Though, if we did succeed... Stuff it in file.
local PlayerExisted= mTracker[PlayerSel].Exist --Shouldn't matter who, just want not nil.
for frame,_ in pairs(PlayerExisted) do --Can't guarantee all frames exist
local s= string.format("%8d|",frame)
for i= 0, 4 do
local machine= mTracker[i]
if machine.Exist[frame] then
s= string.format("%s%8d,%8d:",s,machine.x[frame],machine.y[frame]) --8 digits should be sufficiently large.
else
s= s .. "--------,--------:"
end
end
FileOut:write(s .. CrLf)
--Note that pairs won't necessarily have it in order.
end
FileOut:close()
print("Ghost saved to " .. filename)
end
--##############################################################################
--Split
--*****************************************************************************
local function GetStaticDisplayLoc(sT,PosX,PosY)
--*****************************************************************************
--Apply offsets. Apply scaling. End.
local x= math.floor((PosX - sT.PlX)/sT.s+0.5) + sT.x
local y= math.floor((PosY - sT.PlY)/sT.s+0.5) + sT.y
return x,y
end
--*****************************************************************************
local function GetRotateDisplayLoc(rT,PosX,PosY)
--*****************************************************************************
--Rotatey stuff.
local x= PosX - rT.PlX
local y= PosY - rT.PlY
x,y= -rT.Sine*x+rT.Cosine*y, -rT.Cosine*x-rT.Sine*y
x= math.floor(x/rT.s+0.5) + rT.x
y= math.floor(y/rT.s+0.5) + rT.y
return x,y
end
--*****************************************************************************
local function GridUnderlayS(sT)
--*****************************************************************************
--Might be nice to have a dark colored map underneath the radar.
--For now, have these grid lines.
local range= (sT.r+0.5) * sT.s
--Vertical lines
local Vline= (math.ceil((sT.PlX - range)/0x4000)*0x4000 - sT.PlX) / sT.s + sT.x
while Vline <= sT.Right do
gui.drawLine(Vline,sT.Top,Vline,sT.Bottom,0xFF000080)
Vline= Vline + 0x4000/sT.s
end
--Horizontal lines
local Hline= (math.ceil((sT.PlY - range)/0x4000)*0x4000 - sT.PlY) / sT.s + sT.y
while Hline <= sT.Bottom do
gui.drawLine(sT.Left,Hline,sT.Right,Hline,0xFF000080)
Hline= Hline + 0x4000/sT.s
end
end
--*****************************************************************************
local function MomentumCompass(sT)
--*****************************************************************************
--Yay, compass! In case you're lost! ... Somehow?
--Facing first. So its line is painted under the momentum line.
-- local z= sT.AngleF
local z= WordToAngle(R2s(sT.Addr+0x78,"EWRAM"))
local x= sT.x + sT.r*math.cos(z)
local y= sT.y + sT.r*math.sin(z)
gui.drawLine(sT.x, sT.y,x,y,0xFFFF00FF)
--Momentum second.
z= WordToAngle(R2s(sT.Addr+0x7A,"EWRAM")) --Momentum
x= sT.x + sT.r*math.cos(z)
y= sT.y + sT.r*math.sin(z)
gui.drawLine(sT.x, sT.y,x,y,0xFF00FF00)
end
--*****************************************************************************
local function MomentumAngle(rT)
--*****************************************************************************
--Always facing forward, so omit the facing line. Only our momentum line counts
--Might as well note north, though.
local a= rT.Addr
local Facing= R2u(a+0x78,"EWRAM")
local Momentum= R2u(a+0x7A,"EWRAM")
local Diff= (Facing - Momentum + 0x8000)%0x10000 - 0x8000
--North
local Angle= WordToAngle(Facing)
local HalfR= rT.r/2
local x= rT.x - HalfR*math.cos(Angle)
local y= rT.y + HalfR*math.sin(Angle)
gui.drawLine(x-1,y ,x+1,y ,0xFF808080)
gui.drawLine(x ,y-1,x ,y+1,0xFF808080)
--Momentum, relative to facing
Angle= WordToAngle(Diff)
x= rT.x - rT.r*math.sin(Angle)
y= rT.y - rT.r*math.cos(Angle)
gui.drawLine(rT.x,rT.y,x,y,0xFF00FF00)
end
--*****************************************************************************
local function SetRotating(mT,Rotating)
--*****************************************************************************
--Some functions do things different depending on north or facing.
--Others... Are completely identical. Leave the identical ones alone.
if Rotating then --rT
mT.GetDisplayLoc= GetRotateDisplayLoc
mT.GridUnderlay = function() end --null, I don't have a working solution.
mT.MomentumWatch= MomentumAngle
else --sT
mT.GetDisplayLoc= GetStaticDisplayLoc
mT.GridUnderlay = GridUnderlayS
mT.MomentumWatch= MomentumCompass
end
end
--##############################################################################
--Shared
--*****************************************************************************
local function RivalRadar(mT)
--*****************************************************************************
--The rival watch.
--Oriented based on player's machine facing.
for pl= 0, 4 do --Player also gets painted, naturally always in the center.
if FetchPlayerExist(pl) then
local a= PlAddr(pl)
local x,y= mT:GetDisplayLoc(R4s(a+0,"EWRAM"),R4s(a+4,"EWRAM"))
if InBounds(mT , x,y) then
local clr= RadarColors[pl] or 0xFFC0C0C0 --Fallback shouldn't happen...
gui.drawLine(x-4,y ,x+4,y ,clr)
gui.drawLine(x ,y-4,x ,y+4,clr)
end --If pixel is in bounds
end --If machine exists
end --For each machine
end --Function
--*****************************************************************************
local function GhostTrails(mT)
--*****************************************************************************
--Colored trails of various machines. Blue for the future. Red for the past.
--Of course, I don't know your future, but I know your past's future.
local ClrStep= 0xFF / GhostTrailFrames
local Sf, Ef= InternalFrame-GhostTrailFrames, InternalFrame+GhostTrailFrames
for pl= 0, 4 do
for f= Sf, Ef do
local x,y= FetchOldPos(pl,f)
if x then
x,y= mT:GetDisplayLoc(x,y)
if InBounds(mT,x,y) then
local RB= (f-InternalFrame)*ClrStep
if f < InternalFrame then --In the past
RB= math.floor(math.abs(RB))*0x00010000
else --In the future
RB= math.floor(RB)
end
gui.drawPixel(x,y,0xFF008000+RB)
end -- If pixel is in bounds
end -- If machine existed
end -- For each nearby frame
end -- For each machine
end -- Function
--*****************************************************************************
local function Ghosts(mT)
--*****************************************************************************
--Shows old positions of current frame.
--If it matches our immediate position, we pick a boring enough color.
for pl= 0, 4 do
local x,y= FetchOldPos(pl)
if x then
local addr= PlAddr(pl)
local mx,my= R4s(addr+0x00,"EWRAM"),R4s(addr+0x04,"EWRAM")
local clr= GhostColors[pl]
if (x == mx) and (y == my) then clr= GhostEqualRadar end
x,y= mT:GetDisplayLoc(x,y)
if InBounds(mT,x,y) then
gui.drawLine(x-3,y-3,x+3,y+3,clr)
gui.drawLine(x-3,y+3,x+3,y-3,clr)
end -- If pixel is in bounds
end -- If machine existed
end -- For each machine
end -- Function
--*****************************************************************************
local function PlayerTrail(mT)
--*****************************************************************************
--Well, the game keeps a short list of old positions. Let's display them!
--Some things don't have trails, for some reason...
for i= 0, 3 do
local x= R4s(mT.Addr+0x10 + 8*i,"EWRAM")
local y= R4s(mT.Addr+0x14 + 8*i,"EWRAM")
x,y= mT:GetDisplayLoc(x,y)
if InBounds(mT,x,y) then
gui.drawPixel(x,y,0xFFC0C0C0)
end
end
end
--*****************************************************************************
local function GetMixFunctions(mT)
--*****************************************************************************
mT= mT or {} -- constructor, in case of nil
mT.RivalRadar = RivalRadar
mT.GhostTrails= GhostTrails
mT.Ghosts = Ghosts
mT.PlayerTrail= PlayerTrail
return mT
end
--#############################################################################
--Broken...
--*****************************************************************************
local function GridUnderlayR(rT)
--*****************************************************************************
--Rotated underlay. Now that should be a fun exercise in trig.
--Incomplete function. I'm seriously out of practice in my math, and am not
--getting the Y lines to behave. Do not use this function.
--I have tackled this for a while. I now think I forgot to add offsets based
--on where the corner is, and assume center position instead.
--Get our triangle sides
local Angle= WordToAngle(rT.Facing%0x4000 - 0x2000) --45 degree offset
local Hypotinuse= rT.r * rT.s * SqrtTwo --Radius, scale, to corner of square
local LongSide= math.cos(Angle) * Hypotinuse
local ShortSide= math.sin(Angle) * Hypotinuse
local Xx= {v= math.ceil((rT.PlX - LongSide)/0x4000)*0x4000, min= rT.PlX - LongSide, max= rT.PlX + LongSide, left= rT.PlX - ShortSide, right= rT.PlX + ShortSide}
local Yy= {v= math.ceil((rT.PlY - LongSide)/0x4000)*0x4000, min= rT.PlY - LongSide, max= rT.PlY + LongSide, left= rT.PlY - ShortSide, right= rT.PlY + ShortSide}
Angle= WordToAngle(rT.Facing%4000) -- Don't need the diagonal now
local Sine= math.sin(Angle)
local Cosine= math.cos(Angle)
local Tangent = math.tan(rT.AngleF)
-- local Cotangent= math.cot(rT.AngleF)
-- while x < MaxX do
-- Xx.v= Xx.v + 0x4000
-- end
while Yy.v < Yy.max do
local x1,y1 , x2,y2
if Yy.v > Yy.left then
x1,y1= GetRotateDisplayLoc(rT,
-- rT.PlX - (Yy.v-Yy.min)*Cosine/Sine,
rT.PlX + (Yy.v-Yy.max),
Yy.v)
else
if Angle ~= 0 then
x1,y1= GetRotateDisplayLoc(rT,
rT.PlX,
-- rT.PlX + (Yy.v-Yy.min)*rT.Cosine/rT.Sine,
-- rT.PlX - (Yy.v-Yy.min)*rT.Cosine/rT.Sine,
-- rT.PlX + (Yy.v-Yy.min)*rT.Sine/rT.Cosine,
-- rT.PlX - (Yy.v-Yy.min)*rT.Sine/rT.Cosine,
Yy.v)
end
end
if Yy.v > Yy.right then
if Angle ~= 0 then
x2,y2= GetRotateDisplayLoc(rT,
rT.PlX + 0x2000,
-- rT.PlX + (Yy.v-Yy.max)*Cosine/Sine,
Yy.v)
end
else
x2,y2= GetRotateDisplayLoc(rT,
rT.PlX + 0x2000,
-- rT.PlX + (Yy.v-Yy.min)*Sine/Cosine,
Yy.v)
end
if x1 and x2 then gui.drawLine(x1,y1,x2,y2,0xFF404040) end
Yy.v= Yy.v + 0x4000
end
end
--#############################################################################
--Misc display
--*****************************************************************************
local function ClrBySign(v)
--*****************************************************************************
if v < 0 then return 0xFFFFFF00 end
if v > 0 then return 0xFF00FFFF end
return 0xFFFF00FF
end
--*****************************************************************************
local function MachineHUD_hex(n)
--*****************************************************************************
--Pick a machine, show its stats.
--So long as there is a vague reason to do so, things are in hexadecimal.
--Otherwise, decimal.
local a= PlAddr(n)
local x, y= R4s(a+0x00,"EWRAM"), R4s(a+0x04,"EWRAM")
local Facing, Momentum= R2u(a+0x78,"EWRAM"), R2u(a+0x7A,"EWRAM")
local Hysteresis= bit.band(R1u(a+0xC8,"EWRAM"),0x10) ~= 0
local clr= nil
if Hysteresis then clr= 0x80FF2000 end
gui.pixelText( 0, 0,string.format("%8X",x))
gui.pixelText( 0, 7,string.format("%8X",y))
gui.pixelText( 0, 16,string.format("%8X",R2s(a+0x74,"EWRAM")),0xFFFFFFFF,clr) --Speed
x= x - R4s(a+0x08,"EWRAM")
y= y - R4s(a+0x0C,"EWRAM")
gui.pixelText( 33, 0,string.format("%4X",math.abs(x)),ClrBySign(x))
gui.pixelText( 33, 7,string.format("%4X",math.abs(y)),ClrBySign(y))
local v= math.floor(math.sqrt(x*x + y*y)) -- Distance formula
gui.pixelText( 0, 23,string.format("%8X",v),0xFF00FFFF) -- Change in position
--Facing, momentum, and their difference.
gui.pixelText( 0,160,string.format("%4X",Facing) ,0xFFFF00FF)
gui.pixelText( 0,167,string.format("%4X",Momentum),0xFF00FF00)
v= (Facing - Momentum + 0x8000)%0x10000 - 0x8000
gui.pixelText( 0,174,string.format("%4X",math.abs(v)),ClrBySign(v))
--Elevation
local Height,VertVel= R4s(a+0x54,"EWRAM"),R2s(a+0x84,"EWRAM")
-- gui.pixelText(207, 21,string.format("%8d",Height))
-- gui.pixelText(207, 28,string.format("%8d",VertVel))
--Acceleration: -12 per frame. -16 if not holding down after some point.
--I assume it's always -12, so you know the farthest you can go.
if Height > 0 then
v= math.ceil((VertVel + math.sqrt(VertVel*VertVel + 4*6*Height))/12)
else v= 0
end
gui.pixelText(224, 14,string.format("%4d",v))
gui.pixelText(224, 0,string.format("%4X",R2u(a+0x8A,"EWRAM"))) --Pow
gui.pixelText(224, 7,string.format("%4d",R1u(a+0xA2,"EWRAM"))) --Lap seg
-- gui.pixelText(224, 21,string.format("%4d",R1u(a+0x9E,"EWRAM"))) --Split
gui.pixelText(224,153,string.format("%4d",R2u(a+0x8C,"EWRAM"))) --Boost timer
gui.pixelText(224,145,string.format("%4d",R1u(a+0xA1,"EWRAM"))) --Trigger timer
end
--*****************************************************************************
local function MachineHUD_dec(n)
--*****************************************************************************
--Pick a machine, show its stats.
--No hexadecimal here. It's all decimal.
--Don't recommend viewing angles like this.
local a= PlAddr(n)
local x, y= R4s(a+0x00,"EWRAM"), R4s(a+0x04,"EWRAM")
local Facing, Momentum= R2u(a+0x78,"EWRAM"), R2u(a+0x7A,"EWRAM")
local Hysteresis= bit.band(R1u(a+0xC8,"EWRAM"),0x10) ~= 0
local clr= nil
if Hysteresis then clr= 0x80FF2000 end
gui.pixelText( 0, 0,string.format("%8d",x))
gui.pixelText( 0, 7,string.format("%8d",y))
gui.pixelText( 0, 16,string.format("%8d",R2s(a+0x74,"EWRAM")),0xFFFFFFFF,clr) --Speed
x= x - R4s(a+0x08,"EWRAM")
y= y - R4s(a+0x0C,"EWRAM")
gui.pixelText( 33, 0,string.format("%+5d",x),ClrBySign(x))
gui.pixelText( 33, 7,string.format("%+5d",y),ClrBySign(y))
local v= math.floor(math.sqrt(x*x + y*y)) -- Distance formula
gui.pixelText( 0, 23,string.format("%8d",v),0xFF00FFFF) -- Change in position
--Facing, momentum, and their difference.
gui.pixelText( 0,160,string.format("%5d",Facing) ,0xFFFF00FF)
gui.pixelText( 0,167,string.format("%5d",Momentum),0xFF00FF00)
v= (Facing - Momentum + 0x8000)%0x10000 - 0x8000
gui.pixelText( 0,174,string.format("%5d",math.abs(v)),ClrBySign(v))
--Elevation
local Height,VertVel= R4s(a+0x54,"EWRAM"),R2s(a+0x84,"EWRAM")
-- gui.pixelText(207, 21,string.format("%8d",Height))
-- gui.pixelText(207, 28,string.format("%8d",VertVel))
--Acceleration: -12 per frame. -16 if not holding down after some point.
--I assume it's always -12, so you know the farthest you can go.
if Height > 0 then
v= math.ceil((VertVel + math.sqrt(VertVel*VertVel + 4*6*Height))/12)
else v= 0
end
gui.pixelText(224, 14,string.format("%4d",v))
gui.pixelText(220, 0,string.format("%5d",R2u(a+0x8A,"EWRAM"))) --Pow
gui.pixelText(224, 7,string.format("%4d",R1u(a+0xA2,"EWRAM"))) --Lap seg
-- gui.pixelText(224, 21,string.format("%4d",R1u(a+0x9E,"EWRAM"))) --Split
gui.pixelText(224,153,string.format("%4d",R2u(a+0x8C,"EWRAM"))) --Boost timer
gui.pixelText(224,145,string.format("%4d",R1u(a+0xA1,"EWRAM"))) --Trigger timer
end
--*****************************************************************************
local function BasicHUD()
--*****************************************************************************
--Generally for basic calculations and all that.
--Also a scratch field for various tests.
for i= 0, 4 do
local addr= PlAddr(i)
local machine= R1s(addr+0xB0,"EWRAM")
local clr= RadarColors[i]
if not FetchPlayerExist(i) then clr= 0xFFA0A0A0 end
-- gui.pixelText(231,160+7*i,string.format("%2d",v),clr)
gui.pixelText(227,160+7*i,string.format("%3d",R1u(addr+0xA2,"EWRAM")),clr)
end
end
--#############################################################################
--Management
--*****************************************************************************
local function DisplayHUD(mT)
--*****************************************************************************
mT:GridUnderlay()
mT:MomentumWatch()
mT:GhostTrails()
mT:Ghosts()
mT:RivalRadar()
mT:PlayerTrail()
end
--Immediate
local StatsTbl= GetMixFunctions()
if TrackerFile then
LoadTracker(TrackerFile)
event.onexit(function() SaveTracker(TrackerFile) end)
end
--*****************************************************************************
while true do
--*****************************************************************************
--Our overhead.
InternalFrame= FetchInternalFrame()
local keyboard= input.get()
if keyboard[key_ToggleGhostRecord] then flag_RecordGhost= not flag_RecordGhost end
--Is player selection always 0? Marked as such for now.
FillTable( StatsTbl,StaticX,StaticY,StaticR,StaticS,0)
SetRotating(StatsTbl,false)
DisplayHUD(StatsTbl)
PartialFillTable(StatsTbl,RotateX,RotateY,RotateR,RotateS)
SetRotating(StatsTbl,true)
DisplayHUD(StatsTbl)
MachineHUD_hex(PlayerSel)
if flag_RecordGhost then
RecordPos()
gui.pixelText(120,160,"REC",0xFFFF2000)
end
BasicHUD()
emu.frameadvance()
end
--#############################################################################
--eof. Well, extra data on hand below.
--[[
During game play
EWRAM:14930[Size=0xD0][Count=5?] Machine data
EWRAM:17720,1u[4] - Acceleration (by gear)
EWRAM:17724,2u[4] - Top Speed (by gear)
EWRAM:1772C,2u - Hysteresis, speed to fall to when over limit
EWRAM:1772E,2u - Hysteresis friction (boost maintenance)
EWRAM:17730,2u - (Boost top speed?)
EWRAM:17732,2u - (Boost acceleration?)
EWRAM:17734,2u - (Boost hysteresis?)
EWRAM:17736,2u - (Boost hysteresis friction?)
EWRAM:17738,1s - ?
EWRAM:17739,1x - (0)
EWRAM:1773A,1u - (change in speed?)
EWRAM:1773B,1u - (change in speed?)
EWRAM:1773C,1x - (0)
EWRAM:1773D,1u - (change in speed?)
EWRAM:1773E,1u - (released acceleration friction?)
EWRAM:1773F,1u - (braking friction?)
EWRAM:17740,2u - (rotation?)
EWRAM:17742,2u - (rotation?)
EWRAM:17744,2u - (rotation?)
EWRAM:17746,2u - (rotation?)
EWRAM:17748,2u - (rotation?)
EWRAM:1774A,2u - (rotation?)
EWRAM:1774C,2u - (rotation?)
EWRAM:1774E,2u - (rotation?)
IWRAM:1B10,2u - Related to how fast you go...
During tweaking screen
IWRAM:2EA0,1u[4] - Acceleration (by gear)
IWRAM:2EA4,2u[4] - Top speed (by gear)
IWRAM:2F81,1s - Acceleration/Top Speed adj
]]--