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