Language:
Lua     Change language:
Pastebin: 98417
Author: dwangoac
Subject: Re: General purpose NES item finder
Created: 2008-10-16 02:52:10
Download and save
Toggle line numbers
1-- Item finder luck manipulation / memory monitor script by dwangoac on 2008-10-09 
2 
3--[[ General notes:  This was written to find item drops in High Speed; the initial button mask reflects this.  Most of the behavior of this script can be altered 
4by touching the variables  below and this should be easily adaptable to other games.  The script's default settings will find item drops in High Speed on the 
5main table.  I'm still new to Lua scripting so some things may still be sub-optimal but the script has been fairly well tested. There are many variables that are 
6seperated into p1 and p2 variable names when they really don't need to be.  TODO: break out a few things into functions. 
7 
8 
9How this script works and how to use it: 
10 
11The script tries random input one frame at a time while monitoring the memory address specified by memoryToWatch until the current value matches a desired 
12value (or values) specified by targetMem.  An undesirable memory address value (or values) can be specified with the variable avoidMemoryToWatch and if a 
13match is found the attempt is considered unsuccessful and a new attempt is started immediately. 
14The script will try random input for the number of frames specified by maxFrames and will start over from the initial savestate if the desired result is not found. 
15The script will keep trying new combinations of random input for the number of attemps specified by maxAttempts; if no desirable solution is found, it will 
16prompt to determine if it should try again for the same number of attempts.  If a desirable solution is found, the script displays the number of frames required 
17and asks if the solution should be kept.  If answered yes, the script ends and saves the game state in the slot defined by the variable userSaveSlot.  If answered 
18no, the solution is considered unsuccessful and a new attempt is started immediately. 
19 
20The buttons that can be randomized are highly configurable.  The pxButtonMask tables define which buttons can be randomly pressed, while the 
21pxButtonOverride tables specify buttons that should always be held down while the randomization is taking place.  This could be used for short segments 
22to ensure right is always pressed or the start button could be removed from the button mask to prevent the script from pausing the game, for instance. 
23It's possible to specify an override but also leave it in the buttonmask which reverses the logic and randomly releases the button instead (with the same net effect). 
24 
25Other options: 
26- Currently, a bug in FCEUX prevents input from both player 1 and player 2 from being accepted on the same frame.  The playerInput value specifies which 
27player's input to randomize. 
28- Setting debugText to 1 displays a large amount of information on the screen but significantly increases the CPU load per frame and is therefore disabled 
29to allow finding solutions as fast as possible. 
30- Calling movie.rerecordcounting(false) should prevent the rerecord count from being increased by the script but the rerecord count may be bumped sometimes 
31anyway; I need to investigate this further to see if there's a bug. 
32- Each variable has specific usage notes listed below. 
33 
34More information: 
35There is limited Lua scripting documentation available for FCEUX at this time so please feel free to post in the Lua Discussion thread under the FCE Ultra 
36forum at tasvideos.org/forum.  I'm on irc.freenode.net in #nesvideos as dwangoac and can answer questions on using the script.  Please do let me know if you 
37are able to put this script to good use.  Thanks!  ]] 
38 
39 
40userSaveSlot = 10 -- Optional user save slot to save to; make this one higher than the slot number you want or set to nil to not use a slot 
41 
42memoryToWatch = 0x064F -- The memory address to watch (only one address is monitored, but a for loop could be added to monitor more locations) 
43targetMem = { 64 }  -- A list of desirable values we're looking for at the address specified by memoryToWatch 
44 
45avoidMemoryCheckEnabled = true -- Set to false to disable undesirable match testing (see comments below) 
46avoidMemoryToWatch = 0x064F -- Watch this memory address for changes as well  but... 
47avoidMem = { 66 } -- Treat this value as something undesirable that should be avoided; prematurely restart attempt if value matches 
48 
49frameCount = 0 -- starting frameCounter value 
50maxFrames = 100 -- The mamximum number of frames to attempt random input before loading the savestate and trying again 
51 
52attempts = 0 -- starting number of attempts value 
53maxAttempts = 100 -- The number of times to load a savestate and try random inputs again before giving up 
54promptMoreAttempts = true -- set to false to cause the "Try another x times?" message to not appear (useful with short attempt count runs) 
55showAttempts = true -- set to true to show number of attempts (not as CPU intensive as enabling debugText as it only updates when frameCount >= maxFrames) 
56 
57playerInput = 1 -- Set to 1 to manipulate P1's input and set to 2 to manipulate P2's input; for whatever reason only one player's input is accepted per frame with joypad.set 
58 
59p1Buttons = { "left", "down", "up", "right", "select", "start", "B", "A" } -- P1, 
60p2Buttons = { "left", "down", "up", "right", "select", "start", "B", "A" } -- p2 button map which defines the static button names to pass to joypad.set 
61 
62p1ButtonMask = { "left", "down", "up", "right", ".", "start", ".", "A" } -- p1, 
63p2ButtonMask = { "left", "down", "up", "right", "select", "start", "B", "A" } -- p2 button mask which defines which buttons can be pressed; set to "." if you do not want a specific button to be used for random input 
64 
65p1ButtonOverride = { ".", ".", ".", ".", ".", ".", ".", "." } -- p1, 
66p2ButtonOverride = { ".", ".", ".", ".", ".", ".", ".", "." } -- p2 button override which forces a specific button to be held down while other buttons are randomized; you;ll probably want to remove the corresponding button from the buttonmask 
67 
68debugText = 0 -- Set to 1 for significantly more UI display; this may noticably slow down the emulation speed 
69movie.rerecordcounting(false) --  Avoids racking up the rerecord count; set to true to increment the rerecord count while using this script 
70 
71-- Create empty tables for remaining variables 
72frameButtons = {} 
73buttonList = {} 
74matchFound =  false 
75 
76local startSave = savestate.create() -- Create a savestate object 
77savestate.save(startSave) -- Save the state of the game at the point where the script was run 
78 
79while attempts < maxAttempts do 
80    FCEU.speedmode("maximum") -- This can be changed to "normal" as needed 
81    if debugText==1 then 
82        gui.text(5,10, "Item search started"
83    end 
84    savestate.load(startSave) -- load initial savestate 
85    if showAttempts == true then 
86        attemptsPrint = string.format("Attempt %s",attempts + 1) -- starts at attempt 0, so print one higher 
87        gui.text(195,190,attemptsPrint); 
88    end 
89    math.randomseed( os.time() ) -- seed the randomizer with something fresh on every new attempt 
90    for i = 1, maxFrames do 
91        inp1 = {} -- Clears list of input every new frame 
92        listpos = 1 -- this value ties all of the various tables together so things are always pointed to the same table position 
93        for j,button1 in ipairs(p1Buttons) do 
94            if  button1 == p1ButtonOverride[listpos] then -- check to see if button should normally always be pressed 
95                pressbutton1 = p1Buttons[listpos] 
96                inp1[pressbutton1] = true 
97            end 
98            if button1 == p1ButtonMask[listpos] then -- find out if this button can be pressed;  this can negate the buttonoverride table and proceed to randomize buttons anyway 
99                randomizedButton1 = math.random(2) -- pick 1 or 2 
100                if randomizedButton1 == 2 then -- add button press to P1's input list for this frame 
101                    pressbutton1 = p1Buttons[listpos] 
102                    inp1[pressbutton1] = true 
103                end 
104            end 
105        listpos = listpos + 1 
106        end 
107        if debugText==1 then -- show all inputs for this frame 
108            textRow = 110 
109            gui.text(5,100,"p1 input"); 
110            for inpkey1, inpval1 in pairs(inp1) do 
111                inpPrint1 = string.format("%s %s", inpkey1, tostring(inpval1)) 
112                gui.text(5,textRow,inpPrint1); 
113                textRow = textRow + 10 
114            end 
115        end 
116 
117        -- player 2 input section; TODO: Break both P1 and P2 input out into a single function as this is a duplicate of P1's input section 
118        inp2 = {} 
119        listpos = 1 
120        for k,button2 in ipairs(p2Buttons) do 
121            if  button2 == p2ButtonOverride[listpos] then -- check to see if button should normally always be pressed 
122                pressbutton2 = p2Buttons[listpos] 
123                inp2[pressbutton2] = true 
124            end 
125            if button2 == p2ButtonMask[listpos] then -- find out if this button can be pressed 
126                randomizedButton2 = math.random(2) -- pick 1 or 2 
127                if randomizedButton2 == 2 then -- add button press to P2's input list for this frame 
128                    pressbutton2 = p2Buttons[listpos] 
129                    inp2[pressbutton2] = true 
130                end 
131            end 
132        listpos = listpos + 1 
133        end 
134        if debugText==1 then 
135            textRow = 110 
136            gui.text(200,100,"p2 input"); 
137            for inpkey2, inpval2 in pairs(inp2) do 
138                inpPrint2 = string.format("%s %s", inpkey2, tostring(inpval2)) 
139                gui.text(200,textRow,inpPrint2); 
140                textRow = textRow + 10 
141            end 
142        end 
143 
144        if playerInput == 1 then -- For some reason P1 and P2 can't be specified on the same frame, so this picks one...  TODO: Investigate and file  bug 
145            joypad.set(1, inp1) -- insert player 1's input 
146        else 
147            joypad.set(2, inp2) -- insert player 2's input 
148        end 
149        FCEU.frameadvance() 
150        frameCount = frameCount + 1 
151        if debugText==1 then -- shows frameCount in realtime 
152            frameCountPrint = string.format("Framecount = %s",frameCount) 
153            gui.text(5,10,frameCountPrint); 
154        end 
155 
156        watchMem = memory.readbyte(memoryToWatch) -- look to see if a memory value we are searching for has matched 
157        avoidWatchMem = memory.readbyte(avoidMemoryToWatch) -- look to see if a memory value we are trying to avoid has matched 
158        if debugText==1 then -- shows memory addresses in realtime 
159            watchMemPrint = string.format("watchMem = %s",watchMem) 
160            gui.text(180,10,watchMemPrint); 
161        end 
162        for l,avoidCheck in ipairs(avoidMem) do -- check for all undesirable memory  address values 
163            if avoidWatchMem == avoidCheck and avoidMemoryCheckEnabled == true then 
164                avoidMatch = true -- Something undesirable happened - this is used  below to trigger a  break 
165            else 
166                for m,matchCheck in ipairs(targetMem) do -- check for all desirable memory  address values 
167                    if matchFound == false and watchMem == matchCheck then 
168                        local foundSave = savestate.create(userSaveSlot) -- Saves in one higher slot than specified 
169                        savestate.save(foundSave) 
170                        matchFound = true 
171                        foundSolutionPrint = string.format("Match found in %s frames (value found = %s). Keep solution?", frameCount, watchMem) 
172                        response = gui.popup(foundSolutionPrint,"yesno") -- if answered  Yes, the script exits so manual input can  be resumed 
173                        if response == "yes" then 
174                            promptMoreAttempts = false 
175                            attempts = maxAttempts + 1 --  forces  script exit                    
176                        else -- if answered no, throw  out the result and try again 
177                            savestate.load(startSave) -- load for  another attempt 
178                        end 
179                    end 
180                end 
181            end 
182        end 
183        if attempts >= maxAttempts or avoidMatch == true then -- prevents an extra loop if a solution has been accepted or cancels the current loop if something undesirable happened 
184            avoidMatch = false -- Reset for  next iteration 
185            break 
186        end 
187    end 
188    frameCount = 0 
189    attempts = attempts + 1 
190    if promptMoreAttempts == true and attempts >= maxAttempts then 
191        noSolutionPrint = string.format("No match found after %s attempts.  Try another %s times?",maxAttempts, maxAttempts) 
192        response = gui.popup(noSolutionPrint,"yesno") -- if answered  Yes, reset attempts count and  start over 
193        if response == "yes" then 
194            attempts = 1 
195        end 
196    end 
197end 
198 
Download and save
Toggle line numbers
Thread:
[97003] High Speed item finder by dwangoac at 2008-09-29 20:53:40 (diff)
  [97004] Re: High Speed item finder by dwangoac at 2008-09-29 21:01:58 (diff)
    [97648] Re: High Speed item finder by dwangoac at 2008-10-06 20:06:01 (diff)
      [97912] Re: General purpose NES item finder by dwangoac at 2008-10-09 17:23:32 (diff)
        [98417] Re: General purpose NES item finder by dwangoac at 2008-10-16 02:52:10
Tip: Click the line numbers to toggle highliting on that line.

Paste followup:

Language:
Author:
Subject:


    Tabstop:     bigger biggest
Note: You can prefix a line with "@@@" to highlight it.