Posts for FatRatKnight

Editor, Experienced Forum User, Published Author, Skilled player (1174)
Joined: 9/27/2008
Posts: 1085
Purposes of the random numbers (as identified so far): R1: 020E9D04 - Some pre-decision things (before you input commands in battle) - - Turn order - - I'm guessing this is the time the enemy "thinks" - Some post-decision things (After you input commands) - - Or perhaps this is the time the enemy "thinks". I'm not sure. - Player attacks - - Whether you hit - - Damage done - - Is it a critical? (not in any particular order, these are guesses) - - 1 more roll for Landsknecht. I'm guessing it's for 2-hit, whether or not the L even has it! - Enemy item drops R2: 020E9D08 - Chop/Mine/Take points, even if you can't get anything from them - Steps until next encounter - Type of encounter - Mysteriously rolled 50 times for entering the option from the title screen - - Opening the option from the party menu has no effect on RNG. - Called for player animations for unknown reasons - - 322: Melee, blunt - - 370: Melee, slash - - 346: Melee, pierce - - Landsknecht - - - 322: Crush - - Dark Hunter - - - 456: Viper - - Survivalist - - - 822 (per enemy): Trickery - - Troubadour - - - 23 (per ally): Shelter - - Alchemist - - - 1629: Poison Unfortunately, R1 is what decides everything important in a battle. R1 is hard to manipulate. It does not change depending on the DS clock at start-up, it does not increment through any out-of-battle actions, and any in-battle calls to R1 happen strictly when needed by the game. R1 is the crucial RNG for actually winning battles, and doesn't change easy. The only important effects R2 has is frequency of battles and what you'll crash into next. That's it. Peeking into the settings from the title screen to pre-roll it 50 times, a starting clock of 12:08:04 AM will give the maximum number of steps first time out. A starting clock of 1:02:07 PM will give the smallest number of steps until your first encounter. Although, if you happen to be nearby a gather point, even without the right skill on anyone, one can use it to manipulate what the next encounter will be. There's usually three different encounters you can find in a particular area, so if it's important to change up what your next battle is, it's a good idea to keep this in mind. The period of the RNG is 65536. R2 will probably wrap around back to old numbers several times over thanks to how rapidly animations cause it to roll. Especially Poison. R1 will need an awful lot of rolling before it comes back to reusing old numbers again. Okay, I think I'll start a new test run. Names I think I'll take for now: Test Run - Guild name S.Bowman - Survivalist M.Healer - Medic T.Jacks - Troubadour A.Pyro - Alchemist A.Cyan - Alchemist Any encoders around? I don't know how to produce half-decent videos, so feel free to encode whatever I do.
Editor, Experienced Forum User, Published Author, Skilled player (1174)
Joined: 9/27/2008
Posts: 1085
Okay, I took a deeper look at how it generates the next RNG. This line should explain it well enough: NewR= OldR*109 + 1021 Every time the game wants a new random number, it does this, but keeps it a two byte value. This is applied to R1 or R2 as necessary. I created a script which helps me to look at number of RNG rolls. I'm using it to check for a few things, such as quickly telling me just how many rolls take place with each action. So far, this is what I saw: - R2: 50 rolls - entering the options menu from title screen - R2: 1 roll - Entering the forest (encounter stuff?) - R2: 1 roll - Initiated a battle - R1: 2 rolls - Message saying Treerats have appeared (there are 2 of them) - R1: 13 rolls - Prior to your command input (Determining turn order and enemy actions, perhaps?) - R1: 12 rolls - Round begin / initiate attack of Alchemist - R2: 322 rolls - Melee animation (Wand) of Alchemist - R1: 3 rolls - Treerat initiate attack - R1: 3 rolls - initiate attack of Survivalist - R2: 370 rolls - Melee animation (Knife) of Survivalist ... Eh, let's not bore you with the turn by turn. The Wand animation rolls 322 times, and Knife animation rolls 370 times. Enemy attacks do not appear to roll R2 crazy-lots like this. So I'm going to analyze a few things using this script. Definitely better than doing things by hand -- Any thoughts on trying to check 370 rolls by hand? The script is this: Download EO_simpleRNGcheck.lua
Language: lua

local R= {{addr= 0x020E9D04, count= 0, panic= 0}, {addr= 0x020E9D08, count= 0, panic= 0}} R[1].n= memory.readword(R[1].addr) R[2].n= memory.readword(R[2].addr) --***************************************************************************** local function Roll(rng) return bit.band( (rng*109 + 0x03FD) , 0xFFFF) end --***************************************************************************** --***************************************************************************** local function ScanRNGs() --***************************************************************************** for i= 1, 2 do local test= memory.readword(R[i].addr) local c= 0 local sanity= false if test ~= R[i].n then local rng= R[i].n for i= 1, 200 do c= c+1 rng= Roll(rng) if rng == test then sanity= true; break end end if sanity then R[i].count= R[i].count + c else R[i].panic= R[i].panic + 1 end R[i].n= test end end end emu.registerafter(ScanRNGs) --***************************************************************************** local function DisplayRNGs() --***************************************************************************** for i= 1, 2 do gui.text(40*i, 1, string.upper(string.format('%x',R[i].n))) gui.text(40*i,13, R[i].count,"green") gui.text(40*i,25, R[i].panic,"red") end end local keys, lastkeys= {}, {} local function UpdateKeys() lastkeys= keys; keys= input.get() end local function Press(k) return keys[k] and (not lastkeys[k]) end --***************************************************************************** local function ResetStats() --***************************************************************************** UpdateKeys() if Press("space") then for i= 1, 2 do R[i].count= 0 R[i].panic= 0 end end end gui.register(function() ResetStats(); DisplayRNGs() end)
I'm curious about names and what party I should make. In my quick test, these are the names I picked: Goo Goo - guild name C.Cannon - Landsknecht F.Knight - Protector GreenElf - Survivalist Look! HP - Medic Destiny - Alchemist Then there's SDA's route which says starting with TSMAA might be desirable. Considering how the RNG works, I get the impression that anything route-wise that may work there will apply here. I suspect a real-time speed-runner can match the route we make, step for step, and will successfully pull off anything we do using the actual console. All it takes is setting the clock to whatever we set and timing the initial moment where they turn on the game so that it matches down to the second. Fractions of a second doesn't matter here, it just needs to be precise to the second, not 1/60 of a second increment. Then it's closely studying what we do and matching everything from there. But other than that, someone still has to pioneer the (detailed, luck manipulating) route. I'm getting ideas from the RNG stuff, and since ShinerCCC at SDA did a good job of pioneering a general route, I might actually make some attempt to put the route to the test, TAS style. But first, names. I am going to spend several frames on significant names. This cost in time is likely a one-shot deal (all instances the names show up happen "instantly"; I do not expect extra lag), and we get recognizable names throughout the run. I feel the entertainment is justified for the tiny cost in time, so anyone got naming suggestions?
Post subject: Buttons lua script.
Editor, Experienced Forum User, Published Author, Skilled player (1174)
Joined: 9/27/2008
Posts: 1085
A general-purpose script for producing buttons. I hope the helpful help text I added helps helpfully. Silly bit of code, but it's yet another way of allowing the user to interact with a lua script. I hope it feels convenient. ... Maybe I should create sample scripts that uses this... Download YummyButton.lua
Language: lua

--[[ Welcome to my Buttons script! To start, put in the following lines: require("thisScript") NewBtn{x= 1, y= 1, fn= function() ((Various code here)) end} And all of a sudden, you have this bluish button on the top left corner of the emulator window, once you run the script. Use the mouse to click the button. The code you put in will happen. Now, there are ways to configure these buttons to your desires. For one thing, make as many NewBtn objects as you like. This script will keep track of 'em. For another, change that x and y up there if you don't like the NewBtn being stuck in the top-left corner. Finally, there's various options you can use to tweak your NewBtn desires. REQUIRED: x - X position. Where is the button horizontally? y - Y position. Where do we put it vertically? fn - Function. The button had better do something!! Optional: text - Cute text to display on button. May size button automatically. x1 - Identical to the otherwise required x. If both present, x has priority x2 - The right side of the button y1 - Identical to y. y1 can take y's place, but if you make both, y is used y2 - The bottom side of the button width - Width of button. Would interfere with x2, but x2 takes priority. height- Height of button. Use if you don't feel like using y2. color - General color scheme of the button. This affects all aspects of color. MBC - Main Border Color. "Main" is when your mouse isn't over the button. MFC - Main Fill Color. ... I use "Main" for lack of better words... SBC - Sub Border Color. "Sub" is used when you're mousing over the button. SFC - Sub Fill Color. Anyone have better words for me? tag - Spare variable. Included functions: draw - Draws the button. Don't bother using it, it's automatically called. recolor- Modifies the color scheme. Takes in one value depicting base color. move - Moves the button so that the top-left corner matches the x,y input. While it is packaged for simple use as is, the more savvy programmers may want to read on. I pass the button object to the clicked button function as the only parameter. I can't think of any real way of passing variables you want for it, but I added the "tag" variable for this purpose. Technically, after the button is constructed, you can add new parameters to the button object itself, but the "tag" variable seemed like a convenient thing to add to the constructor. In addition, I put the HandleBtns function in gui.register by default, but go ahead and call it in another function and replace the gui.register callback. This function will return whatever the button function returns, so if it is in your interest to have the button function return something, it will do so through _FRK_Btn.HandleBtns. The NewBtn constructor returns the button object it created as a table for you to mess around with, should you have a reason to. Maybe you want to move it around or change its colors, who knows. Finally, _FRK_Btn.MB holds all my buttons. You can, in the middle of creating new buttons, set another variable (MB2, perhaps?), set _FRK_Btn.MB = {}, then create more buttons. You'll have two different sets of buttons which you can switch between, but you'll have to make your own functions to do the switch. But if you're not experienced in lua programming, none of the previous four paragraphs are of any interest to you. The simple use of these buttons are good enough for many uses. ]]-- _FRK_Btn= {} local EMUCHWID= 6 local EMUCHHGT= 8 if stylus then EMUCHWID= 6; EMUCHHGT= 8 elseif snes9x then EMUCHWID= 4; EMUCHHGT= 6 elseif VBA then EMUCHWID= 4; EMUCHHGT= 6 end _FRK_Btn.ClickType= "leftclick" _FRK_Btn.MB= {} keys, lastkeys= {}, {} function UpdateKeys() lastkeys= keys; keys= input.get() end function Press(k) return keys[k] and not lastkeys[k] end --***************************************************************************** function _FRK_Btn.FindBtn() --***************************************************************************** if (keys.xmouse == lastkeys.xmouse) and (keys.ymouse == lastkeys.ymouse) then return end local X,Y= keys.xmouse, keys.ymouse for i= 1, #_FRK_Btn.MB do -- linear scan, darn it! local n= _FRK_Btn.MB[i] if (Y >= n.y1) and (Y <= n.y2) and (X >= n.x1) and (X <= n.x2) then return i end end return -1 end --***************************************************************************** function _FRK_Btn.ClickBtn(id) --***************************************************************************** if id and _FRK_Btn.MB[id] then return _FRK_Btn.MB[id]:fn() end end --***************************************************************************** function _FRK_Btn.DrawUnsel(B) --***************************************************************************** gui.box(B.x1, B.y1, B.x2, B.y2, B.MFC, B.MBC) gui.text(B.xt, B.yt, B.text) end --***************************************************************************** function _FRK_Btn.DrawSel(B) --***************************************************************************** gui.box(B.x1, B.y1, B.x2, B.y2, B.SFC, B.SBC) gui.text(B.xt, B.yt, B.text) end --***************************************************************************** function _FRK_Btn.ShowBtns() --***************************************************************************** B= _FRK_Btn.MB for i= 1, #B do B[i]:draw() end end --***************************************************************************** function _FRK_Btn.Recolor(B,color) --***************************************************************************** -- Does not like strings. Still will accept them, however. local t= type(color) if t == "string" then -- Uh, no. No tricky color stuff. Sorry. B.MBC= Cns.color B.SBC= Cns.color B.MFC= 0x00000040 B.SFC= 0xC0C0C0C0 --Oh, not a string! Better be table or number, please. else if t == "table" then color= 0 local q= color.r or color[1] if q then color= bit.lshift(q,24) end q= color.g or color[2] if q then color= bit.bor(color,bit.lshift(q,16)) end q= color.b or color[3] if q then color= bit.bor(color,bit.lshift(q, 8)) end q= color.a or color[4] if q then color= bit.bor(color,q) else color= bit.bor(color,0xFF) end end B.MBC= color B.MFC= bit.rshift(bit.band(color,0xFEFEFEFE),1) B.SBC= bit.bor(color+bit.band(0xFFFFFFFF-color,0xFEFEFEFE)/2,0xFF) B.SFC= color end end --***************************************************************************** function _FRK_Btn.Move(B, x,y) --***************************************************************************** -- Moves the whole button for you! if x then local XDiff= x - B.x1 B.x1= x B.x2= B.x2 + XDiff B.xt= B.xt + XDiff end if y then local YDiff= y - B.y1 B.y1= y B.y2= B.y2 + YDiff B.yt= B.yt + YDiff end end --***************************************************************************** function _FRK_Btn.NewBtn(Cns) --***************************************************************************** -- the button constructor! --Insert universal function pointers while creating a new table. local NewTbl= { draw= _FRK_Btn.DrawUnsel, recolor= _FRK_Btn.Recolor, move= _FRK_Btn.Move } --Insert presumed information. NewTbl.fn= Cns.fn NewTbl.x1= Cns.x or Cns.x1 NewTbl.y1= Cns.y or Cns.y1 NewTbl.tag= Cns.tag --Check if user defined the right side. if Cns.x2 then NewTbl.x2= math.max(NewTbl.x1,Cns.x2) NewTbl.x1= math.min(NewTbl.x1,Cns.x2) elseif Cns.width then NewTbl.x2= NewTbl.x1 + Cns.width end --Check if user defined the bottom side. if Cns.y2 then NewTbl.y2= math.max(NewTbl.y1,Cns.y2) NewTbl.y1= math.min(NewTbl.y1,Cns.y2) elseif Cns.height then NewTbl.y2= NewTbl.y1 + Cns.height end --Look for text. Do tricky stuff based on where to put the darn text or --how to resize the button. if Cns.text then NewTbl.text= tostring(Cns.text) local len= #NewTbl.text * EMUCHWID if NewTbl.x2 then NewTbl.xt= math.floor((NewTbl.x1 + NewTbl.x2 - len)/2) else NewTbl.x2= NewTbl.x1 + len + 3 NewTbl.xt= NewTbl.x1 + 2 end if NewTbl.y2 then NewTbl.yt= math.floor((NewTbl.y1 + NewTbl.y2 - 8)/2) else NewTbl.y2= NewTbl.y1 + EMUCHHGT + 4 NewTbl.yt= NewTbl.y1 + 2 end else NewTbl.text= "" NewTbl.xt= NewTbl.x1 NewTbl.yt= NewTbl.y1 if not NewTbl.x2 then NewTbl.x2= NewTbl.x1 + 8 end if not NewTbl.y2 then NewTbl.y2= NewTbl.y1 + 8 end end --Mess with colors! if Cns.color then NewTbl:recolor(Cns.color) else NewTbl.MBC= Cns.MBC or 0x0020FFC0 NewTbl.MFC= Cns.MFC or 0x00000040 NewTbl.SBC= Cns.SBC or 0x0080FFFF NewTbl.SFC= Cns.SFC or 0x2000C0C0 end --Finally add the button to our MB array. local index= #(_FRK_Btn.MB)+1 _FRK_Btn.MB[index]= NewTbl --Here's the button object in case you want to handle it yourself. return NewTbl end local ID= -1 --***************************************************************************** function _FRK_Btn.HandleBtns() --***************************************************************************** UpdateKeys() local id= _FRK_Btn.FindBtn() if id then if ID ~= -1 then _FRK_Btn.MB[ID].draw= _FRK_Btn.DrawUnsel end if id ~= -1 then _FRK_Btn.MB[id].draw= _FRK_Btn.DrawSel end ID= id end _FRK_Btn.ShowBtns() if Press(_FRK_Btn.ClickType) and (ID ~= -1) then return _FRK_Btn.ClickBtn(ID) end end --============================================================================= if not FRK_NoAuto then --============================================================================= gui.register(_FRK_Btn.HandleBtns) NewBtn= _FRK_Btn.NewBtn end
Editor, Experienced Forum User, Published Author, Skilled player (1174)
Joined: 9/27/2008
Posts: 1085
Language: lua

local counter= 0 local function Fn() counter= counter+1 print(counter) end gui.register(Fn)
It... Works while paused! A thousand cheers for this progress! True, it runs slowly, and only when there's activity with either mouse or keyboard, but still, it runs while paused! This is a beautiful day!
Editor, Experienced Forum User, Published Author, Skilled player (1174)
Joined: 9/27/2008
Posts: 1085
Luck, uh... I've found not much. I've put zero effort in battles so far, as this WIP doesn't go in a single battle so far. Somehow, the thought didn't cross my mind to check this rather critical detail. Ask not how I managed to do this, even I'm not sure. I did, however, look through GameFAQs for a bit and spotted a topic that pointed me here, a link for a .rar file containing useful data. Apparently, a counter for random battles increments depending on what tile you step on. I get the impression you can't manipulate whether a random battle will occur by delaying your step, as depending on what square you go on to, a constant number will get added. Also, every enemy bases their actions depending on certain conditions, such as what turn number it is, whether some status is applied, or whatever. Still, there are places in their "thought process" where they will randomly decide which one of a few actions to use. But I still need to check up on the RNG stuff. I'll be looking into things now and can hopefully tell you what can be manipulated. Edit: I certainly won't mind if someone produces a quick encode. Then again, the contents of the run so far generally include messing with options, forming a party of LPSMA with names far longer than 1 character each, and doing the first mission to map out the first floor in record time. No battles in the straight-line walk, however. Edit2: I found the RNG (or a part of it), at addresses 020E9D04 and 020E9D08, both are 2-byte values. I'll call them R1 and R2. Unlike a few other things I've been looking for, these do not appear to change addresses in certain situations. This is good, as a simple memory watch is all we'll need to read them. The RNGs only change when the game needs a random number. Or I cheat and poke new values in there directly, but that's... Cheating. I even learn Cure for a Medic and even using that out of battle isn't changing any RNG. It's possible we need to save and reset to change up the RNG to something more favorable on occasion. Gathering materials at item points changes R2. Attack animations changes R2 rapidly for some reason. R1 apparently changes once per action or something. I haven't taken a close look at causes these changes yet. An initial R2 value is decided by the clock. I'm watching both R1 and R2 as I reset the game a few times, and while R1 seems to default to the same value of 0x04A8, R2 seems to be set to (second + minute*10 + hour*100). That gives an initial range of 0 to 2949 for R2, or 0x0000 to 0x0B85. Not as though these numbers mean anything yet for us, not until we get further information, of course. I'll keep looking. So far, the RNG seems to be trouble to manipulate. If nothing else, we probably can change the party formation so that enemies end up attacking exactly the ones who can take the hits.
Editor, Experienced Forum User, Published Author, Skilled player (1174)
Joined: 9/27/2008
Posts: 1085
A basic test run getting past the first test in the first game. As far as long range plans go, I have absolutely none of that. As of this moment, I'm not even remotely certain what to aim for. Or anything remotely considered a basic strategy at this point. But the mapping seems to have a lot of potential for entertainment. This has been a great test for my multitrack script, and I will probably continue to run something to help tweak out any flaws in said script (I found a flaw with DeSmuME 0.9.6's emu.frameadvance). Let it be this game, I suppose. But again, I'm quite worthless where long range plans are concerned. If I'm ever going to finish this, I will need help. There's no question about this. I'm a willing runner at this moment, but knowing myself, there's no guarantee I'll even come close to finishing, so I suppose I'll make the best start I can for others to work with. A little guidance, however, should help speed up progress. Knowing my lack of plans, I would otherwise hit every dead-end that doesn't help the more knowledgeable person.
Editor, Experienced Forum User, Published Author, Skilled player (1174)
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.
Post subject: adelikat, I'm posting here instead. Hope it works out better
Editor, Experienced Forum User, Published Author, Skilled player (1174)
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.
Post subject: Multitrack script for DeSmuME. Nearly there! (31 KB code)
Editor, Experienced Forum User, Published Author, Skilled player (1174)
Joined: 9/27/2008
Posts: 1085
Here's my Work-In-Progress script. Some stuff I planned is partly implemented but not working. There are ways to switch options around in massive quantities, but only a few of them can be switched individually at this moment. I'm using a way to handle input similar to what I did in this subtitles script. It works as a repeater for certain functions just fine. This is yet another rewrite of my Multitrack script. Hopefully, I've organized things even better than before. The rewrite is incomplete, but keeping it private wouldn't generate any interest in it (not as though posting it publicly here did any better...), so here it is now: Download Multitrack_DeSmuME.lua
Language: lua

--Multitracker v3 --Wow, I rewrite this script a LOT local PresetSwitch= "E" -- Switch between a few different presets --Display local solid= "numpad*" -- Make the display less local clear= "numpad/" -- or more transparant. local DispUp= "numpad8" local DispDn= "numpad2" -- For moving the local DispRt= "numpad6" -- display around. local DispLf= "numpad4" local MoreFutr= "numpad3" local LessFutr= "numpad9" -- These will determine local MorePast= "numpad7" -- how many frames you local LessPast= "numpad1" -- want to display. local ResetFP= "numpad5" local opt= "numpad+" -- For changing control options local Insert= "A" local Delete= "S" local BackupReadSwitch= "R" -- Revert to backup copy local BackupWriteSwitch= "F" -- Copy input into backup local BackupDisplaySwitch= "V" local MessageDuration= 400 local OptionPreset= { {name= "Edit Mode", DispX= 190, DispY= -70, Past= -12, Future= 12, opaque= 1, TJ= true , FJ= true , RJ= true , SJ= true , CS= true , RS= true , SS= true , StyP= -30, StyF= 30}, {name= "Standby", DispX= 150, DispY= -10, Past= 0, Future= 0, opaque= 0, TJ= true , FJ= true , RJ= false, SJ= false, CS= true , RS= false, SS= false, StyP= 0, StyF= 0}, {name= "Joypad Overwrite", DispX= 190, DispY= -90, Past= -12, Future= 16, opaque= 1, TJ= true , FJ= true , RJ= false, SJ= false, CS= true , RS= true , SS= true , StyP= -60, StyF= 60}, {name= "Stylus Overwrite", DispX= 190, DispY= -70, Past= -6, Future= 6, opaque= 1, TJ= true , FJ= true , RJ= true , SJ= true , CS= true , RS= false, SS= false, StyP= -10, StyF= 10} } local shade= 0x00000080 local white= 0xFFFFFFFF local red= 0xFF2000FF local green= 0x00FF00FF local mint= 0xC0FFC0FF local blue= 0x0040FFFF local orange=0xFFC000FF local yellow=0xFFFF00FF local cyan= 0x00FFFFFF local purple=0x8020FFFF local redish=0xC00000FF local ltgrey=0xC0C0C0FF local Clr= { white ,red ,orange,green, white ,red ,orange,green, mint ,purple,yellow,cyan, ltgrey,redish,orange,green, --Watermark ltgrey,redish,orange,green, mint ,purple,yellow,cyan } local WtrInterval= 10 --############################################################################# --############################################################################# --Global necessities local NULLTBL= {} -- I prefer not constructing a new table every time. local function NULLFN() end --You never know when you need to call nothing. local btn={"left","up","right","down","A","B","X","Y","L","R","start","select", "lid","debug"} local ThisInput= {} local ThisStylus={} local JoypadList= {} local StylusList= {} local BackupList= {} --Joypad only local ShowBackup= true local BackupOverride= false local JoypadInsert= 0 local JoypadBuffer= {} local StylusInsert= 0 local StylusBuffer= {} --local CopyBuffer_J= {} --local CupyBuffer_S= {} local TrueSwitch, FalseSwitch= {},{} local ReadJoy, StickyJoy= {},{} local DispX,DispY , Past,Future , StyP,StyF , Opaque local ControlStylus, ReadStylus, StickyStylus local WID , HGT= 256 , 192 local fc, lastfc= 0, 0 --***************************************************************************** local function UpdateFC() lastfc= fc; fc= movie.framecount() end --***************************************************************************** --My global "clock", so to speak. --***************************************************************************** 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 --***************************************************************************** --Range checking and range limiting functions. local MsgTmr= 0 local MsgTxt= "" local MsgClr= white --***************************************************************************** local function DisplayMessage() --***************************************************************************** if MsgTmr < MessageDuration then gui.text(1,-190,MsgTxt,MsgClr) MsgTmr= MsgTmr+1 end end --***************************************************************************** local function SetMessage(str,clr) --***************************************************************************** if not str then MsgTmr= math.huge; return end MsgTmr= 0 MsgTxt= str if clr then MsgClr= clr else MsgClr= white end end --############################################################################# --############################################################################# --Handle outside scripts --require("YummyButton") --local CreateBtn= {text= "Y", } --for i= 1, #btn do --end --############################################################################# --############################################################################# --Joypad handler --***************************************************************************** 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(inp, button) -- Expects... Certain numbers! --***************************************************************************** -- Returns true or false. True if the indicated button is pressed -- according to the input. False otherwise. return ( bit.band(inp , bit.lshift( 1, button )) ~= 0 ) end --***************************************************************************** local function LoadJoypad() --***************************************************************************** local joys= JoypadList[fc] if BackupOverride == "read" then joys= BackupList[fc] end for i= 1, #btn do if joys and ReadJoy[i] and ReadJoynum(joys, i) then ThisInput[btn[i]]= TrueSwitch[i] else ThisInput[btn[i]]= FalseSwitch[i] end end end --***************************************************************************** local function RegBeforeHandleJoypad() --***************************************************************************** --DeSmuME, emu.registerbefore. Not portable across emulators. --If the "invert" option existed like in FCEUX, this function would be one line local joys= joypad.peek() for i= 1, #btn do if type(ThisInput[btn[i]]) == "string" then joys[btn[i]]= not joys[btn[i]] else joys[btn[i]]= ThisInput[btn[i]] end end joypad.set(joys) end --***************************************************************************** local function RegAfterHandleJoypad() --***************************************************************************** --Just say fc is properly updated by now. local joys= JoyToNum(joypad.read()) JoypadList[fc-1]= joys if (not BackupList[fc-1]) or BackupOverride == "write" then BackupList[fc-1]= joys end LoadJoypad() end Jo, LastJo= joypad.peek(), joypad.peek() --***************************************************************************** local function ScanJoypad() --***************************************************************************** LastJo= Jo Jo= joypad.peek() for i= 1, #btn do if (not Jo[btn[i]]) and LastJo[btn[i]] and StickyJoy[i] then if ThisInput[btn[i]] then ThisInput[btn[i]]= FalseSwitch[i] else ThisInput[btn[i]]= TrueSwitch[i] end end end end --############################################################################# --############################################################################# --Stylus handler --***************************************************************************** local function StyToNum(Stys) -- Expects a table containing stylus control --***************************************************************************** -- Returns a number representing stylus position. -- Low numbers are x, high is y. if not Stys.touch then return false end return Stys.x + bit.lshift(Stys.y,8) end --***************************************************************************** local function ReadStynum(inp) --***************************************************************************** -- Returns two values, x,y. If not pressed, returns false,false instead. if not inp then return false , false end return bit.band(inp,0xFF) , bit.rshift(bit.band(inp,0xFF00),8) end --***************************************************************************** local function ClearStylus() --***************************************************************************** ThisStylus.touch= false ThisStylus.x= 0 ThisStylus.y= 0 end --***************************************************************************** local function LoadStylus() --***************************************************************************** local x,y= ReadStynum(StylusList[fc]) if x then ThisStylus.touch= true; ThisStylus.x= x; ThisStylus.y= y else ClearStylus() end end --***************************************************************************** local function RegBeforeHandleStylus() --***************************************************************************** local Stys= stylus.peek() if not ControlStylus then Stys.touch= false end if (not Stys.touch) and ThisStylus.touch then stylus.set(ThisStylus) end end --***************************************************************************** local function RegAfterHandleStylus() --***************************************************************************** --fc is updated by this point. Joy. StylusList[fc-1]= StyToNum(stylus.get()) if ReadStylus then LoadStylus() end end --***************************************************************************** local function ScanStylus() --***************************************************************************** if StickyStylus then local Poke= stylus.peek() if Poke.touch then ThisStylus.x= Poke.x ThisStylus.y= Poke.y ThisStylus.touch= true end end end --############################################################################# --############################################################################# --Display --***************************************************************************** local function FakeBox(x1,y1,x2,y2,fill) --***************************************************************************** --Gets around DeSmuME's problem of boxes that cross over top and touch screen. --Fills only. No support for borders, yet. local test = (y1 < 0) if (y2 < 0) then test= not test end if test then gui.box(x1,y1,x2, 0,fill) gui.box(x1, 0,x2,y2,fill) else gui.box(x1,y1,x2,y2,fill) end end --***************************************************************************** local function FakeBox2(x1,y1,x2,y2,fill,border) --***************************************************************************** local test = (y1 < 0) if (y2 < 0) then test= not test end if test then gui.line(x1,y1,x2,y1,border) gui.line(x1,y2,x2,y2,border) if not within(y1,y2-1,y2+1) then -- ... Forget it... end else gui.box(x1,y1,x2,y2,fill,border) end end --***************************************************************************** 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.box(x ,y ,x+2,y+1,color) -- ### gui.pixel(x ,y+2,color) -- # # gui.pixel(x+2,y+2,color) end function Draw.B(x,y,color) -- ### gui.line(x ,y ,x+2,y ,color) -- # gui.line(x ,y+2,x+2,y+2,color) -- ### gui.pixel(x ,y+1,color) end function Draw.X(x,y,color) -- # # gui.line(x ,y ,x+2,y+2,color) -- # gui.pixel(x ,y+2,color) -- # # gui.pixel(x+2,y ,color) end function Draw.Y(x,y,color) gui.line(x+1,y+1,x+1,y+2,color) -- # # gui.pixel(x ,y ,color) -- # gui.pixel(x+2,y ,color) -- # end function Draw.L(x,y,color) -- # gui.line(x ,y+2,x+2,y+2,color) -- # gui.line(x ,y ,x ,y+1,color) -- ### end function Draw.R(x,y,color) gui.line(x ,y ,x ,y+2,color) -- ## gui.line(x+1,y ,x+1,y+1,color) -- ## gui.pixel(x+2,y+2,color) -- # # end function Draw.start(x,y,color) -- # gui.line(x+1,y ,x+1,y+2,color) -- ### gui.pixel(x ,y+1,color) -- # gui.pixel(x+2,y+1,color) end function Draw.select(x,y,color) -- ### 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 function Draw.lid(x,y,color) -- # gui.line(x ,y ,x+2,y+2,color) -- ## gui.line(x ,y+1,x+1,y+2,color) -- ## end function Draw.debug(x,y,color) -- # gui.pixel(x+1,y ,color) gui.pixel(x ,y+2,color) -- # # gui.pixel(x+2,y+2,color) end function Draw.stylus(x,y,color) -- ### gui.line(x ,y ,x+2,y ,color) -- # gui.line(x ,y+2,x+2,y+2,color) -- ### gui.pixel(x+1,y+1,color) end --***************************************************************************** local function ShowFrame(x,y,input) --***************************************************************************** --DeSmuME specific display. It has no separate players to scan. --input = Absolute frame local WtrMrk= 1 if input%WtrInterval == 0 then WtrMrk= 13 end x= x-4 for i= 1, #btn do local clrnum= WtrMrk --Display is rough... Use this color, but if Tuesday, that color instead! if input == fc then local test= ThisInput[btn[i]] if test == "inv" then test= not Jo[btn[i]] elseif test == nil then test= Jo[btn[i]] end if test then clrnum= clrnum+3 else clrnum= clrnum+1 end elseif JoypadList[input] then if ReadJoynum(JoypadList[input], i) then if (input > fc) and not ReadJoy[i] then clrnum= clrnum+2 -- Ignored else clrnum= clrnum+3 -- Pressed end else clrnum= clrnum+1 -- Not pressed end end if ShowBackup and BackupList[input] then if ReadJoynum(BackupList[input], i) then clrnum= clrnum+8 -- Oh, there's a backup else clrnum= clrnum+4 -- Backup not pressed end end Draw[btn[i]](x+i*4,y,Clr[clrnum]) end end local MaxRange= math.floor(HGT/2 - 3) local width= #btn*4 --***************************************************************************** local function JoypadDisplay() --***************************************************************************** local up, dn if Past <= -1 then up= DispY + Past*4 -3 dn= DispY + math.min(-1,Future)*4 +1 FakeBox(DispX,up,DispX+width,dn,shade) end if Future >= 1 then up= DispY + math.max(1,Past)*4 +1 dn= DispY + Future*4 +5 FakeBox(DispX,up,DispX+width,dn,shade) end if Past <= 0 and Future >= 0 then local color= blue for i= 1, #btn do if not JoypadList[fc] then color= yellow; break end local test = ThisInput[btn[i]] if ReadJoynum(JoypadList[fc], i) then test= not test end if test then color= green; break end end gui.box(DispX-1, DispY-2, DispX+width+1, DispY+4, shade,color) end local X= DispX+1 for i= Past, Future do local Y= DispY + i*4 if i < 0 then Y= Y-2 elseif i > 0 then Y= Y+2 end local FC= fc+i ShowFrame(X,Y,FC) if ShowBackup and BackupList[FC] then local color= green if (BackupList[FC] == JoypadList[FC]) then color= blue end gui.line(DispX-2,Y,DispX-2,Y+2,color) end end end --***************************************************************************** local function StylusDisplay1() --***************************************************************************** -- Pixel trail version -- Anyone with better ideas? It's not particularly easy to see these pixels. local x,y,color local cmp local tot if StyP == -1 then x,y= ReadStynum(StylusList[fc-1]) if x then gui.pixel(x,y,0xFF8000FF) end else cmp= (-256)/(StyP+1) tot= 0 for i= StyP, -1 do x,y= ReadStynum(StylusList[fc+i]) color= (0xFF00007F + math.floor(math.min(tot,0x80)) + bit.lshift(math.floor(math.max(tot-0x80,0)),16) ) if x then gui.pixel(x,y,color) end tot= tot+cmp end end if StyF == 1 then x,y= ReadStynum(StylusList[fc+1]) if x then gui.pixel(x,y,0x0080FFFF) end else cmp= (256)/(StyF-1) tot= 0 for i= StyF, 1, -1 do x,y= ReadStynum(StylusList[fc+i]) color= (0x0000FF7F + math.floor(math.min(tot,0x80)) + bit.lshift(math.floor(math.max(tot-0x80,0)),16) ) if x then gui.pixel(x,y,color) end tot= tot+cmp end end x,y= ReadStynum(StylusList[fc]) if x then gui.pixel(x,y,green) end if ThisStylus.touch then gui.pixel(ThisStylus.x,ThisStylus.y,white) end end --***************************************************************************** local function NormalizeDisplay() --***************************************************************************** DispX= Limits(DispX,2,WID-width-2) DispY= Limits(DispY,-HGT-Past*4+3,HGT-Future*4-6) end local btnswitches= {TrueSwitch, FalseSwitch, ReadJoy, StickyJoy} local TrueVals= { "inv" , nil , true , true } local Mat= {} --***************************************************************************** local function OptionsDisplay() --***************************************************************************** for i= 1, #btnswitches do for j= 1, #btn do local txt= "N" if btnswitches[i][j] == TrueVals[i] then txt= "Y" end gui.text(10*j, 8*i, txt) end end end --############################################################################# --############################################################################# --Handle user input local lastkeys, keys= input.get(), input.get() --***************************************************************************** local function UpdateKeys() lastkeys= keys; keys= input.get() end --***************************************************************************** local repeater= 0 local KF, RF --KeyFunction, RepeaterFunction --***************************************************************************** local function KeyReader() --***************************************************************************** UpdateKeys() for k,v in pairs(keys) do local ThisKey= k if keys.control then ThisKey= "c+" .. k end if not lastkeys[k] then repeater= 0 if KF[ThisKey] then KF[ThisKey]() end if RF[ThisKey] then RF[ThisKey]() end end if repeater >= 3 then if RF[ThisKey] then RF[ThisKey]() end end end repeater= repeater+1 end --***************************************************************************** local function MouseHandler() --***************************************************************************** --KeyReader should be called first, and that updates our keys already! if (not keys.leftclick) or (not lastkeys.leftclick) then return end if keys.control then DispX= DispX + keys.xmouse - lastkeys.xmouse DispY= DispY + keys.ymouse - lastkeys.ymouse else ScanStylus() end end --***************************************************************************** local function MouseHandler2() --***************************************************************************** if keys.rightclick and lastkeys.rightclick then DispX= DispX + keys.xmouse - lastkeys.xmouse DispY= DispY + keys.ymouse - lastkeys.ymouse end ScanStylus() end local KF1, KF2, RF1= {}, {}, {} ------------------------------------------------------------------------------- RF1[solid]= function() Opaque= math.min(Opaque + .125 , 1) end RF1[clear]= function() Opaque= math.max(Opaque - .125 , 0) end ------------------------------------------------------------------------------- RF1[DispUp]= function() DispY= DispY-1 end RF1[DispDn]= function() DispY= DispY+1 end RF1[DispRt]= function() DispX= DispX+1 end RF1[DispLf]= function() DispX= DispX-1 end ------------------------------------------------------------------------------- RF1[MoreFutr]= function() Future= Future+1; Past= math.max(Past,Future-MaxRange) end RF1[LessFutr]= function() Future= Future-1; Past= math.min(Past,Future) end RF1[MorePast]= function() Past = Past -1; Future= math.min(Future,Past+MaxRange) end RF1[LessPast]= function() Past = Past +1; Future= math.max(Future,Past) end KF1[ResetFP]= function() Future= 12; Past= -12 end ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- KF1.space= function() --Mostly a hack ... I'm out of joypad stuff... ------------------------------------------------------------------------------- if ThisStylus.touch then ClearStylus() else LoadStylus() end end if false then ------------------------------------------------------------------------------- KF1[BackupDisplaySwitch]= function() ------------------------------------------------------------------------------- end KF2[OptUp]= function() end KF2[OptDn]= function() end KF2[OptRt]= function() end KF2[OptLf]= function() end KF2[OptHit]= function() end end local HandleDisplay, UsualDisplay ------------------------------------------------------------------------------- KF1[opt]= function() ------------------------------------------------------------------------------- KF= KF2 RF= NULLTBL HandleDisplay= OptionsDisplay end --Cheap functions. Bad on runtime with enough frames stacked up. ------------------------------------------------------------------------------- KF1[Insert]= function() ------------------------------------------------------------------------------- local frame= fc while JoypadList[frame] do frame=frame+1 end for i= frame, (fc+1), -1 do JoypadList[i]= JoypadList[i-1] StylusList[i]= StylusList[i-1] end JoypadList[fc]= 0 StylusList[fc]= false LoadJoypad() LoadStylus() end ------------------------------------------------------------------------------- KF1[Delete]= function() ------------------------------------------------------------------------------- local i= fc while JoypadList[i] do JoypadList[i]= JoypadList[i+1] StylusList[i]= StylusList[i+1] i=i+1 end LoadJoypad() LoadStylus() end ------------------------------------------------------------------------------- KF1[BackupReadSwitch]= function() ------------------------------------------------------------------------------- if BackupOverride == "read" then BackupOverride= false SetMessage("End backup read",white) else BackupOverride= "read" SetMessage("Reading from backup list...",green) end end ------------------------------------------------------------------------------- KF1[BackupWriteSwitch]= function() ------------------------------------------------------------------------------- if BackupOverride == "write" then BackupOverride= false SetMessage("End backup overwrite",white) else BackupOverride= "write" SetMessage("Merging input into backup...",yellow) end end ------------------------------------------------------------------------------- KF1[BackupDisplaySwitch]= function() ShowBackup= not ShowBackup end ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- KF2[opt]= function() ------------------------------------------------------------------------------- KF= KF1 RF= RF1 HandleDisplay= UsualDisplay end local OC= 0 ------------------------------------------------------------------------------- KF1[PresetSwitch]= function() ------------------------------------------------------------------------------- OC= (OC % #OptionPreset)+1 SetMessage(OptionPreset[OC].name,green) DispX= OptionPreset[OC].DispX DispY= OptionPreset[OC].DispY Past= OptionPreset[OC].Past Future=OptionPreset[OC].Future Opaque=OptionPreset[OC].opaque StyP= OptionPreset[OC].StyP StyF= OptionPreset[OC].StyF if OptionPreset[OC].TJ then for i= 1, #btn do TrueSwitch[i]= "inv" end else for i= 1, #btn do TrueSwitch[i]= true end end if OptionPreset[OC].FJ then for i= 1, #btn do FalseSwitch[i]= nil end else for i= 1, #btn do FalseSwitch[i]= false end end for i= 1, #btn do ReadJoy[i]= OptionPreset[OC].RJ StickyJoy[i]= OptionPreset[OC].SJ end ControlStylus= OptionPreset[OC].CS ReadStylus= OptionPreset[OC].RS StickyStylus= OptionPreset[OC].SS LoadJoypad() LoadStylus() end KF1[PresetSwitch]() --############################################################################# --############################################################################# --Top level routines to actually run my stuff. KF= KF1 RF= RF1 --***************************************************************************** function UsualDisplay() --***************************************************************************** if Opaque > 0 then gui.opacity(Opaque) NormalizeDisplay() JoypadDisplay() StylusDisplay1() gui.opacity(1) end end HandleDisplay= UsualDisplay --***************************************************************************** local function UnpausedSanityScan() --***************************************************************************** --For unpaused stateload with this script running. --Ensures we have the proper input when the frame count spontaneously changes. UpdateFC() if fc ~= lastfc then if BackupOverride then SetMessage("Auto-ended backup read/write",white) BackupOverride= false end LoadJoypad() LoadStylus() end end --***************************************************************************** local function LoadFix() --***************************************************************************** local _fc= movie.framecount() if Within(_fc,fc,fc+1) then return end UpdateFC() if BackupOverride then SetMessage("Auto-ended backup read/write",white) BackupOverride= false end LoadJoypad() LoadStylus() end --***************************************************************************** local function RegBefore() --***************************************************************************** UnpausedSanityScan() RegBeforeHandleJoypad() RegBeforeHandleStylus() end emu.registerbefore(RegBefore) --***************************************************************************** local function RegAfter() --***************************************************************************** UpdateFC() RegAfterHandleJoypad() RegAfterHandleStylus() if keys.space then KF1.space() end --Hack end emu.registerafter(RegAfter) --***************************************************************************** local function RegGui() --***************************************************************************** LoadFix() KeyReader() MouseHandler2() ScanJoypad() HandleDisplay() DisplayMessage() end gui.register(RegGui)
I found a glitch with DeSmuME's frameadvance. Have this as the only piece of code: while true do emu.frameadvance() end Now run the one-line script and use the frame advance key. The emulator's frame counter will increment, but no actual emulation will take place. Pausing and unpausing the emulation still works fine, however. The window header of the version I'm using reads DeSmuME 0.9.6 x86.
Editor, Experienced Forum User, Published Author, Skilled player (1174)
Joined: 9/27/2008
Posts: 1085
Considering some effort I went through, I have a pretty good idea on what's happening, even with the language barrier. Most of the run seems to be trying to get enough MAGI to use the Trash Can Glitch. The Dragon Race Warp is quite familiar to me as well. What's not so familiar is the fact the one monster transforms into powerful things early on, or the strange inventory of the robots. The early teleport is nice, too. What I want to know is what is unique in JPN that isn't in USA. Let us know of these glitches when there is time.
Editor, Experienced Forum User, Published Author, Skilled player (1174)
Joined: 9/27/2008
Posts: 1085
To the extent of my observation, the "After" register is called after the user hits the frame advance key (or let frames get played out while running), but before "Boundary" or "Before" runs. However, I've only used GB ROMs, and not GBA ROMs, so if the lua reacts any differently here, this would be news to me. I have not been able to read the joypad then immediately change the input feed to the emulation successfully. "After" has been the closest thing I've got to instant user control through frame-advance, and even then, I need to call upon input.get instead of joypad.get with script-specific controls overriding the usual emulator controls. The fact "Gui" is updated potentially less frequently due to frame skip makes sense. After all, it should be kind of tied to the display, so if it isn't updating the display, it doesn't surprise me that "Gui" isn't called on those undisplayed frames.
Editor, Experienced Forum User, Published Author, Skilled player (1174)
Joined: 9/27/2008
Posts: 1085
Oh, snap. Is there a Gens Multitrack script? If not, I'm going to make one ASAP. If there is, link to it soon, please. There's like one or two people using my Multitrack scripts, not including me, to the extent of my knowledge. I would love to see more use come of it. EDIT: I've just been informed Gens has multitrack built-in. Silly me... Regardless, think I should try to port a multitrack script over anyway?
Editor, Experienced Forum User, Published Author, Skilled player (1174)
Joined: 9/27/2008
Posts: 1085
I've "finished" my subtitles script, at least, as far as getting the most basic features I want in there. It's right here, in case you are too lazy to scroll two posts up to see the giant white blot. Feel free to try it out and see how well it works out. It is tweaked for use with DeSmuME, but I'm sure a little adjustment of numbers near the top would have it work well enough for other emulators that use fixed-width text. Also feel free to cannibalize any piece of code in there for your use. I'm sure someone will find use for having a way to type in any arbitrary text they'd like from the emulator window.
Post subject: How emulators handle joypad and inputs from lua.
Editor, Experienced Forum User, Published Author, Skilled player (1174)
Joined: 9/27/2008
Posts: 1085
I have played around with only four emulators: - FCEUX - Snes9x - VBA - DeSmuME I can only tell you what I know of these ones, as I haven't tried out the others. However, there are subtle differences that will come up if you attempt to handle the joypads in another emulator. The exact same thing you've been doing in one emulator may completely fail in another. First, I will refer to one of four registers, "Boundary", "Before", "After", and "Gui". A joypad.get in one register may not necessarily give the same result as joypad.get in another register. Some details of what exactly I mean about them: "Boundary" - A piece of code you typically place in a loop with emu.frameadvance(). "Before" - Created a function that's passed to emu.registerbefore as a parameter. Typically, this is after the framecount and joypads are updated, but before the actual frame of emulation takes place. Apparently, not always true among all emulators. "After" - A function passed to emu.registerafter. Usually, this would be after the frame is emulated. Apparently, this isn't always true between emulators. "Gui" - Function passed to gui.register. This function would be called when the emulator updates the display. For some, this happens only once per frame. For others, they repeatedly call this function while paused, which is a mighty useful effect when dealing with user input. In addition, the following functions are the focus of this post: joypad.get - Usually takes a number representing the player whose input you wish to read. It simply reads the joypad, whatever state it may be in at the time. joypad.peek - Usually takes a number, like with joypad.get. However, it reads whatever keys the user wants to press, rather than the previous frame's input. The difference is subtle, but very clear if it's possible to run the lua code while the emulation is paused -- get will always return the same table, but peek will keep changing with the user's key presses while the emulation is paused. joypad.set - Usually takes a number representing the player, and always takes a table representing the input you want done next frame. Depending on the register you place this in, it may take place immediately, wait until next frame, or is completely ignored. input.get - No parameters. Returns the state of the keyboard and mouse in a table. When dealing with joypad input, you usually don't want to use this, unless you're using a piece of "run-while-paused" code without joypad.peek available. However, this does open another avenue of working with user input, and is useful for creating runtime options in more complex scripts. I have gone into detail of each component I will talk about as I analyze each emulator I have tried. I wish for you to get a brief understanding as I do about each component so that you can read that which follows with an intuition that better approximates my own. I would prefer that you don't get lost while reading my stuff. But now's the time I go into each emulator I have tried... FCEUX 2.1.4 joypad.get returns the previous frame's input on "Boundary", and the current frame's input on "Before", "After", and "Gui". Reads movie input. There is no joypad.peek in this emulator, as of time of this writing. joypad.set will apply the input immediately on "Boundary", and will apply it next frame in the other registers. "Gui" has the advantage of running while emulation is paused, but I recommend having it affect another variable that "Boundary" can then read and set the joypads as appropriate. joypad.set has four options for each button, giving complete control: - true (force the button to be pressed) - false (force the button unpressed) - nil (leave control of the button to the user) - "invert" (reverses the user's control of the button) Critical error: With how get and set works, it is impossible to read the joypad on the current frame, decide what it means, then change the joypad before emulation needs it. On "Boundary", you pick up the previous frame's input, not the current frame. On "Before", you get the current frame's input, but joypad.set() can't do a thing to the joypad now. This feels like a design issue to me. However, the fact joypad.set does allow for four options and the fact "Gui" is run while emulation is paused gives a great deal of control. Should joypad.peek be implemented, I hope to see it gain perfect control. Snes9x 1.51 v6 joypad.get gets the current frame's input in any register, even "Boundary". It will read changes made by lua, but lua won't get the chance to do so at "Boundary". Reads movie input. There is no joypad.peek in this emulator, as of time of this writing. joypad.set will apply the input immediately in "Boundary" and is completely ignored in any other register. This has caused great confusion in me when I was transitioning from FCEUX to Snes9x lua, but I since learned emulators act differently. It is not possible to have code run while emulation is paused, except with a fake pause by repeatedly loading the same savestate every frame. joypad.set has only two options, however: - nil (forces the button unpressed) - anything not nil (forces the button to be pressed) A critical error in joypad manipulation, such as one seen in FCEUX and VBA, is averted, due to the fact you get the immediate input and can change it before the emulation updates. I am quite thankful for this, as there's no way to run code while emulation is paused. There is a lua-side workaround to the limited joypad options: Download JoypadSetSnes9x.lua
Language: lua

function JoypadSetSnes9x(player,inputs) local TempInput= joypad.get(player) for btn,val in pairs(inputs) do -- This inherently skips nil if not val then -- it's obviously set to false by user. TempInput[btn]= false elseif type(val) == "string" then -- Mimic FCEUX's "invert" option TempInput[btn]= not TempInput[btn] else -- It effectively evaluates to true otherwise. TempInput[btn]= true end end joypad.set(player, TempInput) end
The joypad control is done quite well here. Although it lacks the four options like FCEUX, a lua-side workaround is possible thanks to the fact you can read and manipulate the joypad before the emulation gets a hold of it. I just only wish that lua can freely run while emulation is paused so that we can get perfect control. VBA v22 joypad.get gets the previous frame's input on "After" and the current frame's input on "Boundary", "Before", and "Gui". Movie data is not read. There is no joypad.peek in this emulator, as of time of this writing. joypad.set will apply the input immediately on "After", and waits until the next frame on "Bondary", "Before", and "Gui". I fought hard trying to figure out what logic the darn emulator was using. I finally got it: "After" takes place before "Boundary", "Before", and "Gui", so any variables set in the latter three registers isn't seen by "After" until the next frame. Like with Snes9x, there's no way to run lua code while emulation is paused, except hacking it by repeatedly loading the same state every frame. joypad.set has exactly two options, just like in Snes9x: - nil (forces the button unpressed) - anything not nil (forces the button to be pressed) Critical error: It is impossible to read the current joypad to make decisions of the current situation. There are no joypad-related workarounds from lua-side. If you absolutely need to hijack joypad control from lua while still reading the user's input intelligently, do it in "After" and use input.get instead. This completely negates the user's setup from the emulator side, but it's at least something. Furthermore, with how the registers work, you can't mimic FCEUX's joypad.set options from lua. There's also no way to run lua code while emulation is paused. Then there's the fact it doesn't read movie input. The whole thing combines into a horrible mess that I have a very difficult time working with. Someone. Hear my plea. Get joypad.get to read movie data. Fix the registers so that I can get and immediately set before the emulation updates. Allow code to run while paused. I don't know how, but if you developers can do it, you will have my thanks. As it stands, VBA is torturous to use in my attempts to produce a viable multitrack script. DeSmuME 0.9.6 joypad.get reads the previous frame's input in "Boundary" and "Before". I haven't checked if it reads movie data. There's also a separate stylus.get function which works similarly, except for use with the touchscreen controls. joypad.peek works well in this emulator. It picks up whatever buttons the user is holding at the time. As well, there is stylus.peek like above. joypad.set immediately applies input on "Boundary" and "Before". Similarly, stylus.set works as such, too. Code can run while paused under "Gui". Excellent... joypad.set has three options for each button. Almost there, but not quite: - true (force the button to be pressed) - false (force the button unpressed) - nil (leave control of the button to the user) No critical errors here. Although joypad.get and joypad.set are incompatible together like with VBA and FCEUX, joypad.peek gets around that problem by looking at the user's input rather than the previous frame's input. If you want the "invert" option that exists for FCEUX, use the Snes9x code I've written up there, except replace joypad.get with joypad.peek and get rid of all instances of the identifier player (and accompanying comma, if any). Of the four emulators I've tried, this is the only one to provide perfect joypad control through lua. I'd tweak joypad.set to follow the four options present in FCEUX, but otherwise, I'm pretty satisfied with what's there. A great job has been done here. Seeing as I haven't tried other emulators, this is where I stop. Let me know of anything I missed. I want to at least ensure someone is aware that the joypad manipulation for one emulator will generally break horribly in another. Your Snes9x joypad.get & joypad.set manipulation will fail in FCEUX and VBA, and you need to be aware of joypad.peek to get it to work right in DeSmuME. Remember, these are simply my views on how each emulator works. I'm not saying my word is the absolute truth, I could be wrong in a few places. I started this thread so that any problems are more visible and hopefully help others to come up with tests of their own to see the inner mechanics of the lua in each emulator.
Editor, Experienced Forum User, Published Author, Skilled player (1174)
Joined: 9/27/2008
Posts: 1085
Download DoNotBotherRunningIt.lua
Language: lua

--I shall demonstrate the forum's ineptitude in reading the --backslash as an escape character for another backslash! local str1= "Nothing wrong here" local str2= "But lookie here\\" local str3= "It did not read the backslashes right!" print("So now what am I supposed to do?")
... Man, I'm real bad at this lua stuff. I'm not even using the strings I just created! This is a cleverly disguised bug report. See if you can find it!
Editor, Experienced Forum User, Published Author, Skilled player (1174)
Joined: 9/27/2008
Posts: 1085
Here's effectively what I have, working with DeSmuME's implementation of lua: Download SubtitleMakerV1.lua
Language: lua

--FatRatKnight local BIGNUM= 999999999 -- Even Desert Bus has only 216270341 frames! local BypassFrameAdvanceKey= "N" --Set this to the frameadvance key. local SubtitlesFile= "UnlikelyDuplicateFileName42.lua" local WID= 6 -- Width of each character. DeSmuME uses 6. local HGT= 9 -- Height. I'm arbitrarily using 9 here. local identifier -- List of commands: local DelChar= "backspace" -- Delete a single character local NewLine= "enter" -- New line local LeftArr= "left" local RightAr= "right" local UpArrow= "up" local DownArr= "down" local Del_Sub= "c+X" -- Erase the whole subtitle. Hit twice to confirm local Kil_Sta= "c+G" -- Sets the subtitle's start to frame zero local Set_Sta= "c+H" -- Sets the subtitle's start to now local Kil_End= "c+T" -- Sets the subtitle's end to BIGNUM local Set_End= "c+Y" -- Sets the subtitle's end to now local SaveTxt= "c+S" -- Saves subtitles to file local NextSub= "numpad3" -- Selects the next active subtitle local ResetPs= "numpad5" -- Sets subtitle's x,y to 1,1 local TglStat= "numpad6" -- Show/hide information about selected subtitile local S_Count= "home" -- Counts current number of stored subtitles local T_AutoS= "insert" -- Toggles whether the script saves on exit local white= 0xFFFFFFFF local red= 0xFF0000FF local fade= 0xFFFFFF80 local f_red= 0xFF000080 --***************************************************************************** function FBoxOld(x1, y1, x2, y2, color) --***************************************************************************** -- Gets around the problem of double-painting the corners. -- It acts like the old-style border-only box. if (x1 == x2) and (y1 == y2) then -- Sanity: Is it a single dot? gui.pixel(x1,y1,color) elseif (x1 == x2) or (y1 == y2) then -- Sanity: A straight line? gui.line(x1,y1,x2,y2,color) else --(x1 ~= x2) and (y1 ~= y2) local temp if x1 > x2 then temp= x1; x1= x2; x2= temp -- Sanity: Without these checks, end -- This function may end up putting if y1 > y2 then -- two or four out-of-place pixels temp= y1; y1= y2; y2= temp -- near the corners. end gui.line(x1 ,y1 ,x2-1,y1 ,color) -- top gui.line(x2 ,y1 ,x2 ,y2-1,color) -- right gui.line(x1+1,y2 ,x2 ,y2 ,color) -- bottom gui.line(x1 ,y1+1,x1 ,y2 ,color) -- left end end --***************************************************************************** function FakeBox(x1, y1, x2, y2, Fill, Border) --***************************************************************************** -- Gets around the problem of double-painting the corners. -- It acts like the new-style fill-and-border box. Not perfectly, however... if not Border then Border= Fill end gui.box(x1,y1,x2,y2,Fill,0) FBoxOld(x1,y1,x2,y2,Border) end local BigNum= 0xFFFFFFFF + 1 -- 0x100000000 doesn't parse right for my needs. --***************************************************************************** local function ColorGlitch(color) --***************************************************************************** --There is a glitch in FCEUX and VBA where inserting a number with red --represented as 0x80 or higher does not display properly. This function --changes this number to work with this glitch. if color >= 0x80000000 then color = color-BigNum end return color end --***************************************************************************** local function ProcessColors() --***************************************************************************** white= ColorGlitch(white) red= ColorGlitch(red) fade= ColorGlitch(fade) f_red= ColorGlitch(f_red) end local box= gui.box if stylus then --DeSmuME. Platform: DS WID= 6; HGT= 9; elseif snes9x then --Snes9x. Platform: SNES WID= 4; HGT= 8; box= FakeBox elseif vba then --VisualBoy Advance. Platforms: GBA, GB, GBC, SGB WID= 4; HGT= 8;ProcessColors() elseif FCEU then --FCE Ultra / FCEUX. Platform: NES. WID= 2; HGT= 9;ProcessColors();box= FakeBox end local KeyTable= { a="a",b="b",c="c",d="d",e="e",f="f",g="g",h="h",i="i",j="j",k="k",l="l",m="m", n="n",o="o",p="p",q="q",r="r",s="s",t="t",u="u",v="v",w="w",x="x",y="y",z="z", tilde="`",minus="-",plus="=", leftbracket="[",rightbracket="]",backslash="\\", semicolon=";",quote="'", comma=",",period=".",slash="/", A="A",B="B",C="C",D="D",E="E",F="F",G="G",H="H",I="I",J="J",K="K",L="L",M="M", N="N",O="O",P="P",Q="Q",R="R",S="S",T="T",U="U",V="V",W="W",X="X",Y="Y",Z="Z", TILDE="~",MINUS="_",PLUS="+", LEFTBRACKET="{",RIGHTBRACKET="}",BACKSLASH="|", SEMICOLON=":",QUOTE="\"", COMMA="<",PERIOD=">",SLASH="?", numpad1="1",numpad2="2",numpad3="3",numpad4="4",numpad5="5", numpad6="6",numpad7="7",numpad8="8",numpad9="9",numpad0="0", space= " ", SPACE= " "} KeyTable["1"]="!" KeyTable["2"]="@" KeyTable["3"]="#" KeyTable["4"]="$" KeyTable["5"]="%" KeyTable["6"]="^" KeyTable["7"]="&" KeyTable["8"]="*" KeyTable["9"]="(" KeyTable["0"]=")" local S= {} local SubIndex= 1 -- Spot in array object is found local Sb= {"Test",x=4,y=4,s=0,e=BIGNUM,i=1} local ln, ch= 1, 0 S[1]= Sb local lastkeys, keys= input.get(), input.get() --***************************************************************************** local function UpdateKeys() lastkeys= keys; keys= input.get() end --***************************************************************************** --***************************************************************************** local function within(v,l,h) return (v>=l) and (v<=h) end --***************************************************************************** --***************************************************************************** local function SortSubs() --***************************************************************************** table.sort(S, function (a,b) return (a.s < b.s) end) for i= 1, #S do S.i= i end end --***************************************************************************** local function GetRight(subtitle) --***************************************************************************** local x= 0 for i= 1, #subtitle do x= math.max( x, #(subtitle[i]) ) end return x*WID + subtitle.x + 1 end --***************************************************************************** local function GetBottom(subtitle) --***************************************************************************** return #subtitle*HGT + subtitle.y + 1 end --***************************************************************************** local function ClickedSubGeneral(x,y) --***************************************************************************** -- Horribly inefficient way: Scan EVERY sub until we get a match. local frame= emu.framecount() for i= 1, #S do if ( (frame >= S[i].s) and (frame <= S[i].e) and (x >= S[i].x-2) and (x <= GetRight(S[i])) and (y >= S[i].y-2) and (y <= GetBottom(S[i])) ) then return i end end return nil end --***************************************************************************** local function ClickedSubFCEUX(x,y) --***************************************************************************** --Someone who made FCEUX decided to make the gui "off by 8". --So, here's the offset to fix that! return ClickedSubGeneral(x,y-8) end if FCEU then ClickedSub= ClickedSubFCEUX else ClickedSub= ClickedSubGeneral end --***************************************************************************** local function DeselectSub() --***************************************************************************** --... How did I forget this? end --***************************************************************************** local KeyFunctions= {} --***************************************************************************** -- Greatly simplifies KeyReader for me. -- Hotkeys for every funtion I want available to the user. ------------------------------------------------------------------------------- KeyFunctions[DelChar]= function () ------------------------------------------------------------------------------- --Deletes a character or line break. if ch == 0 then if ln > 1 then local s= table.remove(Sb,ln) ln= ln-1 ch= #(Sb[ln]) Sb[ln]= Sb[ln] .. s end else Sb[ln]= string.sub(Sb[ln],1,ch-1) .. string.sub(Sb[ln],ch+1) ch= ch-1 end end ------------------------------------------------------------------------------- KeyFunctions[NewLine]= function () ------------------------------------------------------------------------------- --Adds a new line. Thought it did differently? local s= string.sub(Sb[ln],ch+1) Sb[ln]= string.sub(Sb[ln],1,ch) ln= ln+1 ch= 0 table.insert(Sb,ln,s) end ------------------------------------------------------------------------------- KeyFunctions[LeftArr]= function () ------------------------------------------------------------------------------- --Basic left-arrow functionality. Smart enough. ch= ch-1 if ch < 0 then if ln > 1 then ln= ln-1 ch= #(Sb[ln]) else ch= 0 end end end ------------------------------------------------------------------------------- KeyFunctions[RightAr]= function () ------------------------------------------------------------------------------- --Basic right-arrow function. Works well enough. ch= ch+1 if ch > #(Sb[ln]) then ln= ln+1 if ln > #Sb then ln= #Sb ch= #(Sb[ln]) else ch= 0 end end end ------------------------------------------------------------------------------- KeyFunctions[UpArrow]= function () ------------------------------------------------------------------------------- ln= math.max(ln-1,1) ch= math.min(ch,#(Sb[ln])) end ------------------------------------------------------------------------------- KeyFunctions[DownArr]= function () ------------------------------------------------------------------------------- ln= math.min(ln+1,#Sb) ch= math.min(ch,#(Sb[ln])) end ------------------------------------------------------------------------------- KeyFunctions[Del_Sub]= function () ------------------------------------------------------------------------------- if Sb then table.remove(S,Sb.i); Sb= nil end end ------------------------------------------------------------------------------- KeyFunctions[Kil_Sta]= function () ------------------------------------------------------------------------------- if Sb then Sb.s= 0 print("StartPoint now zero!") end end ------------------------------------------------------------------------------- KeyFunctions[Set_Sta]= function () ------------------------------------------------------------------------------- if Sb then Sb.s= movie.framecount() Sb.e= math.max(Sb.s,Sb.e) print("Start:" , Sb.s) end end ------------------------------------------------------------------------------- KeyFunctions[Kil_End]= function () ------------------------------------------------------------------------------- if Sb then Sb.e= BIGNUM print("EndPoint now super late!") end end ------------------------------------------------------------------------------- KeyFunctions[Set_End]= function () ------------------------------------------------------------------------------- if Sb then Sb.e= movie.framecount() Sb.s= math.min(Sb.s,Sb.e) print("End:" , Sb.e) end end ------------------------------------------------------------------------------- KeyFunctions[SaveTxt]= function () ------------------------------------------------------------------------------- print("sorting...") SortSubs() local FileOut= io.open(SubtitlesFile,"w") print("writing to " .. SubtitlesFile) for i= 1, #S do local str= string.format("S(%d,%d,%d,%d",S[i].s,S[i].e,S[i].x,S[i].y) for j= 1, #(S[i]) do str= str .. ",\"" .. S[i][j] .. "\"" end str= str .. ")\n" FileOut:write(str) end FileOut:close() print("Done!") end ------------------------------------------------------------------------------- KeyFunctions["leftclick"]= function () ------------------------------------------------------------------------------- local ind= ClickedSub(keys.xmouse,keys.ymouse) if Sb ~= S[ind] then if Sb and (Sb[1] == "" and not Sb[2]) then if ind and (ind > Sb.i) then ind= ind-1 end table.remove(S,Sb.i) end Sb= S[ind]; if Sb then ln= #Sb; ch= #(Sb[ln]) end end end ------------------------------------------------------------------------------- KeyFunctions["c+leftclick"]= function () ------------------------------------------------------------------------------- --Ooh, handle control+click! if Sb and (Sb[1] == "" and not Sb[2]) then table.remove(S,Sb.i) end Sb={"",x=keys.xmouse,s=movie.framecount(),y=keys.ymouse,e=BIGNUM} table.insert(S,Sb) Sb.i= #S; ch= 0; ln= 1 end --***************************************************************************** local function KeyReader() --***************************************************************************** UpdateKeys() for k,v in pairs(keys) do if not lastkeys[k] then local ThisKey= k if lastkeys.control then ThisKey= "c+" .. ThisKey end if ThisKey == BypassFrameAdvanceKey then --... It's a bypass. Don't process. elseif KeyFunctions[ThisKey] then KeyFunctions[ThisKey]() else if keys.shift then ThisKey= string.upper(ThisKey) else ThisKey= string.lower(ThisKey) end if KeyTable[ThisKey] and Sb then Sb[ln]= ( string.sub(Sb[ln],1,ch) .. KeyTable[ThisKey] .. string.sub(Sb[ln],ch+1) ) ch= ch+1 end end end end if keys.leftclick and lastkeys.leftclick and Sb then Sb.x= Sb.x + keys.xmouse - lastkeys.xmouse Sb.y= Sb.y + keys.ymouse - lastkeys.ymouse end end --***************************************************************************** local function ShowAllSubs() --***************************************************************************** for i= 1, #S do if within(movie.framecount(), S[i].s,S[i].e) then for j= 1, #(S[i]) do gui.text(S[i].x, S[i].y+(j-1)*HGT, S[i][j]) end local color= fade if S[i] == Sb then color= white end box(S[i].x-2, S[i].y-2, GetRight(S[i]), GetBottom(S[i]), 0, color) elseif S[i] == Sb then gui.opacity(.5) for j= 1, #(S[i]) do gui.text(S[i].x, S[i].y+(j-1)*HGT, S[i][j]) end box(S[i].x-2, S[i].y-2, GetRight(S[i]), GetBottom(S[i]), 0, white) gui.opacity(1) end end end --***************************************************************************** local function DrawCursor() --***************************************************************************** -- Big boxy style if Sb then local CX= Sb.x + WID*ch local CY= Sb.y + HGT*(ln-1) box(CX,CY,CX+WID-1,CY+HGT-1, f_red, fade) end end --***************************************************************************** local function Fn() --***************************************************************************** KeyReader() ShowAllSubs() DrawCursor() end --***************************************************************************** local function FnFCEUX() --***************************************************************************** KeyReader() ShowAllSubs() DrawCursor() gui.pixel(0,0,0) -- FCEUX fails to clear screen with no calls to gui. end if FCEU then gui.register(FnFCEUX) else gui.register(Fn) end local NoRunWhilePausedWarning= ("Warning: This emulator does not run " .. "lua scripts while the emulation is paused. In order for the script to " .. "react to your typing, you must have the emulator running. " .. "Apologies for this inconvenince." ) if FCEU then print("FCEU emulation detected.") print("Warning: This emulator uses variable-width text. This script is not", "set up for variable width. The display will incorrectly estimate the", "length of the text. Keep this in mind as you are using this script.") elseif vba then print("Visual Boy Advance detected.") print(NoRunWhilePausedWarning) elseif stylus then --DeSmuME print("DeSmuME detected") elseif snes9x then print("Snes9x detected") print(NoRunWhilePausedWarning) else print("Script not tailored for this emulator. Please edit this script and", "manually change WID and HGT values to let the boxes match the text.", "Additionally, I do not know of its nuances. Use with caution.") end print("\r\nThis script allows for arbitrary typing. Keep in mind that this", "will likely react with the hotkeys set in the emulator. I have no way", "of overriding the hotkeys. You are advised to make a copy of the", "config file, then remove most or all hotkeys, especially those that", "pertain to letters, numbers, or punctuation on the keyboard.") print("\r\nThis can produce a file for use with the reader script.")
Oh, dear. It would appear the parser on this forum doesn't recognize backslash as an escape character for a following backslash. There are numerous problems so far, but darn it! I can type stuff! And the stuff will show up! It's a text editor at this point, and not a subtitles maker. But subtitles kind of requires a bit of text editing anyway. Without which, what are you going to do? Stare at the monitor and use your psychic powers? This is an adaptation of the Subtitler script I've made for FCEUX's built-in subtitles. Seeing as I've only allowed for adding or removing the latest character in that script, that fact reflects here as well. In spite of having a cute cursor that moves left or right, typing a character always adds it to the end of the line. I'll, uh... Fix that, at some point... There is support for backspace. It does a horrible job at deleting lines. There is support for adding new lines with the enter key. It, too, does a horrible job, considering it erases the next line entirely when the cursor is on a previous line. I've got pretty smart left and right arrow keys. No accompanying up and down arrow keys, though. I chose not to search for special characters like carriage return or line feed, preferring to use separate strings instead. Seeing that this is the format I used in my previous subtitling stuff, I thought I should remain consistent as such. It's not all that easy, but I'm so far getting somewhere. I'm just posting this to show progress. Not to show awesomeness, since I haven't reached that yet. Edit: First piece of awesomeness at last: Movable text, spawnable text. Great neat stuffs! Ctrl+click to create a new box of text! Ctrl+X to kill the box! Edit2: There we go. Now it saves subtitles in the format readable by my actual subtitle viewer script. Edit3: It's now starting to feel more like a general purpose script. It has *known* support for FCEUX, Snes9x, VBA, and DeSmuME. I should work on the reader script to more appropriately match this script...
Editor, Experienced Forum User, Published Author, Skilled player (1174)
Joined: 9/27/2008
Posts: 1085
Any script which takes user input (input.get()) would almost certainly benefit from a "run-while-paused" function. Scripts that merely display information (displaying and interpreting memory addresses) and bots generally don't benefit so much from a "run-while-paused" function. Anything that doesn't involve input.get(), basically. A few things I've been doing practically requires "run-while-paused" to even work. A "subtitle maker" script will need it -- Will you want to have the game running dozens of seconds or even minutes while you're still typing out the same subtitle?
Editor, Experienced Forum User, Published Author, Skilled player (1174)
Joined: 9/27/2008
Posts: 1085
Technically, they already have the gui.register function. It's just that it's called only once per frame, and not repeatedly during pause. They might need to change how a few things work to get it to happen right. Probably still worth pestering about, considering just how useful a "run-while-paused" piece of code is. And wow, I didn't know gui.register works the way it does for so many different emulators. I originally thought the fact gui.register works the way it did for FCEUX is due to a glitch in the implementation, given what the documentation said:
Register a function to be called between a frame being prepared for displaying on your screen and it actually happening. Used when that 1 frame delay for rendering is not acceptable.
Editor, Experienced Forum User, Published Author, Skilled player (1174)
Joined: 9/27/2008
Posts: 1085
According to the documentation, that works for gens. I don't believe it works for many other emulators. I want to summarize the "run-while-paused" lua capabilities... FCEUX - gui.register DeSmuME - gui.register Final Burn Alpha - gui.register (I think. I never used this emu) PCSX - I'm not sure. gui.register works there, right? Gens - gens.wait, gens.redraw (never used this emu) Snes9x - Not possible (Oh, right. DarkKobold happened.) Visual Boy Advance - Not possible (Here too, probably) Mupen - Has no lua, if I recall correctly. Never used it. JPC-rr - I tried looking at this. I got confused... I'm pretty sure Snes9x, VBA, and DeSmuME uses the same text scheme, while FCEUX uses its own unique variable-width form. I do not have any knowledge of other emulators here in this case. Oh, don't worry. As much as it feels like a competition to me, I won't actually hide anything, and if you want my help on something, I'll be glad to give it. I'm currently thinking about clicking on one spot on the emu window as the top-left corner of the text, showing a basic display of where your next character will show up, and using the arbitrary "type whenever you want" of the sibtitler script I linked to before. And I want to be able to select any arbitrary text you want to move or whatever and hit a key to delete the whole thing entirely, put an end-frame on it, or just draw a box instead. No typing numbers on the part of the user.
Suggest: Make it produce an ASS compatible subtitle file (textual with timing and formatting tags) as the movie is played back.
Uh, hmm... That would require me knowing the format in the first place. I have no such knowledge at this time. Oh, nuts. I just realized my multitrack script can work beautifully on many more emulators than just FCEUX, with a few tweaks. And I happen to use all the ones that don't, except FCEUX. Actually, I've made a start on DeSmuME, but haven't finished it thanks to the fact I haven't figured out how to display the script's stored stylus input.
Post subject: adelikat, I've altered the code once more!
Editor, Experienced Forum User, Published Author, Skilled player (1174)
Joined: 9/27/2008
Posts: 1085
adelikat asked me to create a new piece of lua to help out in timing the hits perfectly in realtime. Could use some tweaking, as I only put in a minimal amount of testing, and this is mostly for adelikat, but I figure why the heck wouldn't I dump the script on the public? The basic idea is to help one time hits perfectly for a certain competitor. Something about how after he blocks your punch, you can punch an exact number of frames after that block to get a star or something. There's a false positive when you get hit, which I haven't bothered to put in a check for. Seeing as I don't have access to the FCEUX SVN for some odd reason, I'm pasting the code here. Download PunchOutTraining_Tiger.lua
Language: lua

-- For Mike Tyson's Punch Out!! when fighting Tiger -- Intended to help time hits in real time. --FatRatKnight local AutoCheat= true local CLOCK= 0x0306 --Game clock local HEART= 0x0324 --Mac's low digit for his heart counter. local EHP= 0x0398 -- Enemy HP address local HRT= 0x0325 -- Mac's Heart's been dropped indicator local TMR= 75 -- Frames in advance for your punches. local UPP= 19 -- Frames prior to the golden zone to display. local BND= -8 -- KEEP NEGATIVE!! Frames after the golden zone. local threshold= 22-- How many frames before the target timing does it allow? local deadzone= 18 -- Frames to ignore a block following a failed punch. local DISPx= 180 local DISPy= 180 local DISPx2= DISPx+11 -- Right side of box. Adjust that plus to your need local Dsp= {line= true, bar= true, stealth= 0, stick= 30} local deviation= 0 local accuracy= 0 local perfect= 0 local earlies= 0 local lates= 0 local accearly= 0 local acclate= 0 local LastHit local HitTiming local DEBUG_Status= "nil" local keys= input.get() local lastkeys= keys --***************************************************************************** function UpdateKeys() lastkeys= keys; keys= input.get() end function Press(btn) return (keys[btn] and not lastkeys[btn]) end function NULLFn() end --***************************************************************************** --***************************************************************************** local function Is_Blocked() return (memory.readbyte(HRT) == 0x80) end --***************************************************************************** --***************************************************************************** local function AB1() local p= joypad.get(1); return (p.A or p.B) end --***************************************************************************** local Pad, LastPad= {}, {} --***************************************************************************** local function AB2() --***************************************************************************** LastPad= Pad Pad= joypad.get(1) return ( (Pad.A and LastPad.A) or (Pad.B and LastPad.B) ) end local timer= 0 --***************************************************************************** local function IdleStuffs() --***************************************************************************** timer= timer-1 if timer <=0 then LastHit= nil emu.registerafter(NULLFn) end end local MainLoop -- There exists a MainLoop. Needed for a few function. local DZ --***************************************************************************** local function ForcedWait() --***************************************************************************** DEBUG_Status= "Deadzone, ignoring you!!" DZ= DZ-1 if DZ <= 0 then emu.registerbefore(MainLoop) end end --***************************************************************************** local function Reset() --***************************************************************************** DEBUG_Status= "Reset" HitTiming= nil end --***************************************************************************** local function ScanHit() --***************************************************************************** DEBUG_Status= "Scanned!" timer= Dsp.stick emu.registerafter(IdleStuffs) LastHit= HitTiming deviation= deviation + HitTiming if HitTiming < 0 then accuracy= accuracy - HitTiming lates= lates+1 acclate= acclate - HitTiming elseif HitTiming > 0 then accuracy= accuracy + HitTiming earlies= earlies+1 accearly= accearly + HitTiming else perfect= perfect+1 end end --***************************************************************************** local function WaitForIt() --***************************************************************************** DEBUG_Status= "Timing..." HitTiming= HitTiming-1 if HitTiming < BND then Reset(); emu.registerbefore(MainLoop) else if AB2() and HitTiming < threshold then ScanHit() DZ= deadzone emu.registerbefore(ForcedWait) end end end --***************************************************************************** local function NewHit() --***************************************************************************** DEBUG_Status= "BLOCKED" HitTiming= TMR emu.registerbefore(WaitForIt) end --***************************************************************************** function MainLoop() --***************************************************************************** DEBUG_Status= "Main" if Is_Blocked() then NewHit() end end emu.registerbefore(MainLoop) --***************************************************************************** function ResetStats() --***************************************************************************** deviation= 0 accuracy= 0 perfect= 0 earlies= 0 lates= 0 accearly= 0 acclate= 0 end local SparklyJoy= 0 local SparkyColors={"red","white","green","blue"} --***************************************************************************** local function DisplayStuffs() --***************************************************************************** --I'm handling options just for you!! UpdateKeys() if Press("insert") then Dsp.line= not Dsp.line end if Press("delete") then Dsp.bar= not Dsp.bar end if Press("home") then Dsp.stealth= Dsp.stealth+1 end if Press("end") then Dsp.stealth= math.max(Dsp.stealth-1,0) end if Press("pageup") then Dsp.stick= Dsp.stick+1 end if Press("pagedown") then Dsp.stick= math.max(Dsp.stick-1,0) end if Press("numpad5") then ResetStats() end if Press("numpad0") then AutoCheat= not AutoCheat end if keys.leftclick and lastkeys.leftclick then DISPx= DISPx + keys.xmouse - lastkeys.xmouse DISPx2= DISPx2 + keys.xmouse - lastkeys.xmouse DISPy= DISPy + keys.ymouse - lastkeys.ymouse end -- Okay, enough options handling. On we go! if Dsp.bar then for i= BND, UPP do if i <= 0 or i > Dsp.stealth or LastHit then local Y = DISPy - i*4 if i < 0 then Y= Y+2 elseif i > 0 then Y= Y-2 end local color= "black" if i == LastHit then color= "red" elseif i == HitTiming then color= "green" end gui.box(DISPx, Y, DISPx2, Y+2, 0, color) end end if LastHit == 0 then --Sparkly joy! SparklyJoy= (SparklyJoy%3)+1 gui.drawbox(DISPx-2, DISPy-2, DISPx2+2, DISPy+4, 0, SparkyColors[SparklyJoy]) gui.drawbox(DISPx, DISPy, DISPx2, DISPy+2, 0, SparkyColors[SparklyJoy+1]) else SparklyJoy= 0 gui.drawbox(DISPx-2, DISPy-2, DISPx2+2, DISPy+4, 0, "green") end end if Dsp.line then local ThisX= 240 local ThisY= 100 gui.box(ThisX-1,ThisY,ThisX+1,ThisY+2+TMR-BND, "black", "white") gui.pixel(ThisX,ThisY+1+TMR,"white") if HitTiming then gui.pixel(ThisX,ThisY+1+TMR-HitTiming,"green") end end if LastHit then if LastHit < 0 then gui.text(128,80,"Late " .. -LastHit) elseif LastHit > 0 then gui.text(128,80,"Early " .. LastHit) else gui.drawbox(127,79,142,88,"green") gui.text(128,80,"OK") end end gui.text( 1,110,"Timing error: " .. accuracy) gui.text( 1,120,"Deviation: " .. deviation) gui.text( 1,140,"Perfect hits: " .. perfect) gui.text( 1,160,"Early: " .. earlies .. " : " .. accearly) gui.text( 1,170,"Late: " .. lates .. " : " .. acclate) local Tot=(perfect + earlies + lates) if Tot ~= 0 then local temp= perfect / Tot temp= math.floor(temp*1000)/10 gui.text( 1,185,"Accuracy: " .. temp .. "%") temp= deviation / Tot temp= math.floor(temp*100)/100 gui.text(1,195,"Dev.Rate: " .. temp) else gui.text( 1,185,"Accuracy: --.-%") gui.text(1,195,"Dev.Rate: -.--") end gui.text(145, 14,memory.readbyte(EHP)) gui.text( 1, 1,DEBUG_Status) gui.text(230, 1,Dsp.stick) if AutoCheat then gui.text(88,1,"Eternal Cheat Acitve!") end end gui.register(DisplayStuffs) --***************************************************************************** function CheatEternalBattle() --***************************************************************************** memory.writebyte(CLOCK, 0) memory.writebyte(EHP, 96) memory.writebyte(HEART, 7) end while true do if AutoCheat then CheatEternalBattle() end emu.frameadvance() end
EDIT: Added a deadzone, and fixed a mixup of early/late stuffs. EDIT2: Added a ResetStats button, at numpad5. It resets stats. Joy. EDIT3: Now has cheats and shows deviation!
Editor, Experienced Forum User, Published Author, Skilled player (1174)
Joined: 9/27/2008
Posts: 1085
Sorry about disappearing for a while. I only now got around to taking a good look at this script. Seems pretty exclusive to FCEUX with the need for iup, but darn sweet regardless. At the moment, I'm trying to formulate something even more intuitive than the pop-up box. This would take a "run-while-paused" gui.register and something like this script's ability to type stuff on the spot. The script I linked to attempts to make FCEUX's natural subtitles easier to handle. Yep, I feel like we're competing on subtitles now. A small competition, but let's see what we can build! ... Not many emulators support a "run-while-paused" gui.register. This does limit the scope of use somewhat...
Editor, Experienced Forum User, Published Author, Skilled player (1174)
Joined: 9/27/2008
Posts: 1085
I've played the first game a bit, not the second. I get the impression it's slow going most of the way. I don't actually know enough details to really plan out what to do. I suppose the first question is: What battles are absolutely required? And in those battles, what are the bare-minimums you can get away with, even with perfect luck? And of course, how do we get to those minimums? And if we go above said minimums, how much faster can we go with a bit more grinding beforehand? To the extent of my knowledge, there are frighteningly few free immediately usable items sitting around. Almost everything you'll be getting are from selling the various junk to the shop and buying the resulting new stuff. Still, I don't know a handy list of these usable free items. How would these work out? Finally, there's the map itself. You've got long walks ahead. One thing you have full control over is what you can do with the map. Write out a story, play Tic-Tac-Toe or Checkers, draw pixel art. I'm sure there's plenty of stuff one can do with the map! ... Which should run out all too quick, though. Someone really creative might need to stop by. I know, I don't have much to say except the most basic questions. I don't know enough of the game to formulate anything remotely considered strategy. Is there really no one else here who knows more than my limited amounts?
Editor, Experienced Forum User, Published Author, Skilled player (1174)
Joined: 9/27/2008
Posts: 1085
Actually, there's like only two important RNG addresses to look at, and another 3 semi-important ones. It's not horribly complicated once you realize that the "chance to hit", "enemy thinks", and "get an ability?" are on one RNG, and the "damage dealt", "turn order", and "what ability?" are on another RNG. All other RNGs, aside from the three dedicated to encounters, are largely unimportant in this run. Once I set myself up the right RNG value for one address so I get the right ability, I only need to modify the memory for another RNG address to see which ones I should aim for. There's a bunch of possible values where my StonGaze misses against Ashura. Obviously those are bad. Then there are a bunch more where I don't get an ability. Those are bad, too. Finally, once I know the target values that can work, I then run the game normally and try to manipulate up the target values that works. It helps that I can increment one without having to mess with the other, though. Gives me a bit more freedom in that I can affect the does it happen without affecting the what.
Editor, Experienced Forum User, Published Author, Skilled player (1174)
Joined: 9/27/2008
Posts: 1085
Edit: I forgot to mention, I redid the Rhino battle after adjusting the RNG beforehand. It's a bit faster now. This is reflected in this new WIP. Definitely possible to grab Explode off of Ashura, alright! Trouble is the amount of time I spent manipulating just that. I'm going to look at other battles for my manipulation needs. Besides that, I picked up Blitz from the Woodman, and that might waste a pile of frames. Finally, I had to begin an extra round after Woodman to increment C0A6 to the exact value I needed it. Woodman has a chance to resist becoming instant art. That limits some possibilities for C0A6 already. C0A6 can also trigger learning an undesired ability, which messes with C0A5 as well. So basically, I should look for a value of C0A6 with which I can enter fighting Woodman where it sets itself up against Ashura without needing any special actions between the battles, or else pick a fight after Woodman and mess with the RNG at one of those three battles instead. Maybe two or even all three of them so I can take advantage of the pre-round RNG shuffling a few times without the slow actions taking place. At least with Woodman, C0A5 is unimportant. For Ashura, I must begin the battle with C0A5 == 0x80, or I can't learn Explode. C0A5 also determines turn order, and while 0x7F would otherwise be fine with Mask's Hammer incrementing C0A5, my Medusa goes first, preventing that increment. I feel it's a bit faster to increment C0A5 just once more pre-Ashura rather than re-order my party. FFL2 is a maze with these RNGs. Many ways one can go, one merely needs to follow a path through to see if it hits a dead-end. As for me, I just need to find all the shortcuts.
Editor, Experienced Forum User, Published Author, Skilled player (1174)
Joined: 9/27/2008
Posts: 1085
I think I can top both players in this "frame war"... #????: FatRatKnight's NES Battle Chess "redefined goal" in 00:00.00 Having taken a look at the Art Alive submission for ideas, I realized that there's actually a faster way to reach the goal of "completion". Come on, checkmate is such a time-wasting goal! No cheating. the only goal I made for this run is to paint the title screen, and this doesn't need a frame of input. This is monumentally faster than any strategy that uses the goal of checkmate. Possible improvements: Submit a negative frame movie. One that eats up negative bytes, so that I can have more space on my hard disk. ... Or something like that. Wasn't there a frame war between two players, where both lost? (Someone else went and submitted something way the heck faster) In any case, it's nice to see the silliness come to an end, but I did get a few laughs out of it. I chose not to waste submission space for the "faster run", instead preferring to place it here in this post.