Post subject: You mean random subliminal messages like "pinball rocks"?
Moderator, Senior Ambassador, Experienced player (898)
Joined: 9/14/2008
Posts: 1007
FatRatKnight wrote:
Well, the backup feature is added in....
Awesome! I've spent several hours with it and I honestly believe that this is the essence of the meaning "tool assisted" - IMHO, tools like this which allow moving forward and backward in the stream of input with such ease will become the de facto standard method of creating a TAS. There is a small learning curve (smaller than vi at the very least :) but the speed of editing once the basic controls are mastered is worth the effort. As far as the backup feature is concerned I extensively relied on it when going back and fourth through the same section. I must confess that I never used any of the features beyond the coloring of the original buttons pressed on each frame but being able to restore from the backup may be a very handy feature. I produced an Otocky WIP using the backup feature if anyone is interested.[/url] I still plan on writing a getting started tutorial as a whole lot can be done with multitrack2 without knowing how to use all of its advanced features or even knowing that they exist. It'll probably take me a week or two to get to it but I'll try to get something out in the near future. Thanks again for everything, A.C. ******
I was laid off in May 2023 and could use support via Patreon or onetime donations as I work on TASBot Re: and TASBot HD. I'm dwangoAC, part of the senior staff of TASVideos as the Senior Ambassador and BDFL of the TASBot community; I post TAS content on YouTube.com/dwangoAC based on livestreams from Twitch.tv/dwangoAC.
Post subject: Exactly, my friend! With these, we can take over the world!
Editor, Skilled player (1172)
Joined: 9/27/2008
Posts: 1085
Great to hear you're doing well with it. And even if the only use for the backup is the colors, letting you know what changed, you're still using an addition you requested. And it's certainly a good request, alright. It helps to have a different perspective on how to use this script. I myself (should) know every detail with this script and how it works (I created the darn thing!), which causes me to miss out on all the problems that an unfamiliar person would crash into. The path I traveled is certainly different from yours, and I am interested to see your view. You can also suggest how I should rearrange the options, if you think there is a way I can make it a bit more friendly, if you like. I recall my attempt on the Snes9x port. Thinking over it, I believe it wouldn't be too hard to implement a useful backup system even without the script running while emulation is paused.
Post subject: adelikat, I'm posting here instead. Hope it works out better
Editor, Skilled player (1172)
Joined: 9/27/2008
Posts: 1085
Considering how horribly awful my connection is, I'm going to post what I can think of here. IRC is very hard to deal with when you've got frequent disconnects. Distressingly frequent. adelikat, if you look here, I hope to answer a few questions. You were looking for a way to overwrite the player input. Make the script not inject it at all, correct? Hold "end", click the 'all' button in the third column, turning off what should be marked as "list" in the pop-up text. Now the script will avoid replaying that input. Even if you didn't do that, the script will react to your input anyway. Holding down your joypad buttons will unpress these keys, and if it were keys you didn't touch the first time, they will now be pressed without affecting the other buttons on the joypad. The way I set the script up is to improve precise control, as a sort of an editing script. The script-specific keys, activated by the fourth column, don't do so well when held. It's a one-tap joypad button switch that you don't hold, and on frame advance, it will push that joypad button for you. No good if you just want to hold down the key over many frames. There is no convenient option or button in there that automatically sets things in the overwrite mode you want Going into the options and ticking the "list" options back and forth between players is the closest this script has. My philosophy at the time was the fact I wanted all future input maintained, and one would rather make tight adjustments. Regardless, this is giving me ideas. When I finish my multitrack script for DeSmuME, I'll see about getting v3 to work on FCEUX and improve on things from there. I'm making this post public. For the reason that I want to show awareness of some things I can (and possibly should) do to this script. Plus my lack of competent instructions. Some things I never even think of becomes quite an issue to someone else.
Editor, Skilled player (1172)
Joined: 9/27/2008
Posts: 1085
Language: lua

--Major switch players on keypress if press("A") then if plmin ~= plmax then plmax= 1 elseif plmin == players then plmin= 1 else plmin= plmin+1 plmax= plmax+1 end for i= 1, players do for j= 1, #btn do TrueSwitch[i][j]= true FalseSwitch[i][j]= false ReadList[i][j]= true ScriptEdit[i][j]= false end end if plmin == plmax then local i= plmin for j= 1, #btn do TrueSwitch[i][j]= "inv" FalseSwitch[i][j]= nil ReadList[i][j]= false ScriptEdit[i][j]= false end end SetInput() end
EDIT: Oh, I think I messed up somewhere in the code. Fixed. Copy/paste this somewhere into function ItIsYourTurn(). Change the "A" to whatever key you want. adelikat was wondering about some way to conveniently switch players and having the script ignore the input list it records for the newly selected player. Here you go, adelikat, a quick patch for you. I'm not in a particularly good mood at this moment. I'm pretty sure we all intended goodness, but that still doesn't prevent me from feeling like I failed earlier. Do not discuss my mood here, please.
Editor, Skilled player (1172)
Joined: 9/27/2008
Posts: 1085
Just making an attempt at simplifying the code. I've actually stripped out a lot of its intelligence (by rewriting the code from scratch, again) and making it as simple as I know how. Use the mouse to move the display (drag the display around). Switch players with the Home key. Insert a frame with Insert key. Delete a frame with Delete key. Toggle between "ignore stored input" and "mix user/stored input" modes with End key. Show more or less frames with Numpad keys, specifically 1 3 7 or 9. The script is compelled to block user input on undisplayed players. The script always allows user input on the currently displayed player, but may XOR it with the stored input or leave it untouched. The script shows a basic display of user input. The script leaves the emulator's controls alone for individual players -- That is, the player 1 controls only affects player 1, player 2 controls affects player 2, and so forth. The reason being the emulator's implementation as of 2.1.4a actually locks me out from doing the proper modifications. Specifically, "Boundary" lets me modify the joypad immediately, but "Before" lets me read the joypad immediately. Unfortunately, the order does not allow the immediate read prior to the immediate write, ergo, it is impossible to update player 2 controls using player 1 controls, without it being one frame late. Download MtEdit.lua
Language: lua

local PLAYERS = 2 local btn = {"right","left","down","up","start","select","B","A"} --Joypad --############################################################################# -- Keyboard Control mapping local PlayerSwitch= "home" local solid= "pageup" -- Make the display less local clear= "pagedown" -- or more transparant. local mp= "numpad7" --More past. Tweak display to see further back! local lp= "numpad1" --Less past. You'll see less of the input stream. local mf= "numpad3" --More future. local lf= "numpad9" --Less future. local AutoList= true local Insert= "insert" local Delete= "delete" local AutoSwitch= "end" --############################################################################# -- Default values local Opq= 1 local DispX, DispY= 50, 80 --############################################################################# local PlSel= 1 local ThisJoypad= {} local JoypadList= {} local UserControl= {} local ListControl= {} for pl= 1, PLAYERS do ThisJoypad[pl]= {} JoypadList[pl]= {} UserControl[pl]= "inv" ListControl[pl]= true end local fc , Lastfc= 0 , 0 --***************************************************************************** local function UpdateFC() Lastfc= fc; fc= movie.framecount() end --***************************************************************************** --***************************************************************************** local function Within(V,l,h) return (V >= l) and (V <= h) end local function Limits(V,l,h) return math.max(math.min(V,h),l) end local function NullFN() return end --***************************************************************************** --############################################################################# --############################################################################# --Joypad --***************************************************************************** local function JoyToNum(Joys) -- Expects a table containing joypad buttons --***************************************************************************** -- Returns a number 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(1,i)) end end return joynum end --***************************************************************************** local function ReadJoynum(Jn, button) -- Expects... Certain numbers! --***************************************************************************** -- Returns true or false. True if the indicated button is pressed -- according to the input. False otherwise. return ( bit.band(Jn , bit.lshift( 1, button )) ~= 0 ) end --***************************************************************************** local function LoadJoypad(pl) --***************************************************************************** -- Sets up the joypad to inject into the emulation. local joys= JoypadList[pl][fc] if ListControl[pl] then for b= 1, #btn do if joys and ReadJoynum(joys, b) then ThisJoypad[pl][btn[b]]= (UserControl[pl] or true) else ThisJoypad[pl][btn[b]]= (UserControl[pl] and nil) end end else for b= 1, #btn do ThisJoypad[pl][btn[b]]= (UserControl[pl] and nil) end end --UserControl is "inv" or false. I abuse the shortcutting of or & and. end --***************************************************************************** local function RegBoundaryHandleJoypad() --***************************************************************************** for pl= 1, PLAYERS do joypad.set(pl, ThisJoypad[pl]) end end --***************************************************************************** local function RegAfterHandleJoypad() --***************************************************************************** for pl= 1, PLAYERS do JoypadList[pl][Lastfc]= JoyToNum(joypad.get(pl)) LoadJoypad(pl) end end --############################################################################# --############################################################################# --Display (Joypad) --***************************************************************************** local Draw= {} --***************************************************************************** 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.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.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.A(x,y,color) -- ### gui.line(x ,y ,x ,y+2,color) -- ### gui.line(x+1,y ,x+1,y+1,color) -- # # gui.line(x+2,y ,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.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) -- ### gui.line(x ,y ,x+2,y ,color) -- # # gui.line(x ,y+2,x+2,y+2,color) -- ### gui.pixel(x ,y+1,color) gui.pixel(x+2,y+1,color) end Draw[0]= function(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 Draw[1]= function(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 -- ### Draw[2]= function(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 Draw[3]= function(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 Draw[4]= function(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 -- # Draw[5]= function(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 Draw[6]= function(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 -- ### Draw[7]= function(left, top, color) -- # gui.line(left ,top ,left+1,top ,color)-- ## gui.line(left+2,top ,left+1,top+4,color)-- # end -- # Draw[8]= function(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 Draw[9]= function(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 --***************************************************************************** local function GetColor(Jn,b) --***************************************************************************** if not Jn then return "white" end --Does not exist if not ReadJoynum(Jn,b) then return "red" end --Not pressed return "green" --Button pressed end --***************************************************************************** local function PaintFrame(x,y,Jn) --***************************************************************************** for b= 1, #btn do Draw[btn[b]](x+4*b,y,GetColor(Jn,b)) end end --***************************************************************************** local function PaintBorder(x,y) --***************************************************************************** local color= "green" if not AutoList then color= -0x003FFF01 end gui.line(x, y,x, y+4,color) gui.line(x+2+4*#btn,y,x+2+4*#btn,y+4,color) gui.pixel(x+1 ,y ,color) gui.pixel(x+1 ,y+4,color) gui.pixel(x+1+4*#btn,y ,color) gui.pixel(x+1+4*#btn,y+4,color) if PLAYERS > 1 then Draw[PlSel](x-4,y,color) end end local Past, Future= -5, 5 --***************************************************************************** local function PaintJoypadList(x,y) --***************************************************************************** gui.box(x+3,y+4*Past-1,x+4*#btn+3,y+4*Future+3,0x00000080) for i= Past, Future do PaintFrame(x,y+4*i, JoypadList[PlSel][fc+i]) end if Past <= 0 and Future >= 0 then PaintBorder(x+2,y-1) end end --############################################################################# --############################################################################# --User local lastkeys, keys= input.get(), input.get() --***************************************************************************** local function UpdateKeys() lastkeys= keys; keys= input.get() end local function Press(k) return keys[k] and (not lastkeys[k]) end --***************************************************************************** local KF= {} --***************************************************************************** local function KeyReader() --***************************************************************************** for k,v in pairs(keys) do if (not lastkeys[k]) and KF[k] then KF[k]() end end end local HandleMouse --***************************************************************************** local function HandleMouse_Main() --***************************************************************************** if keys.leftclick and lastkeys.leftclick then DispX= DispX + keys.xmouse - lastkeys.xmouse DispY= DispY + keys.ymouse - lastkeys.ymouse end end --***************************************************************************** local function HandleMouse_Option() --***************************************************************************** end HandleMouse= HandleMouse_Main local MaxRange= 54 ------------------------------------------------------------------------------- KF[solid]= function() Opq= math.min(Opq+0.125 , 1); gui.opacity(Opq) end KF[clear]= function() Opq= math.max(Opq-0.125 , 0); gui.opacity(Opq) end ------------------------------------------------------------------------------- KF[mp]=function() Past = Past -1; Future= math.min(Future,Past+MaxRange) end KF[lp]=function() Past = Past +1; Future= math.max(Future,Past) end KF[mf]=function() Future= Future+1; Past= math.max(Past,Future-MaxRange) end KF[lf]=function() Future= Future-1; Past= math.min(Past,Future) end local PlSwitch --***************************************************************************** local function PlSw() --***************************************************************************** for pl= 1, PLAYERS do UserControl[pl]= false ListControl[pl]= true end UserControl[PlSel]= "inv" ListControl[PlSel]= AutoList for pl= 1, PLAYERS do LoadJoypad(pl) end end PlSwitch= PlSw PlSw() ------------------------------------------------------------------------------- KF[PlayerSwitch]= function() ------------------------------------------------------------------------------- PlSel= (PlSel%PLAYERS)+1 PlSwitch() end ------------------------------------------------------------------------------- KF[Insert]= function() ------------------------------------------------------------------------------- local frame= fc while JoypadList[PlSel][frame] do frame=frame+1 end for i= frame, (fc+1), -1 do JoypadList[PlSel][i]= JoypadList[PlSel][i-1] end JoypadList[PlSel][fc]= 0 LoadJoypad(PlSel) end ------------------------------------------------------------------------------- KF[Delete]= function() ------------------------------------------------------------------------------- local i= fc while JoypadList[PlSel][i] do JoypadList[PlSel][i]= JoypadList[PlSel][i+1] i=i+1 end LoadJoypad(PlSel) end ------------------------------------------------------------------------------- KF[AutoSwitch]= function() ------------------------------------------------------------------------------- AutoList= not AutoList ListControl[PlSel]= AutoList end --############################################################################# --############################################################################# --Registry --***************************************************************************** local function RegisterAfter() --***************************************************************************** UpdateFC() RegAfterHandleJoypad() end emu.registerafter(RegisterAfter) --***************************************************************************** local function RegisterGui() --***************************************************************************** UpdateKeys() KeyReader() HandleMouse() DispX= Limits(DispX,-2,251-#btn*4) DispY= Limits(DispY,1+-4*Past,220-4*Future) PaintJoypadList(DispX, DispY) gui.pixel(0,0,0) end gui.register(RegisterGui) --***************************************************************************** local function RegisterLoad() --***************************************************************************** UpdateFC() for pl= 1, PLAYERS do LoadJoypad(pl) end end savestate.registerload(RegisterLoad) --***************************************************************************** while true do --Register boundary --***************************************************************************** RegBoundaryHandleJoypad() emu.frameadvance() end
Editor, Skilled player (1172)
Joined: 9/27/2008
Posts: 1085
There's a number of features I can think of that may enhance the script in some way. While MtEdit is a rewrite that's supposed to make things much simpler, it's also much less filled with features. I will attempt to identify each individual feature in their own packages. Store and replay input . Implemented In both the older Mutlitrack2 and MtEdit, this feature is the heart and soul of this script. Without it, the task that this script does will be nothing alike what it does now. Currently, Multitrack2 allows absolute control over individual buttons, allowing one to allow or restrict control on an individual button basis. This is rather complicated, and I feel is the sole reason why it's not popular. I also realize it give an impractical level of control. MtEdit does not provide that silly control. Instead, the control is restricted to only the currently displayed player, and the only option to tweak the control is either mix the user's input with what's currently displayed (XOR logic), or do not apply stored input to displayed player, passing through the user's input. Both Multitrack2 and MtEdit can read from a movie. Play a movie file, begin one of these scripts whenever you feel like, and let the movie play out. The movie on read-only mode will inject the movie's input to the emulator, which in turn will be detected by the script, and thus stored in the script as well. Unfortunately, this script has no way to handle resets. I don't know how to detect and trigger a reset from the lua side, and if there are functions provided for those, I would certainly love to know. Insert and delete frame . Implemented Multitrack2 does some complicated way of handling inserts and deletes, which goes pretty fast even in an arbitrarily long input list. MtEdit goes a much simpler route, which might take a while if the input list is long enough. Inserts and deletes simply remove or add frames on the spot, shoving future input aside to make room or close the gap. Great if you found some frame savings somewhere and don't want to redo the future stuff by hand. This feature practically requires "run while paused" code. That is, the emulator must be able to run lua while the emulation is paused. Without it, inserting and deleting frames would be rather unwieldy at best. Paused input handling Multitrack2 has it. MtEdit does not. This absolutely requires "run while paused" code, as there's no viable workaround without it. It also really wants joypad.peek, but a less useful alternative being input.get can work, but script-specific keys can be kind of wonky. Generally, it allows one to simply tap a button to toggle the joypad button. Particularly good as a form of "sticky keys", especially when precision control is desired and there's no viable way to hold that many keys on your keyboard down. Though, with the scripts as they are, one can run part of the input through, load state, then run the other part of the desired input. Single joypad control Allow the same joypad controls of one player to affect all players. Great if you're switching through players and would prefer not to switch to the player 2 controls. Use the player 1 controls for all four players! In FCEUX, this is almost not possible. Mainly because you can't do an immediate read then write with modifications on the same frame. I can override all joypad controls and force the user to use script-specific keys as a workaround, but that is sort of messy to implement, not to mention the user has to configure the script controls instead of the emulator controls. Backup input list Multitrack2 has this implemented. MtEdit, at the time of this writing, does not. Basically, have a second track of input. This track mirrors the primary list for any frames that aren't previously recorded. Pretty senseless if you're adding new frames, but useful if there's some part you want to go back and edit, as it'll show what the input was like. As a backup input, one should be able to write to it or have the script play it back. Input display . Implemented Another fairly significant part of the script is the fact I display a region of input. It does no good to store and replay input when there's no way to see what the script is going to do. Well, unless the script never messes with the current player and plays back other players. Multitrack2 borders the current frame completely, while MtEdit simply brackets the current frame. Mainly because it simplifies the code to not have to have a 2 pixel offset for previous and following frames around the current frame. But it might be best to just use the offset and get the full borders again. Adjusting this display should come standard. There are four buttons to show more or less of the input region, and the mouse can move the display around. MtEdit does not have a keyboard control to move the display, but it's not hard to add. Variable opacity . Implemented It's simple enough -- Adjust the opacity of the display. Sometimes, you want to see the action behind the display, and there isn't any particular location you can stick it that doesn't block something important. Requires the gui.opacity function. FCEUX's color limitation makes this feature less effective, sadly. Watermarks A watermark is defined to be, in this case, a darker shade of color used once every few frames in the display. Even if there's nothing in particular to show at the moment, the different shade of color will provide some sense of motion through frames. FCEUX's color limitation makes it rather tricky to pull off, however. Unsorted features Various features that I can't really place under control or display. All player option Multitrack2 has it, MtEdit does not. Display all players instead of only one. I haven't figured out a good intuitive way of displaying all players' inputs, but Multitrack2 does use one idea. This also affects some controls, as now all players are displayed. Rewind Multitrack2 has it, MtEdit does not. The script could store a variety of savestates for use with rewinding back through some of it. As this script is practically an input editor, a rewind function fits nicely. Savestates are expensive on processing, and emulation of simpler systems (like the NES, which FCEUX emulates) makes the cost more manageable. I've also found it tricky to implement the timing well, especially during unpaused rewinds. If there are any other features I can think of, I'll update this post. I may want to use most or all of these, but I want to make sure I write the code in a clean way as I do it.
Site Admin, Skilled player (1236)
Joined: 4/17/2010
Posts: 11268
Location: RU
MtEdit link looks wrong.
Warning: When making decisions, I try to collect as much data as possible before actually deciding. I try to abstract away and see the principles behind real world events and people's opinions. I try to generalize them and turn into something clear and reusable. I hate depending on unpredictable and having to make lottery guesses. Any problem can be solved by systems thinking and acting.
Editor, Skilled player (1172)
Joined: 9/27/2008
Posts: 1085
feos wrote:
MtEdit link looks wrong.
Fixed. (I hope)