diff --git a/documentation/lua/basicbot_framework.lua b/documentation/lua/basicbot_framework.lua new file mode 100644 index 00000000..d4559b98 --- /dev/null +++ b/documentation/lua/basicbot_framework.lua @@ -0,0 +1,581 @@ +-- LuaBot, concept bot for FCEU and ZSnes and any other emu that has compatible Lua engine +-- qFox, 30 July 2008 +-- version 1.04 + +-- Botscript data following +-- Rom: ROMNAME +-- Comment: COMMENT +-- Version: VERSION + +local function realcount(t) -- accurately count the number of elements of a table, even if they contain nil values. not fast, but accurate. + local n = 0; + for i,_ in pairs(t) do + n = n + 1; + end; + return n; +end; + +local maxvalue = 100; -- always "true", yes, pressed +local minvalue = 0; -- always "false", no, released + +-- the bot kind of works like this function. for every option, a value is requested from the programmer. +-- if that value is higher then a random value 0-99, this function returns true, else false. +-- the idea is that keys are pressed based on a heuristic value. if you want absolute values, simply +-- return values like minvalue or maxvalue, or their syntactic equals yes/no and press/release (to make +-- the sourcecode easier to read). you can use the variable maybe for 50% chance. +-- this function also reduces a lot of calls to random() if the user uses absolute values. +local function rand_if(n) + if (n <= minvalue) then return false; end; + if (n >= maxvalue) then return true; end; + if (n > math.random(minvalue, maxvalue-1)) then return true; end; + return false; +end; + +local loopcounter = 0; -- counts the main loop +local key1 = {}; -- holds the to be pressed keys this frame for player 1 +local key2 = {}; -- holds the to be pressed keys this frame for player 2 +local lastkey1 = {}; -- keys pressed in previous frame for player 1 +local lastkey2 = {}; -- keys pressed in previous frame for player 2 +local frame = 0; -- number of frames (current value is current frame count, incremented at the start of a new frame) +local attempt = 1; -- number of attempts (current value is current attempt, incremented after the end of an attempt) +local segment = 1; -- number of segments (current value is current segment, incremented after the end of a segment) +local okattempts = 0; -- number of successfull attempts (including rollback) +local failattempts = 0; -- number of failed attempts (including rollback) + +local segments = {}; -- table that holds every segment, each segment is another table that consists of the score, ties, savestate (begin of segment), lastkeys, keys pressed, etc. +segments[1] = {}; -- initialize the first segment, we initialize the savestate right after the before code has ran + +-- these dont have to be used, but it makes it easier to control here +local maxframes = 400; +local maxattempts = 200; +local maxsegments = 100; + +local playingbest = false; -- when going to the next segment, we need to play the best segment to record, this indicates when we're doing so +local keyrecording1 = {}; -- every key pressed for player 1 is put in here +local keyrecording2 = {}; -- every key pressed for player 2 is put in here + +-- some constants/macro's/whatever to make source easier to read +local press = maxvalue; +local release = minvalue; +local yes = maxvalue; +local maybe = maxvalue/2; -- 50% +local no = minvalue; + +-- static constants, will be used by the frontend later +local X = 95; +local Y = 30; +local Z = 0; +local P = 0; +local Q = 0; + +local vars = {}; -- variable table. each cell holds a variable. variables are remembered accross segments + +local outputcounter = 0; +local updateevery = 1000; + +-- user defined functions + +local function getScore() -- score of current attempt + local result = no; + -- SCORE + return result; +end; + +local function getTie1() -- tie breaker of current attempt in case score is equal + local result = no; + -- TIE1 + return result; +end; + +local function getTie2() -- second tie breaker + local result = no; + -- TIE2 + return result; +end; + +local function getTie3() -- third tie breaker + local result = no; + -- TIE3 + return result; +end; + +local function getTie4() -- fourth tie breaker + local result = no; + -- TIE4 + return result; +end; + +local function isRunEnd() -- gets called 3x! twice in the main loop (every frame). determines whether the bot should quit. + local result = no; + -- ISRUNEND + return result; +end; + +local function mustRollBack() -- drop back to previous segment? called at the end of a segment + local result = no; + -- MUSTROLLBACK + return result; +end; + +local function isSegmentEnd() -- end of current segment? (usually just x frames or being really stuck (to rollback)) + local result = no; + -- ISSEGMENTEND + return result; +end; + +local function isAttemptOk() -- is current run ok? like, did you die? (then the run is NOT ok... :). return no for no and yes for yes or be left by chance. + local result = yes; + -- ISATTEMPTOK + return result; +end; + +local function isAttemptEnd() -- end of current attempt? (like when you die or reach a goal) + local result = no; + -- ISATTEMPTEND + return result; +end; + +-- the next 2x8 functions determine whether a button should be pressed for player 1 and 2 +-- return yes or no for absolute values, return anything between minvalue and maxvalue +-- to set a chance of pressing that button. + +local function pressKeyA1() + local result = no; + -- bA1 + return result; +end; + +local function pressKeyB1() + local result = no; + -- bB1 + return result; +end; + +local function pressKeyStart1() + local result = no; + -- START1 + return result; +end; + +local function pressKeySelect1() + local result = no; + -- SELECT1 + return result; +end; + +local function pressKeyUp1() + local result = no; + -- UP1 + return result; +end; + +local function pressKeyDown1() + local result = no; + -- DOWN1 + return result; +end; + +local function pressKeyLeft1() + local result = no; + -- LEFT1 + return result; +end; + +local function pressKeyRight1() + local result = no; + -- RIGHT1 + return result; +end; + +local function pressKeyA2() + local result = no; + -- bA2 + return result; +end; + +local function pressKeyB2() + local result = no; + -- bB2 + return result; +end; + +local function pressKeyStart2() + local result = no; + -- START2 + return result; +end; + +local function pressKeySelect2() + local result = no; + -- SELECT2 + return result; +end; + +local function pressKeyUp2() + local result = no; + -- UP2 + return result; +end; + +local function pressKeyDown2() + local result = no; + -- DOWN2 + return result; +end; + +local function pressKeyLeft2() + local result = no; + -- LEFT2 + return result; +end; + +local function pressKeyRight2() + local result = no; + -- RIGHT2 + return result; +end; + +local function pressKeyA3() + local result = no; + -- bA3 + return result; +end; + +local function pressKeyB3() + local result = no; + -- bB3 + return result; +end; + +local function pressKeyStart3() + local result = no; + -- START3 + return result; +end; + +local function pressKeySelect3() + local result = no; + -- SELECT3 + return result; +end; + +local function pressKeyUp3() + local result = no; + -- UP3 + return result; +end; + +local function pressKeyDown3() + local result = no; + -- DOWN3 + return result; +end; + +local function pressKeyLeft3() + local result = no; + -- LEFT3 + return result; +end; + +local function pressKeyRight3() + local result = no; + -- RIGHT3 + return result; +end; + +local function pressKeyA4() + local result = no; + -- bA4 + return result; +end; + +local function pressKeyB4() + local result = no; + -- bB4 + return result; +end; + +local function pressKeyStart4() + local result = no; + -- START4 + return result; +end; + +local function pressKeySelect4() + local result = no; + -- SELECT4 + return result; +end; + +local function pressKeyUp4() + local result = no; + -- UP4 + return result; +end; + +local function pressKeyDown4() + local result = no; + -- DOWN4 + return result; +end; + +local function pressKeyLeft4() + local result = no; + -- LEFT4 + return result; +end; + +local function pressKeyRight4() + local result = no; + -- RIGHT4 + return result; +end; + +-- now follow the "events", one for the start and end of a frame, attempt, segment and whole bot. none of them need to return anything + +local function onStart() -- this code should run before the bot starts, for instance to start the game from power on and get setup the game + -- ONSTART +end; + +local function onFinish() -- code ran after the bot finishes + -- ONFINISH +end; + +local function onSegmentStart() -- code ran after initializing a new segment, before onAttemptStart(). framecount is always one fewer then actual frame! + -- ONSEGMENTSTART +end; + +local function onSegmentEnd() -- code ran after a segment finishes, before cleanup of segment vars + -- ONSEGMENTEND +end; + +local function onAttemptStart() -- code ran after initalizing a new attempt, before onInputStart(). not ran when playing back. framecount is always one fewer then actual frame! + -- ONATTEMPTSTART +end; + +local function onAttemptEnd(wasOk) -- code ran after an attempt ends before cleanup code, argument is boolean true when attempt was ok, boolean false otherwise. not ran when playing back + -- ONATTEMPTEND +end; + +local function onInputStart() -- code ran prior to getting input (keys are empty). not ran when playing back + -- ONINPUTSTART +end; + +local function onInputEnd() -- code ran after getting input (lastkey are still valid) (last function before frame ends, you can still manipulate the input here!). not ran when playing back + -- ONINPUTEND +end; + +-- the bot starts here.. + +onStart(); -- run this code first + +segments[segment].savestate = savestate.create(); -- create anonymous savestate obj for start of first segment +savestate.save(segments[segment].savestate); -- save current state to it, it will be reloaded at the start of each frame +local startkey1 = key1; -- save the last key pressed in the onStart. serves as an anchor for the first segment +local startkey2 = key2; +local startvars = vars; -- save the vars array (it might have been used by the onStart) +lastkey1 = key1; -- to enter the loop... +lastkey2 = key2; +--FCEU.speedmode("maximum"); -- uncomment this line to make the bot run faster ("normal","turbo","maximum") + +onSegmentStart(); +onAttemptStart(); + +collectgarbage(); -- just in case... + +-- This will loops for each frame, at the end of the while +-- the frameadvance is called, causing it to advance +while (rand_if(isRunEnd())) do + loopcounter = loopcounter + 1; -- count the number of botloops + --gui.text(200,10,loopcounter); -- print it on the right side if you want to see the number of total frames + + if (not playingbest and rand_if(isAttemptEnd())) then -- load save state, continue with next attempt (disabled when playing back best) + -- record this attempt as the last attempt + if (not segments[segment].prev) then segments[segment].prev = {}; end; + segments[segment].prev.frames = frame; + segments[segment].prev.attempt = attempt; + segments[segment].prev.score = getScore(); + segments[segment].prev.tie1 = getTie1(); + segments[segment].prev.tie2 = getTie2(); + segments[segment].prev.tie3 = getTie3(); + segments[segment].prev.tie4 = getTie4(); + segments[segment].prev.ok = rand_if(isAttemptOk()); -- this is the check whether this attempt was valid or not. if not, it cannot become the best attempt. + -- update ok/failed attempt counters + if (segments[segment].prev.ok) then + okattempts = okattempts + 1; + onAttemptEnd(true); + else + failattempts = failattempts + 1; + onAttemptEnd(false); + end; + + -- if this attempt was better then the previous one, replace it + -- its a long IF, but all it checks (lazy eval) is whether the current + -- score is better then the previous one, or if its equal and the tie1 + -- is better then the previous tie1 or if the tie1 is equal to the prev + -- etc... for all four ties. Only tie4 actually needs to be better, tie1 + -- through tie3 can be equal as well, as long as the next tie breaks the + -- same tie of the previous attempt :) + if (segments[segment].prev.ok and (not segments[segment].best or (getScore() > segments[segment].best.score or (getScore() == segments[segment].best.score and (getTie1() > segments[segment].best.tie1 or (getTie1() == segments[segment].best.tie1 and (getTie1() > segments[segment].best.tie1 or (getTie1() == segments[segment].best.tie1 and (getTie1() > segments[segment].best.tie1 or (getTie1() == segments[segment].best.tie1 and getTie1() > segments[segment].best.tie1)))))))))) then + -- previous attempt was better then current best (or no current best + -- exists), so we (re)place it. + if (not segments[segment].best) then segments[segment].best = {}; end; + segments[segment].best.frames = segments[segment].prev.frames; + segments[segment].best.attempt = segments[segment].prev.attempt; + segments[segment].best.score = segments[segment].prev.score; + segments[segment].best.tie1 = segments[segment].prev.tie1; + segments[segment].best.tie2 = segments[segment].prev.tie2; + segments[segment].best.tie3 = segments[segment].prev.tie3; + segments[segment].best.tie4 = segments[segment].prev.tie4; + segments[segment].best.keys1 = keyrecording1; -- backup the recorded keys + segments[segment].best.keys2 = keyrecording2; -- backup the recorded keys player 2 + segments[segment].best.lastkey1 = lastkey1; -- backup the lastkey + segments[segment].best.lastkey2 = lastkey2; -- backup the lastkey + segments[segment].best.vars = vars; -- backup the vars table + end + + if (rand_if(isSegmentEnd())) then -- the current segment ends, replay the best attempt and continue from there onwards... + onSegmentEnd(); + if (rand_if(mustRollBack())) then -- rollback to previous segment + gui.text(50,50,"Rolling back to segment "..(segment-1)); + segments[segment] = nil; -- remove current segment data + attempt = 0; -- will be incremented in a few lines to be 1 + segment = segment - 1; + segments[segment].best = nil; + segments[segment].prev = nil; + collectgarbage(); -- collect the removed segment please + else + playingbest = true; -- this will start playing back the best attempt in this frame + end; + end; + + -- reset vars + attempt = attempt + 1; + frame = 0; + keyrecording1 = {}; -- reset the recordings :) + keyrecording2 = {}; + -- set lastkey to lastkey of previous segment (or start, if first segment) + -- also set the vars table to the table of the previous segment + if (segment == 1) then + lastkey1 = startkey1; + lastkey1 = startkey1; + vars = startvars; + else + lastkey1 = segments[segment-1].best.lastkey1; + lastkey2 = segments[segment-1].best.lastkey2; + vars = segments[segment-1].best.vars; + end; + -- load the segment savestate to go back to the start of this segment + if (segments[segment].savestate) then -- load segment savestate and try again :) + savestate.load(segments[segment].savestate); + else + fceu.crash(); -- this crashes because fceu is a nil table :) as long as gui.popup() doesnt work... we're crashing because no save state exists..? it should never happen. + end; + + if (rand_if(isRunEnd())) then break; end; -- if end of run, break out of main loop and run in end loop. + + if (not playingbest) then onAttemptStart(); end; -- only call this when not playing back best attempt. has decreased frame counter! + end; -- continues with (new) attempt + + -- increase framecounter _after_ processing attempt-end + frame = frame + 1; -- inrease the frame count (++frame?) + + if (playingbest and segments[segment].best) then -- press keys from memory (if there are any) + gui.text(10,150,"frame "..frame.." of "..segments[segment].best.frames); + if (frame >= segments[segment].best.frames) then -- end of playback, start new segment + playingbest = false; + lastkey1 = segments[segment].best.lastkey1; + lastkey2 = segments[segment].best.lastkey2; + vars = segments[segment].best.vars; + segment = segment + 1; + segments[segment] = {}; + -- create a new savestate for the start of this segment + segments[segment].savestate = savestate.create(); + savestate.save(segments[segment].savestate); + -- reset vars + frame = 0; -- onSegmentStart and onAttemptStart expect this to be one fewer... + attempt = 1; + --key1 = {}; -- i dont think this has to be done just for the two events below. they get reset anyways before input. + --key2 = {}; + keyrecording1 = {}; -- reset recordings :) + keyrecording2 = {}; + -- after this, the next segment starts because playingbest is no longer true + onSegmentStart(); + onAttemptStart(); + frame = 1; -- now set it to 1 + else + key1 = segments[segment].best.keys1[frame]; -- fill keys with that of the best attempt + key2 = segments[segment].best.keys2[frame]; + gui.text(10,10,"Playback best of segment "..segment.."\nFrame: "..frame); + end; + end; + + if (rand_if(isRunEnd())) then break; end; -- if end of run, break out of main loop + -- note this is the middle, this is where an attempt or segment has ended if it would and started if it would! + -- now comes the input part for this frame + + if (not playingbest) then -- when playing best, the keys have been filled above. + -- press keys from bot + gui.text(10,10,"Attempt: "..attempt.." / "..maxattempts.."\nFrame: "..frame.." / "..maxframes); + if (segments[segment] and segments[segment].best and segments[segment].prev) then + gui.text(10,30,"Last score: "..segments[segment].prev.score.." ok="..okattempts..", fail="..failattempts.."\nBest score: "..segments[segment].best.score); + elseif (segments[segment] and segments[segment].prev) then + gui.text(10,30,"Last score: "..segments[segment].prev.score.."\nBest score: none, fails="..failattempts); + end; + gui.text(10,50,"Segment: "..segment); + + key1 = {}; + key2 = {}; + + onInputStart(); + + -- player 1 + if (rand_if(pressKeyUp1()) ) then key1.up = 1; end; + if (rand_if(pressKeyDown1())) then key1.down = 1; end; + if (rand_if(pressKeyLeft1())) then key1.left = 1; end; + if (rand_if(pressKeyRight1())) then key1.right = 1; end; + if (rand_if(pressKeyA1())) then key1.A = 1; end; + if (rand_if(pressKeyB1())) then key1.B = 1; end; + if (rand_if(pressKeySelect1())) then key1.select = 1; end; + if (rand_if(pressKeyStart1())) then key1.start = 1; end; + + -- player 2 + if (rand_if(pressKeyUp2()) ) then key2.up = 1; end; + if (rand_if(pressKeyDown2())) then key2.down = 1; end; + if (rand_if(pressKeyLeft2())) then key2.left = 1; end; + if (rand_if(pressKeyRight2())) then key2.right = 1; end; + if (rand_if(pressKeyA2())) then key2.A = 1; end; + if (rand_if(pressKeyB2())) then key2.B = 1; end; + if (rand_if(pressKeySelect2())) then key2.select = 1; end; + if (rand_if(pressKeyStart2())) then key2.start = 1; end; + + onInputEnd(); + + lastkey1 = key1; + lastkey2 = key2; + + keyrecording1[frame] = key1; -- record these keys + keyrecording2[frame] = key2; -- record these keys + end; + + -- actually set the keys here. + joypad.set(1, key1); + joypad.set(2, key2); + + -- next frame + FCEU.frameadvance(); +end; + +onFinish(); -- allow user cleanup before starting the final botloop + +-- now enter an endless loop displaying the results of this run. +while (true) do + if (segments[segment].best) then gui.text(30,100,"end: max attempt ["..segment.."] had score = "..segments[segment].best.score); + elseif (segment > 1 and segments[segment-1].best) then gui.text(30,100,"end: no best attempt ["..segment.."]\nPrevious best score: "..segments[segment-1].best.score); + else gui.text(30,100,"end: no best attempt ["..segment.."] ..."); end; + FCEU.frameadvance(); +end; + +-- i dont think it ever reaches this place... perhaps it should, or some event or whatever... +segments = nil; +collectgarbage(); -- collect the segment data... anything else is probably not worth it... diff --git a/documentation/lua/basicbot_front.lua b/documentation/lua/basicbot_front.lua new file mode 100644 index 00000000..c85d9886 --- /dev/null +++ b/documentation/lua/basicbot_front.lua @@ -0,0 +1,268 @@ +-- BasicBot, a LuaBot frontend +-- qFox, 30 July 2008 +-- version 1.04 (unstable load button!) + +-- we need iup, so include it here +local iuplua_open = package.loadlib("iuplua51.dll", "iuplua_open"); +iuplua_open(); +local iupcontrolslua_open = package.loadlib("iupluacontrols51.dll", "iupcontrolslua_open"); +iupcontrolslua_open(); + +local botVersion = 1; -- check this version when saving/loading. this will change whenever the botsave-file changes. + +-- callback function to clean up our mess +function emu.OnClose.iuplua() + --iup.Message ("IupMessage", "OnClose!"); + if(emu and emu.OnCloseIup ~= nil) then + emu.OnCloseIup(); + end + iup.Close(); +end + +local handles = {}; -- this table should hold the handle to all dialogs created in lua +local dialogs = 0; -- should be incremented PRIOR to creating a new dialog +-- called by the onclose event +function emu.OnCloseIup() + if (handles) then -- just in case the user was "smart" enough to clear this + local i = 1; + while (handles[i] ~= nil) do -- cycle through all handles, false handles are skipped, nil denotes the end + if (handles[i] and handles[i].destroy) then -- check for the existence of what we need + -- close this dialog + handles[i]:destroy(); + handles[i] = nil; + end; + i = i + 1; + end; + end; +end; + +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="Basic Bot 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("basicbot_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 + return; + end; + local nellen = string.len("\n"); -- platform independent + fh = assert(io.open(file.value,"r")); + print("seek: "..fh:seek()); + print("version: "..fh:read("*l")); + print("seek: "..fh:seek()); + if (true) then return; end; + for token,crap in pairs(reftable) do + local len = fh:read("*n"); -- read line (length) + if (not len) then break; end; + fh:seek("set",fh:seek("cur", nellen)); -- remove the aesthetic return + print(len); + reftable[token].value = fh:read(len); + fh:seek("set",fh:seek("cur", nellen)); -- remove the aesthetic return + end; + end; +end; + +dialogs = dialogs + 1; +createGUI(dialogs); +while (true) do + FCEU.frameadvance(); +end; \ No newline at end of file diff --git a/documentation/lua/smb1.lua b/documentation/lua/smb1.lua new file mode 100644 index 00000000..a5fb5e5f --- /dev/null +++ b/documentation/lua/smb1.lua @@ -0,0 +1,73 @@ +-- super mario bros 1 hitbox script +-- Super Mario Bros. (JU) (PRG0) [!].rom +-- by qFox +-- 28 july 2008 + +local function box(x1,y1,x2,y2,color) + -- gui.text(50,50,x1..","..y1.." "..x2..","..y2); + if (x1 > 0 and x1 < 255 and x2 > 0 and x2 < 255 and y1 > 0 and y1 < 224 and y2 > 0 and y2 < 224) then + gui.drawbox(x1,y1,x2,y2,color); + end; +end; + +-- hitbox coordinate offsets (x1,y1,x2,y2) +local mario_hb = 0x04AC; -- 1x4 +local enemy_hb = 0x04B0; -- 5x4 +local coin_hb = 0x04E0; -- 3x4 +local fiery_hb = 0x04C8; -- 2x4 +local hammer_hb= 0x04D0; -- 9x4 +local power_hb = 0x04C4; -- 1x4 + +-- addresses to check, to see whether the hitboxes should be drawn at all +local mario_ch = 0x000E; +local enemy_ch = 0x000F; +local coin_ch = 0x0030; +local fiery_ch = 0x0024; +local hammer_ch= 0x002A; +local power_ch = 0x0014; + + +while true do + -- from 0x04AC are about 0x48 addresse that indicate a hitbox + -- different items use different addresses, some share + -- there can for instance only be one powerup on screen at any time (the star in 1.1 gets replaced by the flower, if you get it) + -- we cycle through the animation addresses for each type of hitbox, draw the corresponding hitbox if they are drawn + -- we draw: mario (1), enemies (5), coins (3), hammers (9), powerups (1). (bowser and (his) fireball are considered enemies) + + -- mario + if (memory.readbyte(mario_hb) > 0) then box(memory.readbyte(mario_hb),memory.readbyte(mario_hb+1),memory.readbyte(mario_hb+2),memory.readbyte(mario_hb+3), "green"); end; + + -- enemies + if (memory.readbyte(enemy_ch ) > 0) then box(memory.readbyte(enemy_hb), memory.readbyte(enemy_hb+1), memory.readbyte(enemy_hb+2), memory.readbyte(enemy_hb+3), "green"); end; + if (memory.readbyte(enemy_ch+1) > 0) then box(memory.readbyte(enemy_hb+4), memory.readbyte(enemy_hb+5), memory.readbyte(enemy_hb+6), memory.readbyte(enemy_hb+7), "green"); end; + if (memory.readbyte(enemy_ch+2) > 0) then box(memory.readbyte(enemy_hb+8), memory.readbyte(enemy_hb+9), memory.readbyte(enemy_hb+10),memory.readbyte(enemy_hb+11), "green"); end; + if (memory.readbyte(enemy_ch+3) > 0) then box(memory.readbyte(enemy_hb+12),memory.readbyte(enemy_hb+13),memory.readbyte(enemy_hb+14),memory.readbyte(enemy_hb+15), "green"); end; + if (memory.readbyte(enemy_ch+4) > 0) then box(memory.readbyte(enemy_hb+16),memory.readbyte(enemy_hb+17),memory.readbyte(enemy_hb+18),memory.readbyte(enemy_hb+19), "green"); end; + + -- coins + if (memory.readbyte(coin_ch ) > 0) then box(memory.readbyte(coin_hb), memory.readbyte(coin_hb+1), memory.readbyte(coin_hb+2), memory.readbyte(coin_hb+3), "green"); end; + if (memory.readbyte(coin_ch+1) > 0) then box(memory.readbyte(coin_hb+4), memory.readbyte(coin_hb+5), memory.readbyte(coin_hb+6), memory.readbyte(coin_hb+7), "green"); end; + if (memory.readbyte(coin_ch+2) > 0) then box(memory.readbyte(coin_hb+8), memory.readbyte(coin_hb+9), memory.readbyte(coin_hb+10), memory.readbyte(coin_hb+11), "green"); end; + + -- (mario's) fireballs + if (memory.readbyte(fiery_ch ) > 0) then box(memory.readbyte(fiery_hb), memory.readbyte(fiery_hb+1), memory.readbyte(fiery_hb+2), memory.readbyte(fiery_hb+3), "green"); end; + if (memory.readbyte(fiery_ch+1) > 0) then box(memory.readbyte(fiery_hb+4), memory.readbyte(fiery_hb+5), memory.readbyte(fiery_hb+6),memory.readbyte(fiery_hb+7), "green"); end; + + -- hammers + if (memory.readbyte(hammer_ch ) > 0) then box(memory.readbyte(hammer_hb), memory.readbyte(hammer_hb+1), memory.readbyte(hammer_hb+2), memory.readbyte(hammer_hb+3), "green"); end; + if (memory.readbyte(hammer_ch+1) > 0) then box(memory.readbyte(hammer_hb+4), memory.readbyte(hammer_hb+5), memory.readbyte(hammer_hb+6), memory.readbyte(hammer_hb+7), "green"); end; + if (memory.readbyte(hammer_ch+2) > 0) then box(memory.readbyte(hammer_hb+8), memory.readbyte(hammer_hb+9), memory.readbyte(hammer_hb+10),memory.readbyte(hammer_hb+11), "green"); end; + if (memory.readbyte(hammer_ch+3) > 0) then box(memory.readbyte(hammer_hb+12),memory.readbyte(hammer_hb+13),memory.readbyte(hammer_hb+14),memory.readbyte(hammer_hb+15), "green"); end; + if (memory.readbyte(hammer_ch+4) > 0) then box(memory.readbyte(hammer_hb+16),memory.readbyte(hammer_hb+17),memory.readbyte(hammer_hb+18),memory.readbyte(hammer_hb+19), "green"); end; + if (memory.readbyte(hammer_ch+5) > 0) then box(memory.readbyte(hammer_hb+20),memory.readbyte(hammer_hb+21),memory.readbyte(hammer_hb+22),memory.readbyte(hammer_hb+23), "green"); end; + if (memory.readbyte(hammer_ch+6) > 0) then box(memory.readbyte(hammer_hb+24),memory.readbyte(hammer_hb+25),memory.readbyte(hammer_hb+26),memory.readbyte(hammer_hb+27), "green"); end; + if (memory.readbyte(hammer_ch+7) > 0) then box(memory.readbyte(hammer_hb+28),memory.readbyte(hammer_hb+29),memory.readbyte(hammer_hb+30),memory.readbyte(hammer_hb+31), "green"); end; + if (memory.readbyte(hammer_ch+8) > 0) then box(memory.readbyte(hammer_hb+32),memory.readbyte(hammer_hb+33),memory.readbyte(hammer_hb+34),memory.readbyte(hammer_hb+35), "green"); end; + + -- powerup + if (memory.readbyte(power_ch) > 0) then box(memory.readbyte(power_hb),memory.readbyte(power_hb+1),memory.readbyte(power_hb+2),memory.readbyte(power_hb+3), "green"); end; + + gui.text(5,32,"Green rectangles are hitboxes!"); + + FCEU.frameadvance() +end \ No newline at end of file diff --git a/documentation/lua/smb1_iup.lua b/documentation/lua/smb1_iup.lua new file mode 100644 index 00000000..9a341cad --- /dev/null +++ b/documentation/lua/smb1_iup.lua @@ -0,0 +1,195 @@ +-- super mario bros 1 hitbox script +-- Super Mario Bros. (JU) (PRG0) [!].rom +-- by qFox +-- 28 july 2008 + +-- This script shows hitboxes of anything that has them +-- It also displays the found hitboxes in a output window with iup +-- Includes a toggle to automatically kill all enemies within 50px range of mario :p + +-- we need iup, so include it here +local iuplua_open = package.loadlib("iuplua51.dll", "iuplua_open"); +iuplua_open(); +local iupcontrolslua_open = package.loadlib("iupluacontrols51.dll", "iupcontrolslua_open"); +iupcontrolslua_open(); + +-- callback function to clean up our mess +function emu.OnClose.iuplua() + gui.popup("OnClose!"); + if(emu and emu.OnCloseIup ~= nil) then + emu.OnCloseIup(); + end + iup.Close(); +end + +local handles = {}; -- this table should hold the handle to all dialogs created in lua +local dialogs = 0; -- should be incremented PRIOR to creating a new dialog +-- called by the onclose event +function emu.OnCloseIup() + if (handles) then -- just in case the user was "smart" enough to clear this + local i = 1; + while (handles[i] ~= nil) do -- cycle through all handles, false handles are skipped, nil denotes the end + if (handles[i] and handles[i].destroy) then -- check for the existence of what we need + -- close this dialog + handles[i]:destroy(); + handles[i] = nil; + end; + i = i + 1; + end; + end; +end; + + +local running = true; +local restrainingorder = false; +local myoutput; +function createGUI(n) + local mybutton = iup.button{title="Close (exits the main loop)"}; + mybutton.action = function(self, x) + running = false; + --handles[n]:destroy(); + --handles[n] = false; + end; + myoutput = iup.multiline{size="200x200",expand="YES",value="Debug crap should be here"} + nottooclose = iup.toggle{title="Kill enemies that come too close", value="OFF"}; + nottooclose.action = function(self, v) restrainingorder = (v == 1); end; -- v is 0 or 1 + handles[n] = + iup.dialog{ + iup.frame + { + iup.vbox + { + mybutton, + nottooclose, + myoutput, + title="Lua tools are izi!" + } + } + }; + handles[n]:showxy(iup.CENTER, iup.CENTER) +end + +dialogs = dialogs + 1; +createGUI(dialogs); + +local outstr; +local function knifeEnemyIfTooClose(enemynumber) -- enemynumber starts at 1 + -- we add the suffix for x coords because the screen is 255 positions wide but smb has two pages + -- loaded at any given time. some enemy can be in page 2 while you are on page one. since a byte + -- can only hold 255 values, this would wrap around and make it seem like the enemy is on the same + -- page. hence we add the number of pages they are one, if they are equal, it all works out :) + local mx = memory.readbyte(0x0086)+(255*memory.readbyte(0x006D)); + local my = memory.readbyte(0x00CE); + local ex = memory.readbyte(0x0086+enemynumber)+(255*memory.readbyte(0x006D+enemynumber)); + local ey = memory.readbyte(0x00CE+enemynumber); + local d = math.sqrt(((mx-ex)^2)+((my-ey)^2)); -- pythagoras ;) + outstr = outstr .. d.." < 30.0 ?\n"; + if (d < 50.0 and restrainingorder and memory.readbyte(0x0015+enemynumber) ~= 40) then -- dont kill of horizontal moving platforms. we kinda need them. + -- KNIFE! + outstr = outstr .. "Knifing next enemy!\n"; + memory.writebyte(0x001D+enemynumber, 0xFF); -- this address denotes an enemy state. writing FF to it kills them as if hit by a star (so not flatten). + end; + return d; +end; + +-- draw a box and take care of coordinate checking +local function box(x1,y1,x2,y2,color) + -- gui.text(50,50,x1..","..y1.." "..x2..","..y2); + if (x1 > 0 and x1 < 255 and x2 > 0 and x2 < 255 and y1 > 0 and y1 < 224 and y2 > 0 and y2 < 224) then + gui.drawbox(x1,y1,x2,y2,color); + end; +end; + +-- hitbox coordinate offsets (x1,y1,x2,y2) +local mario_hb = 0x04AC; -- 1x4 +local enemy_hb = 0x04B0; -- 5x4 +local coin_hb = 0x04E0; -- 3x4 +local fiery_hb = 0x04C8; -- 2x4 +local hammer_hb= 0x04D0; -- 9x4 +local power_hb = 0x04C4; -- 1x4 + +-- addresses to check, to see whether the hitboxes should be drawn at all +local mario_ch = 0x000E; +local enemy_ch = 0x000F; +local coin_ch = 0x0030; +local fiery_ch = 0x0024; +local hammer_ch= 0x002A; +local power_ch = 0x0014; + +local a,b,c,d; +while (running) do + outstr = ''; + -- from 0x04AC are about 0x48 addresse that indicate a hitbox + -- different items use different addresses, some share + -- there can for instance only be one powerup on screen at any time (the star in 1.1 gets replaced by the flower, if you get it) + -- we cycle through the animation addresses for each type of hitbox, draw the corresponding hitbox if they are drawn + -- we draw: mario (1), enemies (5), coins (3), hammers (9), powerups (1). (bowser and (his) fireball are considered enemies) + + -- mario + if (memory.readbyte(mario_hb) > 0) then + a,b,c,d = memory.readbyte(mario_hb),memory.readbyte(mario_hb+1),memory.readbyte(mario_hb+2),memory.readbyte(mario_hb+3); + box(a,b,c,d, "green"); + outstr = outstr .. "Mario: <"..a..","..b..","..c..","..d..">\n"; + end; + + -- enemies + if (memory.readbyte(enemy_ch ) > 0) then + a,b,c,d = memory.readbyte(enemy_hb), memory.readbyte(enemy_hb+1), memory.readbyte(enemy_hb+2), memory.readbyte(enemy_hb+3); + box(a,b,c,d, "green"); + outstr = outstr .. "Enemy 1: <"..memory.readbyte(0x0016).."> <"..a..","..b..","..c..","..d.."> "..knifeEnemyIfTooClose(1).."\n"; + end; + if (memory.readbyte(enemy_ch+1) > 0) then + a,b,c,d = memory.readbyte(enemy_hb+4), memory.readbyte(enemy_hb+5), memory.readbyte(enemy_hb+6), memory.readbyte(enemy_hb+7); + box(a,b,c,d, "green"); + outstr = outstr .. "Enemy 2: <"..memory.readbyte(0x0017).."> <"..a..","..b..","..c..","..d.."> "..knifeEnemyIfTooClose(2).."\n"; + end; + if (memory.readbyte(enemy_ch+2) > 0) then + a,b,c,d = memory.readbyte(enemy_hb+8), memory.readbyte(enemy_hb+9), memory.readbyte(enemy_hb+10),memory.readbyte(enemy_hb+11); + box(a,b,c,d, "green"); + outstr = outstr .. "Enemy 3: <"..memory.readbyte(0x0018).."> <"..a..","..b..","..c..","..d.."> "..knifeEnemyIfTooClose(3).."\n"; + end; + if (memory.readbyte(enemy_ch+3) > 0) then + a,b,c,d = memory.readbyte(enemy_hb+12),memory.readbyte(enemy_hb+13),memory.readbyte(enemy_hb+14),memory.readbyte(enemy_hb+15); + box(a,b,c,d, "green"); + outstr = outstr .. "Enemy 4: <"..memory.readbyte(0x0019).."> <"..a..","..b..","..c..","..d.."> "..knifeEnemyIfTooClose(4).."\n"; + end; + if (memory.readbyte(enemy_ch+4) > 0) then + a,b,c,d = memory.readbyte(enemy_hb+16),memory.readbyte(enemy_hb+17),memory.readbyte(enemy_hb+18),memory.readbyte(enemy_hb+19) + box(a,b,c,d, "green"); + outstr = outstr .. "Enemy 5: <"..memory.readbyte(0x001A).."> <"..a..","..b..","..c..","..d.."> "..knifeEnemyIfTooClose(5).."\n"; + end; + + -- coins + if (memory.readbyte(coin_ch ) > 0) then box(memory.readbyte(coin_hb), memory.readbyte(coin_hb+1), memory.readbyte(coin_hb+2), memory.readbyte(coin_hb+3), "green"); end; + if (memory.readbyte(coin_ch+1) > 0) then box(memory.readbyte(coin_hb+4), memory.readbyte(coin_hb+5), memory.readbyte(coin_hb+6), memory.readbyte(coin_hb+7), "green"); end; + if (memory.readbyte(coin_ch+2) > 0) then box(memory.readbyte(coin_hb+8), memory.readbyte(coin_hb+9), memory.readbyte(coin_hb+10), memory.readbyte(coin_hb+11), "green"); end; + + -- (mario's) fireballs + if (memory.readbyte(fiery_ch ) > 0) then box(memory.readbyte(fiery_hb), memory.readbyte(fiery_hb+1), memory.readbyte(fiery_hb+2), memory.readbyte(fiery_hb+3), "green"); end; + if (memory.readbyte(fiery_ch+1) > 0) then box(memory.readbyte(fiery_hb+4), memory.readbyte(fiery_hb+5), memory.readbyte(fiery_hb+6),memory.readbyte(fiery_hb+7), "green"); end; + + -- hammers + if (memory.readbyte(hammer_ch ) > 0) then box(memory.readbyte(hammer_hb), memory.readbyte(hammer_hb+1), memory.readbyte(hammer_hb+2), memory.readbyte(hammer_hb+3), "green"); end; + if (memory.readbyte(hammer_ch+1) > 0) then box(memory.readbyte(hammer_hb+4), memory.readbyte(hammer_hb+5), memory.readbyte(hammer_hb+6), memory.readbyte(hammer_hb+7), "green"); end; + if (memory.readbyte(hammer_ch+2) > 0) then box(memory.readbyte(hammer_hb+8), memory.readbyte(hammer_hb+9), memory.readbyte(hammer_hb+10),memory.readbyte(hammer_hb+11), "green"); end; + if (memory.readbyte(hammer_ch+3) > 0) then box(memory.readbyte(hammer_hb+12),memory.readbyte(hammer_hb+13),memory.readbyte(hammer_hb+14),memory.readbyte(hammer_hb+15), "green"); end; + if (memory.readbyte(hammer_ch+4) > 0) then box(memory.readbyte(hammer_hb+16),memory.readbyte(hammer_hb+17),memory.readbyte(hammer_hb+18),memory.readbyte(hammer_hb+19), "green"); end; + if (memory.readbyte(hammer_ch+5) > 0) then box(memory.readbyte(hammer_hb+20),memory.readbyte(hammer_hb+21),memory.readbyte(hammer_hb+22),memory.readbyte(hammer_hb+23), "green"); end; + if (memory.readbyte(hammer_ch+6) > 0) then box(memory.readbyte(hammer_hb+24),memory.readbyte(hammer_hb+25),memory.readbyte(hammer_hb+26),memory.readbyte(hammer_hb+27), "green"); end; + if (memory.readbyte(hammer_ch+7) > 0) then box(memory.readbyte(hammer_hb+28),memory.readbyte(hammer_hb+29),memory.readbyte(hammer_hb+30),memory.readbyte(hammer_hb+31), "green"); end; + if (memory.readbyte(hammer_ch+8) > 0) then box(memory.readbyte(hammer_hb+32),memory.readbyte(hammer_hb+33),memory.readbyte(hammer_hb+34),memory.readbyte(hammer_hb+35), "green"); end; + + -- powerup + if (memory.readbyte(power_ch) > 0) then box(memory.readbyte(power_hb),memory.readbyte(power_hb+1),memory.readbyte(power_hb+2),memory.readbyte(power_hb+3), "green"); end; + + gui.text(5,32,"Green rectangles are hitboxes!"); + + if (myoutput) then + myoutput.value = outstr; + end; + + FCEU.frameadvance() +end + +gui.popup("script exited main loop"); + diff --git a/documentation/lua/smb2.lua b/documentation/lua/smb2.lua new file mode 100644 index 00000000..6b01b1b6 --- /dev/null +++ b/documentation/lua/smb2.lua @@ -0,0 +1,128 @@ +-- unfinished mario bros 2 script +-- shows (proper!) grid for horizontal levels (b0rks in vertical levels for now) +-- shows any non-air grid's tile-id +-- is sloooow :p + +-- Super Mario Bros. 2 (U) (PRG0) [!].rom +-- qFox +-- 31 july 2008 + +local function box(x1,y1,x2,y2,color) + -- gui.text(50,50,x1..","..y1.." "..x2..","..y2); + if (x1 > 0 and x1 < 0xFF and x2 > 0 and x2 < 0xFF and y1 > 0 and y1 < 239 and y2 > 0 and y2 < 239) then + gui.drawbox(x1,y1,x2,y2,color); + end; +end; +local function text(x,y,str) + if (x > 0 and x < 0xFF and y > 0 and y < 240) then + gui.text(x,y,str); + end; +end; +local function toHexStr(n) + local meh = "%X"; + return meh:format(n); +end; + +while (true) do + if (memory.readbyte(0x0010) > 0x81) then memory.writebyte(0x0010, 0x6D); end; -- birdo fires eggs constantly :p + + -- px = horzizontal page of current level + -- x = page x (relative to current page) + -- rx = real x (relative to whole level) + -- sx = screen x (relative to viewport) + local playerpx = memory.readbyte(0x0014); + local playerpy = memory.readbyte(0x001E); + local playerx = memory.readbyte(0x0028); + local playery = memory.readbyte(0x0032); + local playerrx = (playerpx*0xFF)+playerx; + local playerry = (playerpy*0xFF)+playery; + local playerstate = memory.readbyte(0x0050); + local screenoffsetx = memory.readbyte(0x04C0); + local screenoffsety = memory.readbyte(0x00CB); + + local playersx = (playerx - screenoffsetx); + if (playersx < 0) then playersx = playersx + 0xFF; end; + + local playersy = (playery - screenoffsety); + if (playersy < 0) then playersy = playersy + 0xFF; end; + + if (playerstate ~= 0x07) then + box(playersx, playersy, playersx+16, playersy+16, "green"); + end; + + text(2,10,"x:"..screenoffsetx); + text(2,25,"y: "..screenoffsety); + text(230,10,memory.readbyte(0x04C1)); + text(100,10,"Page: "..playerpx..","..playerpy); + text(playersx,playersy,playerrx.."\n"..playery); + + local startpx = 0x0015; + local startpy = 0x001F; + local startx = 0x0029; + local starty = 0x0033; + local drawn = 0x0051; + local type = 0x0090; + for i=0,9 do + local estate = memory.readbyte(drawn+i); + if (estate ~= 0) then + local ex = memory.readbyte(startx+i); + local epx = memory.readbyte(startpx+i); + local ey = memory.readbyte(starty+i); + local epy = memory.readbyte(startpy+i); + local erx = (epx*0xFF)+ex; + local ery = (epy*0xFF)+ey; + local esx = (ex - screenoffsetx); + if (esx < 0) then esx = esx + 0xFF; end; + local esy = (ey - screenoffsety); + if (esy < 0) then esy = esy + 0xFF; end; + + --text(10, 20+(16*i), i..": "..esx.." "..erx); -- show enemy position list + + -- show enemy information + if ((erx > playerrx-127) and erx < (playerrx+120)) then + --text(esx,esy,erx); -- show level x pos above enemy + local wtf = "%X"; + text(esx,esy,wtf:format(memory.readbyte(type+i))); -- show enemy code + if (estate == 1 and i < 5) then + box(esx, esy, esx+16, esy+16, "red"); + else + box(esx, esy, esx+16, esy+16, "blue"); + end; + end; + end; -- enemy info + + -- show environment + -- i have playerrx, which is my real position in this level + -- i have the level, which is located at 0x6000 (the SRAM) + -- each tile (denoted by one byte) is 16x16 pixels + -- each screen is 15 tiles high and about 16 tiles wide + -- to get the right column, we add our playerrx/16 to 0x6000 + -- to be exact: + -- 0x6000 + (math.floor(playerrx/16) * 0xF0) + math.mod(playerx,0x0F) + + local levelstart = 0x6000; -- start of level layout in RAM + local addleftcrap = math.mod(screenoffsetx,16)*-1; + local leftsplitrx = (memory.readbyte(0x04BE)*0xFF) + (screenoffsetx + addleftcrap); -- column start left. add addleftcrap to iterative stuff to build up + local columns = leftsplitrx/16; -- column x of the level is on the left side of the screen + + for i=0,15 do + text(addleftcrap+(i*16)-1, 37, toHexStr(columns+i)); + for j=0,17 do + box(addleftcrap+(i*16), 1+(j*16), addleftcrap+(i*16)+16, 1+(j*16)+16, "green"); + end; + end; + + local columntop = levelstart + ((math.floor(columns/16))*0xF0) + math.mod(columns,16); + text(10,80,toHexStr(columns).." "..toHexStr(columntop)); + for i=0,14 do + for j=0,15 do + local tile = memory.readbyte(columntop+(i*0x10)+j); + if (tile ~= 0x40) then + text(-2+addleftcrap+(j*16),21+(i*16),toHexStr(tile)); + end; + end; + end; + + end; + FCEU.frameadvance(); +end; \ No newline at end of file diff --git a/documentation/lua/tetris.lua b/documentation/lua/tetris.lua new file mode 100644 index 00000000..640bde3a --- /dev/null +++ b/documentation/lua/tetris.lua @@ -0,0 +1,148 @@ +-- http://www.datacrystal.org/wiki/Tetris:RAM_map +-- Tetris (U) [!].rom +-- qFox + +local function realcount(t) -- accurately count the number of elements of a table, even if they contain nil values. not fast, but accurate. + local n = 0; + for i,_ in pairs(t) do + n = n + 1; + end; + return n; +end; + +local function getPiece(n) -- returns table with information about this piece + -- every piece consists of 4 blocks + -- so piece will contain the information about these blocks + -- for every block is the offset compared to the x,y position of the piece + -- the x,y is written in a 4x4 plane of blocks, 1x1 being the topleft block + -- x,y is the position as given by tetris + local piece = {}; + if (n == 0) then -- T right + piece.pos = {{2,1},{1,2},{2,2},{3,2}}; + piece.rel = {{0,-1},{-1,0},{0,0},{1,0}}; + piece.anchor = {2,2}; + elseif (n == 1) then -- T up + piece.pos = {{1,1},{1,2},{2,2},{1,3}}; + piece.rel = {{0,-1},{0,0},{1,0},{0,1}}; + piece.anchor = {1,2}; + elseif (n == 2) then -- T down + piece.pos = {{1,1},{2,1},{3,1},{2,2}}; + piece.rel = {{-1,0},{0,0},{1,0},{0,1}}; + piece.anchor = {2,1}; + elseif (n == 3) then -- T left + piece.pos = {{2,1},{1,2},{2,2},{2,3}}; + piece.rel = {{0,-1},{-1,0},{0,0},{0,1}}; + piece.anchor = {2,2}; + elseif (n == 4) then -- J left + piece.pos = {{2,1},{2,2},{1,3},{2,3}}; + piece.rel = {{0,-1},{0,0},{-1,1},{0,1}}; + piece.anchor = {2,2}; + elseif (n == 5) then -- J up + piece.pos = {{1,1},{1,2},{2,2},{3,2}}; + piece.rel = {{-1,-1},{-1,0},{0,0},{1,0}}; + piece.anchor = {2,2}; + elseif (n == 6) then -- J right + piece.pos = {{1,1},{2,1},{1,2},{1,3}}; + piece.rel = {{0,-1},{1,-1},{0,0},{0,1}}; + piece.anchor = {1,2}; + elseif (n == 7) then -- J down + piece.pos = {{1,1},{2,1},{3,1},{3,2}}; + piece.rel = {{-1,0},{0,0},{1,0},{1,1}}; + piece.anchor = {2,1}; + elseif (n == 8) then -- Z horz + piece.pos = {{1,1},{2,1},{2,2},{3,2}}; + piece.rel = {{-1,0},{0,0},{0,1},{1,1}}; + piece.anchor = {2,1}; + elseif (n == 9) then -- Z vert + piece.pos = {{2,1},{1,2},{2,2},{1,3}}; + piece.rel = {{1,-1},{0,0},{1,0},{0,1}}; + piece.anchor = {1,2}; + elseif (n == 10) then -- O + piece.pos = {{1,1},{2,1},{1,2},{2,2}}; + piece.rel = {{-1,0},{0,0},{-1,1},{0,1}}; + piece.anchor = {2,1}; + elseif (n == 11) then -- S horz + piece.pos = {{2,1},{3,1},{1,2},{2,2}}; + piece.rel = {{0,0},{1,0},{-1,1},{0,1}}; + piece.anchor = {2,1}; + elseif (n == 12) then -- S vert + piece.pos = {{1,1},{1,2},{2,2},{2,3}}; + piece.rel = {{0,-1},{0,0},{1,0},{1,1}}; + piece.anchor = {1,2}; + elseif (n == 13) then -- L right + piece.pos = {{1,1},{1,2},{1,3},{2,3}}; + piece.rel = {{0,-1},{0,0},{0,1},{1,1}}; + piece.anchor = {1,2}; + elseif (n == 14) then -- L down + piece.pos = {{1,1},{2,1},{3,1},{1,2}}; + piece.rel = {{-1,0},{0,0},{1,0},{-1,1}}; + piece.anchor = {2,1}; + elseif (n == 15) then -- L left + piece.pos = {{1,1},{2,1},{2,2},{2,3}}; + piece.rel = {{-1,-1},{0,-1},{0,0},{0,1}}; + piece.anchor = {2,2}; + elseif (n == 16) then -- L up + piece.pos = {{3,1},{1,2},{2,2},{3,2}}; + piece.rel = {{1,-1},{-1,0},{0,0},{1,0}}; + piece.anchor = {2,2}; + elseif (n == 17) then -- I vert + piece.pos = {{1,1},{1,2},{1,3},{1,4}}; + piece.rel = {{0,-2},{0,-1},{0,0},{0,1}}; + piece.anchor = {1,3}; + elseif (n == 18) then -- I horz + piece.pos = {{1,1},{2,1},{3,1},{4,1}}; + piece.rel = {{-2,0},{-1,0},{0,0},{1,0}}; + piece.anchor = {3,1}; + else + return nil; + end; + piece.id = n; + return piece; +end; + + -- 0,0 - 10,20 + -- translated it's: <95,40> to <175,200> + -- x starts at 1, y starts at 0... + -- each block is 8x8 +local areax = 95; +local areay = 40; +local blocksize = 8; + +while (true) do + -- get position of current piece (now) + local x = (memory.readbyte(0x0060)*blocksize)+areax; + local y = (memory.readbyte(0x0061)*blocksize)+areay; + local now = getPiece(memory.readbyte(0x0062)); -- get piece info + -- draw a nice box around it + gui.drawbox(95,40,175,200,"red"); + gui.text(5,5,"xy: <"..x..", "..y..">"); + if (now and x > 0 and y > 0 and x < 250 and y < 200) then + gui.drawbox(x+(now.rel[1][1]*blocksize),y+(now.rel[1][2]*blocksize),x+(now.rel[1][1]*blocksize)+blocksize,y+(now.rel[1][2]*blocksize)+blocksize,"green"); + gui.drawbox(x+(now.rel[2][1]*blocksize),y+(now.rel[2][2]*blocksize),x+(now.rel[2][1]*blocksize)+blocksize,y+(now.rel[2][2]*blocksize)+blocksize,"green"); + gui.drawbox(x+(now.rel[3][1]*blocksize),y+(now.rel[3][2]*blocksize),x+(now.rel[3][1]*blocksize)+blocksize,y+(now.rel[3][2]*blocksize)+blocksize,"green"); + gui.drawbox(x+(now.rel[4][1]*blocksize),y+(now.rel[4][2]*blocksize),x+(now.rel[4][1]*blocksize)+blocksize,y+(now.rel[4][2]*blocksize)+blocksize,"green"); + end; + gui.text(5,15,"Now: "..memory.readbyte(0x0062).."\nNext: "..memory.readbyte(0x00BF)); + + -- draw the filled field and do some counting + local start = 0x0400; -- toprow + local height = -1; + local filled = 0; + for j=0,19 do + for i=0,9 do + if (memory.readbyte(start+(j*10)+i) ~= 0xEF) then + filled = filled + 1; + if (height == -1) then height = j; end; + gui.drawbox(areax+(i*blocksize),areay+(j*blocksize),areax+(i*blocksize)+blocksize,areay+(j*blocksize)+blocksize,"red"); + end; + end; + end; + if (height == -1) then height = 0; + else height = 20 - height; end; + gui.text(5,30,"Height: "..height.."\nFilled: "..filled.." ("..math.floor(filled/(height)*10).."%)"); + + + --local inp1 = joypad.read(1); + --joypad.set(1,{right=1}); + FCEU.frameadvance(); +end; \ No newline at end of file diff --git a/documentation/lua/tmnt.lua b/documentation/lua/tmnt.lua new file mode 100644 index 00000000..3901d2fc --- /dev/null +++ b/documentation/lua/tmnt.lua @@ -0,0 +1,51 @@ +-- Teenage Mutant Ninja Turtles (U)[!].rom +-- qFox +-- 31 july 2008 + +local function box(x1,y1,x2,y2,color) + -- gui.text(50,50,x1..","..y1.." "..x2..","..y2); + if (x1 > 0 and x1 < 255 and x2 > 0 and x2 < 255 and y1 > 0 and y1 < 241 and y2 > 0 and y2 < 241) then + gui.drawbox(x1,y1,x2,y2,color); + end; +end; +local function text(x,y,str) + if (x > 0 and x < 255 and y > 0 and y < 240) then + gui.text(x,y,str); + end; +end; +local function pixel(x,y,color) + if (x > 0 and x < 255 and y > 0 and y < 240) then + gui.drawpixel(x,y,color); + end; +end; + +while (true) do + local stuff = 0x023C; -- start of tile data, 4 bytes each, y, ?, ?, x. every tile appears to be 10x10px + -- print boxes for all the tiles + -- invalid tiles are automatically hidden because their x/y coords are out of range, i guess + for i=0,0x30 do + x = memory.readbyte(stuff+3+(i*4)); + y = memory.readbyte(stuff+(i*4)); + box(x,y+1,x+7,y+16,"red"); + end; + + -- print player's health + local x = memory.readbyte(0x0480); + local y = memory.readbyte(0x0460); + local hp = memory.readbyte(0x0077+memory.readbyte(0x0067)); -- get health of current char, there are 4 chars and 4 healths + text(x-10,y-20,hp); + + -- print enemy hp + local startx = 0x0484; + local starty = 0x0464; + local starthp = 0x0564; + for i=0,11 do + x = memory.readbyte(startx+i); + y = memory.readbyte(starty+i); + hp = memory.readbyte(starthp+i); + --box(x-5,y-5,x+5,y+5,"green"); -- their 'center', or whatever it is. + text(x-5,y-20,hp); + end; + + FCEU.frameadvance(); +end; \ No newline at end of file