Posts for FatRatKnight

Post subject: Breaking news: The world ended. I finished AH!
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
http://tasvideos.org/userfiles/user/FatRatKnight May come late, but I've got some completed runs now. Just in case I lose track, anyone has permission to submit one of these files in my name, as long as it hasn't already been submitted. ... Anything necessary about using later versions of VBA? I know, it's been a long time, and well... Hi. I have a complete file. There's still a few things I want to deal with before I submit myself, but I'm not complaining if someone else submits the darn thing before I'm ready. I held up everyone looking forward to the DTC3 improvements. So, uh... Sorry about taking so long. In any case, 3-3 was a bit of a pain to go through. I ran out of spread shots just as I was picking up another orb, which makes me feel awesome that I made that work. What makes me feel a bit less awesome is that I lost around 20 frames getting another spread orb later. I hate those psychotic walking bombs. 3-4, the final boss... I was thinking the RNG isn't in a nice spot. Then I looked several hundred RNG rolls forward and backward, and realized I probably have the best RNG I'm ever going to have in this run. Alas, due to how I ended up messing with weapon drops, I'm nearly a second slower on the boss kill compared to T5. Though somehow T5 still was slower, perhaps due to some animation taking longer on that platform or something. But hey. We have something submittable now.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
I don't actually know the limits of the off-map glitching. But I don't think it is possible to corrupt the stack into having the game jump into an area of memory where you have your city in. Regardless, here are the mechanics I am aware of: - You can only place Roads, Power Lines, or Mass Transit in the black gunk. - Such tiles are in the East-West version, regardless of adjacent tiles. - East-West Roads are value 0x32, East-West Power Lines are 0x62, East-West Mass Transit are 0x72 The spot in memory where the city begins is at address 0x7F0200. Each tile is a two-byte value somewhere in the range of 0x0000 to 0x03FF. Some of the upper bits are used, but the purpose is currently unknown. If you want greater control, there is a map stat that is tracked by individual bits. Starting at address 0x7FA598 is a bit-array containing which tiles are currently powered. Although in order for the power to go from one tile to another, you need connecting Power Lines or buildings. You can instead substitute power plant centers if the break in Power Lines is that important. Even so, you don't have absolute perfect control, but it should be enough to produce a nice function, especially with 1500 bytes to do so with. You also need the money somehow for multiple power plants. That leaves breaking the stack, or other pointers to jump into our creation. I can't confirm this as possible. since I have made one attempt earlier in glitching my money but failing to do so. There's probably a two-byte boundary that our lovely off-map glitch can't get past, so we're probably stuck somewhere in 0x7F0000 to 0x7FFFFF. This is a guess, though. The only spots in that range I don't have in my RAM map are 0x7F0000 to 0x7F01FF, and 0x7FF8F4 to 0x7FFFFF. Everything else in there is definitely taken by map stats. There's still plenty of stuff to look at before 0x7F0000, but I'm not sure we can glitch that. It's open to further research, that much is certain. But I'm not entirely hopeful.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
Questions I ask myself for 100%: - Does the game track it? - Is it irreversible once done? - Is it done by non-repeating events? - Does it give me more to do? If yes to all, do it. Don't ask further questions. That is my philosophy, but many would say I'm closer to the extreme of leaving absolutely nothing undone. I'm certainly more on this extreme than what I observed everyone here is at. Does the game track it? Very obvious if the game's HUD tells you. Less obvious if not. It appears some people's definition ends at the HUD, despite any other visual clues or mechanical effects. The game cares about what's on the HUD just as much as it does that one lonely chest deep in some dungeon. It's all there in the save file, anyway. Every bit of data needs to be saved, and every event that is flagged as "done" or "not done" is somewhere in there. While I say flag every one of them in there as "done", at least all of those that can be. Some can argue that I would do too much, but I will say in return that I will have unambiguously produced a 100% run. But why did I do this tiny thing that no one will notice or care about? I will say, does that make my run "less 100%" than if I didn't do it? What I see here, the bulk of the trouble is defining what of this stuff the game tracks as "important enough" to include in a 100%. I will ask a silly question: Why is the HUD important? Is it visibility? Suppose I never show it. No viewer can confirm or deny 100% status then, without watching the whole run. Is it irreversible once done? You can only do some events once. You can only gain the rewards of certain places once. Things done might improve you in some way, and there may well be nothing intended in the game to take that away. I'm pretty certain many people are in agreement here that picking up a rupee does not count as irreversible, but getting a new rupee wallet is. Triggering cutscenes usually is irreversible, but perhaps there's a few that are "lost" after doing something, never to be triggered. Does the act of triggering an event that prevents another from triggering considered just as fine? Okay, bringing up cutscenes is probably silly. Even I, someone who prefers getting that one tiny useless chest in the depths of some dungeon, feels iffy about triggering unneeded cutscenes just to say it's irreversibly triggered. But if the trigger disappears from a different event, does the game track it? Is there any way of knowing aside from hacking the save? Interesting questions, perhaps. But now we're moving back towards my first main question of what the game tracks. But irreversible events tend to be a pretty good starting point when finding stuff that could move you towards 100%. Oh, just a side note: If you can reverse an event by erasing the save and starting over from scratch, that string of actions does not remove my label of "irreversible." Is it done by non-repeating events? In other words, dieing just to increment some counter is a repeating, although irreversible event. Gathering stuff from defeated enemies is a repeating event. Going from place to place is a repeating event. Sure, this could be stuff that can be tracked by the game, but really, repeating events. I don't think the fluff here is important, but as I already listed the question, I felt the need to explain myself about it. Does it give me more to do? Setting every possible camera angle in every possible position during every possible situation gives me more to do, but doing that is filed under "no" with my other three questions. But the spirit of the question here isn't what ridiculous crazy mindless stuff the player can do. What I want to know is, will it look much like an any% for this segment if I don't do this? Is there something meaningful I can do in the meantime that would further the goals of doing irreversible non-repeating events tracked by the game? If clearing a temple "100% style" means I grab the bow and slay the boss, am I not already doing just what the any% is doing? Of course, doing more stuff takes more time, and therefore slows the run down. People will complain that you didn't do this. People will complain you didn't need to do that, and you could have gone faster by skipping it. There is no middle ground where everyone is happy, so I choose one extreme and stick with it. I doubt I can change the course of discussion in just one post. However, the fact the issue of what 100% means has been going on long enough that I got a bit upset. Thus, prompting me to get this account out of its cobwebs and provide a piece of my thoughts to you. Basically, I want you all to get things together and decide now what a proper 100% is. No one will be happy, and I don't expect to be fully satisfied with any definition set forth, but at least we'll have a run.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
deuxhero wrote:
Mario Golf: Advance Tour seems like it would benefit a LOT from even just basic trial and error savestate use to pull off impossible shots, it isn't too long (I can beat singles in a few hours unassisted and thats going for everything/talking to everyone and making no effort to be quick). Doubles would be tricker (Neil is terrible at golf unless you are in a sandtrap, then he is a savant)
Here's my .vbm for you. (Though I notice the quoted message is months old...) (fixed link) Since I don't have my TASing set-up, I figure there's no harm in sharing what I have now. As for my own wishes... F-Zero: Maximum Velocity ... Uh, I'll figure out others somehow. Eye of the Beholder for the GBA is another thought, but... The battles are slow in that version. I doubt it'll be a good run to make. Still, I know about a glitch involving long-term buffs and unequipping the Dagger of Venom in there, in case anyone needs that (cast a stat buff, remove Dagger of Venom, camp, re-equip and remove, camp, ...).
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
I had received a PM asking about this script. I have salvaged some scripts from my TASing computer. As I haven't managed to get my usual TASing stuff on this new computer, I didn't test out whether these scripts work as is. It will take a while before I can get things working well again. I recall MtEdit was basically a somewhat simplified version of Multitrack2, so that I can make changes to it better, or something. Regardless, here are two different scripts: The first one is Multitrack2 The second one is MtEdit Whether they work is currently unknown. So are their settings. I am copying these files here as is, as the pastey link certainly isn't working now, and any access is better than nothing. Download Multitrack2.lua
Language: lua

-- XOR Multitrack for Snes9x by FatRatKnight -- It simply records all input and XORs it with the user's button presses. -- Still a few problems, but things are definitely much smoother. --*************** local players= 1 -- You may tweak the number of players here. --*************** --Display local PlayerSwitch= "home" -- For viewing other players local ListSwitch= "end" -- Should the script use or ignore its own list? local JoypadSwitch= "delete" -- Should we allow or deny your input? local btn={"left","up","right","down","A","B","Y","X","L","R","start","select"} --Try to avoid changing btn. You may reorder btn's stuff if you don't --like the default order, however. local solid= "pageup" -- Make the display less local clear= "pagedown" -- or more transparant. local DispN= "numpad8" local DispS= "numpad2" -- For moving the local DispE= "numpad6" -- display around. local DispW= "numpad4" local MoreFutr= "numpad3" local LessFutr= "numpad1" -- These will determine local MorePast= "numpad7" -- how many frames you local LessPast= "numpad9" -- want to display. local ResetFP= "numpad5" --Various colors I'm using. If you wish to bother, go ahead. local shade= 0x00000080 local white= 0xFFFFFFFF local red= 0xFF0000FF local green= 0x00FF00FF local blue= 0x0040FFFF local orange=0xFFC000FF --***************************************************************************** --Please do not change the following, unless you plan to change the code: local plmin , plmax= 1 , 1 local fc= movie.framecount() local InputList= {} local OptionUseList= {} local OptionUseJoypad= {} local keys, lastkeys= {}, {} for pl= 1, players do InputList[pl]= {} OptionUseList[pl]= true OptionUseJoypad[pl]= true end --***************************************************************************** print("Running Multitrack Script made by FatRatKnight.", "\r\nIts primary use is to preserve input after a loadstate.", "\r\nNot as functional as what I have in FCEUX, but this works.", "\r\nThis assumes you're using frame advance, so unpaused playerswitching", "will toggle through the players at a rapid pace.\r\n") print(PlayerSwitch,"- Display another player's input") print(ListSwitch, "- Toggle whether the script uses its list") print(JoypadSwitch,"- Toggle whether the joypad input is used\r\n") print(solid,"- Make display more opaque.") print(clear,"- Fade the display a bit.") print(DispN,DispS,DispE,DispW,"- Move the display.") print(MoreFutr,LessFutr,MorePast,LessPast,"- Adjust how much the display shows") print(ResetFP,"- Reset display around current frame\r\n") print("When running this script, you'll see a bunch of symbols appear.", "This basically says what the script has recorded so far.", "As I can't accept input while paused, you'll need to hold the", "option key down and hit frame advance.\r\n") print("But the script has basic, useful functionality. It works as well as", "well as I hoped. And remember, edit the script if you don't like some", "of the controls. The options are near the top.\r\n") print("Players:",players) --***************************************************************************** function FBoxOld(x1, y1, x2, y2, color) --***************************************************************************** -- Gets around Snes9x's problem of double-painting the corners. -- The double-paint is visible with non-opaque drawing. -- It acts like the old-style border-only box. -- Slightly messes up when x2 or y2 are less than their counterparts. if (x1 == x2) and (y1 == y2) then gui.pixel(x1,y1,color) elseif (x1 == x2) or (y1 == y2) then gui.line(x1,y1,x2,y2,color) else --(x1 ~= x2) and (y1 ~= y2) 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 Snes9x's problem of double-painting the corners. -- It acts like the new-style fill-and-border box. if not Border then Border= Fill end gui.box(x1,y1,x2,y2,Fill,0) FBoxOld(x1,y1,x2,y2,Border) end --***************************************************************************** local Draw= {} --Draw[button]( Left , Top , color ) --***************************************************************************** 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.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.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.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) -- ### FBoxOld(x ,y ,x+2,y+2,color) -- # # end -- ### function Draw.A(x,y,color) -- ### FBoxOld(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 ,y+2,color) -- ## gui.line(x+1,y+1,x+2,y+2,color) -- # # gui.pixel(x+2,y ,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.D0(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 function Draw.D1(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 function Draw.D2(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 function Draw.D3(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 function Draw.D4(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 function Draw.D5(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 function Draw.D6(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 function Draw.D7(left, top, color) gui.line(left ,top ,left+1,top ,color) gui.line(left+2,top ,left+1,top+4,color) end function Draw.D8(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 function Draw.D9(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) gui.pixel(left+2,top+3,color) end Draw[0],Draw[1],Draw[2],Draw[3],Draw[4]=Draw.D0,Draw.D1,Draw.D2,Draw.D3,Draw.D4 Draw[5],Draw[6],Draw[7],Draw[8],Draw[9]=Draw.D5,Draw.D6,Draw.D7,Draw.D8,Draw.D9 --***************************************************************************** function DrawNum(right, top, Number, color, bkgnd) --***************************************************************************** -- Paints the input number as right-aligned. -- Returns the x position where it would paint another digit. -- It only works with integers. Rounds fractions toward zero. local Digit= {} local Negative= false if Number < 0 then Number= -Number Negative= true end Number= math.floor(Number) if not color then color= "white" end if not bkgnd then bkgnd= "clear" end local i= 0 if Number < 1 then Digit[1]= 0 i= 1 end while (Number >= 1) do i= i+1 Digit[i]= Number % 10 Number= math.floor(Number/10) end if Negative then i= i+1 end local left= right - i*4 FakeBox(left+1, top-1, right+1, top+5,bkgnd,bkgnd) i= 1 while Draw[Digit[i]] do Draw[Digit[i]](right-2,top,color) right= right-4 i=i+1 end if Negative then gui.line(right, top+2,right-2,top+2,color) right= right-4 end return right end --***************************************************************************** function limits( value , low , high ) -- Expects numbers --***************************************************************************** -- Returns value, low, or high. high is returned if value exceeds high, -- and low is returned if value is beneath low. return math.max(math.min(value,high),low) end --***************************************************************************** function within( value , low , high ) -- Expects numbers --***************************************************************************** -- Returns true if value is between low and high. False otherwise. return ( value >= low ) and ( value <= high ) end --***************************************************************************** function JoyToNum(Joys) -- Expects a table containing joypad buttons --***************************************************************************** -- Returns a number from 0 to 4095, representing button presses. -- These numbers are the primary storage for this version of this script. local joynum= 0 for i= 1, 12 do if Joys[btn[i]] then joynum= bit.bor(joynum, bit.rshift(0x1000,i)) end end return joynum end --***************************************************************************** function ReadJoynum(input, button) -- Expects... Certain numbers! --***************************************************************************** -- Returns true or false. True if the indicated button is pressed -- according to the input. False otherwise. return ( bit.band(input , bit.rshift( 0x1000,button )) ~= 0 ) end --***************************************************************************** function ShowOnePlayer(x,y,color,button,pl) --***************************************************************************** -- Displays an individual button. -- Helper function for DisplayInput. Called as HighlightButton x= x + 4*button - 3 Draw[btn[button]](x,y,color) end --***************************************************************************** function ShowManyPlayers(x,y,color,button,pl) --***************************************************************************** -- Displays an individual button. Uses 2x3 boxes instead of button graphics. -- Helper function for DisplayInput. Called as HighlightButton x= x + (players + 1)*(button - 1) + pl gui.line(x,y,x,y+2,color) end local DispX, DispY= 160, 70 local Past, Future= -12, 20 local Opaque= 1 --***************************************************************************** function DisplayOptions(width) -- Expects width calculated by DisplayInput --***************************************************************************** -- Returns true if Opaque is 0, as a signal to don't bother painting. -- Separated from DisplayInput to make it clear which half is doing what. -- Change opacity? if keys[solid] then Opaque= Opaque + 1/8 end if keys[clear] then Opaque= Opaque - 1/8 end Opaque= limits(Opaque,0,1) gui.opacity(Opaque) if Opaque == 0 then return true end -- Don't bother processing display if there's none to see. -- How many frames to show? if keys[LessFutr] then Future= Future-1 if Future < Past then Past= Future end end if keys[MoreFutr] then Future= Future+1 if Future > Past+53 then Past= Future-53 end end if keys[MorePast] then Past= Past-1 if Past < Future-53 then Future= Past+53 end end if keys[LessPast] then Past= Past+1 if Past > Future then Future= Past end end if keys[ResetFP] then Past= -12; Future= 12 end -- Move the display around? if keys[DispS] then DispY= DispY+1 end if keys[DispW] then DispX= DispX-1 end if keys[DispE] then DispX= DispX+1 end if keys[DispN] then DispY= DispY-1 end if keys["leftclick"] and lastkeys["leftclick"] then DispX= DispX + keys.xmouse - lastkeys.xmouse DispY= DispY + keys.ymouse - lastkeys.ymouse end DispX= limits(DispX,1,254-width) DispY= limits(DispY,3-4*Past,218-4*Future) return nil -- Signal to display the input end --***************************************************************************** function DisplayInput() --***************************************************************************** -- Paints on the screen the current input stored within the script's list. -- Rather a shoddy job at loadstate, however. --Are we showing all players or just one? local HighlightButton= ShowOnePlayer local width= 48 if plmin ~= plmax then HighlightButton= ShowManyPlayers width= 12*players + 12 end --For both setting options and asking if we should bother displaying ourselves if DisplayOptions(width) then return end --Display frame offsets we're looking at. local RtDigit= DispX + width + 22 if RtDigit > 254 then RtDigit= DispX - 4 end local TpDigit= DispY + 4*Past - 2 DrawNum(RtDigit,TpDigit,Past,white,shade) if Future > Past+1 then TpDigit= DispY + 4*Future DrawNum(RtDigit,TpDigit,Future,white,shade) end --Show cute little box around current frame if Past <= 0 and Future >= 0 then local color= green local temp= 0 for pl= plmin, plmax do if OptionUseJoypad[pl] then temp= temp+1 end end if temp == 0 then color= red elseif (plmin ~= plmax) and (temp ~= players) then color= orange end FBoxOld(DispX-1,DispY-2,DispX+width+1,DispY+4,color) end --Finally, we get to show the actual buttons! for i= Past, Future do local Y= DispY + 4*i if i < 0 then Y=Y-2 elseif i > 0 then Y=Y+2 end gui.box(DispX,Y-1,DispX+width,Y+3,shade,shade) for pl= plmin, plmax do local scanz= InputList[pl][fc+i] for button= 1, 12 do local color if not scanz then color= white elseif ReadJoynum(scanz,button) then color= green else color= red end if (not OptionUseList[pl]) and i >= 0 then color= bit.bor(color, 0xC080C000) end HighlightButton(DispX,Y,color,button,pl) end end end end --***************************************************************************** function SetInput() --***************************************************************************** -- Sets the joypads. It's smart, at long last! for pl= 1, players do local temp= InputList[pl][fc-1] if temp and OptionUseList[pl] then local ThisInput= {} if OptionUseJoypad[pl] then ThisInput= joypad.get(pl) end for i= 1, 12 do if ReadJoynum(temp,i) then ThisInput[btn[i]]= not ThisInput[btn[i]] end end joypad.set(pl, ThisInput) end end end --***************************************************************************** function CatchInput() --***************************************************************************** -- For use with registerbefore. It will get the input and paste it into -- the input list. fc= movie.framecount() for pl= 1, players do InputList[pl][fc-1]= JoyToNum(joypad.get(pl)) end end emu.registerbefore(CatchInput) --***************************************************************************** function ItIsYourTurn() --***************************************************************************** -- Rather stripped down from FCEUX version. Only has basic functionality. -- Mainly just there to set options and controls now. --Ensure things are nice, shall we? fc= movie.framecount() lastkeys= keys keys= input.get() --Switch players on keypress if keys[PlayerSwitch] then if plmin ~= plmax then plmax= 1 elseif plmin == players then plmin= 1 else plmin= plmin+1 plmax= plmax+1 end end --Sets controller to pick either this script or the player. if keys[ListSwitch] then for pl= plmin, plmax do OptionUseList[pl]= not OptionUseList[pl] end end if keys[JoypadSwitch] then for pl= plmin, plmax do OptionUseJoypad[pl]= not OptionUseJoypad[pl] end end --Display which player we're selecting. Upperleft corner seems good. if plmin == plmax then gui.text(1,2,plmin) else gui.text(1,2,"A") end SetInput() collectgarbage("collect") end gui.register(DisplayInput) emu.pause() while true do ItIsYourTurn() emu.frameadvance() end
Download MtEdit.lua
Language: lua

local PLAYERS = 1 local btn={"left","up","right","down","A","B","Y","X","L","R","start","select"} --############################################################################# -- 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 rs= "numpad5" --Reset display local AutoList= true local Insert= "insert" local Delete= "delete" local AutoSwitch= "end" local Pl1Switch= "numpad+" --############################################################################# -- Default values local Opq= 1 local DispX, DispY= 50, 80 local Past, Future= -12, 12 local WatermarkInterval= 12 local Pl1Control= true --Colors local White =-0x00000001 --0xFFFFFFFF local Red =-0x00DFFF01 --0xFF2000FF local Green = 0x00FF00FF local Orange=-0x007FFF01 --0xFF8000FF --############################################################################# 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 JoypadGetAlternate(player) --***************************************************************************** if Pl1Control then return joypad.get(1) end return joypad.get(player) end --***************************************************************************** local function JoypadSetFCEUXStyle(player,inputs) --***************************************************************************** -- Snes9x has two values: Force on, force off. -- FCEUX has four: Force on, force off, user passthrough, and user invert. -- I need the four, but I can at least emulate the four from lua. -- This is only possible because Snes9x can read+modify on the immediate frame. local TempInput= JoypadGetAlternate(player) for btn,val in pairs(inputs) do -- This skips nil; User passthrough if not val then -- Lua-side false; Force off TempInput[btn]= false elseif type(val) == "string" then -- A string, eh? User invert TempInput[btn]= not TempInput[btn] else -- Assume what's left is true; Force on TempInput[btn]= true end end joypad.set(player, TempInput) end --***************************************************************************** local function RegBoundaryHandleJoypad() --***************************************************************************** for pl= 1, PLAYERS do JoypadSetFCEUXStyle(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 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 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 Neg(n) if n >= 0x80000000 then n= n - 0xFFFFFFFF - 1 end return n end local function Pos(n) if n < 0 then n= n + 0xFFFFFFFF + 1 end return n end --***************************************************************************** local function GetColorBasic(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 GetColor(Frame,b) --***************************************************************************** local c= GetColorBasic(JoypadList[PlSel][Frame],b) if Frame%WatermarkInterval == 0 then c= bit.bor(Neg(Pos(bit.band(c, -0x03030400))*0.75),0xFF) end return c end --***************************************************************************** local function PaintFrame(x,y,Frame) --***************************************************************************** for b= 1, #btn do Draw[btn[b]](x+4*b,y,GetColor(Frame,b)) end end --***************************************************************************** local function PaintBorder(x,y) --***************************************************************************** local color= Green if not AutoList then color= Orange 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 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, 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() --***************************************************************************** -- No "run while paused" code version. for k,v in pairs(keys) do if 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 KF[rs]=function() Past, Future= -12, 12 end ------------------------------------------------------------------------------- KF[Pl1Switch]= function() Pl1Control= not Pl1Control 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(); fc= fc-1 --Snes9x acts a tad off for the UpdateFC routine. 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
I have been away for such a long time, that I can't even remember most of the details of this script. I recall having trouble getting the emulator's configuration through the lua side, so that's why the override from the script. Until I take a second look, I will say I haven't figured out how to read the lsnes configuration from lua. I also recall an annoying bug in the current script, but I can't recall the particular details. Something about the script failing to get the right input on state load or something. Until I can put the time into figuring out how my own stuff works again, you're more or less on your own. Which is too bad, since I'm really the only "expert" on this script, and I've lost any memories necessary to give a decent answer. As there does not appear to be anyone else who would know the details, this means there's no one alive who can help. At least, not at this moment. My response is very late, as I've only now saw your message. And I apologize for the lack of help, but there isn't much I can say. The moment I can devote the time to figuring out how my own script works, I will get on explaining the details.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
Parks need to be nearby certain areas in order to provide Land Value in those areas. There are certain rules involving 4x4 squares the game uses to determine Land Value by pretty tiles. Parks in the same 4x4 provide the largest boost, though any in the adjacent 4x4 squares still provide 1/4 of that massive boost. Regardless, I have enough money on Easy to place quite a few Parks on the areas where they're not as useful. As for forests, I've taken a look on Map 061, and note that there are many forests to build around. In fact, I can't even get around from one area of the map to the other corner without going back to the area I start in due to these forests. I might need some smaller sprites to work with. Maybe Space Invaders or some Dr. Mario virus. It's a pretty tight fit in some areas. Still, I'll take a look to see where the Frog Mario can fit. Most of the construction will be done with spring colors. I don't expect it turning winter by the time I place the last few Parks. Time will be largely frozen due to the fact buttons are pressed most of the time.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
I will attempt to place the comparison of the two runs in some perspective. Well, my perspective, but I'm sure you can guess what I mean. The 20 minute glitched run aims to beat the game as soon as possible, using an SRAM corrupting mid-save reset. Through use of an improved set of tools (lsnes), an exact timing of this reset can be chosen. This run, having the same goal as the 20 minute run as pointed out, gets through much quicker due to an evolution in emulation. By all technical standards, this run should obsolete the old one. Better emulation (in theory), faster time, and equivalent goals (primary goal being speed, and allowing SRAM corruption). What here says not to? On the other hand, the 20 minute run shows more of the game, more numerous apparently senseless actions, and the wafers of the known game universe being blasted through. A lot is seen, the unexpected and nonsensical is frequent, and there's enough intact that the unengaged viewer might know what should happen, or at least what shouldn't be. This new run goes through very little of the game, and the grand mess of the 20 minute run is entirely absent. By most artistic standards (or at least my artistic standards, considering how subjective it is), so little is offered by this run, that compared to the older run, looks like a largely uneventful morning stroll. Sure, I still enjoyed watching it, but it had nothing like the old run. The technical and the artistic have rarely been in conflict, but this run, and the presence of the older 20 minute run, puts the two squarely on opposing sides. Probably why there's a few pages of debate all of a sudden. If we had something like lsnes back then, and the submitted run was this one, then it would likely be published. Without the technical reason pushing for it, no one would have sought to produce the 20 minute glitchfest. And honestly, I believe artistic TASes are rare. That is, one that aims more for artistry than speed. Yes, the glitchfest did aim for speed as a primary goal, but it has lost in that goal with this run now submitted. This leaves only the art it provides, and I would hate to see that obsoleted by this run, as this has almost none of it. So, obsoletion? No, not without an accompanying run that provides equal or better quality entertainment, even at cost of speed. We now have the speed to beat the run. We don't have a fascinating enough run to beat the entertainment side, however. Even so, the fact remains: This run has incredible speed. The goal is the same as before, there's a clear way to obsolete it (go faster... If that's possible), and on its own, is still entertaining. We also have another published movie that glitches straight to the end screen, forget the final boss or even the credits. To reject this one would fly in the face of another movie's acceptance. The difference between corruption of RAM or SRAM in the Super Mario World run and this one is only technical, and not what most viewers would pay attention to. Acceptance? Yes. An example of speed at all costs, but still not a boring run to watch. The flavorful 20 minute run may make this one feel bland, but anything that bright would make other stuff look dim. There's probably a few problems that do crop up from my thoughts, though. For one thing, how do you obsolete entertainment? What entertainment run could beat the 20 minute run without a significant number of people disagreeing? Does the order they're even submitted in become important (would there be a bias towards the earlier submitted run, for example)? I say it is possible to obsolete entertainment, but it is difficult to do so. If it were so easy, a far larger portion of the published movies here would be silly movies for entertainment's sake rather than speed. At least, that is my belief. How does one obsolete entertainment? I say again, it is difficult, and I can't really say I know how. Just that I believe it's possible. Even so, the old run invokes a number of glitches only possible with SRAM corruption. I still want to see the numerous visible glitches in some run, so the only room I see left for that would be in a playaround. Another problem is categorization of these runs. The glitchfest may be placed in a different category, perhaps a playaround, but the fact remains that it wasn't originally intended to be just a playaround. In fact, the goals of each run are similar, if not identical. Regardless, it does have enough quirks that it is as good as any other playaround, by my thoughts. Regardless of the intention of each run, the results really differentiate the two. Both should stand. You probably can't easily tell they have the same goal by watching the two runs. Finally, we'd break the site's (TASVideos) structure by having one movie taking two movies to obsolete it. There are publications that obsolete more than one movie, but no publication that took more than one movie to obsolete it. Should a better playaround come along, should it be the one to obsolete the 20 minute run, or should this movie do it? These are my lengthy thoughts. I'm sure there are still plenty that disagree with my sentiments, but I wanted to give the greatest clarity that I can give about the situation I see and what I would like to see from this situation.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
Well, I do have three tones, maybe. I realize I'm pretty bad at coming up with a good pattern to use. The effects of difficulty are as follows: - Starting cash - Chance of disasters - Transit maintenance - Money gained from taxes - Effect on RCI-meter by taxes - The I portion of RCI-meter, beyond the taxes effect If there are other effects, I haven't noticed. Overall, slower growth on Hard because the game treats my 0% taxes as 2% when crippling my RCI-meter. Of course, everything that has to do with money is meaningless, or even beneficial in the case of the starting cash, as it means I build fewer things to run out of money. But in terms of in-game time, the loss in demands will have an impact, as well as the fact I have a worse temporary Land Value in many places. Do I pick Hard, knowing I'll lose a few in-game months (unless you can perfectly balance the zone populations, of which my test cities had no such luck during early growth even on Easy), or Easy, having selected a difficulty that tells the game to give me no trouble?
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
I've already thought of that typing idea while working with MtEdit -- I call it Sticky input. It felt like an obvious enough idea that there was no need to mention it. I'm quite surprised this wasn't thought of by many others, until now. Still, thanks for sharing the idea. At least this idea has gotten much needed awareness, of which I haven't given.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
There are enough open land tiles that there will be plenty of areas left uncovered by parks. So that gives me three tones to work with, provided being lucky doesn't prove too difficult. Also, my build path will try to cover areas where I need the temporary Land Value to get my edge C-Zones grow to some decent population. There's plenty of open land to cross over, plenty of areas to cover on the way. I still need to work around the forests, however. That does limit my build space somewhat. I'll be taking a look at some of the sprites I might want to imitate. Familiar faces would certainly attract attention.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
Well, I've decided on TASVILLE for the moment. After some cursory TASing of planting Parks all over (1996 in total), I realized I have plenty of time to create amusing shapes out of them, even when avoiding the forests and trying not to pass over a square without planting a Park (remember, any button presses freeze time). I already thought of Tetris pieces, for one. All 7 standard shapes in every possible unique rotation is only 76 Parks. Still have another 1920 to go. If it helps, Parks can come with or without tree. I haven't figured out the luck manipulation here, so I may just allow the trees to appear at random rather than try to control it. It will take approximately 1.5 minutes (movie time) to plant 1996 Parks. I start with 2 tiles of Mass Transit (-$40) so that I have expenses to pay for that money glitch.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
Although the Consolation Team Contest itself has failed, the goal I had intended, completion of this game, has still come through. I am quite glad to see this run up. I will watch it at my own time. Thank you for having finished this run, despite the premature end of the contest.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
There are no lowercase letters. Things like TASHKENT and TASHOLE might not transfer the intended meaning all that well. I'm still thinking about going with TASVILLE, though TAS-TOWN does have some appeal. But TOASSPRU has a few silly possible interpretations, at least. I could, of course, use FATRATKN for author recognition as well. But this has a drawback in that others who join in to help produce this TAS won't be able to easily change the city name to match (luck manipulation). A more self-explanatory name would be FAST600K. ... I should put some effort in getting that first year done... I know what to do to start it: Taxes to 0%, speed to fast, and select the Mass Transit tool to place two tiles of rail in order to glitch the money. Then parks. Lots of parks.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
For the name, there is a limit of 8 characters. Available characters: Uppercase letters, numbers, comma, period, hyphen, and space. It looks like I may name the city TASVILLE, place parks in various areas, and glitch the water away. Hopefully, time will pass fast enough with nothing much to process that the first year wouldn't need that much time. So, basically... - Go through title screen, menu, map 061, name, Easy, confirm. - Set taxes to 0% and speed to fast. - TAS-build a few rails and lots of parks. - Wait out first year. - Glitch my money to $999999 abusing the tax screen. - Do off-map stuff to remove water and shrink my utility buildings. - Build entire city at the speed of TAS. - Sit back, relax, and manipulate luck to speed up the population boom. - 600k population. Celebrate. Do not skip message. - Show statistics and map stuff. - Trigger disasters, manipulate luck to 0 population. - As a method to provide closure, select END and leave the game with the sleepy moon. I think I have a bit of lua to go through. If I'm going to manipulate off-screen buildings, I probably should have a script tell me what's going on...
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
I might want to try this layout. I already have posted a movie that shows how to replace water through off-map glitching. So it's entirely possible to reconstruct a waterless map without using that free land scenario. But basically, on that map, I have shrunk down the power plants and other stuff by that off-map glitching. More room for more zones. Surely the minimum in-game time goal would ask for such glitching. I succeed in powering the entire city using only 6 Nuclear Power. Well, the important stuff, anyway. Every power line I put down is important, in order to guide the path the game uses to power zones up. I also note I can place a bunch of parks initially to raise the Land Value of various places, then replace them with zones. The game won't recalculate the Land Value for several months, allowing good initial growth, especially for C-Zones at some edges. Whether it's better to pass time by building a functional city with the starting $20000 or watch grass grow is one thing I'm asking myself. I'm curious what I should do for a 600k population run. Use the off-map glitching? Place parks instead of a working city with the starting cash? What should be the freaking name of this freaky city? If I'm going to submit a TAS, I will use lsnes for my final run. I'm just using Snes9x as an easy emulator to make tests.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
I was simply TASing both sides by hand, aided by MtEdit. I have all sorts of explanations in my submission text. The specials (like LEK BLIND) are activated when a line of Yoshi cookies are cleared, and you generally don't want to clear a line of Yoshi cookies when the special is a bad one for you. Regardless, I see the rejection. No complaints towards this rejection. Yoshi's Cookie is simply a bad game choice, then. Unless someone figures out an entertaining way to play it, anyway. Hence the submission around April Fools'.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
CoolKirby wrote:
Oh, it's not a serious submission? Or is he trying to submit a real run around April Fool's Day, like Tompa did last year?
It's still serious, just that I try to pick a poor game or goal choice (for April Fools') but still try hard on it. I expect judges to treat this like any other run submitted. That is, give it a chance just as fair as any other submission. And if watching a 2-player versus fight it out with one another isn't entertaining enough, I will respect the decision. If accepted, this will come as a mild surprise, but I'll still respect the decision anyway!
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
That was me hating it. Don't mean to cause harsh feelings, but I prefer honesty over keeping things gentle. I am thankful for the decision to change, however. Please tell me that the reasons I've given had also helped to swing the decision. I'd hate it if the only thing that did it was my hate, as then I'd hate myself for hating, and that, in turn, just works out in a stable loop. ... As for insults, I will never outright intentionally say them. That's no good for anything, really. I may have exaggerated on the insults part, but my strong dislike is still in there. But as for a new extension name... I think that these "movies" are more like replay files. It's not an actual media file like .mp4, so I think the term "replay" fits better. Still, "movie" isn't entirely inaccurate, as it is showing a sequence of actions like a movie, and is a more familiar term around here. So, my top suggestions (until someone comes up with something better): .bkm - Suggested before, and feels unique enough. .rbk - Replay, Bizhawk - Hopefully seems clear enough. .bkmr - Adding Replay on top of the previous bkm. Other thoughts that crossed my mind, but decided they don't sound right: .brf - Bizhawk Replay File - except this is too close to barf... .rfb - Replay File, Bizhawk - Actually, rf reminds me of radio frequency. .bkr - Bizhawk Replay. - Because we're bakers. .bzrp - Bizhawk Replay - Just something I burped out.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
Nothing wrong with the name Bizhawk... Though I'm still kind of iffy about sharing a name with that spam company, but before this thread, I didn't even know it existed. But I'll pester about the extension naming. There is, in my eyes, a lot of evidence that suggests the .tas extension was a poor choice in naming. 1) Non-TASers might record stuff using this emulator. The extension name is potentially misleading. This point was brought up multiple times. I feel I do not need to reiterate all that is said already. 2) Bizhawk's apparent purpose is to be a multi-system emulator. From what I gather, this emulator's primary purpose is to unify various system emulation cores under a single common gui. The fact the emulator allows TAS is only a secondary feature, and certainly not placed above casual movie making. 3) Outright stated NOT to be a part of TASVideos. This implies that it was not made purely for TASing. Yet here it is, a board dedicated to it, and attention was definitely attracted. The fact a .tas extension was provided as a replay file extension would indicate that it has a strong link to a TASing community, but since it was stated not to be a part of TASVideos, this indicated link does not seem to exist. 4) The emulator is still young. Perhaps not a good reason in and of itself, but it does lessen the negative impact of a rename now than it would a year down the road. How many people have known of this emulator 4 weeks ago? 2 months ago? There's certainly many more who know of it now, but give it a year, and that will spread to far more. A rename now will at least catch most of that spread. I will outright state that I hate the .tas extension name. Not a simple dislike, not a wondering thought that it could be better, but more towards throwing insults and greater unwillingness to even use the emulator thanks to the name of the extension alone. Yes, I hate it, and I mean it! Even so, I prefer to remain as constructive as possible, and insults are certainly not going to help anyone. And "greater unwillingness" doesn't mean I absolutely refuse to use the emulator ever, just that I see another reason to avoid actively using it. I'll still try it out at some point, but I hate the extension name harshly enough to actually make a post about it. Emotional? Probably. Are the reasons I listed enough to accumulate my hatred? Probably not, the rest of the way is likely just emotional. I just do not like commonly established terms given a strong potential to be misused. My initial impressions of Bizhawk may be rather poor, but adelikat wants it to work. That's reason enough for me to support it, despite any features in it I hate. I may want them to change, but I won't push to some breaking point if the development says no. I will push, as evidenced by this post, but I sure hope not destructively so.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
If someone who is routinely using a Gamecube/Wii emulator (rog, that's you, right?) is complaining about CPU usage of this emulator when idle, then that pretty much rules out my use of this thing. My computer is not all that powerful, and it would mean trouble navigating through other things while I leave the emulator in the background, paused. What, exactly, is it doing, anyway? If it's so that an EmuLua function known as gui.register can work while paused, this is something I will both appreciate and hate at the same time. There are other ways to implement "run Lua while emulation is paused" methods to use. I certainly am enjoying around 0 CPU usage from lsnes when it is paused, while still able to run bits of lua code I like (which of course takes CPU, but it's zero until I hit a key that triggers the Lua hook). As it stands, though, the high reported CPU usage on idle is a hurdle that I will have trouble getting over.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
If you somehow missed this fact from the title I have up there, I have it in nice big words here, just in case. MtEdit is a TASing lua script designed to hold on to the user's input separate from the emulator and play it back, allowing the user to make modifications as desired, and with a neat display to go with it, showing exactly what you did. The script has since gone through a complete rewrite in v2. It will auto-detect which controllers are plugged into lsnes when it starts up, and will also respond to lsnes hotkeys for the controllers. By the way, I'm holding on to all earlier versions I've made. Below is v1, but v2 is linked to above. I prefer that you try the latest stuff. Download MtEdit_lsnes_v01.lua
Language: lua

--Leeland Kirwan (FatRatKnight) -- The legendary MtEdit I've always wanted to implement. -- Alas, bare-bones features only. -- No inserts/deletes -- No macros -- No individualized button control -- SNES controller only -- No watermarks -- No subframe control -- Useful stuff that is in place: -- List of input displayed -- Allow user input mixed in with recorded stream -- Control for multiple players -- Lag display --############################################################################# --############################################################################# -- Setup local Players= bit.value( -- Uncomment players you need. 0, -- Port 0. Almost certainly player 1. -- 1, -- Multitap for Port 0. -- 2, -- Multitap for Port 0. -- 3, -- Multitap for Port 0. 4, -- Port 1. Usually player 2. -- 5, -- Multitap for Port 1. -- 6, -- Multitap for Port 1. -- 7, -- Multitap for Port 1. nil -- To ensure proper syntax. Otherwise serves no purpose. ) -- The script controls override user's setup in lsnes, for controller 1. local Controls= { -- Adjust these as necessary. [0]="g", -- B Oh, leave the [0]= you see there alone. "t", -- Y But feel free to change stuff in the "quotes". "r", -- Select "u", -- Start The script overrides whatever controls you "w", -- Up set up in lsnes. So pick keys you want as "s", -- Down your controls here. "a", -- Left "d", -- Right "h", -- A "y", -- X "f", -- L "j" -- R } for i= 0, 11 do Controls[Controls[i]]= i end -- Don't change this. local cmd_NextPlayer= "l" -- Switch to next player. local cmd_MtEditRead= "z" -- Allow the script to mix recorded input with user? local cmd_InputMode= "x" -- Switch through input modes. (Only 2) --local cmd_CustomKey= "c" -- Custom --############################################################################# --############################################################################# -- Useful global junk. Please don't change. local PlSel= 0 -- Player selection local function NullFN() end -- A "do nothing" function, just in case. local HeldBtns= {} -- 16 bits; Number for button storage. local LastBtns= {} -- 32 bits; Various status bits of last frame. local ReadMovie= {} -- Read from movie? local ReadMacro= {} -- Read from macro? local BtnCtrlA= {} -- Control masks for individual buttons. local BtnCtrlB= {} -- 00=Ignore 01=Normal 10=Sticky 11=Hold local InputList= {} -- Will contain inputs. As well as two subframe inputs. --local OverSnoop= {} -- Just in case input is polled more than three times. local ResetList= {} -- Will store all the resets that take place. for Pl= 0, 7 do HeldBtns[Pl]= 0x0000 LastBtns[Pl]= 0x00000000 ReadMovie[Pl]= 0x0FFF ReadMacro[Pl]= 0x0000 BtnCtrlA[Pl]= 0x0FFF BtnCtrlB[Pl]= 0x0000 InputList[Pl]= {} end local inp_Top= 0 -- Where to locate my input list... local inp_Lef= -96 local inp_Off= -27 -- The top line's relative frame. local ExpectedSize= 56 -- Without access to screen height, I have to guess. local inp_Display= gui.bitmap_new(95,ExpectedSize*8-1,true) -- My display. local Tiles= {} -- Holds all relevant tile stuff. Tiles[Shape][Color] local BlankFrame= gui.bitmap_new(95,7,true) -- Blank frames local This_Subframe= 0 -- I'll track my own subframes... --***************************************************************************** local function PlayerLoop(Fn) --***************************************************************************** -- It appears I need this loop frequently. -- Good with lambda expressions, too! for p= 0, 7 do if bit.extract(Players,p) ~= 0 then Fn(p) end end end --############################################################################# --############################################################################# -- Icons --***************************************************************************** local function Dbmp7x7(n,clr) --***************************************************************************** -- Converts a number into a monochrome 7x7 BITMAP. -- Enough space in a double for this to happen. So I use it. -- However, readability suffers greatly due to this. local bitmap= gui.bitmap_new(7,7,true) for i= 0, 48 do local c= -1 if bit.extract(n,i) == 1 then c= clr end gui.bitmap_pset(bitmap, -- DBITMAP object. i%7, -- x math.floor(i/7), -- y c -- color ) end return bitmap -- Be sure to hand out what I just made. end ------------------------------------------------------------------------------- local Icons7x7= { [0]= 0x0FF1E37F8F1BF, -- B Incidentally, I just care my icons work. 0x060C1830F3343, -- Y 0x198CB4A484486, -- Select I've put no effort in the 0x0994A848894F6, -- Start readability of these numbers. 0x18DB3E3870408, -- Up 0x02041C38F9B63, -- Down Left to right, top to bottom, 0x10383C3EF3840, -- Left 7 bits per line, 0x00439EF878381, -- Right bit-packed, made by hand. 0x18FFFFC78DF1C, -- A 0x10F33C30F3343, -- X I could use bit.value and give a more 0x1FFF83060C183, -- L visual representation in this code. 0x18F1BF7F8F1BF -- R But I already have these numbers... } -- If I find I need to make edits, I may switch to bit.value. ------------------------------------------------------------------------------- -- Make white tiles. local BlankTiles= {} for i= 0, 11 do BlankTiles[i]= Dbmp7x7(Icons7x7[i],0xFFFFFF) end local Tc= { -- Tile color -- Red Green [0]= 0xFF2000, 0x00FF00, -- Saturated, normal 0xFF8080, 0xA0FFA0, -- Light, lag indicator 0xA00000, 0x008000, -- Dark, Watermark 0xC04040, 0x40C040, -- Desaturated, lag+watermark } for x= 0, 11 do Tiles[x]= {} for c= 0, #Tc do Tiles[x][c]= gui.bitmap_new(7,7,true,Tc[c]) gui.bitmap_blit(Tiles[x][c],0,0, BlankTiles[x],0,0,7,7 , 0xFFFFFF) end end -- Produce a blank frame. ------------------------------------------------------------------------------- for x= 0, 11 do gui.bitmap_blit(BlankFrame,x*8,0, BlankTiles[x],0,0,7,7) -- Produce my tile on the spot end --############################################################################# --############################################################################# -- Display -- B Y s S ^ v < > A X L R local x_off= {[0]= 6,5,3,4,1,1,0,2,7,6,0,7} -- Offsets for use with displaying local y_off= {[0]= 2,1,1,1,0,2,1,1,1,0,0,0} -- the immediate joypad stuff. local ModeNames= {[0]= "Ignore","Normal","Sticky","Hold"} local ModeClrs= {[0]= 0xFF2000,0xFFFFFF,0x00FF00,0x00FFFF} --***************************************************************************** local function InputDisplay(Pl) --***************************************************************************** -- For the immediate user input. local Right, Bottom= gui.resolution() gui.bottom_gap(26) local BCa, BCb= BtnCtrlA[Pl], BtnCtrlB[Pl] gui.rectangle(Pl*64,Bottom,64,24,1,0x000080,0x000080) PlayerLoop(function(p) local HB= HeldBtns[p] for i= 0, 11 do local c= Tiles[i][bit.extract(HB,i)] gui.bitmap_draw(p*64 + x_off[i]*8,Bottom+1+y_off[i]*8,c) end end) local mode= bit.bor( -- Make an assumption about modes, for today... bit.extract(BCa,0), bit.extract(BCb,false,0) ) gui.text(inp_Lef,Bottom+10,ModeNames[mode],ModeClrs[mode]) gui.text(-10,Bottom+10,PlSel,0x00FFFF) end --***************************************************************************** local function ShowList() --***************************************************************************** -- Its only purpose is to splat the image on screen. gui.left_gap(-inp_Lef+1) gui.rectangle(inp_Lef-1, inp_Top + inp_Off*(-8) - 1, 97, 9, 1, 0x00FFFF, 0x404040) -- Cyan, dark grey. gui.bitmap_draw(inp_Lef,inp_Top,inp_Display) -- Oh, and show whether we are picking up movie inputs. local RM= ReadMovie[PlSel] for i= 0, 11 do local clr= 0xFF0000 if bit.extract(RM,i) == 1 then clr= 0x00FF00 end gui.circle(inp_Lef+3+8*i,451,4,1,clr,clr) end end --***************************************************************************** local function BlitFrame(Pl, y_pos, frame) --***************************************************************************** -- Modifies a single line in main image. -- It should request the frame from lsnes first, then poll myself for it. -- Who should take priority? lsnes, as it knows the past, and if in readonly, -- it knows the future, too. I just hold on to the input just in case... -- For this revision, however, I handle it all myself. local GetFrame= InputList[Pl][frame] if GetFrame then for x= 0, 11 do local tile= Tiles[x][bit.extract(GetFrame,x,x+16)] gui.bitmap_blit(inp_Display,x*8,y_pos*8, tile,0,0,7,7) end else -- Wait, we didn't get a frame? Blit a blank, instead! gui.bitmap_blit(inp_Display,0,y_pos*8, BlankFrame,0,0,95,7) end end --***************************************************************************** local function UpdateBox(Pl, frame) --***************************************************************************** -- Adjusts the current image. Only two sets of blits rather than 56! -- This assumes the only things to read are the frame we just did and the next -- frame after the end of the display. I hope that's an appropriate assumption. -- Elsewhere in code, I have already moved the display, in case of multi-core -- processors. This simply exists to clean up that last bit of detail. BlitFrame(Pl, ExpectedSize-1, -- Position: Last line in image frame+inp_Off+ExpectedSize-1) -- How far past the mid is it? BlitFrame(Pl, -inp_Off - 1, -- Position: Just before current line frame-1) -- We're almost looking at it anyway. end --***************************************************************************** local function RefreshBox(Pl, frame) --***************************************************************************** -- Repaints the current image from scratch. Use for long frame jumps. local StartFrame= frame + inp_Off for y= 0, ExpectedSize-1 do BlitFrame(Pl, y,StartFrame+y) end end RefreshBox(PlSel, movie.currentframe()) -- Undefined frame to known frame is a jump. local lsnesBGui= InputDisplay -- the display by pointing them to NullFN. --############################################################################# --############################################################################# -- Joypad control --***************************************************************************** local function GetKeyboardHolds() --***************************************************************************** -- Extracts direct from keyboard to figure out which keys are held. -- Returns a value similar to input.geta(). Well, for controllers, anyway. local keys= input.raw() local c= 0 for i= 0, 11 do local key= keys[Controls[i]] c= bit.bor(c, bit.lshift(key.last_rawval,i)) end return c end --***************************************************************************** local function UpdateControl(Pl, frame) --***************************************************************************** -- Get Movie data, may need to take into account subframes and multiple players PlayerLoop(function(p) local MovieData= InputList[p][frame] or 0x0000 HeldBtns[p]= bit.band(MovieData,ReadMovie[p]) -- Apply mask end) -- Get Macro data. In a later version, anyway. -- Get HoldMode data (Copy last frame data) -- local HoldData= bit.band( -- BtnCtrlA, BtnCtrlB, -- Construct mask -- LastBtns -- ... Something besides last frame's input... -- ) -- Get NormalMode data local KeyData= bit.band( BtnCtrlA[Pl], bit.bnot(BtnCtrlB[Pl]), -- Construct mask GetKeyboardHolds() -- Get controls direct from keyboard. ) HeldBtns[Pl]= bit.bxor(HeldBtns[Pl],KeyData) end --***************************************************************************** local function NormalMode(index, KeyState) --***************************************************************************** -- Toggle relevant button. No questions asked. HeldBtns[PlSel]= bit.bxor(HeldBtns[PlSel], bit.value(index)) end --***************************************************************************** local function HoldMode(index, KeyState) --***************************************************************************** -- Toggle the relevant button if key is pressed, do nothing if released. HeldBtns[PlSel]= bit.bxor(HeldBtns[PlSel], bit.lshift(KeyState,index)) end local lsnesHandleBtn= { [0]=NullFN, -- Ignore mode. Block user input. NormalMode, -- Normal mode. Read directly from keyboard. HoldMode, -- Sticky mode. Toggle on keypress, but clear after advancing HoldMode -- Hold mode. Toggle on keypress, maintain state on advance. } --############################################################################# --############################################################################# -- Keyboard commands --***************************************************************************** KeyPress= {} -- On KeyDown, run keyed function --***************************************************************************** -- A table used for a generic routine to call arbitrary functions. -- I simply list functions in KeyPress, and the generic routine does the rest. ------------------------------------------------------------------------------- KeyPress[cmd_MtEditRead]= function() -- Toggle input mix ------------------------------------------------------------------------------- if ReadMovie[PlSel] == 0 then ReadMovie[PlSel]= 0x0FFF else ReadMovie[PlSel]= 0 end end ------------------------------------------------------------------------------- KeyPress[cmd_InputMode]= function() -- Switch input modes ------------------------------------------------------------------------------- if BtnCtrlA[PlSel] ~= 0 then BtnCtrlA[PlSel]= 0 BtnCtrlB[PlSel]= 0x0FFF -- To Sticky mode! else BtnCtrlA[PlSel]= 0x0FFF -- To Normal mode! BtnCtrlB[PlSel]= 0 end end ------------------------------------------------------------------------------- KeyPress[cmd_NextPlayer]= function() ------------------------------------------------------------------------------- local Sanity= PlSel repeat PlSel= (PlSel+1)%8 until (bit.extract(Players,PlSel) ~= 0) or (Sanity == PlSel) RefreshBox(PlSel, movie.currentframe()-1) end ------------------------------------------------------------------------------- --KeyPress[cmd_CustomKey]= function() -- Hold this key, press a control key. ------------------------------------------------------------------------------- --end --***************************************************************************** KeyRelease= {} -- On KeyUp, run keyed function --***************************************************************************** ------------------------------------------------------------------------------- --KeyRelease[cmd_CustomKey]= function() ------------------------------------------------------------------------------- --end --############################################################################# --############################################################################# -- lsnes tie-in. --***************************************************************************** function on_paint() --***************************************************************************** -- Handle some frame-advancing flag I made up myself. -- Besides that flag, just call the display functions. ShowList() -- Paint the main display InputDisplay(PlSel) -- Paint the current user input -- Possibly include subframe display (right side gap?) -- Possibly include macro display (right side gap?) end gui.repaint() -- May as well request a paint. --***************************************************************************** function on_keyhook(s,t) --***************************************************************************** -- A generic "Handle Commands" routine, but with support for special keys for -- the controller. Just get rid of the else and the lines it does, and you have -- a generic KeyPress routine. if KeyPress[s] then if t.last_rawval == 1 then KeyPress[s]() gui.repaint() end else -- Must be the keyed controller input, then. local c= Controls[s] local mode = bit.bor( bit.extract(BtnCtrlA[PlSel],c), bit.extract(BtnCtrlB[PlSel],false,c) ) lsnesHandleBtn[mode](c, t.last_rawval) gui.repaint() end -- if it's just a key release for a command key, don't request a repaint. end -- Tell lsnes to pay attention to my keys: for k,v in pairs(KeyPress) do input.keyhook(k,true) end --generic for k,v in pairs(KeyRelease) do input.keyhook(k,true) end --generic for i= 0, 11 do input.keyhook(Controls[i],true) end --cnt --***************************************************************************** local function SubframeWatch() -- on_idle(25ms). --***************************************************************************** -- There exists no callback where emulation halts, waiting further user input. -- on_input(true) is called immediately after emulation resumes. I need -- something that is called immediately prior to waiting on user. -- Hence, this function abusing on_idle for this purpose. -- Update based on some subframe values. -- Handle subframe controls. end --***************************************************************************** function on_frame_emulated() --***************************************************************************** -- A full frame advance finishes now. Use standard update. local frame= movie.currentframe()-1 PlayerLoop(function(p) InputList[p][frame]= LastBtns[p] end) on_idle= nil -- Turn off the subframe watch -- Not in a subframe! UpdateControl(PlSel, movie.currentframe()) -- New frame. Update controls. This_Subframe= 0 -- Reset subframe count. UpdateBox(PlSel,movie.currentframe()) end local FrameJumped= false --***************************************************************************** local function FrameJump() -- on_post_load, on_rewind --***************************************************************************** -- Ensure our display and controls still match up. FrameJumped= true -- We jumped. on_input isn't 100% with this... UpdateControl(PlSel, movie.currentframe()) -- Reset controls This_Subframe= 0 -- Reset subframe count RefreshBox(PlSel, movie.currentframe()-1) -- Reset display gui.repaint() -- Poor timing of on_paint requires a wasteful second call. end on_post_load= FrameJump; on_rewind= FrameJump --***************************************************************************** function on_input(b) --***************************************************************************** -- Make sure what the user sees is being applied to the actual controls. if (not b) or FrameJumped then -- A new frame. -- Move the display NOW! The multicore compliant way to do so. gui.bitmap_blit(inp_Display,0,0, -- Dangerous use of blit.(src=dst) inp_Display,0,8,95,ExpectedSize*8-9) -- Shift display up one frame. -- Attempt reset (if any) if (not b) then -- If this fails, then you've glitched my reset control if ResetList[movie.currentframe()] then input.reset(ResetList[movie.currentframe()]) end end This_Subframe= 0 -- Keep an internal counter. No lsnes provided call. end FrameJumped= false -- Input trickery PlayerLoop(function(p) input.seta(p, HeldBtns[p]) local lsnesInput= input.geta(p) -- I want only one return value! LastBtns[p]= bit.bor(0x0FFF0000, lsnesInput) end) -- Set up on_idle, shall we? This_Subframe= This_Subframe+1 on_idle = SubframeWatch -- Ensure on_idle is happy. set_idle_timeout(25000) -- 25 ms. My manual gui.subframe_update() -- ... I sure hope no TASer finds need to mash subframe advance at 40Hz. end --***************************************************************************** function on_snoop(port,pad,btn,v) --***************************************************************************** -- Handle player 1. Ignore the others (this version, anyway). -- Read up values to properly display last frame status. local Pl= port*4 + pad if bit.extract(Players, Pl) == 0 then return end -- Don't handle unexpected if btn >= 12 then return end -- Don't mess with out-of-range buttons. LastBtns[Pl]= bit.bor(bit.band(LastBtns[Pl],bit.bnot(bit.value(btn))), bit.lshift(v, btn) ) -- Clear lagged status. local LagBit= bit.lshift(0x10000,btn) LastBtns[Pl]= bit.band(LastBtns[Pl],bit.bnot(LagBit)) end print("MtEdit loaded. Please enjoy the script.") --############################################################################# --############################################################################# -- Safeguards. To ensure a previous script has no effect. gui.subframe_update(false) -- If true, chokes up my fps. Ouch. on_video= nil -- Not an encoding script on_frame= nil -- Bad timing for on_paint on_startup= nil -- May wish to use, if someone manages to load script early? on_quit= nil -- If I need persistent data, I might want this. on_reset= nil -- For detecting resets. Unused this version, however. on_readwrite= nil -- Might be handy. Unused for now. on_pre_load= nil -- Nothing to handle before it loads. on_err_load= nil -- Nothing to handle the previous not-handling. on_pre_save= nil -- If I need to save stuff to the state, might be handy. on_err_save= nil -- Unused. Not sure of any potential need. on_post_save= nil -- Unused. Again, not sure of any potential need.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
Currently developing MtEdit for lsnes and I spot a critical error in callback implementation that, for the most part, makes the entire script useless. on_input(false) is called just prior to a successful state load. It is not called on the frame advance that may follow. The value of movie.currentframe() returned for on_input at this time is the value just before the state load. End result: It is impossible for me to load the proper input and apply it following a successful state load. It is also impossible for an input script to override input immediately following a state load. MtEdit requires the possibility of absolute control over the user's input, and TASing is all about state loads (among other things; MtEdit is a TASing script), the script's use is severely limited. Despite errors in callbacks, lsnes does get user input without trouble. It gets this user input without calling on_input, for whatever reason. Selecting "Run Lua script" and selecting some lua script does not clear out the previous script's functions. Intentional or not, this can cause some unexpected interactions between scripts. Notably, I have a test script that sets on_paint to nil before state load, and to some specified function after state load or load error (I dislike on_paint's timing relative to some callbacks). Quite a surprise, then, that I should see this script's display when I run a different script and load state. Or see nothing if I have on_post_load in this new script. A lua-side workaround is simple enough: Set all unused callbacks to nil. This ensures previous scripts generally stop interfering. Though, I'm still worried how the local variables in there are still taking up memory, however. At least I'm aware of this behavior. I'm not necessarily calling it a glitch, but what it does is certainly not what I expected. Restarting lsnes is sure to clean out the lua mess that crops up, as well, so a fresh start works as intended.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
Multitrack and MtEdit scripts for FCEUX (TASEdit thoroughly removes need for it) Multitrack script for Snes9x (Pastey link broken? I'll bring the script back up on request.) Multitrack script for DeSmuME (Further attempts abruptly stopped when this computer couldn't run later versions of DeSmuME) I've had some hefty discouragement on my attempts to produce a good script to function similarly to TASEdit here (look at how many responses are on the Snes9x and DeSmuME versions!). Regardless, I'm working on MtEdit for lsnes and hope it will serve the purpose quite well there. Since I'm more or less familiar with my own scripts, I can make a port attempt such that basic functionality exists for some other target emulator. The "today's name" for this script is MtEdit. While Multitrack2 is the name for older versions, I do not want that name used when referring to my latest attempts.
Editor, Experienced Forum User, Published Author, Skilled player (1173)
Joined: 9/27/2008
Posts: 1085
Next time you update this topic, I recommend changing the title to reflect the current version. I have one suggestion: "lsnes rr1-Δ4ε1: Rerecording with bSNES core". Hopefully it will give a nice sense of progress to anyone who wish to follow. Another error spotted in rr1-Δ4ε1: Hotkey modifications (alt, ctrl, meta, shift) in wxwidgets do not appear to be working properly. Setting a modification to be pressed is either ignored (set hotkey to e and shift to be pressed) or blocks the key entirely (set hotkey to q and shift to be pressed). Besides that, wxwidgets doesn't like alt keys for the reason of those pulldown menus. I have begun TASing attempts (Yoshi's Cookie) and it appears to be sync-stable so far. As things continue to develop, I may want to provide a variety of input lua scripts to help ease others into using lsnes. At the moment, I am holding on to one (if not PM'd already, it will be shortly sent), which although is only good for one controller, is a useful step forward as I get more familiar and the lua functions get better developed.