FatRatKnight -
I'm going to work on combining this script with a rewind script, if you don't mind.
EDIT:
Here is attempt #1 at combining scripts:
EDIT #2:
I added the ability to select both players. This really is an 'all' option.
-- Multi-track rerecording for FCEUX
-- To make up for the lack of an internal multi-track rerecording in FCEUX.
-- It may get obsolete when TASEdit comes around.
--FatRatKnight
-- Stuff that you are encouraged to change to fit what you need.
--*****************************************************************************
local players= 2 -- You may tweak the number of players here.
local opaque= 0 -- Default opacity. 0 is solid, 4 is invisible.
local dispX, dispY= 10, 99 -- For the display stuffs
local saveMax = 1000; -- Max Rewind Power
-- Control scheme. You may want to change these.
local selectplayer = "S" -- For selecting which player
local recordingtype= "space" -- For selecting how to record
local show, hide = "pageup", "pagedown" -- Opacity adjuster
local scrlup, scrldown, scrlleft, scrlright= "numpad8", "numpad2", "numpad4", "numpad6" -- Input display mover
local rewind = "R"
local key = {"right", "left", "down", "up", "C", "V", "X", "Z"}
local btn = {"right", "left", "down", "up", "start", "select", "B", "A"}
-- Don't change btn, unless it's to adjust the spacing to keep it lined up neatly.
--*****************************************************************************
-- Stuff that really shouldn't be messed with, unless you plan to change the code significantly.
local optType= {"Both", "Keys", "List", "Off"} -- Names for option types
local keys, lastkeys = {}, {} -- To hold keyboard input
local Pin, thisInput = {}, {} -- Input manipulation array
local option = {} -- Options for individual button input by script
local fc, lastfc= nil, nil -- Frame counters
local pl= 1 -- Player selected
local repeater; -- for holding a button
--The stuff below is for more advanced users, enter at your own peril!
local saveArray = {};--the Array in which the save states are stored
local saveCount = 0;--used for finding which array position to cycle through
local saver; -- the variable used for storing the save state
local rewindCount = 0;--this stops you looping back around the array if theres nothing at the end
local savePreventBuffer = 1;--Used for more control over when save states will be saved, not really used in this version much.
-- Initialize tables for each player.
for i= 1, players do
Pin[i] = {}
thisInput[i] = {}
option[i] = {}
for j= 1, 8 do
option[i][btn[j]]= 1
end
end
-- Intention of options:
-- "Both" Script buttons and input list used
-- "Keys" Script buttons used
-- "List" Input list used
-- "Off" Script will not interfere with button
--*****************************************************************************
function press(button)
--*****************************************************************************
-- Checks if a button is pressed.
-- The tables it accesses should be obvious in the next line.
if keys[button] and not lastkeys[button] then
return true
end
return false
end
function pressrepeater(button)
if keys[button] then
if not lastkeys[button] or repeater == 3 then
repeater = 0;
return true
else
repeater = repeater + 1;
end
end;
return false
end
--*****************************************************************************
function newframe()
--*****************************************************************************
-- Psuedo-detection of frame-advance or state load.
-- The lack of a function to otherwise detect state loads demands this cheap hack.
-- I will admit I'm not aware of things. Does there exist such a function already?
-- The values it accesses are: fc, lastfc
if not fc then
return nil -- Sanity check
end
if not lastfc then -- Although I could return errors or
return "stateload" -- different strings, I'm using these
end -- for my purposes in this script.
if fc == lastfc +1 then -- I hope the string it returns
return "frameadvance" -- explains the intention.
elseif fc ~= lastfc then -- A bug occurs when you load a state
return "stateload" -- equal to current frame or current frame+1
end
return nil -- fc == lastfc. No advance or load
end
-- Primary function. ... Why did I pick itisyourturn, anyway?
--*****************************************************************************
function itisyourturn()
--*****************************************************************************
keys= input.get() -- Standard get info stuff.
if pressrepeater(rewind) then
if rewindCount==0 then
--makes sure you can't go back too far could also include other things in here, left empty for now.
else
savestate.load(saveArray[saveCount-1]);
saveCount = saveCount-1;
rewindCount = rewindCount-1;
if saveCount==0 then
saveCount = saveMax;
end;
end;
end;
--Need to grab after a possible load.
fc= movie.framecount() -- Without which, what can I do?
-- Selection: Players
if press(selectplayer) then -- Hopefully, this block is
pl= pl + 1 -- self-explanatory.
if pl > players+1 then --
pl= 1 -- If not, it... Selects a player...
end -- Joy.
end
-- Option: Opacity
if press(hide) and opaque < 4 then
opaque= opaque+1
end
if press(show) and opaque > 0 then
opaque= opaque-1
end
gui.transparency(opaque)
-- Option: Move input display by keyboard
if press(scrlup) then
dispY= dispY - 1
end
if press(scrldown) then
dispY= dispY + 1
end
if press(scrlleft) then
dispX= dispX - 1
end
if press(scrlright) then
dispX= dispX + 1
end
-- Option: Move input display by mouse
if keys["leftclick"] and lastkeys["leftclick"] then
dispX= dispX + keys.xmouse - lastkeys.xmouse
dispY= dispY + keys.ymouse - lastkeys.ymouse
end
framed= newframe()
if framed then
for P= 1, players do
-- Store input if you advanced the frame.
if framed == "frameadvance" then
if not Pin[P][lastfc] then
Pin[P][lastfc]= {} -- Initialize table if none exists.
end
Pin[P][lastfc]= joypad.get(P)
end
-- Load stored input, if it exists.
-- Shouldn't matter if frame advanced or state loaded. It's a new frame, darn it!
if Pin[P][fc] then
for i= 1, 8 do
op= option[P][btn[i]]
if op == 1 or op == 3 then -- "Both" or "List"
thisInput[P][btn[i]]= Pin[P][fc][btn[i]]
else
thisInput[P][btn[i]]= nil
end
end
else -- No input to load, so erase stuff.
for i= 1, 8 do
thisInput[P][btn[i]]= nil
end
end
end -- Loop for each player
-- Resets key input, so it counts as pressed again if held through frame advance.
for i= 1, 8 do
lastkeys[key[i]]= nil
end
end -- If framecount changed
-- We're done checking if a new frame happened.
-- Now we're going to check for change in input!
if keys[recordingtype] then -- Are you setting options?
for i= 1, 8 do
-- Make sure to change the options of whatever key we press. Not the ones we don't press.
if pl == players + 1 then
for q = 1,players,1 do
if press(key[i]) then
option[q][btn[i]]= option[q][btn[i]]+1
if option[q][btn[i]] > 4 then
option[q][btn[i]]= 1
end
end
end
else
if press(key[i]) then
option[pl][btn[i]]= option[pl][btn[i]]+1
if option[pl][btn[i]] > 4 then
option[pl][btn[i]]= 1
end
end
end;
-- Display current options
gui.transparency(0)
gui.text(20,20+12*i,btn[i])
if pl == players + 1 then
gui.text(50,20+12*i,optType[option[1][btn[i]]])
else
gui.text(50,20+12*i,optType[option[pl][btn[i]]])
end -- individual button loop
end;
else -- Oh, you're not setting options. Actual input, eh?
for i= 1, 8 do
-- Check options to decide if we should ignore you. Else, toggle the keyed button.
if pl == players + 1 then
for q = 1,players,1 do
op= option[q][btn[i]]
if op == 1 or op == 2 then -- "Both" or "Keys"
if press(key[i]) then -- Spread the AND to the next line
if thisInput[q][btn[i]] then
thisInput[q][btn[i]]= nil
else
thisInput[q][btn[i]]= true
end
end
end
end;
else
op= option[pl][btn[i]]
if op == 1 or op == 2 then -- "Both" or "Keys"
if press(key[i]) then -- Spread the AND to the next line
if thisInput[pl][btn[i]] then
thisInput[pl][btn[i]]= nil
else
thisInput[pl][btn[i]]= true
end
end
end
end
end -- individual button loop
-- Display current input.
dispX= math.min(math.max(dispX,-2),219) -- Limits hack
dispY= math.min(math.max(dispY,51),185)
for i= -10, 10 do
Y= dispY
if i < 0 then
Y= Y-3
elseif i > 0 then
Y= Y+3
end
temp= {} -- I may want to pick thisInput instead of the frame list.
if pl == players+1 then
qtemp = 1;
else
qtemp = pl;
end;
if i == 0 then
temp= thisInput[qtemp]
else
temp= Pin[qtemp][fc+i]
end
color= nil
for j= 1, 8 do
if not temp then
color= "white"
elseif temp[btn[j]] then
color= "green"
else
color= "red"
end
gui.drawbox(dispX +4*j,Y +4*i,dispX+2 +4*j,Y+2 +4*i,color)
end
end
gui.drawbox(dispX+2,Y-5,dispX+36,Y+1,"blue")
end -- Done with "options" if
-- Display selected player. I might want to move this around.
if pl <= players then
gui.text(30,10,pl)
else
gui.text(30,10,"B")
end;
-- Feed the input to the emulation.
if movie.mode() ~= "playback" then
for P= 1, players do
joypad.set(P, thisInput[P])
end
end
lastfc= fc -- Standard "this happened last time" stuff
lastkeys= keys -- Don't want to keep registering key hits.
end
gui.register(itisyourturn)
-- I'm think I should leave it up to the user to decide if the input should stick.
FCEU.pause();
while true do
FCEU.frameadvance()
saveCount=saveCount+1;
rewindCount = math.max(rewindCount + 1,saveMax);
if saveCount==saveMax+1 then
saveCount = 1;
end
if saveArray[saveCount] == nil then
saver = savestate.create();
else
saver = saveArray[saveCount];
end;
savestate.save(saver);
saveArray[saveCount] = saver; -- I'm pretty sure this line is unnecessary.
end
-- Possible inconveniences include:
-- * You should reload the script every time you switch through movies.
-- Failure to reload will result in old, "junk" input showing up. It hasn't been erased yet.
-- * Won't care about what you set up for your normal player input. Though it works with it fine.
-- * Could screw up under specific circumstances involving loading a state located 1 frame later.
Its... funky. Its almost working. Let me know what you think.