-- BeeBee, LuaBot Frontend v1.07 -- qFox, 2 August 2008 -- we need iup, so include it here (also takes care of cleaning up dialog when script exits) require 'auxlib'; local botVersion = 1; -- check this version when saving/loading. this will change whenever the botsave-file changes. function createTextareaTab(reftable, tmptable, token, tab, fun, val) -- specific one, at that :) reftable[token] = iup.multiline{title="Contents",expand="YES", border="YES" }; -- ,value=val}; tmptable[token] = iup.vbox{iup.label{title="function "..fun.."()\n local result = no;"},reftable[token],iup.label{title=" return result;\nend;"}}; tmptable[token].tabtitle = tab; end; function createTextareaTab2(reftable, tmptable, token, fun, arg) -- specific one, at that :) this one generates no return values reftable[token] = iup.multiline{title="Contents",expand="YES", border="YES" }; --,value=fun}; if (arg) then tmptable[token] = iup.vbox{iup.label{title="function "..fun.."(wasOk) -- wasOk (boolean) is true when the attempt was ok\n"},reftable[token],iup.label{title="end;"}}; else tmptable[token] = iup.vbox{iup.label{title="function "..fun.."()\n"},reftable[token],iup.label{title="end;"}}; end; tmptable[token].tabtitle = fun; end; function createGUI(n) -- this table will keep the references for easy and fast lookup. local reftable = {}; -- this table we wont keep, it holds references to (mostly) cosmetic elements of the dialog local tmptable = {}; -- ok, dont be intimidated by the next eight blocks of code. they basically all say the same! -- every line creates an element for the gui and sets it up. every block is a tabbed pane and -- they are all put into another tabbed pane themselves. all references are put into a table -- paired with the tokens in the basicbot framework. this allows us to easily walk through -- all the pairs of and replace them in the file, uppon writing. reftable.ROMNAME = iup.text{title="rom name", size="300x", value="something_or_the_other.rom"}; tmptable.ROMNAME = iup.hbox{iup.label{title="Rom name: ", size="50x"}, reftable.ROMNAME, iup.fill{}}; reftable.COMMENT = iup.text{title="comment", size="300x",value="a botscript for some game"}; tmptable.COMMENT = iup.hbox{iup.label{title="Comment: ", size="50x"}, reftable.COMMENT, iup.fill{}}; reftable.VERSION = iup.text{title="version", size="70x",value="1.00"}; tmptable.VERSION = iup.hbox{iup.label{title="Version: ", size="50x"}, reftable.VERSION, iup.fill{}}; tmptable.SAVE = iup.button{title="Save contents"}; -- the callback is set after the dialog is created. we need the references to all controls for saving to work :) tmptable.LOAD = iup.button{title="Load contents"}; tmptable.WRITE = iup.button{title="Write bot script"}; general = iup.vbox{tmptable.ROMNAME,tmptable.COMMENT,tmptable.VERSION,iup.fill{size="5x",},tmptable.SAVE,iup.fill{size="5x",},tmptable.LOAD,iup.fill{size="5x",},tmptable.WRITE,iup.fill{}}; general.tabtitle = "General"; createTextareaTab(reftable, tmptable, "bA1", "A", "isPressedA1", "a1"); createTextareaTab(reftable, tmptable, "bB1", "B", "isPressedB1", "b1"); createTextareaTab(reftable, tmptable, "START1", "Start", "isPressedStart1", "start1"); createTextareaTab(reftable, tmptable, "SELECT1","Select", "isPressedSelect1", "select1"); createTextareaTab(reftable, tmptable, "UP1", "Up", "isPressedUp1", "up1"); createTextareaTab(reftable, tmptable, "DOWN1", "Down", "isPressedDown1", "down1"); createTextareaTab(reftable, tmptable, "LEFT1", "Left", "isPressedLeft1", "left1"); createTextareaTab(reftable, tmptable, "RIGHT1", "Right", "isPressedRight1", "right1"); tabs1 = iup.vbox{iup.tabs{tmptable.bA1,tmptable.bB1,tmptable.START1,tmptable.SELECT1,tmptable.UP1,tmptable.DOWN1,tmptable.LEFT1,tmptable.RIGHT1}}; tabs1.tabtitle = "Player 1"; createTextareaTab(reftable, tmptable, "bA2", "A", "isPressedA2", "a2"); createTextareaTab(reftable, tmptable, "bB2", "B", "isPressedB2", "b2"); createTextareaTab(reftable, tmptable, "START2", "Start", "isPressedStart2", "start2"); createTextareaTab(reftable, tmptable, "SELECT2","Select", "isPressedSelect2", "select2"); createTextareaTab(reftable, tmptable, "UP2", "Up", "isPressedUp2", "up2"); createTextareaTab(reftable, tmptable, "DOWN2", "Down", "isPressedDown2", "down2"); createTextareaTab(reftable, tmptable, "LEFT2", "Left", "isPressedLeft2", "left2"); createTextareaTab(reftable, tmptable, "RIGHT2", "Right", "isPressedRight2", "right2"); tabs2 = iup.vbox{iup.tabs{tmptable.bA2,tmptable.bB2,tmptable.START2,tmptable.SELECT2,tmptable.UP2,tmptable.DOWN2,tmptable.LEFT2,tmptable.RIGHT2}}; tabs2.tabtitle = "Player 2"; createTextareaTab(reftable, tmptable, "bA3", "A", "isPressedA3", "a3"); createTextareaTab(reftable, tmptable, "bB3", "B", "isPressedB3", "b3"); createTextareaTab(reftable, tmptable, "START3", "Start", "isPressedStart3", "start3"); createTextareaTab(reftable, tmptable, "SELECT3","Select", "isPressedSelect3", "select3"); createTextareaTab(reftable, tmptable, "UP3", "Up", "isPressedUp3", "up3"); createTextareaTab(reftable, tmptable, "DOWN3", "Down", "isPressedDown3", "down3"); createTextareaTab(reftable, tmptable, "LEFT3", "Left", "isPressedLeft3", "left3"); createTextareaTab(reftable, tmptable, "RIGHT3", "Right", "isPressedRight3", "right3"); tabs3 = iup.vbox{iup.tabs{tmptable.bA3,tmptable.bB3,tmptable.START3,tmptable.SELECT3,tmptable.UP3,tmptable.DOWN3,tmptable.LEFT3,tmptable.RIGHT3}}; tabs3.tabtitle = "Player 3"; createTextareaTab(reftable, tmptable, "bA4", "A", "isPressedA4", "a4"); createTextareaTab(reftable, tmptable, "bB4", "B", "isPressedB4", "b4"); createTextareaTab(reftable, tmptable, "START4", "Start", "isPressedStart4", "start4"); createTextareaTab(reftable, tmptable, "SELECT4","Select", "isPressedSelect4", "select4"); createTextareaTab(reftable, tmptable, "UP4", "Up", "isPressedUp4", "up4"); createTextareaTab(reftable, tmptable, "DOWN4", "Down", "isPressedDown4", "down4"); createTextareaTab(reftable, tmptable, "LEFT4", "Left", "isPressedLeft4", "left4"); createTextareaTab(reftable, tmptable, "RIGHT4", "Right", "isPressedRight4", "right4"); tabs4 = iup.vbox{iup.tabs{tmptable.bA4,tmptable.bB4,tmptable.START4,tmptable.SELECT4,tmptable.UP4,tmptable.DOWN4,tmptable.LEFT4,tmptable.RIGHT4}}; tabs4.tabtitle = "Player 4"; createTextareaTab2(reftable, tmptable, "ONSTART", "onStart", false); createTextareaTab2(reftable, tmptable, "ONFINISH", "onFinish", false); createTextareaTab2(reftable, tmptable, "ONSEGMENTSTART","onSegmentStart", false); createTextareaTab2(reftable, tmptable, "ONSEGMENTEND", "onSegmentEnd", false); createTextareaTab2(reftable, tmptable, "ONATTEMPTSTART","onAttemptStart", false); createTextareaTab2(reftable, tmptable, "ONATTEMPTEND", "onAttemptEnd", true); createTextareaTab2(reftable, tmptable, "ONINPUTSTART", "onInputStart", false); createTextareaTab2(reftable, tmptable, "ONINPUTEND", "onInputEnd", false); tabs5 = iup.vbox{iup.tabs{tmptable.ONSTART, tmptable.ONFINISH, tmptable.ONSEGMENTSTART, tmptable.ONSEGMENTEND, tmptable.ONATTEMPTSTART, tmptable.ONATTEMPTEND, tmptable.ONINPUTSTART, tmptable.ONINPUTEND}}; tabs5.tabtitle = "Events"; createTextareaTab(reftable, tmptable, "SCORE", "score", "getScore", "score"); createTextareaTab(reftable, tmptable, "TIE1", "tie1", "getTie1", "tie1"); createTextareaTab(reftable, tmptable, "TIE2", "tie2", "getTie2", "tie2"); createTextareaTab(reftable, tmptable, "TIE3", "tie3", "getTie3", "tie3"); createTextareaTab(reftable, tmptable, "TIE4", "tie4", "getTie4", "tie4"); tabs6 = iup.vbox{iup.tabs{tmptable.SCORE,tmptable.TIE1,tmptable.TIE2,tmptable.TIE3,tmptable.TIE4}}; tabs6.tabtitle = "Score"; createTextareaTab(reftable, tmptable, "ISRUNEND", "isRunEnd", "isRunEnd", "isRunEnd"); createTextareaTab(reftable, tmptable, "MUSTROLLBACK", "mustRollBack", "mustRollBack", "mustRollBack"); createTextareaTab(reftable, tmptable, "ISSEGMENTEND", "isSegmentEnd", "isSegmentEnd", "isSegmentEnd"); createTextareaTab(reftable, tmptable, "ISATTEMPTEND", "isAttemptEnd", "isAttemptEnd", "isAttemptEnd"); createTextareaTab(reftable, tmptable, "ISATTEMPTOK", "isAttemptOk", "isAttemptOk", "isAttemptOk"); tabs7 = iup.vbox{iup.tabs{tmptable.ISRUNEND,tmptable.MUSTROLLBACK,tmptable.ISSEGMENTEND,tmptable.ISATTEMPTEND,tmptable.ISATTEMPTOK}}; tabs7.tabtitle = "Selection"; playertabs = iup.tabs{general,tabs1,tabs2,tabs3,tabs4,tabs5,tabs6,tabs7,title}; handles[n] = iup.dialog{playertabs, title="BeeBee; BasicBot Frontend", size="450x200"} handles[n]:showxy(iup.CENTER, iup.CENTER) -- now set the callback function for the save button. this will use all the references above. -- these remain ok in the anonymous function by something called "closures". this means that -- these variables, although local to the scope of the function, will remain their value in -- the anonymous function. hence we can refer to them and fetch their contents, even though -- you cant refer to them outside the context of the createGUI function. tmptable.WRITE.action = function(self, n) local file = iup.filedlg{allownew="YES",dialogtype="SAVE",directory="./lua",showhidden="YES",title="Save botfile"}; file:popup(iup.ANYWHERE,iup.ANYWHERE); if (file.value == "NULL") then iup.Message("An error occurred trying to save your settings"); return; elseif (file.status == "-1") then iup.Message("IupFileDlg","Operation canceled"); return; end -- ok, file selected, if an error occurred or user canceled, the function already returned, so lets write the bot! -- get the framework first. we need it to find the relevant tokens local fh = assert(io.open("luabot_framework.lua","r")); local framework = fh:read("*a"); fh:close(); -- now replace all tokens by gui values -- this is where the reftable comes in very handy :p for token,obj in pairs(reftable) do local st00pid = (reftable[token].value or ""); framework = string.gsub(framework, "-- "..token, st00pid, 1); -- if nothing was entered, obj.value returns nil (not ""), so we have to make that translation end; -- open the file, if old file, clear it if (file.status == "1") then fh = assert(io.open(file.value,"wb")); else -- (file.status == "0") fh = assert(io.open(file.value,"w+b")); -- clear file contents end; -- write it fh:write(framework); -- close it (automatically flushed) fh:close(); fh = nil; iup.Message ("Success", "Bot written to "..file.value.."!"); end; tmptable.SAVE.action = function(self, n) local file = iup.filedlg{allownew="YES",dialogtype="SAVE",directory="./lua",showhidden="YES",title="Save botfile",extfilter="BasicBot (*.bot)|*.bot|All files (*.*)|*.*|"}; file:popup(iup.ANYWHERE,iup.ANYWHERE); if (file.status == 1) then -- cancel return; end; -- open the file, if old file, clear it if (file.status == "1") then fh = assert(io.open(file.value,"wb")); else -- (file.status == "0") fh = assert(io.open(file.value,"w+b")); -- clear file contents end; -- allow us to detect the botfile version (warn the user if it's different?) fh:write(botVersion.."\n"); -- now replace all tokens by gui values -- this is where the reftable comes in very handy :p for token,obj in pairs(reftable) do print("------"); print(token.." control -> "..tostring(obj)); print(".value: "..tostring(obj.value)); local st00pid = obj.value; if (not st00pid) then st00pid = ""; end; print(string.len(st00pid)); fh:write(string.len(st00pid).."\n"); if (string.len(st00pid) > 0) then fh:write(st00pid); end; fh:write("\n"); end; fh:close(); iup.Message ("Success", "Settings saved!"); end; tmptable.LOAD.action = function (self, n) -- this function currently crashes fceux without notification -- possibly because offsets are badly calculated, but serves as an example now local file = iup.filedlg{allownew="NO",dialogtype="OPEN",directory="./lua",showhidden="YES",title="Save botfile",extfilter="BasicBot (*.bot)|*.bot|All files (*.*)|*.*|"}; file:popup(iup.ANYWHERE,iup.ANYWHERE); if (file.status == 1) then -- cancel iup.Message ("Success", "Canceled by you..."); return; end; local nellen = string.len("\n"); -- platform independent fh = assert(io.open(file.value,"r")); fh:read("*n"); -- version fh:read("*l"); -- return local len; local data; for token,crap in pairs(reftable) do len = fh:read("*n"); -- read line (length) if (not len) then iup.Message ("Warning", "End of file reached too soon!"); break; end; -- no more data... (should we erase the rest?) fh:read("*l"); -- return data = fh:read(len); if (not data) then iup.Message ("Warning", "End of file reached too soon!"); break; end; -- no more data... (should we erase the rest?) reftable[token].value = data; fh:read("*l"); -- return end; iup.Message ("Success", "Settings loaded!"); end; end; dialogs = dialogs + 1; createGUI(dialogs); while (true) do FCEU.frameadvance(); end;