User File #39484842602326983

Upload All User Files

#39484842602326983 - GBA F-Zero: Maximum Velocity - Nifty grid lines! (lua)

FZMV_BizHawk_v2.lua
837 downloads
Uploaded 6/5/2017 4:38 AM by FatRatKnight (see all 245)
Complicating my script some. I hope my organization works out.
Left side, static display:
  • Top of display is north
  • Green line for momentum, purple line for facing
  • Crosshairs for where machines are located
  • Grid line underlay!
Right side, rotating display:
  • Top of display toward player facing
  • Green line for momentum, small grey cross for north
  • Crosshairs for where machines are located
Didn't want to work out rotating grid lines. At least, not yet. I've aligned the grid lines with what I suspect to be how the game tiles things.
And it has a few other things, I suppose. Some stats on the right are seen, some basic thing with machine data or something.
--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?
]]--