Posts for FatRatKnight

Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
Ah, the last DTC was a good experience for me. I'm not just talking about being part of the champion team, but the experience during the TAS creation as a team was certainly an enjoyable one. If it's anything at all like last time, I'll have a nice driving force where I'm given pretty consistent pressure, feedback, and information. While I have found myself lost into delving into some game's mechanics, instead of TASing various projects, I do believe I'll stay focused on the DTC4, much like I did for the DTC3. And hopefully it'll get me back into shape for TASing the DTC3 game. As for what team, assign me randomly. Or some team could claim me, I guess. "So, just how much is a DTC3 champion worth, eh?" Somehow, I imagine someone claiming me right away, then asking for like 5 bucks to give me away.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
Each enemy has a specific EXP value associated with it. Whatever is being built up uses the difference of this specific value and whatever the level of the thing you're building up. The "armored dwarf" things leading up to the boss of Ruins is 30 EXP. The "wizard clone" battle is also 30 EXP. The "giant scorpion" boss is 50 EXP. With an Arm Strength of 5, I will, at first, gain 25 EXP per hit against the "wizard clone" (30 - 5; 30 from base EXP, the 5 from Arm Strength), then the boss will provide a healthy dose of Arm Strength (50 - whatever AS I have then, per hit). Once Arm Strength is properly boosted like this, there aren't really any monsters that will serve to boost it quite like that again. At least, not in the Tower. The bosses of Tower Top only have a base EXP of 50, which with a heightened Arm Strength, looks less appealing. I'm probably more encouraged to get repeated 1 damage against that boss at Foot of Tower. For whatever reason, the game slowly allocates EXP rather than giving it all right away, meaning I can get a decent boost as though I still have a low amount of Arm Strength, until the boss perishes. Uncle Mario's TAS demonstrates this, as the "Arm Strength increased" messages keep coming for a while. The luck stat gradually goes up and down, between 0 and some maximum, inclusive. The fastest way to change it is to sit still and do nothing, but it's at least 100 frames before it changes one point. There is a maximum value your luck can go to, and this is based on your level. Unfortunately, building levels means you have a higher maximum MP, and that's more MP you must recover between teleports. Levels do nothing for MP recovery -- Knowledge is what affects recovery. Besides that, in order to build levels, you need to actually kill stuff to be awarded EXP towards level. Arm Strength just needs you to beat stuff up, kills are optional. There's a few long-range plans that will be affected by what I do early on. In particular, I may want to pick up a few Hardening Potions if I plan to use one of those fragile Blade of Muramasa. 20 extra hits ain't bad. I might want to try out Fire Magic against the ninja boss, but this naturally requires a few tests (Leather Shield is no good against those shurikens). Hard to say what's the best path, but I really need more information about that boss trio before I get in too deep.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
Your run has caught my interest. So I made my own start into the game. I am roughly 2586 frames ahead at the start of Ruins Area 3, with about 2100 of it due to a different route through Ruins Area 1. There is a forced wait at each door. One is stuck waiting about 5 frames every time. During this wait, one can shift the inventory selection to an adjacent item slot, or take a nap for 3 frames, without wasting time. Napping like this restores, by my estimation, 3/4 of an HP and 9 extra units of mana regeneration (with 5 Knowledge, you need 75 units for 1 MP, and you generally restore 1 unit at a fairly rapid rate). It also freezes the luck counter as well. With the new route I picked through Ruins Area 1, I pick up a 10-use Sledgehammer. Judging from your run, this alone will last me exactly long enough to reach Foot of Tower, but no further. Luckily, there is a treasure chest in the area containing another 10-use Sledgehammer just a few steps out of the way. Watching the rest of your run, this appears to be enough to last the remainder of the game. This is knowledge I would not have had without your current run. Understandably, you overbought these things without knowing if you got enough, but now we know more. I recall not doing a whole lot of damage against that trio of bosses in Fortress 4F in a normal run of this game. I take it even a Blade of Muramasa isn't helping you deal more than 2 damage? Seems we're just going to have to deal with the long battle, then. If you want a TASing partner, I think I can handle working through this game. Although, I do have other TAS projects I probably should be working on anyway, my interests tend to drift everywhere. This is my fancy memory watch, in lua script form: Download Brandish.lua
Language: lua

local R1u= memory.readbyte local R2u= memory.readword local R4u= memory.readdword local R1s= memory.readbytesigned local R2s= memory.readwordsigned local R4s= memory.readdwordsigned --***************************************************************************** local function GuiTextRight(x,y,str,c1,c2) --***************************************************************************** str= tostring(str) x= x - (#str)*4 gui.text(x,y,str,c1,c2) end --***************************************************************************** local function StatAndExp(x,y,addr,str) --***************************************************************************** GuiTextRight( x,y,R1u(addr)) gui.text( x,y,string.format(".%2d",R1u(addr+1)),0xC0C0C0FF) local Growth= R2u(addr+0x17FA) if Growth ~= 0 then GuiTextRight(x+12,y+7, "+" .. Growth, 0x00FFFFFF) else gui.text(x,y+7,str,0xFFFF00FF) end end --***************************************************************************** local function StatAndMax(x,y,addr) --***************************************************************************** GuiTextRight( x,y,R1u(addr)) gui.text( x,y,string.format("/%3d",R1u(addr+1)),0xC0C0C0FF) end --***************************************************************************** local function DumpStats() --***************************************************************************** StatAndMax( 88,200,0x7E0589) -- HP StatAndMax(220,200,0x7E058B) -- MP GuiTextRight(236,192,R2u(0x7E1D97),0xC0FFC0FF) StatAndMax(120,214,0x7E058D) -- Luck gui.text(120,207,"Luck",0xFFFF00FF) GuiTextRight(148,207,R2s(0x7E1D9B),0xC0C0C0FF) local clr= 0xFFFF00FF if R2s(0x7E1D9B) > 0 then clr= 0x00FF00FF end GuiTextRight(148,214,R2u(0x7E1D99),clr) StatAndExp( 20,210,0x7E058F,"Arm") -- Arm Strength 0x7E1D89 StatAndExp( 50,210,0x7E0591,"Kno") -- Knowledge StatAndExp( 80,210,0x7E0593,"MgE") -- Magic Endurance StatAndExp(200,210,0x7E0595,"Lvl") -- Level GuiTextRight(256, 0,R4u(0x7E43C2)) -- Gold on hand end gui.register(DumpStats)
Alas, it's only a fancy memory watch. Doesn't do things like calculate the RNG or predict the enemy actions.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
I haven't made much progress. I stopped here shortly after my last update. Then I lost interest again and went on to other things. Such as writing a new FAQ on a different game. But it helps to have reminders. I'll try to push myself back into this game. What I was stuck on is that, while I did beat the DTC3 runs significantly (370 frames ahead of leading [T1] DTC3 run), I was stopped at a wall when I couldn't find a frame-perfect way to collect a more optimal weapon. While that purple shot does have short death animations, it lacks the power of spread or even fire/ice weapons. I had gone back to scene 7, the helicopter scene starting around frame 23452. I was gunning for a different RNG and hoping for another grenade pack by blowing up a building I ignored previously. I haven't continued since. Strictly speaking, the run I posted is the backup in case this new route I want to try fails horribly. Now that I talked about it, the fact this run exists is fresh in my mind. I will see to any progress I can make.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
Area 3 beaten. The holdup was due to the fact I couldn't find the solution to kill everything successfully without help. But there help came, and here's the result. For a better listening experience, you might want to try disabling sound channel #6. The engine whine uses that channel, and it might put less of a strain on your ears to turn off this channel while viewing this run. I will now talk about health. It is a 2-byte unsigned value found at address 7E18ED. Yes, it uses the full range of 0~65535, despite only ever showing 16 bars of health (each bar is worth 4096). When you take damage from actual hits, it's always in some multiple of 4096. There are three sources which affects your health in finer values than 4096: - Flying in a healing zone: +300 per frame - Flying "off road": -160 per frame - Held the brakes too long: -160 per frame Notably, holding brakes and flying "off road" do not stack. You still only lose 160 per frame. There was a point in Area 3 where I had trouble. The spot is where the track splits for the first time, a fair ways along this split. When I outlined where the enemies spawn, a non-TASer pointed out a possible solution, to which I then used and found it works. The result is I stick around "off road" for a long time, enough that my health drops to 575 -- 4 more frames "off road", and I'm dead. The game lies -- I don't have 1 HP left, I have closer to 1/7 of an HP left. One of the trouble enemies only had a 3-frame window where I could hit it before it vanishes. Those green enemies give very little opportunity to attack them. I did say "I expect worse cases later on" in an earlier post. This is probably the worst case I can handle. Probably the worst one to worry about for most of the run. ... I hope... But at least the puzzle here is solved. We'll see about later areas...
Post subject: EmuLua functions: Getting the most out of input.get()
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
First, the basics of input.get() It is a function that you call without any parameters. It returns a table containing true for all key values representing keys currently pressed on the keyboard, as well as whether mouse buttons are pressed and coordinates of the mouse relative to the emulator's window. I could, for example, put keys = input.get() somewhere in my code, and somewhere else, I can simply do if keys["A"] then to check whether the A key is currently held. Having good control over input.get() means that your script has interactivity beyond just you tinkering with its code and changing its internal variables. You can have code that changes these internal variables on the fly, or even run many different functions, at a press of a single key, whenever you want. Sure beats restarting the script from scratch just because you didn't like its opacity. What are all the key values? I could put a list here, but I feel it would be vastly more informative to give you a lua script for yourself to test instead. Run it, and press any keys you desire to find out the key's name. Download KeyCheck_v1.lua
Language: lua

local function KeyCheck() local T= input.get() local y= 2 for k,v in pairs(T) do gui.text(2,y,k) y=y+10 end end gui.register(KeyCheck)
Tried this script yet? There are a few caveats with this script, such as the fact it makes no attempt to screen out things like xmouse or ymouse. On the other hand, the script does reveal every key currently held, and I really hope you have no trouble picking past the xmouse and ymouse to find out what the key is. We'll improve on this script later, after I explain better ways of dealing with input.get(). How do we detect when the user presses a key? I've already outlined how one could detect when a key is held, by having typed keys = input.get() and later if keys["A"] then as an example of looking for when the A key is held. However, there are times we want to execute code only once per key press. Maybe you're counting how many times the user pressed space, and you don't want the counter zooming upwards at a rapid pace when it's being held. My favorite method for detecting a key press in EmuLua stuff is as follows: Download PressKey.lua
Language: lua

local keys, lastkeys= input.get(), input.get() local function UpdateKeys() lastkeys= keys; keys= input.get() end local function Press(k) return keys[k] and (not lastkeys[k]) end local function Fn() -- Test function to use the above. UpdateKeys() -- Call this once, and only once per loop! if Press("space") then print("Congratulations! You pressed space! Have a cookie.") end if Press("A") then print("That's the keyboard's A key! That's not a controller A button!") end end gui.register(Fn)
In the code above, I keep two tables. keys is the current keys held, and lastkeys keeps whatever was held a rather short moment ago. I need this so I can tell the moment the user pushes the key down, and not repeatedly run the same code when it is instead held. This is pretty crucial to detecting key presses. Hopefully, UpdateKeys() and Press(k) are short enough that you don't need me to identify what they do. I should hope even a beginner can understand what the code does. There is one further bit of trickery with input.get() one can do. One that makes it easier to handle binding functions to hotkeys in lua code, so to speak. From the above code, you might be doing something like this:
Language: lua

if Press(SomeLetter) then --Insert code or function calls here end if Press(SomeOtherLetter) then --Insert even more code, or possibly function calls here end --Repeat for every function. Wow, this feels inconvenient.
However, when copying or moving this code around, you might end up having to mess with the tabs or spaces to keep it readable. Or if making a new function, you have to add a call here. For you coders, there is a way to streamline the function creation and, at the same time, let the lua code call the function at a key press, without needing to add a call elsewhere in code. My method is simply as follows: Download EmuLuaHotkeyFn.lua
Language: lua

local keys, lastkeys= input.get(), input.get() local function UpdateKeys() lastkeys= keys; keys= input.get() end local KF= {} local function KeyFunctions() UpdateKeys() for key,v in pairs(keys) do if not lastkeys[key] then if KF[key] then KF[key]() end end end end gui.register(KeyFunctions) KF["space"]= function () print("That thing you pressed is the space bar.") end KF["A"]= function () print("Please insert more useful code here.") end
KeyFunctions() uses pairs(keys) to walk through all the currently held keys, and specifically checks if they were pressed or simply held by using lastkeys. If a key was indeed pressed and not held since last update, it will look into KF to see if there exists something there. If so, it will call it as a function. As for creating functions to be placed into table KF, note how I do it. I pick a key, use it as an index for KF, and set whatever I indexed equal to a function, which I define on the spot. I don't need to add a function call to some sort of Master List of function calls, as KeyFunctions() handles that for me for any functions I stick into table KF. But that is more for larger scale projects that use numerous different functions for all the keyboard hotkeys you need to set. In any case, I said earlier that I would improve on the KeyCheck script above, and I will. All it takes is a minor tweak to KeyFunctions. Instead of calling a function in KF, we'll just stick what key we got into some string, which we'll display on screen... Download KeyCheck_v2.lua
Language: lua

local keys, lastkeys= input.get(), input.get() local function UpdateKeys() lastkeys= keys; keys= input.get() end local LastPress= "Nothing, yet." local function KeyCheck2() UpdateKeys() for key,v in pairs(keys) do if not lastkeys[key] then LastPress= key end end gui.text(2,2,LastPress) end gui.register(KeyCheck2)
Now it's a lot less messy of a display. It shows just one key, the last one you've pressed, none of this mess with xmouse and all that. These are a few things that come to mind when handling input.get(). It's a very useful function, and if you ever want to create some interaction with any script, knowing a few tricks can really help with some problems of how to deal with the user's input in a clean way. But if all you make are throwaway scripts that only display RAM values, with possibly a few calculations to them... None of this will really apply. But when you start stepping into a point where you think something along the lines of "I hate having to edit and restart this script just to change this one value over and over," consider the idea of implementing input.get() into the code to change value for you.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
I'm talking about the display of the NES graphics. That is to say, where the gui.box is placed, it's showing the NES graphics 8 pixels down from that spot. And I'm not even sure what it's trying to display on the 8 bottommost pixels. EDIT: Notably, in my first tiny script, seeing the giant screen-filling box isn't intended -- The alpha is 1, meaning it's almost invisible. Seeing any difference here shouldn't happen whether the script is running or not. Setting an alpha of 0, on the other hand, will not shift the NES graphics at all. EDIT2: Here's what I believe is happening... With the recent changes in the lua gui display, it now paints the pixel at a location depending on whatever offset is in Config -> Video... -> Drawing Area. The destination for the pixel is affected, but not the source pixel when mixing colors with alpha blending. Hence, the 8 pixel offset I'm seeing. Using svn2085. EDIT3: Because I'm impatient and constantly worry about whether my responses mean anything, just do this. - Run my 4-line lua script in my previous post. - Play out at least one frame. One of two things should result: - You notice no difference. The bug I describe does not exist. At least, under whatever version you're running. The four line script basically puts up a box that covers the entire screen. But with an alpha of 1 (almost completely transparent), and with FCEUX's color limitation, there really should be no visible difference to speak of. - The game graphics are shifted upwards by 8 pixels This is exactly the bug I am describing. A translucent box shouldn't be moving game graphics, to my knowledge. It should simply blend with the graphics as needed and not end up shifting things around. I'm aware the lua gui may be shifted, but I don't think that should include shifting the game graphics wherever the lua script paints things. FCEUX 2.1.4a is fine here. FCEUX 2.1.5 appears to have broken here. This is as complete as I know how to describe my problem. Apologies for my frequent edits.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
Language: lua

while true do gui.box(0, 8,255,231,0x00FF0001,0x00FF0001) emu.frameadvance() end
Should paint an (almost) invisible green box on the entire screen. You shouldn't notice any difference in color. What's likely a bug is the fact I see everything is shifted up 8 pixels. For more fun, here's a small box you can drag around:
Language: lua

local lastkeys, keys= input.get(), input.get() --***************************************************************************** local function UpdateKeys() lastkeys= keys; keys= input.get() end --***************************************************************************** local function Limits(V,l,h) return math.max(math.min(V,h),l) end --***************************************************************************** local x,y= 50, 50 local function Fn() UpdateKeys() if keys.leftclick then x= Limits(x+keys.xmouse-lastkeys.xmouse , 0 , 235) y= Limits(y+keys.ymouse-lastkeys.ymouse , 8 , 211) end gui.box(x,y,x+20,y+20,0x00FF0001,0x00FF0040) end gui.register(Fn)
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
Here's a straight RNG counter script. It can even detect when the RNG goes backwards, and counts it appropriately. There's a second counter, in red. That tells how many times the script fails to read changes in the RNG. Possibly because it changed in an unusual way. Download FE_RNGcounter.lua
Language: lua

R2u= memory.readword --***************************************************************************** local function roll(A,B,C) --***************************************************************************** return bit.band(0xFFFF, bit.bxor( bit.rshift(A, 5), bit.lshift(B,11), bit.rshift(B,15), bit.lshift(C, 1) ) ) end --***************************************************************************** local function unroll(A,B,C) --***************************************************************************** --Returns what the previous RNG was. local r= bit.band(0xFFFE,bit.bxor(A, bit.rshift(B, 5), bit.lshift(C, 11))) r= bit.bor(r,bit.band(0x0001,bit.bxor(B,bit.rshift(C,5)))) return bit.bor( bit.lshift(bit.band(0x0001,r),15), bit.rshift(bit.band(0xFFFE,r), 1) ) end local Raddr= 0x03000000 local R= {R2u(Raddr), R2u(Raddr+2), R2u(Raddr+4)} --***************************************************************************** local function RNGmatch(a,b,c) --***************************************************************************** return (R2u(Raddr) == a) and (R2u(Raddr+2) == b) and (R2u(Raddr+4) == c) end --***************************************************************************** local function AdvanceRNG() --***************************************************************************** local RR= {R[1], R[2], R[3]} for i= 0, 2000 do if RNGmatch(R[1],R[2],R[3]) then return i end if RNGmatch(RR[1],RR[2],RR[3]) then R= RR; return -i end table.insert(R,1,roll(R[1],R[2],R[3])); R[4]= nil table.insert(RR,unroll(RR[1],RR[2],RR[3])); table.remove(RR,1) end R[1]= R2u(Raddr) R[2]= R2u(Raddr+2) R[3]= R2u(Raddr+4) return false end local Count, Fail= 0, 0 --***************************************************************************** local function CountRolls() --***************************************************************************** local c= AdvanceRNG() if c then Count= Count+c else Fail= Fail+1; Count= 0 end end --***************************************************************************** local function ShowRolls() --***************************************************************************** gui.text(1,1,Count,"green") gui.text(1,9,Fail,"red") end gui.register(ShowRolls) while true do CountRolls() emu.frameadvance() end
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
The submission process here at TASVideos (which you went through to have the movie show up in here) is generally aimed for high-precision runs, either for times that aren't obviously trivial to beat, or for runs that show off the various aspects of the game beautifully if speed isn't a primary goal. A casual run that took little research and one evening to create almost certainly won't be accepted as a submission. A Tool-Assisted Speedrun submitted here is expected to have the player use savestates, frame advance, and any other means to their fullest. Don't hold back on perfecting the run, if you plan on submitting your next run here. We're fine if you post at the forums with your stuff. But as a submission, we're looking for high quality, and judge more harshly based on that. Don't be scared by negative responses here, it's great to see someone else trying!
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
Hey, there's still activity here. Thanks for the subtitles! Just in case, why not list all the unsubtitled videos as well? In a clearly marked second list with [size=9]small size tags[/size] for good measure, perhaps? Those of us who are following this thread will see exactly what there is left to do this way. I may want to take another look at these at some point. If I do, I'll create a list of subtitles that bug me due to spelling, grammar, or whatever.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
Made a new subtitle script. Feel free to download. This is an auxiliary script. I recommend making a new lua file that includes a dofile("SubtitleDisplayer.lua") somewhere near the beginning. This way, all the subtitling you want is in one file and the actual code that does the work doesn't clutter your subtitle file. It now allows out-of-order subtitling. That is, you don't need to worry about keeping all the start frames of the subtitles in ascending order in your subtitles file. Download SubtitleDisplayer.lua
Language: lua

--Aids in displaying lua-based subtitles --FatRatKnight --S(start,end,left,top,line1, line2, ..., lineN) --Subtitle. Displays text --B(start,end,left,top,right,bottom,FillColor,BorderColor) --Box. Paints a colored box at selected area. --R1U(start,end,left,top,text,address) --ReadAddress. Displays one line of text and displays value at address. --Full list of related functions: R1U, R2U, R4U, R1S, R2S, R4S --A(start,end,left,top,FillColor,BorderColor, line1, line2, ..., lineN) --Auto Box&Subtitle. Displays text and a text-fitting box around it. --Additionally, I provide a lowercase letter version of each function. --The end parameter for lowercase is relative to start, so inserting 300 there --means that the subtitle will last 300 frames. Additionally, lowercase also --makes the box routine ask for width and height instead of right-side and --bottom-side. --The R#U and R#S functions need to be fully lowercase. r1U will fail. --But r1u will work just fine. local Subbies= {} local WID= 6 -- Width of each character. DeSmuME uses 6. local HGT= 9 -- Height. I'm arbitrarily using 9 here. if stylus then --DeSmuME. Platform: DS WID= 6; HGT= 9 elseif snes9x then --Snes9x. Platform: SNES WID= 4; HGT= 8 elseif vba then --VisualBoy Advance. Platforms: GBA, GB, GBC, SGB WID= 4; HGT= 8 elseif FCEU then --FCE Ultra / FCEUX. Platform: NES. WID= 2; HGT= 9 end -- Array based functions. --############################################################################# --***************************************************************************** local function DispMsg(T) --***************************************************************************** for i= 1, #T do gui.text(T.x,T.y+HGT*(i-1),T[i],T.c1,T.c2) end end --***************************************************************************** local function DispAddr(T) --***************************************************************************** gui.text(T.x,T.y, string.format(T.txt, T.rv(T.v)) , T.c1,T.c2) end --***************************************************************************** local function DispBox(T) --***************************************************************************** gui.box(T.x,T.y,T.x2,T.y2 , T.c1,T.c2) end --############################################################################# local CurrentIndex= 1 local ActiveSubs= {} --***************************************************************************** local function HandleArray() --***************************************************************************** -- I'll leave the intelligence of add/removal here. --Add while Subbies[CurrentIndex] and (Subbies[CurrentIndex].t <= movie.framecount()) do table.insert(ActiveSubs,Subbies[CurrentIndex]) CurrentIndex= CurrentIndex+1 end --Execute/remove local i= 1 while ActiveSubs[i] do if ActiveSubs[i].e >= movie.framecount() then ActiveSubs[i]:fn() i= i+1 else table.remove(ActiveSubs,i) end end end --***************************************************************************** local function ResetArray() --***************************************************************************** CurrentIndex= 1 ActiveSubs= {} end --***************************************************************************** local function AddToSubbies(T) --***************************************************************************** for i= #Subbies, 1, -1 do if T.t >= Subbies[i].t then table.insert(Subbies,i+1,T) return -- Escape, as we have inserted it! end end table.insert(Subbies,1,T) -- If we go here, the loop failed to insert. end --############################################################################# --############################################################################# -- [S]ubtitle -- Can take an arbitrary number of lines -- Its name is one letter long to save bytes. Lots of them. --***************************************************************************** function S(start,End , left,top , ...) --***************************************************************************** -- This is a CAPITAL S. Use absolutes. if arg.n <= 0 then return end -- Sanity; Must have text! local NewSub= { fn= DispMsg, t= start, e= End, x= left, y= top } for i= 1, arg.n do NewSub[i]= arg[i] end AddToSubbies(NewSub) end --***************************************************************************** function s(start,length , left,top , ...) --***************************************************************************** -- Lowercase s. Use relatives where it makes sense (frame end is it). if arg.n <= 0 then return end -- Sanity; Must have text! local NewSub= { fn= DispMsg, t= start, e= start+length, x= left, y= top } for i= 1, arg.n do NewSub[i]= arg[i] end AddToSubbies(NewSub) end --***************************************************************************** local function ReadMem(start,End , left,top , str,addr , NewFn) --***************************************************************************** -- Not for end-user; Call the R#U/S series instead! -- The usual start,end,left,top applies AddToSubbies{ fn= DispAddr, t= start, e= End, x= left, y= top, txt= str, v= addr, rv= NewFn } end --============================================================================= function R1U(start,End , x1,y1 , str,addr) ReadMem(start,End , x1,y1 , str,addr , memory.readbyteunsigned) end function R2U(start,End , x1,y1 , str,addr) ReadMem(start,End , x1,y1 , str,addr , memory.readwordunsigned) end function R4U(start,End , x1,y1 , str,addr) ReadMem(start,End , x1,y1 , str,addr , memory.readdwordunsigned) end function R1S(start,End , x1,y1 , str,addr) ReadMem(start,End , x1,y1 , str,addr , memory.readbytesigned) end function R2S(start,End , x1,y1 , str,addr) ReadMem(start,End , x1,y1 , str,addr , memory.readwordsigned) end function R4S(start,End , x1,y1 , str,addr) ReadMem(start,End , x1,y1 , str,addr , memory.readdwordsigned) end function r1u(start,length , x1,y1 , str,addr) ReadMem(start,start+length , x1,y1 , str,addr , memory.readbyteunsigned) end function r2u(start,length , x1,y1 , str,addr) ReadMem(start,start+length , x1,y1 , str,addr , memory.readwordunsigned) end function r4u(start,length , x1,y1 , str,addr) ReadMem(start,start+length , x1,y1 , str,addr , memory.readdwordunsigned) end function r1s(start,length , x1,y1 , str,addr) ReadMem(start,start+length , x1,y1 , str,addr , memory.readbytesigned) end function r2s(start,length , x1,y1 , str,addr) ReadMem(start,start+length , x1,y1 , str,addr , memory.readwordsigned) end function r4s(start,length , x1,y1 , str,addr) ReadMem(start,start+length , x1,y1 , str,addr , memory.readdwordsigned) end --============================================================================= -- [B]ox --***************************************************************************** function B(start,End , left,top , right,bottom , cf,cb) --***************************************************************************** AddToSubbies{ fn= DispBox, t= start, e= End, x= left, y= top, x2= right, y2= bottom, c1= cf, c2= cb } end --***************************************************************************** function b(start,length , left,top , width,height , cf,cb) --***************************************************************************** AddToSubbies{ fn= DispBox, t= start, e= start+length, x= left, y= top, x2= left+width, y2= top+height, c1= cf, c2= cb } end -- [A]uto -- Boxes and subtitles, wrapped in one. -- Calculates the box size for you! --***************************************************************************** function A(start,End , left,top , cf,cb , ...) --***************************************************************************** if arg.n <= 0 then return end -- Sanity local NewSub= { fn= DispMsg, t= start, e= End, x= left, y= top } local len= 0 for i= 1, arg.n do NewSub[i]= arg[i] len= math.max(len, string.len(arg[i])) end B(start,End , left-1,top-1 , left+len*WID,top+arg.n*HGT, cf,cb) AddToSubbies(NewSub) end --***************************************************************************** function a(start,length , left,top , cf,cb , ...) --***************************************************************************** if arg.n <= 0 then return end -- Sanity local NewSub= { fn= DispMsg, t= start, e= start+length, x= left, y= top } local len= 0 for i= 1, arg.n do NewSub[i]= arg[i] len= math.max(len, string.len(arg[i])) end B(start,start+length , left-1,top-1 , left+len*WID,top+arg.n*HGT, cf,cb) AddToSubbies(NewSub) end --############################################################################# --############################################################################# local LastFC= 0 --***************************************************************************** local function FrameCountSanity() --***************************************************************************** local FC= movie.framecount() if FC < LastFC then ResetArray() end LastFC= FC end --***************************************************************************** local function HandleSubs() --***************************************************************************** FrameCountSanity() HandleArray() end gui.register(HandleSubs)
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
feos wrote:
MtEdit link looks wrong.
Fixed. (I hope)
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
There's a number of features I can think of that may enhance the script in some way. While MtEdit is a rewrite that's supposed to make things much simpler, it's also much less filled with features. I will attempt to identify each individual feature in their own packages. Store and replay input . Implemented In both the older Mutlitrack2 and MtEdit, this feature is the heart and soul of this script. Without it, the task that this script does will be nothing alike what it does now. Currently, Multitrack2 allows absolute control over individual buttons, allowing one to allow or restrict control on an individual button basis. This is rather complicated, and I feel is the sole reason why it's not popular. I also realize it give an impractical level of control. MtEdit does not provide that silly control. Instead, the control is restricted to only the currently displayed player, and the only option to tweak the control is either mix the user's input with what's currently displayed (XOR logic), or do not apply stored input to displayed player, passing through the user's input. Both Multitrack2 and MtEdit can read from a movie. Play a movie file, begin one of these scripts whenever you feel like, and let the movie play out. The movie on read-only mode will inject the movie's input to the emulator, which in turn will be detected by the script, and thus stored in the script as well. Unfortunately, this script has no way to handle resets. I don't know how to detect and trigger a reset from the lua side, and if there are functions provided for those, I would certainly love to know. Insert and delete frame . Implemented Multitrack2 does some complicated way of handling inserts and deletes, which goes pretty fast even in an arbitrarily long input list. MtEdit goes a much simpler route, which might take a while if the input list is long enough. Inserts and deletes simply remove or add frames on the spot, shoving future input aside to make room or close the gap. Great if you found some frame savings somewhere and don't want to redo the future stuff by hand. This feature practically requires "run while paused" code. That is, the emulator must be able to run lua while the emulation is paused. Without it, inserting and deleting frames would be rather unwieldy at best. Paused input handling Multitrack2 has it. MtEdit does not. This absolutely requires "run while paused" code, as there's no viable workaround without it. It also really wants joypad.peek, but a less useful alternative being input.get can work, but script-specific keys can be kind of wonky. Generally, it allows one to simply tap a button to toggle the joypad button. Particularly good as a form of "sticky keys", especially when precision control is desired and there's no viable way to hold that many keys on your keyboard down. Though, with the scripts as they are, one can run part of the input through, load state, then run the other part of the desired input. Single joypad control Allow the same joypad controls of one player to affect all players. Great if you're switching through players and would prefer not to switch to the player 2 controls. Use the player 1 controls for all four players! In FCEUX, this is almost not possible. Mainly because you can't do an immediate read then write with modifications on the same frame. I can override all joypad controls and force the user to use script-specific keys as a workaround, but that is sort of messy to implement, not to mention the user has to configure the script controls instead of the emulator controls. Backup input list Multitrack2 has this implemented. MtEdit, at the time of this writing, does not. Basically, have a second track of input. This track mirrors the primary list for any frames that aren't previously recorded. Pretty senseless if you're adding new frames, but useful if there's some part you want to go back and edit, as it'll show what the input was like. As a backup input, one should be able to write to it or have the script play it back. Input display . Implemented Another fairly significant part of the script is the fact I display a region of input. It does no good to store and replay input when there's no way to see what the script is going to do. Well, unless the script never messes with the current player and plays back other players. Multitrack2 borders the current frame completely, while MtEdit simply brackets the current frame. Mainly because it simplifies the code to not have to have a 2 pixel offset for previous and following frames around the current frame. But it might be best to just use the offset and get the full borders again. Adjusting this display should come standard. There are four buttons to show more or less of the input region, and the mouse can move the display around. MtEdit does not have a keyboard control to move the display, but it's not hard to add. Variable opacity . Implemented It's simple enough -- Adjust the opacity of the display. Sometimes, you want to see the action behind the display, and there isn't any particular location you can stick it that doesn't block something important. Requires the gui.opacity function. FCEUX's color limitation makes this feature less effective, sadly. Watermarks A watermark is defined to be, in this case, a darker shade of color used once every few frames in the display. Even if there's nothing in particular to show at the moment, the different shade of color will provide some sense of motion through frames. FCEUX's color limitation makes it rather tricky to pull off, however. Unsorted features Various features that I can't really place under control or display. All player option Multitrack2 has it, MtEdit does not. Display all players instead of only one. I haven't figured out a good intuitive way of displaying all players' inputs, but Multitrack2 does use one idea. This also affects some controls, as now all players are displayed. Rewind Multitrack2 has it, MtEdit does not. The script could store a variety of savestates for use with rewinding back through some of it. As this script is practically an input editor, a rewind function fits nicely. Savestates are expensive on processing, and emulation of simpler systems (like the NES, which FCEUX emulates) makes the cost more manageable. I've also found it tricky to implement the timing well, especially during unpaused rewinds. If there are any other features I can think of, I'll update this post. I may want to use most or all of these, but I want to make sure I write the code in a clean way as I do it.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
Here's my current progress into 2-1. Over halfway through so far. A "just in case" online backup copy, mainly. But I figure the audience should know what I'm doing. I made a change to the end of 1-4 to have the proper RNG. The boss battle is somewhat less entertaining for what amounts to roughly 1.5 seconds of savings in the next stage. However, I still attack a cloud at the end. I know of a Cloud who has destroyed a powerful figure...
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
Noteable changes * [...] * New lua functions. *[...]
Yes! Download FnScan.lua
Language: lua

local target= joypad for k,v in pairs(target) do print(k, "- -", type(v)) end
readup - - function read - - function write - - function set - - function readdown - - function getup - - function getdown - - function get - - function ... Nuts. No joypad.peek.
Language: lua

local function Fn() joypad.set(1,joypad.get(2)) end
... Nuts. Still no frame-immediate way of reading then writing to joypad. Doesn't matter where I put this function (emu.frameadvance loop, emu.registerbefore, or whatever else). At least gui.register still runs while paused. That is still quite lovely. As for the accuracy, I say go make the change! The earlier versions of FCEUX will be available for sync stability of older movies, I hope.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
Just making an attempt at simplifying the code. I've actually stripped out a lot of its intelligence (by rewriting the code from scratch, again) and making it as simple as I know how. Use the mouse to move the display (drag the display around). Switch players with the Home key. Insert a frame with Insert key. Delete a frame with Delete key. Toggle between "ignore stored input" and "mix user/stored input" modes with End key. Show more or less frames with Numpad keys, specifically 1 3 7 or 9. The script is compelled to block user input on undisplayed players. The script always allows user input on the currently displayed player, but may XOR it with the stored input or leave it untouched. The script shows a basic display of user input. The script leaves the emulator's controls alone for individual players -- That is, the player 1 controls only affects player 1, player 2 controls affects player 2, and so forth. The reason being the emulator's implementation as of 2.1.4a actually locks me out from doing the proper modifications. Specifically, "Boundary" lets me modify the joypad immediately, but "Before" lets me read the joypad immediately. Unfortunately, the order does not allow the immediate read prior to the immediate write, ergo, it is impossible to update player 2 controls using player 1 controls, without it being one frame late. Download MtEdit.lua
Language: lua

local PLAYERS = 2 local btn = {"right","left","down","up","start","select","B","A"} --Joypad --############################################################################# -- Keyboard Control mapping local PlayerSwitch= "home" local solid= "pageup" -- Make the display less local clear= "pagedown" -- or more transparant. local mp= "numpad7" --More past. Tweak display to see further back! local lp= "numpad1" --Less past. You'll see less of the input stream. local mf= "numpad3" --More future. local lf= "numpad9" --Less future. local AutoList= true local Insert= "insert" local Delete= "delete" local AutoSwitch= "end" --############################################################################# -- Default values local Opq= 1 local DispX, DispY= 50, 80 --############################################################################# local PlSel= 1 local ThisJoypad= {} local JoypadList= {} local UserControl= {} local ListControl= {} for pl= 1, PLAYERS do ThisJoypad[pl]= {} JoypadList[pl]= {} UserControl[pl]= "inv" ListControl[pl]= true end local fc , Lastfc= 0 , 0 --***************************************************************************** local function UpdateFC() Lastfc= fc; fc= movie.framecount() end --***************************************************************************** --***************************************************************************** local function Within(V,l,h) return (V >= l) and (V <= h) end local function Limits(V,l,h) return math.max(math.min(V,h),l) end local function NullFN() return end --***************************************************************************** --############################################################################# --############################################################################# --Joypad --***************************************************************************** local function JoyToNum(Joys) -- Expects a table containing joypad buttons --***************************************************************************** -- Returns a number representing button presses. -- These numbers are the primary storage for this version of this script. local joynum= 0 for i= 1, #btn do if Joys[btn[i]] then joynum= bit.bor(joynum, bit.lshift(1,i)) end end return joynum end --***************************************************************************** local function ReadJoynum(Jn, button) -- Expects... Certain numbers! --***************************************************************************** -- Returns true or false. True if the indicated button is pressed -- according to the input. False otherwise. return ( bit.band(Jn , bit.lshift( 1, button )) ~= 0 ) end --***************************************************************************** local function LoadJoypad(pl) --***************************************************************************** -- Sets up the joypad to inject into the emulation. local joys= JoypadList[pl][fc] if ListControl[pl] then for b= 1, #btn do if joys and ReadJoynum(joys, b) then ThisJoypad[pl][btn[b]]= (UserControl[pl] or true) else ThisJoypad[pl][btn[b]]= (UserControl[pl] and nil) end end else for b= 1, #btn do ThisJoypad[pl][btn[b]]= (UserControl[pl] and nil) end end --UserControl is "inv" or false. I abuse the shortcutting of or & and. end --***************************************************************************** local function RegBoundaryHandleJoypad() --***************************************************************************** for pl= 1, PLAYERS do joypad.set(pl, ThisJoypad[pl]) end end --***************************************************************************** local function RegAfterHandleJoypad() --***************************************************************************** for pl= 1, PLAYERS do JoypadList[pl][Lastfc]= JoyToNum(joypad.get(pl)) LoadJoypad(pl) end end --############################################################################# --############################################################################# --Display (Joypad) --***************************************************************************** local Draw= {} --***************************************************************************** function Draw.left(x,y,color) -- ## gui.line(x+1,y ,x+2,y ,color) -- # gui.line(x+1,y+2,x+2,y+2,color) -- ## gui.pixel(x ,y+1,color) end function Draw.up(x,y,color) -- # gui.line(x ,y+1,x ,y+2,color) -- # # gui.line(x+2,y+1,x+2,y+2,color) -- # # gui.pixel(x+1,y ,color) end function Draw.right(x,y,color) -- ## gui.line(x ,y ,x+1,y ,color) -- # gui.line(x ,y+2,x+1,y+2,color) -- ## gui.pixel(x+2,y+1,color) end function Draw.down(x,y,color) -- # # gui.line(x ,y ,x ,y+1,color) -- # # gui.line(x+2,y ,x+2,y+1,color) -- # gui.pixel(x+1,y+2,color) end function Draw.A(x,y,color) -- ### gui.line(x ,y ,x ,y+2,color) -- ### gui.line(x+1,y ,x+1,y+1,color) -- # # gui.line(x+2,y ,x+2,y+2,color) end function Draw.B(x,y,color) -- # # gui.line(x ,y ,x ,y+2,color) -- ## gui.line(x+1,y+1,x+2,y+2,color) -- # # gui.pixel(x+2,y ,color) end function Draw.start(x,y,color) -- # gui.line(x+1,y ,x+1,y+2,color) -- ### gui.pixel(x ,y+1,color) -- # gui.pixel(x+2,y+1,color) end function Draw.select(x,y,color) -- ### gui.line(x ,y ,x+2,y ,color) -- # # gui.line(x ,y+2,x+2,y+2,color) -- ### gui.pixel(x ,y+1,color) gui.pixel(x+2,y+1,color) end Draw[0]= function(left, top, color) -- ### gui.line(left ,top ,left ,top+4,color)-- # # gui.line(left+2,top ,left+2,top+4,color)-- # # gui.pixel(left+1,top ,color) -- # # gui.pixel(left+1,top+4,color) -- ### end Draw[1]= function(left, top, color) -- # gui.line(left ,top+4,left+2,top+4,color)-- ## gui.line(left+1,top ,left+1,top+3,color)-- # gui.pixel(left ,top+1,color) -- # end -- ### Draw[2]= function(left, top, color) -- ### gui.line(left ,top ,left+2,top ,color)-- # gui.line(left ,top+3,left+2,top+1,color)-- ### gui.line(left ,top+4,left+2,top+4,color)-- # gui.pixel(left ,top+2,color) -- ### gui.pixel(left+2,top+2,color) end Draw[3]= function(left, top, color) -- ### gui.line(left ,top ,left+1,top ,color)-- # gui.line(left ,top+2,left+1,top+2,color)-- ### gui.line(left ,top+4,left+1,top+4,color)-- # gui.line(left+2,top ,left+2,top+4,color)-- ### end Draw[4]= function(left, top, color) -- # # gui.line(left ,top ,left ,top+2,color)-- # # gui.line(left+2,top ,left+2,top+4,color)-- ### gui.pixel(left+1,top+2,color) -- # end -- # Draw[5]= function(left, top, color) -- ### gui.line(left ,top ,left+2,top ,color)-- # gui.line(left ,top+1,left+2,top+3,color)-- ### gui.line(left ,top+4,left+2,top+4,color)-- # gui.pixel(left ,top+2,color) -- ### gui.pixel(left+2,top+2,color) end Draw[6]= function(left, top, color) -- ### gui.line(left ,top ,left+2,top ,color)-- # gui.line(left ,top+1,left ,top+4,color)-- ### gui.line(left+2,top+2,left+2,top+4,color)-- # # gui.pixel(left+1,top+2,color) -- ### gui.pixel(left+1,top+4,color) end -- ### Draw[7]= function(left, top, color) -- # gui.line(left ,top ,left+1,top ,color)-- ## gui.line(left+2,top ,left+1,top+4,color)-- # end -- # Draw[8]= function(left, top, color) -- ### gui.line(left ,top ,left ,top+4,color)-- # # gui.line(left+2,top ,left+2,top+4,color)-- ### gui.pixel(left+1,top ,color) -- # # gui.pixel(left+1,top+2,color) -- ### gui.pixel(left+1,top+4,color) end Draw[9]= function(left, top, color) -- ### gui.line(left ,top ,left ,top+2,color)-- # # gui.line(left+2,top ,left+2,top+3,color)-- ### gui.line(left ,top+4,left+2,top+4,color)-- # gui.pixel(left+1,top ,color) -- ### gui.pixel(left+1,top+2,color) end --***************************************************************************** local function GetColor(Jn,b) --***************************************************************************** if not Jn then return "white" end --Does not exist if not ReadJoynum(Jn,b) then return "red" end --Not pressed return "green" --Button pressed end --***************************************************************************** local function PaintFrame(x,y,Jn) --***************************************************************************** for b= 1, #btn do Draw[btn[b]](x+4*b,y,GetColor(Jn,b)) end end --***************************************************************************** local function PaintBorder(x,y) --***************************************************************************** local color= "green" if not AutoList then color= -0x003FFF01 end gui.line(x, y,x, y+4,color) gui.line(x+2+4*#btn,y,x+2+4*#btn,y+4,color) gui.pixel(x+1 ,y ,color) gui.pixel(x+1 ,y+4,color) gui.pixel(x+1+4*#btn,y ,color) gui.pixel(x+1+4*#btn,y+4,color) if PLAYERS > 1 then Draw[PlSel](x-4,y,color) end end local Past, Future= -5, 5 --***************************************************************************** local function PaintJoypadList(x,y) --***************************************************************************** gui.box(x+3,y+4*Past-1,x+4*#btn+3,y+4*Future+3,0x00000080) for i= Past, Future do PaintFrame(x,y+4*i, JoypadList[PlSel][fc+i]) end if Past <= 0 and Future >= 0 then PaintBorder(x+2,y-1) end end --############################################################################# --############################################################################# --User local lastkeys, keys= input.get(), input.get() --***************************************************************************** local function UpdateKeys() lastkeys= keys; keys= input.get() end local function Press(k) return keys[k] and (not lastkeys[k]) end --***************************************************************************** local KF= {} --***************************************************************************** local function KeyReader() --***************************************************************************** for k,v in pairs(keys) do if (not lastkeys[k]) and KF[k] then KF[k]() end end end local HandleMouse --***************************************************************************** local function HandleMouse_Main() --***************************************************************************** if keys.leftclick and lastkeys.leftclick then DispX= DispX + keys.xmouse - lastkeys.xmouse DispY= DispY + keys.ymouse - lastkeys.ymouse end end --***************************************************************************** local function HandleMouse_Option() --***************************************************************************** end HandleMouse= HandleMouse_Main local MaxRange= 54 ------------------------------------------------------------------------------- KF[solid]= function() Opq= math.min(Opq+0.125 , 1); gui.opacity(Opq) end KF[clear]= function() Opq= math.max(Opq-0.125 , 0); gui.opacity(Opq) end ------------------------------------------------------------------------------- KF[mp]=function() Past = Past -1; Future= math.min(Future,Past+MaxRange) end KF[lp]=function() Past = Past +1; Future= math.max(Future,Past) end KF[mf]=function() Future= Future+1; Past= math.max(Past,Future-MaxRange) end KF[lf]=function() Future= Future-1; Past= math.min(Past,Future) end local PlSwitch --***************************************************************************** local function PlSw() --***************************************************************************** for pl= 1, PLAYERS do UserControl[pl]= false ListControl[pl]= true end UserControl[PlSel]= "inv" ListControl[PlSel]= AutoList for pl= 1, PLAYERS do LoadJoypad(pl) end end PlSwitch= PlSw PlSw() ------------------------------------------------------------------------------- KF[PlayerSwitch]= function() ------------------------------------------------------------------------------- PlSel= (PlSel%PLAYERS)+1 PlSwitch() end ------------------------------------------------------------------------------- KF[Insert]= function() ------------------------------------------------------------------------------- local frame= fc while JoypadList[PlSel][frame] do frame=frame+1 end for i= frame, (fc+1), -1 do JoypadList[PlSel][i]= JoypadList[PlSel][i-1] end JoypadList[PlSel][fc]= 0 LoadJoypad(PlSel) end ------------------------------------------------------------------------------- KF[Delete]= function() ------------------------------------------------------------------------------- local i= fc while JoypadList[PlSel][i] do JoypadList[PlSel][i]= JoypadList[PlSel][i+1] i=i+1 end LoadJoypad(PlSel) end ------------------------------------------------------------------------------- KF[AutoSwitch]= function() ------------------------------------------------------------------------------- AutoList= not AutoList ListControl[PlSel]= AutoList end --############################################################################# --############################################################################# --Registry --***************************************************************************** local function RegisterAfter() --***************************************************************************** UpdateFC() RegAfterHandleJoypad() end emu.registerafter(RegisterAfter) --***************************************************************************** local function RegisterGui() --***************************************************************************** UpdateKeys() KeyReader() HandleMouse() DispX= Limits(DispX,-2,251-#btn*4) DispY= Limits(DispY,1+-4*Past,220-4*Future) PaintJoypadList(DispX, DispY) gui.pixel(0,0,0) end gui.register(RegisterGui) --***************************************************************************** local function RegisterLoad() --***************************************************************************** UpdateFC() for pl= 1, PLAYERS do LoadJoypad(pl) end end savestate.registerload(RegisterLoad) --***************************************************************************** while true do --Register boundary --***************************************************************************** RegBoundaryHandleJoypad() emu.frameadvance() end
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
{insert thrice-stacked quoting here} Indeed. The descriptions are back. It's been a while since the disappearance, but seeing the descriptions in place again has certainly put me in a better mood.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
Ah, that script. I have a version of my own. Considering the link is no longer useful, I'll make a direct post here: Download SimCityViewer.lua
Language: lua

--lua script for snes9x rerecording+lua. For SimCity (U) --Original by Dammit. Mostly rewritten by FatRatKnight, Feb 10 2010. local ShowNum, ShowGrid= true, true local ShowHex= false local Control= 2 local SwitchNum, SwitchGrid= "X","Y" local SwitchVal= "A" local SwitchDisp= "B" local Brd,Bck= {},{} Brd[0],Bck[0]= 0x00000060,0x00000000 --Black ; value zero Brd[1],Bck[1]= 0x005A00C0,0x005A0038 --V.Dark Green; Rock bottom, non-zero Brd[2],Bck[2]= 0x008C00C0,0x008C0038 --Dark Green ; Pitifully low Brd[3],Bck[3]= 0x00BD00C0,0x00BD0038 --Green ; Somewhat low Brd[4],Bck[4]= 0x00FF00C0,0x00FF0038 --Bright Green; Near-mid, but low Brd[5],Bck[5]= 0xFFFF00C0,0xFFFF0038 --Yellow ; Near-mid, but high Brd[6],Bck[6]= 0xFFB500C0,0xFFB50038 --YellowOrange; Somewhat high Brd[7],Bck[7]= 0xFF6300C0,0xFF630038 --Orange ; Rather high, I say Brd[8],Bck[8]= 0xFF0000C0,0xFF000038 --Pure red ; Absolute top local view -- Declaring that view exists here so functions can access it! local FieldW , FieldH= 120 , 100 -- Size of game's map local CamW , CamH = 32 , 28 -- Size of camera local CamX,CamY= 0x7E01BD,0x7E01BF -- Memory where camera position is stored --***************************************************************************** local function Byte_U(Index,TileX,TileY) --***************************************************************************** local Bx= view[Index]["Bx"] local Offset= math.floor(TileX/Bx) + math.floor(TileY/Bx)*math.floor(FieldW/Bx) local Val= memory.readbyte(view[Index]["addr"]+Offset) local C= 0 if Val ~= 0 then C= math.floor((Val+0x1F)/0x20) end return Bck[C], Brd[C], Val end --***************************************************************************** local function Bit___(Index,TileX,TileY) --Assumed box size is 1 --***************************************************************************** local Offset= math.floor(TileX/8) + TileY*math.floor(FieldW/8) local WhichBit= bit.rshift(0x80 , TileX % 8) local Test= memory.readbyte(view[Index]["addr"]+Offset) Test= (bit.band(Test,WhichBit) ~= 0) local Background, Border= 0x00B50038, 0x00B500C0 if Test then Background, Border= 0xFF840038, 0xFF8400C0 end return Background, Border, nil end --***************************************************************************** local function Word_S(Index,TileX,TileY) --Assumed it's Pop.Growth --***************************************************************************** local Bx= view[Index]["Bx"] local Offset= math.floor(TileX/Bx) + math.floor(TileY/Bx)*math.floor(FieldW/Bx) Val= memory.readwordsigned(view[Index]["addr"]+Offset*2) local C= 0 if Val ~= 0 then C= math.floor(( Val+300 )/60) C= math.min(math.max( 1,C ),8) end return Bck[C], Brd[C], Val end view = { -- Now define view, containing above functions -- {addr=0x7F0000, Mthd=Byte_U, Bx=4, name="Mystery" }, -- Was it charts? {addr=0x7F6B00, Mthd=Byte_U, Bx=2, name="Land value" }, {addr=0x7F76B8, Mthd=Byte_U, Bx=2, name="Crime" }, {addr=0x7F8270, Mthd=Byte_U, Bx=2, name="Pollution" }, {addr=0x7F8E28, Mthd=Byte_U, Bx=2, name="Pop.density"}, {addr=0x7F99E0, Mthd=Byte_U, Bx=2, name="Traffic" }, {addr=0x7FA598, Mthd=Bit___, Bx=1, name="Power" }, {addr=0x7FAB74, Mthd=Byte_U, Bx=4, name="LVmod:Parks"}, {addr=0x7FAE62, Mthd=Word_S, Bx=8, name="Growth rate"}, {addr=0x7FAFE8, Mthd=Byte_U, Bx=8, name="Police" }, {addr=0x7FB0AB, Mthd=Byte_U, Bx=8, name="Fire" }--, -- {addr=0x7FB16E, Mthd=Word_S, Bx=8, name="Tmp_Fire" }, -- {addr=0x7FB2F4, Mthd=Word_S, Bx=8, name="Tmp_Police" }, -- {addr=0x7FB47A, Mthd=Word_S, Bx=8, name="Com_Rate?" }, -- {addr=0x7FB600, Mthd=Byte_U, Bx=2, name="AltPollute1"}, -- {addr=0x7FC1B8, Mthd=Byte_U, Bx=2, name="AltPollute2"}, -- {addr=0x7FCD70, Mthd=Byte_U, Bx=4, name="LndVal.Mod "}, -- {addr=0x7FD05E, Mthd=Word_S, Bx=8, name="Tmp2_Police"} } --***************************************************************************** local function FindCam() --***************************************************************************** return memory.readwordsigned(CamX), memory.readwordsigned(CamY) end --***************************************************************************** local function ToDec(Num) --***************************************************************************** if not Num then return end return string.format("%d",Num) end --***************************************************************************** local function ToHex(Num) --***************************************************************************** if not Num then return end if Num < 0 then Num= Num+0x10000 end return string.format("%X",Num) end local ToString= ToDec if ShowHex == true then ToString= ToHex end --***************************************************************************** local function ShowStats(Left,Top,Right,Bottom, Fill,Bord,Str) --***************************************************************************** if ShowGrid then gui.box(Left,Top,Right,Bottom,Fill,Bord) end if Str and ShowNum then if Right-Left >= string.len(Str)*4+3 then gui.text(Left+2,Top+1,Str) end end end --***************************************************************************** local function DisplayScreen(Index) --***************************************************************************** local Left,Top= FindCam() local BoxSize= view[Index]["Bx"] local ReadData= view[Index]["Mthd"] local TileY= math.floor(math.max(Top, 0)/BoxSize)*BoxSize repeat local Temp= math.min(TileY+BoxSize,FieldH+1) local BoxUp= math.max((TileY-Top)*8,0) local BoxDn= math.min((Temp-Top)*8 - 1 , 223) local TileX= math.floor(math.max(Left, 0)/BoxSize)*BoxSize repeat Temp= math.min(TileX+BoxSize,FieldW+1) local BoxLf= math.max((TileX-Left)*8,0) local BoxRt= math.min((Temp-Left)*8 - 1 , 255) local Fill,Bord,Num= ReadData(Index,TileX,TileY) Num= ToString(Num) ShowStats(BoxLf,BoxUp,BoxRt,BoxDn, Fill,Bord,Num) TileX= TileX+BoxSize until (TileX >= Left+CamW) or (TileX >= FieldW) TileY= TileY+BoxSize until (TileY >= Top+CamH) or (TileY >= FieldH) end local Index= 1 --***************************************************************************** local function DisplayStuff() --***************************************************************************** local mode= memory.readbyte(0x7E0009) if (mode == 31) or (mode == 36) then if ShowNum or ShowGrid then DisplayScreen(Index) gui.text(200,215,view[Index].name,0x00FF00FF) end end end gui.register(DisplayStuff) local LastBtn= {} --***************************************************************************** while true do --***************************************************************************** local Btn= joypad.get(Control) if Btn[SwitchVal] and not LastBtn[SwitchVal] then Index= Index+1 if not view[Index] then Index= 1 end end if Btn[SwitchNum] and not LastBtn[SwitchNum] then ShowNum= not ShowNum end if Btn[SwitchGrid] and not LastBtn[SwitchGrid] then ShowGrid= not ShowGrid end LastBtn= Btn emu.frameadvance() end
Should work with the latest Snes9x 1.51 we have. If not, let me know.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
1-4 .vbm It is done. And I attack a vicious cloud at the end. They're vicious, I tell you! I wish to put this stage behind me at last. I'll get back into rolling through the stages ahead, we'll see what's ahead. ... As for the RNG... I may have to deal with whatever I got. I don't see any easy manipulations I can do from the 1-4 side at the boss.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
Area 2 beaten. Though I'm not sure what I should do for entertainment. Feel free to give suggestions. For those curious, I actually get enough points to earn two stocks in this area. This means I skip a ship upgrade animation, going directly to the fourth ship. Incidentally, the ships have the following qualities: 1st: Basic shots only 2nd: Gains a charged shot 3rd: Graphically wider charged shots (I haven't checked if it is mechanically wider) 4th: Faster charged shot 5th: Going "off-road" no longer slows you down. You still take damage, though. 6th: Very fast charged shot A two-byte value at address 7E18EF tells you how charged your shot is. When it is 0xE000 or greater, you may release a powerful shot. The 2nd and 3rd ships charge at +0x0200 per frame, 4th and 5th charge at +0x0280 per frame, and the 6th ship charges as +0x0400 per frame. Effectively, it takes 112 frames to charge for 2nd and 3rd ships, 90 frames for 4th and 5th ships, and 56 frames for the 6th ship. I demonstrate many times in the above run that I can fire basic shots while charging up a powerful shot. Firing these basic shots like this delays the charge time by a single frame per shot. If anyone who plans to beat the SDA record on console wants to know the mechanics of shooting while charging, it is difficult to pull off consistently. You must, on the same frame, release one firing button, Y A or R, and press a different one of Y A or R. Any differences, and either you don't shoot, or you lose your charge. If they overlap, you don't fire a single shot, meaning you pressed the other button before you released the one you were previously holding. If there's a gap, you lose the charge and fire a basic shot or two, meaning you pressed the new button after you released the old one. The timing necessary is cruel, down to frame-precision is necessary. But at least you can feel what your hands are doing, and you get quick feedback on whether you failed or not, and which way you failed on top of that. Trouble is the fact that, from console power-on, unless there's some cheat code I'm unfamiliar with, you need to play through the first area or two before you can get a ship with charged shots to practice with. It's not convenient to get to the point where you can practice. But if those SDA console speedrunners want to try, I hope these instructions let you know what to shoot for. Have fun, as always!
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
While it has only been a short time since my last post, I think it's important enough for a new post. I figured out how to affect the RNG in 1-4! I was looking in the wrong place for what is affecting the RNG. The flying agent has a simple method of flying towards (or away from) the player, no RNGs there. I look at the helicopter, and it has a simple method of flying away from the player, no RNGs here either. The player character has no way to roll the RNG here. Yet despite that, the RNG has been rolling like crazy. After ruling out the local agent, the helicopter, and myself, that leaves... Clouds. Yes, clouds. The white puffy things. Clouds. The thing that hid from my script's default state. Clouds. The thing which I can't blast away. Freaking clouds. As long as there exists 4 clouds, somewhere, the RNG will not roll. If there are 3 or less, the game will roll the RNG once per frame. It will keep rolling until it spawns enough clouds to fill the maximum of 4. Clouds vanish when they are far enough offscreen. I'm never going to trust another cloud, ever again... But at least I know how to control the RNG once more. We'll see what I can do now!
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
DarkKobold wrote:
I doubt anyone who joins will live up to the level of perfection you've set.
While that is likely true, I'm not actually looking for someone who is equally or more capable as myself, or capable of meeting my visions of what is perfection. I don't expect having someone else help will be the magic antidote to this. I'm merely looking for someone who is willing to go through any frustrations with me in IRC. Even if the person isn't highly productive during this time, at least having someone interested in any progress constantly watching and prodding me (and attempting to ask relevant questions about the run itself, occasionally) would keep me better in track. Some effort in a TAS attempt would be rather appreciated, I'm hoping the person at least opens VBA and runs the movie. I've reached helicopter 2 and got its power-up at frame 19424. In the travel between first and second helicopters, I'm 4 frames ahead of the leading DTC3 run, at 390 frames between pick-ups, but the theoretical best I could come up with runs at 367 frames, a difference of 23 (although that 367 test is likely suboptimal, so the difference is even worse). This is a decent result, at least, and I'll likely continue with this result. Either as comparison while still optimizing 1-4 or as my final run. Trust me, I will not go back and fix it for v2 after I move on to 2-1, ever. I'll be at my computer for the next several hours, hopefully TASing something up, in case anyone watching wants to say hi in IRC.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
exileut, part of the problem is that messing with the RNG for a decent result is particularly difficult, let alone perfect results. Awful results come easy, but those would be less optimal than the DTC3 runs, and possibly slower than the theoretical optimal case by a few seconds overall. On the other hand, I probably need to kick myself into finishing anything at this point. DarkKobold, I would gladly accept a public encode from you (or anyone else) when I finish this level. I would also gladly accept help from anyone on this stage, as my last post was a call for help, but if no one does come, I'll work out the stage anyway. Part of it is the fact I wish to work with someone, as a way to keep some sense of motivation. But another part is the fact others will have thoughts I don't have, a source of inspiration. Working in a team generally goes a long way, from my experience in the Dream Team Contest.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
While it comes late, this movie file must surely have... A small fraction of 1-4. Eh, sorry about that. But I'm having some trouble handling the RNG to make the next helicopter appear in just the right place. That is, straight up, straight left, or straight right. Diagonals are certainly slower, as the helicopter travels diagonally away from the player, and if we can get it to appear straight in some direction, we can slow down the helicopter by moving back and forth around some axis, getting us to the heli sooner. If I did have perfect luck, the second helicopter might look like this. Only trouble is telling the RNG to behave. If anyone needs an updated script, I'll be sure to post or edit an update in. EDIT: Here, edited the current script. Anyone from the DTC3 willing to take a look? I now realize why I've been putting off this run for as long as I did, and trying to get the closest thing to a perfect run is proving rather tricky here. I'm having a difficult time judging how to tweak the RNG perfectly here.