Post subject: Multitrack script for VBA. It... Uh, works... (16 KB code)
Editor, Skilled player (1172)
Joined: 9/27/2008
Posts: 1085
By the miracle of input.get, I got this thing working for VBA. ... Somehow, emu.registerafter allows instant reaction whereas other registers and boundary fails. Even emu.registerbefore fails. Additionally, the act of basing a decision for joypad.set using a value modified by joypad.get, directly or otherwise, forces a one-frame delay, killing sync-stability on stateload. I must admit, this is a very confusing lua set-up. There are problems (I can't update the display on stateloads; it does not pick up input from a movie on playback; joypad settings from the emulator is ignored), but at least there's basic multitrack functionality.
-- Multitrack for VBA by FatRatKnight
-- Using joypad.get ruins things, but input.get saves the day. ... HOW!?

local GBA= false

local btn, key, Xoff, Yoff
if GBA then
    btn={"left","up","right","down","A","B","L","R","start","select"}
    key={"left","up","right","down","K","J","H","L","I"    ,"U"     }
    Xoff= 170
    Yoff= 150  --Whoops, I didn't check a GBA ROM. These numbers need adjusting
else
    btn={"left","up","right","down","A","B","start","select"}
    key={"left","up","right","down","K","J","I"    ,"U"     }
    Xoff= 158
    Yoff= 138
end

-- Try to avoid changing btn. You may reorder btn's stuff if you don't
-- like the default order, however.
-- key is your only control into the script. This will override the joypad
-- settings in the emulator. Change that to fit your needs.

--Display
local ListSwitch=   "end"    -- Should the script use or ignore its own list?

local solid= "pageup"      -- Make the display less
local clear= "pagedown"    -- or more transparant.

local DispN= "numpad8"
local DispS= "numpad2"     -- For moving the
local DispE= "numpad6"     -- display around.
local DispW= "numpad4"

local MoreFutr= "numpad3"
local LessFutr= "numpad1"  -- These will determine
local MorePast= "numpad7"  -- how many frames you
local LessPast= "numpad9"  -- want to display.
local ResetFP=  "numpad5"


--Various colors I'm using. If you wish to bother, go ahead.
local shade= 0x00000080
local white= "#FFFFFFFF"
local red=   "#FF0000FF"
local green= 0x00FF00FF
local blue=  0x0040FFFF
local orange="#FFFF00FF"
local fadeRd="#FFC0C0FF"
local fadeGn="#C0FFC0FF"
--*****************************************************************************
--Please do not change the following, unless you plan to change the code:

local fc= movie.framecount()

local InputList= {}
local OptionUseList= true
local keys, lastkeys= {}, {}


--*****************************************************************************
function FBoxOld(x1, y1, x2, y2, color)
--*****************************************************************************
-- Gets around Snes9x's problem of double-painting the corners.
-- The double-paint is visible with non-opaque drawing.
-- It acts like the old-style border-only box.
-- Slightly messes up when x2 or y2 are less than their counterparts.

    if     (x1 == x2) and (y1 == y2) then
        gui.pixel(x1,y1,color)

    elseif (x1 == x2) or  (y1 == y2) then
        gui.line(x1,y1,x2,y2,color)

    else --(x1 ~= x2) and (y1 ~= y2)
        gui.line(x1  ,y1  ,x2-1,y1  ,color) -- top
        gui.line(x2  ,y1  ,x2  ,y2-1,color) -- right
        gui.line(x1+1,y2  ,x2  ,y2  ,color) -- bottom
        gui.line(x1  ,y1+1,x1  ,y2  ,color) -- left
    end
end


--*****************************************************************************
function FakeBox(x1, y1, x2, y2, Fill, Border)
--*****************************************************************************
-- Gets around Snes9x's problem of double-painting the corners.
-- It acts like the new-style fill-and-border box.

if not Border then   Border= Fill   end

    gui.box(x1,y1,x2,y2,Fill,0)
    FBoxOld(x1,y1,x2,y2,Border)
end


--*****************************************************************************
local Draw= {}   --Draw[button]( Left , Top , color )
--*****************************************************************************

function Draw.right(x,y,color)       --    ##
    gui.line(x  ,y  ,x+1,y  ,color)  --      #
    gui.line(x  ,y+2,x+1,y+2,color)  --    ##
    gui.pixel(x+2,y+1,color)
end

function Draw.left(x,y,color)        --     ##
    gui.line(x+1,y  ,x+2,y  ,color)  --    #
    gui.line(x+1,y+2,x+2,y+2,color)  --     ##
    gui.pixel(x  ,y+1,color)
end

function Draw.up(x,y,color)          --     #
    gui.line(x  ,y+1,x  ,y+2,color)  --    # #
    gui.line(x+2,y+1,x+2,y+2,color)  --    # #
    gui.pixel(x+1,y  ,color)
end

function Draw.down(x,y,color)        --    # #
    gui.line(x  ,y  ,x  ,y+1,color)  --    # #
    gui.line(x+2,y  ,x+2,y+1,color)  --     #
    gui.pixel(x+1,y+2,color)
end

function Draw.start(x,y,color)       --     #
    gui.line(x+1,y  ,x+1,y+2,color)  --    ###
    gui.pixel(x  ,y+1,color)         --     #
    gui.pixel(x+2,y+1,color)
end

function Draw.select(x,y,color)      --    ###
    FBoxOld(x  ,y  ,x+2,y+2,color)   --    # #
end                                  --    ###

function Draw.A(x,y,color)           --    ###
    FBoxOld(x  ,y  ,x+2,y+1,color)   --    ###
    gui.pixel(x  ,y+2,color)         --    # #
    gui.pixel(x+2,y+2,color)
end

function Draw.B(x,y,color)           --    # #
    gui.line(x  ,y  ,x  ,y+2,color)  --    ##
    gui.line(x+1,y+1,x+2,y+2,color)  --    # #
    gui.pixel(x+2,y  ,color)
end

function Draw.L(x,y,color)           --    #
   gui.line(x  ,y+2,x+2,y+2,color)   --    #
   gui.line(x  ,y  ,x  ,y+1,color)   --    ###
end

function Draw.R(x,y,color)
   gui.line(x  ,y  ,x  ,y+2,color)   --    ##
   gui.line(x+1,y  ,x+1,y+1,color)   --    ##
   gui.pixel(x+2,y+2,color)          --    # #
end


function Draw.D0(left, top, color)
    gui.line(left  ,top  ,left  ,top+4,color)
    gui.line(left+2,top  ,left+2,top+4,color)
    gui.pixel(left+1,top  ,color)
    gui.pixel(left+1,top+4,color)
end

function Draw.D1(left, top, color)
    gui.line(left  ,top+4,left+2,top+4,color)
    gui.line(left+1,top  ,left+1,top+3,color)
    gui.pixel(left  ,top+1,color)
end

function Draw.D2(left, top, color)
    gui.line(left  ,top  ,left+2,top  ,color)
    gui.line(left  ,top+3,left+2,top+1,color)
    gui.line(left  ,top+4,left+2,top+4,color)
    gui.pixel(left  ,top+2,color)
    gui.pixel(left+2,top+2,color)
end

function Draw.D3(left, top, color)
    gui.line(left  ,top  ,left+1,top  ,color)
    gui.line(left  ,top+2,left+1,top+2,color)
    gui.line(left  ,top+4,left+1,top+4,color)
    gui.line(left+2,top  ,left+2,top+4,color)
end

function Draw.D4(left, top, color)
    gui.line(left  ,top  ,left  ,top+2,color)
    gui.line(left+2,top  ,left+2,top+4,color)
    gui.pixel(left+1,top+2,color)
end

function Draw.D5(left, top, color)
    gui.line(left  ,top  ,left+2,top  ,color)
    gui.line(left  ,top+1,left+2,top+3,color)
    gui.line(left  ,top+4,left+2,top+4,color)
    gui.pixel(left  ,top+2,color)
    gui.pixel(left+2,top+2,color)
end

function Draw.D6(left, top, color)
    gui.line(left  ,top  ,left+2,top  ,color)
    gui.line(left  ,top+1,left  ,top+4,color)
    gui.line(left+2,top+2,left+2,top+4,color)
    gui.pixel(left+1,top+2,color)
    gui.pixel(left+1,top+4,color)
end

function Draw.D7(left, top, color)
    gui.line(left  ,top  ,left+1,top  ,color)
    gui.line(left+2,top  ,left+1,top+4,color)
end

function Draw.D8(left, top, color)
    gui.line(left  ,top  ,left  ,top+4,color)
    gui.line(left+2,top  ,left+2,top+4,color)
    gui.pixel(left+1,top  ,color)
    gui.pixel(left+1,top+2,color)
    gui.pixel(left+1,top+4,color)
end

function Draw.D9(left, top, color)
    gui.line(left  ,top  ,left  ,top+2,color)
    gui.line(left+2,top  ,left+2,top+3,color)
    gui.line(left  ,top+4,left+2,top+4,color)
    gui.pixel(left+1,top  ,color)
    gui.pixel(left+1,top+2,color)
    gui.pixel(left+2,top+3,color)
end


Draw[0],Draw[1],Draw[2],Draw[3],Draw[4]=Draw.D0,Draw.D1,Draw.D2,Draw.D3,Draw.D4
Draw[5],Draw[6],Draw[7],Draw[8],Draw[9]=Draw.D5,Draw.D6,Draw.D7,Draw.D8,Draw.D9
--*****************************************************************************
function DrawNum(right, top, Number, color, bkgnd)
--*****************************************************************************
-- Paints the input number as right-aligned.
-- Returns the x position where it would paint another digit.
-- It only works with integers. Rounds fractions toward zero.

    local Digit= {}
    local Negative= false
    if Number < 0 then
        Number= -Number
        Negative= true
    end

    Number= math.floor(Number)
    if not color then color= "white" end
    if not bkgnd then bkgnd= "clear" end

    local i= 0
    if Number < 1 then
        Digit[1]= 0
        i= 1
    end

    while (Number >= 1) do
        i= i+1
        Digit[i]= Number % 10
        Number= math.floor(Number/10)
    end

    if Negative then  i= i+1  end
    local left= right - i*4
    FakeBox(left+1, top-1, right+1, top+5,bkgnd,bkgnd)

    i= 1
    while Draw[Digit[i]] do
        Draw[Digit[i]](right-2,top,color)
        right= right-4
        i=i+1
    end

    if Negative then
        gui.line(right, top+2,right-2,top+2,color)
        right= right-4
    end
    return right
end


--*****************************************************************************
function limits( value , low , high )  -- Expects numbers
--*****************************************************************************
-- Returns value, low, or high. high is returned if value exceeds high,
-- and low is returned if value is beneath low.

    return math.max(math.min(value,high),low)
end


--*****************************************************************************
function within( value , low , high )  -- Expects numbers
--*****************************************************************************
-- Returns true if value is between low and high. False otherwise.

    return ( value >= low ) and ( value <= high )
end


--*****************************************************************************
function JoyToNum(Joys)  -- Expects a table containing joypad buttons
--*****************************************************************************
-- Returns a number from 0 to 4095, representing button presses.
-- These numbers are the primary storage for this version of this script.

    local joynum= 0

    for i= 1, #btn do
        if Joys[btn[i]] then
            joynum= bit.bor(joynum, bit.lshift(0x1,i))
        end
    end

    return joynum
end

--*****************************************************************************
function ReadJoynum(input, button)  -- Expects... Certain numbers!
--*****************************************************************************
-- Returns true or false. True if the indicated button is pressed
-- according to the input. False otherwise.

    return ( bit.band(input , bit.lshift( 0x1,button )) ~= 0 )
end


--*****************************************************************************
function ShowOnePlayer(x,y,color,button)
--*****************************************************************************
-- Displays an individual button.
-- Helper function for DisplayInput. Called as HighlightButton

    x= x + 4*button - 3
    Draw[btn[button]](x,y,color)
end

local DispX, DispY=  90, 60
local Past, Future= -12, 12
local Opaque= 1
--*****************************************************************************
function DisplayOptions()
--*****************************************************************************
-- Returns true if Opaque is 0, as a signal to don't bother painting.
-- Separated from DisplayInput to make it clear which half is doing what.

-- Change opacity?
    if keys[solid] then Opaque= Opaque + 1/8 end
    if keys[clear] then Opaque= Opaque - 1/8 end
    Opaque= limits(Opaque,0,1)

    gui.opacity(Opaque)
    if Opaque == 0 then  return true  end
    -- Don't bother processing display if there's none to see.

-- How many frames to show?
    if keys[LessFutr] then
        Future= Future-1
        if Future < Past then Past= Future end
    end
    if keys[MoreFutr] then
        Future= Future+1
        if Future > Past+33 then Past= Future-33 end
    end
    if keys[MorePast] then
        Past= Past-1
        if Past < Future-33 then Future= Past+33 end
    end
    if keys[LessPast] then
        Past= Past+1
        if Past > Future then Future= Past end
    end

    if keys[ResetFP] then Past= -12;  Future= 12   end


-- Move the display around?
    if keys[DispS] then DispY= DispY+1 end
    if keys[DispW] then DispX= DispX-1 end
    if keys[DispE] then DispX= DispX+1 end
    if keys[DispN] then DispY= DispY-1 end

    if keys["leftclick"] and lastkeys["leftclick"] then
        DispX= DispX + keys.xmouse - lastkeys.xmouse
        DispY= DispY + keys.ymouse - lastkeys.ymouse
    end

    DispX= limits(DispX,1,Xoff-#btn*4)
    DispY= limits(DispY,3-4*Past,Yoff-4*Future)

    return nil -- Signal to display the input
end


--*****************************************************************************
function DisplayInput()
--*****************************************************************************
-- Paints on the screen the current input stored within the script's list.
-- Rather a shoddy job at loadstate, however.

--Are we showing all players or just one?

--For both setting options and asking if we should bother displaying ourselves
    if DisplayOptions() then  return  end
    local width= #btn*4

--Display frame offsets we're looking at.
    local RtDigit= DispX + width + 22
    if RtDigit > 158 then  RtDigit= DispX - 4  end
    local TpDigit= DispY + 4*Past - 2
    DrawNum(RtDigit,TpDigit,Past,white,shade)

    if Future > Past+1 then
        TpDigit= DispY + 4*Future
        DrawNum(RtDigit,TpDigit,Future,white,shade)
    end

--Show cute little box around current frame
    if Past <= 0 and Future >= 0 then
        FBoxOld(DispX-1,DispY-2,DispX+width+1,DispY+4,green)
    end

--Shade the proper regions efficiently
    if Past < 0 then
        local Y1= DispY + 4*Past -3
        local Y2= DispY - 3
        if Future < -1 then  Y2= DispY + 4*Future +1  end
        FakeBox(DispX,Y1,DispX+width,Y2,shade,shade)
    end
    if Future > 0 then
        local Y1= DispY + 5
        local Y2= DispY + 4*Future+5
        if Past > 1 then  Y1= DispY + 4*Past +1  end
        FakeBox(DispX,Y1,DispX+width,Y2,shade,shade)
    end
    if  Past <= 0  and  Future >= 0  then
        FakeBox(DispX,DispY-1,DispX+width,DispY+3,shade,shade)
    end

--Finally, we get to show the actual buttons!
    for i= Past, Future do
        local Y= DispY + 4*i
        if     i < 0 then  Y=Y-2
        elseif i > 0 then  Y=Y+2 end
        local scanz= InputList[fc+i]
        for button= 1, #btn do

            local color
            if not scanz then
                color= white
            elseif ReadJoynum(scanz,button) then
                color= green
            else
                color= red
            end
            if (not OptionUseList) and i >= 0 then
                if     color == green then color= fadeGn
                elseif color == red   then color= fadeRd end
            end

            ShowOnePlayer(DispX,Y,color,button)
        end
    end
end


local ThisInput= {}
--*****************************************************************************
function MessWithInput()
--*****************************************************************************
-- Good grief, VBA manages to be frustrating me!
-- FCEUX, Snes9x, and DeSmuME didn't fuss like this. But man!

    fc= movie.framecount()-1
    lastkeys= keys
    keys= input.get()

    if movie.mode == "playback" then
        InputList[fc]= JoyToNum(joypad.get(1))
    else
        local temp= InputList[fc]
        for i= 1, #btn do
            ThisInput[btn[i]]= keys[key[i]]
            if OptionUseList and temp and ReadJoynum(temp,i) then
                ThisInput[btn[i]]= not ThisInput[btn[i]]
            end
            if ThisInput[btn[i]] == false  then  ThisInput[btn[i]]= nil  end
        end

        joypad.set(1, ThisInput)
        InputList[fc]= JoyToNum(ThisInput)
    end
    fc= fc+1
end

emu.registerafter(MessWithInput)


--*****************************************************************************
function ItIsYourTurn()
--*****************************************************************************
-- Rather stripped down from FCEUX version. Only has basic functionality.
-- Mainly just there to set options and controls now.

--Sets controller to pick either this script or the player.
    if keys[ListSwitch] then
        OptionUseList= not OptionUseList
    end

    collectgarbage("collect")
end
gui.register(DisplayInput)


emu.pause()
while true do
    ItIsYourTurn()
    emu.frameadvance()
end