-- Multitrack v2 for FCEUX by FatRatKnight -- Recent addition: Experimental save. Same format as in .fm2 -- Did not include support to not have it trigger on script exit. Sorry... -- Additionally, there's no *loading* mechanism in this script. Eh... --*************** local players= 2 -- You may tweak the number of players here. --*************** -- Rewind options local saveMax= 50 -- How many save states to keep? Set to 0 to disable local SaveBuf= 12 -- How many frames between saves? Don't use 0, ever. local rewind= "numpad0" -- What key do you wish to hit for rewind? --Keep in mind: Higher saveMax, more savestates, more memory needed. --Control local PlayerSwitch= "home" -- For selecting other players local opt= "end" -- For changing control options local BackupReadSwitch= "numpad-" -- Revert to backup copy local BackupWriteSwitch= "numpad+" -- Copy input into backup local key = {"right", "left", "down", "up", "L", "O", "J", "K"} local btn = {"right", "left", "down", "up", "start", "select", "B", "A"} --Try to avoid changing btn. You may reorder btn's stuff if you don't --like the default order, however. Line up with key for good reasons. --key is the actual keyboard control over the buttons. Change that! --Insertions and deletions local Insert= "insert" -- Inserts a frame at the frame you're at local Delete= "delete" -- Eliminates current frame --Display local BackupDisplaySwitch= "numpad." -- Care to see the backup? 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" --Control options by keyboard local OptUp= "numpad8" local OptDn= "numpad2" -- Controls for changing local OptRt= "numpad6" -- options by keyboard. local OptLf= "numpad4" local OptHit="numpad5" --Various colors I'm using. If you wish to bother, go ahead. local shade= 0x00000080 local white= "#FFFFFFFF" local red= "#FF2000FF" local green= 0x00FF00FF local blue= 0x0040FFFF local orange="#FFC000FF" local yellow="#FFFF00FF" local cyan= 0x00FFFFFF local purple="#8020FFFF" --Default display stuff. Try to keep Past negative. local DispX, DispY= 190, 70 --Position on screen to appear local Past, Future= -12, 20 --How much input to see local Opaque= 1 --0=unseen 1=visible Decimal in between works local ListOfBtnClr= { -- Colors for individual buttons --NotExist Off Ignored On white, red, orange, green, -- No backup to display white, red, orange, green, -- Backup button is off blue, blue, purple, cyan, -- Backup button is on white, red, orange, green, -- Same stuff as above. white, red, orange, green, -- However, we're reading blue, blue, purple, cyan, -- from backup here. white, red, orange, green, -- Likewise, but instead white, red, orange, green, -- of read, we're writing blue, blue, purple, cyan -- to the backup. Joy... } local ListOfChangesClr= { -- Colors for backup-main comparisons --NoMain NoMatch Equal white, green, blue, -- Backup just sits there... white, orange, green, -- Reading from backup... white, yellow, purple -- Writing to backup... } local BackupFileLimit= 9 --***************************************************************************** --Please do not change the following, unless you plan to change the code: local plmin , plmax= 1 , 1 local fc= movie.framecount() local LastLoad= movie.framecount() local saveCount= 0 local saveArray= {} local rewinding= false local BackupList= {} local BackupOverride= {} local DisplayBackup= true local InputList= {} local ThisInput= {} local BufInput= {} local BufLen= {} local TrueSwitch, FalseSwitch= {}, {} local ReadList= {} local ScriptEdit= {} for pl= 1, players do InputList[pl]= {} BackupList[pl]= {} BackupOverride[pl]= false ThisInput[pl]= {} BufInput[pl]= {} BufLen[pl]= 0 TrueSwitch[pl]= {} FalseSwitch[pl]= {} ReadList[pl]= {} ScriptEdit[pl]= {} for i= 1, 8 do TrueSwitch[pl][i]= "inv" FalseSwitch[pl][i]= nil ReadList[pl][i]= true ScriptEdit[pl][i]= false end end --***************************************************************************** -- Just painting instructions here. print("Running Multitrack Script made by FatRatKnight.", "\r\nIts primary use is to preserve input after a loadstate.", "\r\nIt also gives precise control on changing this stored input.", "\r\nEdit the script if you need to change stuff. All options are near the top.", "\r\nThe script is currently set as follows:\r\n") print("Players:",players) if players > 1 then print("Player Switch key:",PlayerSwitch, "\r\nThere is an All Players view as well, beyond the last player.\r\n") else print("With only one player, the PlayerSwitch button is disabled.\r\n") end print("Maximum rewind states:",saveMax) local CalcSeconds= math.floor(saveMax*SaveBuf*100/60)/100 if saveMax > 0 then print("Frames between saves:",SaveBuf, "\r\nRewind key:",rewind, "\r\nThis can go backwards up to about",CalcSeconds,"seconds.", "\r\nRewind is a quick and easy way to back up just a few frames.", "\r\nJust hit",rewind,"and back you go!\r\n") else print("Rewind function is disabled. Cheap on memory!\r\n") end print("Hold >",opt,"< to view control options.", "\r\nYou may use the mouse or keyboard to toggle options.", "\r\nFor keyboard, use",OptUp,OptDn,OptRt,OptLf,"to choose an option and", OptHit,"to toggle the option.", "\r\nFor mouse, it's as simple at point and click.", "\r\nIf viewing All Players, changing options will affect all players.\r\n") print("For the frame display, you can drag it around with the mouse.", "\r\nTo move it by keyboard:",DispN,DispS,DispE,DispW, "\r\nTo change frames to display:",MoreFutr,LessFutr,MorePast,LessPast, "\r\nTo recenter display around current frame:",ResetFP, "\r\nLastly, hide it with >",clear,"< and show it with >",solid,"<\r\n") print("Insert frame:",Insert, "\r\nDelete frame:",Delete, "\r\nThese keys allow you to shift frames around the current frame.", "Perhaps you found an improvement to a segment and want to delete frames.", "Or this small trick you wanted to add requires shifting a few frames forward.", "These buttons let you shift frames around without forcing you to", "modify each and every one of them individually.\r\n") print("Script keys:", "\r\nJoy - Keyboard", "\r\n------------") for i= 1, 8 do print(btn[i],"-",key[i]) end print("By default, these are disabled. Enable them through the control options.", "\r\nThey are a one-tap joypad switch for precision control.", "\r\nIt will affect the currently displayed player, or all of them if", "it's displaying All Players.\r\n") print("If you want to \"load\" an fm2 into this script, run the play file", "while running this script. As the movie on read-only plays out, the", "script will pick up its input. The script does not need to start from", "frame 1 -- Load a state near the end of a movie if you so wish!", "Once input is loaded, you may continue whatever you were doing.\r\n") print("Remember, edit the script if you don't like the current options. All", "the options you should care about are near the top, clearly marked.", "Change the control keys through there, as well as number of players or rewind limits.", "\r\nThis script works with the joypad you actually set from FCEUX's config,", "and they can even cancel input stored in this script.", "\r\n\r\nAnd have fun with this script. I hope your experiments with this script goes well.", "\r\n\r\nLeeland Kirwan, the FatRatKnight.") print("Players:",players," - - ",CalcSeconds,"seconds of rewind.") --***************************************************************************** function FBoxOld(x1, y1, x2, y2, color) --***************************************************************************** -- Gets around FCEUX'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 FCEUX'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.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) 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 local keys, lastkeys= {}, {} --***************************************************************************** function press(button) -- Expects a key --***************************************************************************** -- Returns true or false. -- Generally, if keys is pressed, the next loop around will set lastkeys, -- and thus prevent returning true more than once if held. return (keys[button] and not lastkeys[button]) end local repeater= 0 --***************************************************************************** function pressrepeater(button) -- Expects a key --***************************************************************************** --DarkKobold & FatRatKnight -- Returns true or false. -- Acts much like press if you don't hold the key down. Once held long enough, -- it will repeatedly return true. -- Try not to call this function more than once per main loop, since I've only -- set one repeater variable. Inside a for loop is real bad. if keys[button] then if not lastkeys[button] or repeater >= 3 then return true else repeater = repeater + 1; end elseif lastkeys[button] then -- To allow more calls for other buttons repeater= 0 end; return false end --***************************************************************************** function JoyToNum(Joys) -- Expects a table containing joypad buttons --***************************************************************************** -- Returns a number from 0 to 255, representing button presses. -- These numbers are the primary storage for this version of this script. local joynum= 0 for i= 1, 8 do if Joys[btn[i]] then joynum= bit.bor(joynum, bit.rshift(0x100,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. -- Helper function return ( bit.band(input , bit.rshift( 0x100,button )) ~= 0 ) end --***************************************************************************** function RewindThis() --***************************************************************************** --DarkKobold & FatRatKnight; Original concept by Antony Lavelle if saveCount <= 0 then return false -- Failed to rewind else savestate.load(saveArray[saveCount]); saveCount = saveCount-1; end; return true -- Yay, rewind! end --***************************************************************************** function ShowOnePlayer(x,y,color,button,pl) --***************************************************************************** -- Displays an individual button. -- Helper function for DisplayInput. x= x + 4*button - 3 Draw[btn[button]](x,y,color) end --***************************************************************************** function ShowManyPlayers(x,y,color,button,pl) --***************************************************************************** -- Displays an individual button. Uses 2x3 boxes instead of button graphics. -- Helper function for DisplayInput. x= x + (2*players + 1)*(button - 1) + 2*pl - 1 FBoxOld(x,y,x+1,y+2,color) end Future= limits(Future,Past,Past+53) --Idiot-proofing --***************************************************************************** function DisplayOptions(width) -- Expects width calculated by DisplayInput --***************************************************************************** -- 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 pressrepeater(solid) then Opaque= Opaque + 1/8 end if pressrepeater(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 pressrepeater(LessFutr) then Future= Future-1 if Future < Past then Past= Future end end if pressrepeater(MoreFutr) then Future= Future+1 if Future > Past+53 then Past= Future-53 end end if pressrepeater(MorePast) then Past= Past-1 if Past < Future-53 then Future= Past+53 end end if pressrepeater(LessPast) then Past= Past+1 if Past > Future then Future= Past end end if press(ResetFP) then Past= -12; Future= 12 end -- Move the display around? if pressrepeater(DispS) then DispY= DispY+1 end if pressrepeater(DispW) then DispX= DispX-1 end if pressrepeater(DispE) then DispX= DispX+1 end if pressrepeater(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 --Miscellaneous if press(BackupDisplaySwitch) then DisplayBackup= not DisplayBackup end DispX= limits(DispX,2,254-width) DispY= limits(DispY,3-4*Past,218-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. --Are we showing all players or just one? local HighlightButton= ShowOnePlayer local width= 32 if plmin ~= plmax then HighlightButton= ShowManyPlayers width= 16*players + 8 end --For both setting options and asking if we should bother displaying ourselves if DisplayOptions(width) then return end --Display frame offsets we're looking at. local RtDigit= DispX + width + 22 if RtDigit > 254 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 local color= blue for pl= plmin, plmax do local ThisFrame= InputList[pl][fc-BufLen[pl]] if BufLen[pl] > 0 then ThisFrame= BufInput[pl][1] end if ThisFrame ~= JoyToNum(ThisInput[pl]) then color= green end end FBoxOld(DispX-1,DispY-2,DispX+width+1,DispY+4,color) 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 BackupColor= nil -- For displaying a line for backup for pl= plmin, plmax do local scanz if i < 0 then scanz= InputList[pl][fc+i] elseif i == 0 then scanz= JoyToNum(ThisInput[pl]) elseif i < BufLen[pl] then scanz= BufInput[pl][i+1] else scanz= InputList[pl][fc+i-BufLen[pl]] end for button= 1, 8 do -- Take a number, oh mighty color. There's too much of ya! local color if not scanz then color= 1 --Does not exist elseif ReadJoynum(scanz,button) then if (i <= 0) or (ReadList[pl][button]) then color= 4 --Button on else color= 3 --Button on, will be ignored end else color= 2 --Button off end if DisplayBackup then if BackupList[pl][fc+i] then if ReadJoynum(BackupList[pl][fc+i],button) then color= color+8 else color= color+4 end end if BackupOverride[pl] == "read" then color= color+12 elseif BackupOverride[pl] == "write" then color= color+24 end end HighlightButton(DispX,Y,ListOfBtnClr[color],button,pl) end -- button loop -- Draw cute lines if we wish to see the backup if DisplayBackup and BackupList[pl][fc+i] then if not BackUpColor then if not scanz then BackupColor= 1 elseif scanz == BackupList[pl][fc+i] then BackupColor= 3 else BackupColor= 2 end if BackupOverride[pl] == "read" then BackupColor= BackupColor+3 elseif BackupOverride[pl] == "write" then BackupColor= BackupColor+6 end else -- Eh, we already have a BackupColor? local CheckColor= (BackupColor-1)%3 if not scanz then BackupColor= BackupColor - CheckColor elseif (scanz ~= BackupList[pl][fc+i]) and (CheckColor == 2) then BackupColor= BackupColor - 1 end end end end -- player loop if BackupColor then gui.line(DispX-2,Y,DispX-2,Y+2,ListOfChangesClr[BackupColor]) end end -- loop from Past to Future end --function --***************************************************************************** function SetInput() --***************************************************************************** -- Prepares ThisInput for joypad use. for pl= 1, players do local temp if BackupOverride[pl] == "read" then temp= BackupList[pl][fc] else if BufLen[pl] > 0 then temp= BufInput[pl][1] else temp= InputList[pl][fc-BufLen[pl]] end end for i= 1, 8 do if temp and ReadJoynum(temp,i) and ReadList[pl][i] then ThisInput[pl][btn[i]]= TrueSwitch[pl][i] else ThisInput[pl][btn[i]]= FalseSwitch[pl][i] end end end end --***************************************************************************** function ApplyInput() --***************************************************************************** -- Shoves ThisInput into the actual emulator joypads. if movie.mode() ~= "playback" then for pl= 1, players do joypad.set(pl, ThisInput[pl]) end end end --***************************************************************************** function InputSnap() --***************************************************************************** -- Will shove the input list over. -- Might end up freezing for a moment if there's a few thousand frames to shift for pl= 1, players do if BufLen[pl] < 0 then -- Squish! local pointer= fc while InputList[pl][pointer] do InputList[pl][pointer]= InputList[pl][pointer-BufLen[pl]] pointer= pointer+1 end end if BufLen[pl] > 0 then -- Foom! local pointer= fc while InputList[pl][pointer] do pointer= pointer+1 end -- pointer is now looking at a null frame. -- Assume later frames are also null. while pointer > fc do pointer= pointer-1 InputList[pl][pointer+BufLen[pl]]= InputList[pl][pointer] end -- pointer should now match fc. -- Everything at fc and beyond should be moved over by BufLen. for i=0, BufLen[pl]-1 do InputList[pl][fc +i]= table.remove(BufInput[pl],1) end end BufLen[pl]= 0 -- If it ain't zero before, we want it zero now! end end local XStart, XDiff= 30, 25 local YStart, YDiff= 12, 15 local Status= { {},{},{},{} } local TargA= {FalseSwitch,TrueSwitch,ReadList,ScriptEdit} local TrueA= { nil , "inv" , true , true } local FalsA= { false , true , false , false } local BtnX, BtnY= 0, 0 local BasicText= { "Activate: Lets the joypad activate input.", "Remove: Allows the joypad to remove input.", "List: Should the script remember button presses?", "Keys: Allow the script-specific keys to work?"} local AdvText= { {"With both joypad options on, you have full control.", "Held buttons will toggle the script's stored input."}, -- 1 {"With this on-off set-up, auto-hold will not", "accidentally cancel input, such as from load state."}, -- 2 {"With this off-on set, you can use auto-hold to", "elegantly strip off unwanted input."}, -- 3 {"With both joypad options off, you ensure you can't", "accidentally change what's stored in the script."}, -- 4 {"List on: Script will apply input stored within.", "This is the whole point of the script, ya know."}, -- 5 {"List off: Stored input is ignored. A good idea if", "you don't want the script interfering."}, -- 6 {"Keys on: Script keys will toggle the button press", "for the current frame. Meant for precise edits."}, -- 7 {"Keys off: Script-specific keys are no longer a", "factor. Long-term control is meant for joypad!"}, -- 8 {"Apparently, you've selected 'All players'", "and they have different options set."}, -- 9 {"This is the 'All' button. Selecting this will set", "all 'Yes' or all 'No' for this particular option."} --10 } --***************************************************************************** function ControlOptions() --***************************************************************************** -- Lets the user make adjustments on the various controls of this script. -- The interface, apparently, is most of the work! -- Button selection if (keys.xmouse ~= lastkeys.xmouse) or (keys.ymouse ~= lastkeys.ymouse) then BtnX= math.floor((keys.xmouse- XStart+4)/XDiff) BtnY= math.floor((keys.ymouse- YStart-6)/YDiff) else if press(OptUp) then BtnX= limits(BtnX ,1,4) BtnY= limits(BtnY-1,1,9) end if press(OptDn) then BtnX= limits(BtnX ,1,4) BtnY= limits(BtnY+1,1,9) end if press(OptRt) then BtnX= limits(BtnX+1,1,4) BtnY= limits(BtnY ,1,9) end if press(OptLf) then BtnX= limits(BtnX-1,1,4) BtnY= limits(BtnY ,1,9) end end --Did you punch something? if press("leftclick") or press(OptHit) then if within( BtnY , 1 , 9 ) then if within( BtnX , 1 , 4 ) then local LoopS, LoopF= 1, 8 if BtnY ~= 9 then LoopS, LoopF= BtnY, BtnY end local Target, TstT, TstF= TargA[BtnX], TrueA[BtnX], FalsA[BtnX] for pl= plmin, plmax do for Loop= LoopS, LoopF do if Target[plmax][LoopF] == TstT then Target[pl][Loop]= TstF else Target[pl][Loop]= TstT end end end end end end --Figure out the status for i= 1, 4 do for j= 1, 8 do Status[i][j]= TargA[i][plmin][j] local pl= plmin+1 while (pl <= plmax) do if Status[i][j] ~= TargA[i][pl][j] then Status[i][j]= "???" break end pl= pl+1 end if Status[i][j] == TrueA[i] then Status[i][j]= "Yes" elseif Status[i][j] == FalsA[i] then Status[i][j]= "No" end end end --============================================================================= --Begin drawing. Finally... --Shade the whole freaking screen FakeBox(0,0,255,223,shade,shade) -- Silly little graphics -- Perhaps I shouldn't always assume that button right is a right arrow... Draw.B( XStart + XDiff ,YStart,red) Draw.right( XStart + XDiff + 5,YStart,white) Draw.B( XStart + XDiff +10,YStart,green) Draw.B( XStart + XDiff*2 ,YStart,green) Draw.right( XStart + XDiff*2+ 5,YStart,white) Draw.B( XStart + XDiff*2+10,YStart,red) Draw[btn[1]](XStart + XDiff*3 ,YStart ,green) Draw[btn[1]](XStart + XDiff*3 ,YStart+4,green) Draw[btn[2]](XStart + XDiff*3+ 4,YStart ,green) Draw[btn[2]](XStart + XDiff*3+ 4,YStart+4,red) Draw[btn[3]](XStart + XDiff*3+ 8,YStart ,red) Draw[btn[3]](XStart + XDiff*3+ 8,YStart+4,green) Draw[btn[4]](XStart + XDiff*3+12,YStart ,red) Draw[btn[4]](XStart + XDiff*3+12,YStart+4,red) gui.box(XStart+XDiff*4,YStart,XStart+XDiff*4+14,YStart+6,0,green) Draw[btn[6]](XStart + XDiff*4+ 2,YStart+2,green) Draw[btn[7]](XStart + XDiff*4+ 6,YStart+2,green) Draw[btn[8]](XStart + XDiff*4+10,YStart+2,green) --Highlight button if within( BtnY , 1 , 9 ) then if within( BtnX , 1 , 4 ) then local LowX=BtnX*XDiff +XStart -4 local LowY=BtnY*YDiff +YStart -3 gui.box(LowX,LowY,LowX+XDiff-2,LowY+YDiff-2,blue,blue) --Handle text. Feels sort of hard-coded, however gui.text(1,160,BasicText[BtnX]) local adv if BtnY == 9 then adv= 10 elseif Status[BtnX][BtnY] == "???" then adv= 9 elseif within(BtnX,1,2) and ((Status[3-BtnX][BtnY] == "???")) then adv= 9 elseif within(BtnX,1,2) then adv= 1 if Status[2][BtnY] == "No" then adv= adv + 1 end if Status[1][BtnY] == "No" then adv= adv + 2 end else adv= 5 if BtnX == 4 then adv= adv+2 end if Status[BtnX][BtnY] == "No" then adv= adv+1 end end gui.text(1,175,AdvText[adv][1]) gui.text(1,183,AdvText[adv][2]) end end --Lines! for i= 1, 5 do local Xpos= XStart + XDiff*i -5 gui.line(Xpos,0,Xpos,YStart + YDiff*10-4,green) end for j= 1, 9 do local Ypos= YStart + YDiff*j -4 gui.line( 0, Ypos, XStart + XDiff*5-5, Ypos, green) end gui.line(XStart+XDiff-4, YStart + YDiff*10-4, XStart + XDiff*5-5, YStart + YDiff*10-4, green) --Button labels! for j= 1, 8 do local Ypos= YStart + YDiff*j gui.text( 1, Ypos, btn[j]) Draw[btn[j]](XStart + XDiff - 12, Ypos, green) for i= 1, 4 do gui.text(XStart + XDiff*i, Ypos, Status[i][j]) end end for i= 1, 4 do gui.text(XStart + XDiff*i, YStart + YDiff*9, "All") end end --***************************************************************************** function CatchInput() --***************************************************************************** -- For use with registerbefore. It will get the input and paste it into -- the input list. fc= movie.framecount() for pl= 1, players do if BufLen[pl] > 0 then table.remove(BufInput[pl],1) table.insert(BufInput[pl],InputList[pl][fc-1]) end local NewJoy= JoyToNum(joypad.get(pl)) InputList[pl][fc-1]= NewJoy if not BackupList[pl][fc-1] or (BackupOverride[pl] == "write") then BackupList[pl][fc-1]= NewJoy end end SetInput() end emu.registerbefore(CatchInput) --***************************************************************************** function OnLoad() --***************************************************************************** -- For use with registerload. It updates ThisInput so we're not stuck with -- junk being carried over when we load something. -- Also clears whatever rewind stuff is stored. InputSnap() for pl= 1, players do --Assume user is done messing with backup on load BackupOverride[pl]= false end fc= movie.framecount() LastLoad= fc saveCount= 0 SetInput() ApplyInput() -- Sanity versus unpaused stateload end savestate.registerload(OnLoad) --***************************************************************************** function ItIsYourTurn() --***************************************************************************** -- For use with gui.register. I need to catch input while paused! -- Needed for a half-decent interface. --Ensure things are nice, shall we? local fc_= movie.framecount() lastkeys= keys keys= input.get() if fc ~= fc_ then -- Sanity versus unusual jump (Reset cycle?) OnLoad() end --Process Rewind if saveMax > 0 then -- Don't process if Rewind is disabled if pressrepeater(rewind) or rewinding then rewinding= false if RewindThis() then InputSnap() movie.rerecordcounting(true) fc= movie.framecount() SetInput() if saveCount < 1 then emu.pause() end end end end --Should we bother the backups today? for pl= plmin, plmax do if press(BackupReadSwitch) then if BackupOverride[pl] ~= "read" then BackupOverride[pl]= "read" else BackupOverride[pl]= false end end if press(BackupWriteSwitch) then if BackupOverride[pl] ~= "write" then BackupOverride[pl]= "write" else BackupOverride[pl]= false end end end if keys[BackupReadSwitch] and keys[BackupWriteSwitch] then gui.text(30,1,"Hey, quit holding both buttons!") elseif keys[BackupReadSwitch] then gui.text(30,1,"This lets the script revert to the backup.") elseif keys[BackupWriteSwitch] then gui.text(30,1,"This stores input into the backup.") end --Switch players on keypress if press(PlayerSwitch) then if plmin ~= plmax then plmax= 1 elseif plmin == players then plmin= 1 else plmin= plmin+1 plmax= plmax+1 end end --Display which player we're selecting. Upperleft corner seems good. if plmin == plmax then gui.text(1,2,plmin) gui.text(11,2,BufLen[plmin]) else gui.text(1,2,"A") end --Check if we want to see control options if keys[opt] then gui.opacity(1) ControlOptions() SetInput() --Otherwise, handle what we can see else --Inserts and deletes. --table functions don't work well on tables that don't connect from 1. if pressrepeater(Insert) then for pl= plmin, plmax do BufLen[pl]= BufLen[pl] + 1 if BufLen[pl] > 0 then table.insert(BufInput[pl],1,0) end end SetInput() end if pressrepeater(Delete) then for pl= plmin, plmax do if BufLen[pl] > 0 then table.remove(BufInput[pl],1) end BufLen[pl]= BufLen[pl] - 1 end SetInput() end --Script key handler for pl= plmin, plmax do for i= 1, 8 do if press(key[i]) and ScriptEdit[pl][i] then if ThisInput[pl][btn[i]] then ThisInput[pl][btn[i]]= FalseSwitch[pl][i] else ThisInput[pl][btn[i]]= TrueSwitch[pl][i] end end end end DisplayInput() end --Last bits of odds and ends ApplyInput() collectgarbage("collect") end gui.register(ItIsYourTurn) --***************************************************************************** function Rewinder() --***************************************************************************** -- Contains the saving part of the Rewind engine. -- Original by Antony Lavelle, added by DarkKobold, messed by FatRatKnight if saveMax > 0 then -- Don't process if Rewind is disabled if keys[rewind] and saveCount > 0 then rewinding= true elseif (fc - LastLoad)%SaveBuf == 0 then if saveCount >= saveMax then table.remove(saveArray,1) else saveCount= saveCount+1 end if saveArray[saveCount] == nil then saveArray[saveCount]= savestate.create(); end savestate.save(saveArray[saveCount]); movie.rerecordcounting(false) end end end --emu.registerafter(Rewinder) local TestNumOfDoom= 0 local TestFile= io.open("Backup" .. TestNumOfDoom .. ".txt" , "r") while TestFile do TestFile:close() TestNumOfDoom= TestNumOfDoom+1 if TestNumOfDoom >= BackupFileLimit then break end TestFile= io.open("Backup" .. TestNumOfDoom .. ".txt" , "r") end local FCEUXbtn= {"right", "left", "down", "up", "start", "select", "B", "A"} local FCEUXfrm= {"R","L","D","U","T","S","B","A"} local FCEUXorder= {} for i= 1, 8 do for j= 1, 8 do if btn[i] == FCEUXbtn[j] then FCEUXorder[j]= i break end end end --***************************************************************************** function SaveToFile() --***************************************************************************** -- Creates a file that, effectively, stores whatever's in the script at the -- time. -- List of known glitches: -- It only reads player 1 when deciding when to start or stop. InputSnap() local LatestIndex= 0 local EarliestIndex= math.huge for Frame, Pads in pairs(InputList[1]) do LatestIndex= math.max(LatestIndex,Frame) EarliestIndex= math.min(EarliestIndex,Frame) end local BFile = io.open("Backup" .. TestNumOfDoom .. ".txt" , "w") BFile:write("StartFrame: " .. EarliestIndex .. "\n") for Frame= EarliestIndex, LatestIndex do BFile:write("|0") for pl= 1, 4 do BFile:write("|") if InputList[pl] then if InputList[pl][Frame] then local Pads= InputList[pl][Frame] for i= 1, 8 do if ReadJoynum(Pads,FCEUXorder[i]) then BFile:write(FCEUXfrm[i]) else BFile:write(".") end end else BFile:write(" ") end end end BFile:write("\n") end BFile:close() end emu.registerexit(SaveToFile) emu.pause() while true do Rewinder() emu.frameadvance() end