--GBA F-Zero: Maximum Velocity - General script --Lower-left is the north-oriented display --Lower-right is the facing-oriented display --For use with BizHawk --FatRatKnight --Setup local StaticX,StaticY= 60,220 --Center position local StaticR= 60 --Radius local StaticS= 0x0200 --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 } client.SetGameExtraPadding(0,0,0,120) -- Yay, bottom border --############################################################################## --General 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 --***************************************************************************** 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 WordToAngle(v) --revision 2 --***************************************************************************** -- Input: Angle in 1/65536 of a revolution per unit, clockwise, no offset -- Output: Angle in radians, counterclockwise, no offset return -(v/32768)*math.pi 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= 0x12D60 + pl*0xCC --"EWRAM", can't I use 0x02012D60 and be done? 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 --############################################################################## --Static --***************************************************************************** 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,0xFF404040) 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,0xFF404040) 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 RivalRadarNorth(sT) --***************************************************************************** --Watches for rivals around. --It is oriented northward, by the way. local OriginX,OriginY= sT.PlX,sT.PlY for i= 0, 4 do --We will paint the player as a side-effect here. local a= 0x12D60 + i*0xCC local MachineX,MachineY= R4s(a+0,"EWRAM"),R4s(a+4,"EWRAM") if (R1s(a+0xB6,"EWRAM") ~= -1) then local X= math.floor((MachineX-OriginX)/sT.s+0.5) + sT.x local Y= math.floor((MachineY-OriginY)/sT.s+0.5) + sT.y if InBounds(sT , X,Y) then local clr= RadarColors[i] 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 end end end --############################################################################# --Rotating --***************************************************************************** local function GridUnderlayR(rT) --***************************************************************************** --Rotated underlay. Now that should be a fun exercise in trig. 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 RivalRadarFacing(rT) --***************************************************************************** --The rival watch. --Oriented based on player's machine facing. local OriginX,OriginY= rT.PlX, rT.PlY local Sine= rT.Sine local Cosine= rT.Cosine for i= 0, 4 do --We will paint the player as a side-effect here. local a= 0x12D60 + i*0xCC local MachineX,MachineY= R4s(a+0,"EWRAM"),R4s(a+4,"EWRAM") if (R1s(a+0xB6,"EWRAM") ~= -1) then local X,Y= MachineX-OriginX,MachineY-OriginY X,Y= Sine*X+Cosine*Y, -Cosine*X+Sine*Y X= math.floor(X/rT.s+0.5) + rT.x Y= math.floor(Y/rT.s+0.5) + rT.y if InBounds(rT , X,Y) then local clr= RadarColors[i] 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 end end end --############################################################################# --Misc display --***************************************************************************** local function MachineHUD(n) --***************************************************************************** local a= 0x12D60 + n*0xCC local x, y= R4s(a+0x00,"EWRAM"), R4s(a+0x04,"EWRAM") local Facing, Momentum= R2u(a+0x78,"EWRAM"), R2u(a+0x7A,"EWRAM") gui.pixelText( 0, 0,string.format("%8X",x)) gui.pixelText( 0, 7,string.format("%8X",y)) gui.pixelText( 0, 16,string.format("%8X",R4s(a+0x74,"EWRAM"))) --Speed -- gui.pixelText( 0, 21,string.format("%8X",R4s(a+0x78,"EWRAM"))) --Facing & Momentum x= x - R4s(a+0x08,"EWRAM") y= y - R4s(a+0x0C,"EWRAM") local v= math.floor(math.sqrt(x*x + y*y)) -- Distance formula gui.pixelText( 0, 23,string.format("%8X",v),0xFF00FFFF) -- Change in position gui.pixelText( 0,160,string.format("%4X",Facing) ,0xFFFF00FF) gui.pixelText( 0,167,string.format("%4X",Momentum),0xFF00FF00) v= (Facing - Momentum + 0x8000)%0x10000 - 0x8000 local clr= 0xFFFF00FF if v > 0 then clr= 0xFFFFFF00 end if v < 0 then clr= 0xFF00FFFF end gui.pixelText( 0,174,string.format("%4X",math.abs(v)),clr) 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,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= 0x12D60 + 0xCC*i local v= R1s(addr+0xB6,"EWRAM") local clr= RadarColors[i] if v == -1 then clr= 0xFFA0A0A0 end gui.pixelText(231,160+7*i,string.format("%2d",v),clr) end --[[ for x= 0, 4 do for y= 0, 22 do local addr= 0x12D60 + 0xCC*x + 4*y + 0x80 gui.pixelText(36*x,7*y,string.format("%08X",R4u(addr,"EWRAM"))) end end ]]-- -- for i= 0, 4 do -- local addr= 0x12D60 + 0xCC*x + 4*y + 0x80 -- end end --############################################################################# --Management --***************************************************************************** local function StaticHUD(sT) --***************************************************************************** -- Oriented so north is toward the top. Fun stuff. GridUnderlayS(sT) MomentumCompass(sT) RivalRadarNorth(sT) end --***************************************************************************** local function RotatingHUD(rT) --***************************************************************************** -- Oriented so player facing is toward the top. GridUnderlayR(rT) MomentumAngle(rT) RivalRadarFacing(rT) end --Immediate local StatsTbl= {} --***************************************************************************** while true do --***************************************************************************** --Our overhead. local Player= R1u(0x2B63,"IWRAM") if Player < 5 then FillTable( StatsTbl,StaticX,StaticY,StaticR,StaticS,Player) StaticHUD(StatsTbl) PartialFillTable(StatsTbl,RotateX,RotateY,RotateR,RotateS) RotatingHUD(StatsTbl) MachineHUD(Player) end BasicHUD() emu.frameadvance() end --############################################################################# --eof. Well, extra data on hand below. --[[ IWRAM:2B62,1u - Player machine ID IWRAM:2B63,1u - Player machine memory internal position EWRAM:0A100,1x[Count=0x4000?] Input log history (4.5 minutes) EWRAM:0E560,2x[x=64][y=64] An array of track block index EWRAM:10560,? EWRAM:12D60[Size=0xCC][Count=5] Machine data +00,4s - X position (main) +04,4s - Y position (main) +08,4s - X position (1 frame ago) +0C,4s - Y position (1 frame ago) +10,4s - X position (1 frame ago) +14,4s - Y position (1 frame ago) +18,4s - X position (2 frames ago) +1C,4s - Y position (2 frames ago) +20,4s - X position (3 frames ago) +24,4s - Y position (3 frames ago) +28,4s - X position (4 frames ago) +2C,4s - Y position (4 frames ago) +54,4s - Elevation +74,2s - Speed +78,2x - Facing +7A,2x - Momentum +84,2s - Vertical velocity +8A,2u - Power +8C,2u - Boost timer +8E,2u - Boost timer (mirror) +94,4x - Apparent health (for that visual health meter?) +9F,1u - ? Internal reference ID? +A1,1u - Timer for holding down boost +A2,1u - Lap segment +B6,1s - ID? EWRAM:131CF,1u - ? Player machine selection related? ]]--