diff options
author | Bryan Bishop <kanzure@gmail.com> | 2013-09-10 21:18:30 -0700 |
---|---|---|
committer | Bryan Bishop <kanzure@gmail.com> | 2013-09-10 21:18:30 -0700 |
commit | 50341bae50b4318800dcea5694e95b239ed8fbb0 (patch) | |
tree | d2060ae537a36cc649b22785c46915dd1c9813ca /pokemontools/vba | |
parent | cb65c31968305b0572ca5452291afcf058cef3a2 (diff) | |
parent | 42c7b0348386fdd0d3fc3b64ad81ee8ee7779824 (diff) |
Merge pull request #21 from kanzure/vba-automation
Start moving away from jvm/jython.
Diffstat (limited to 'pokemontools/vba')
-rw-r--r-- | pokemontools/vba/__init__.py | 7 | ||||
-rw-r--r-- | pokemontools/vba/autoplayer.py | 495 | ||||
-rw-r--r-- | pokemontools/vba/keyboard.data | 501 | ||||
-rw-r--r-- | pokemontools/vba/keyboard.py | 64 | ||||
-rw-r--r-- | pokemontools/vba/vba.py | 527 |
5 files changed, 1594 insertions, 0 deletions
diff --git a/pokemontools/vba/__init__.py b/pokemontools/vba/__init__.py new file mode 100644 index 0000000..ea883a7 --- /dev/null +++ b/pokemontools/vba/__init__.py @@ -0,0 +1,7 @@ +""" +pokecrystal/pokered VBA automation module + +dependencies: + python-vba-wrapper (vba_wrapper) + vba-linux +""" diff --git a/pokemontools/vba/autoplayer.py b/pokemontools/vba/autoplayer.py new file mode 100644 index 0000000..9aa8f4a --- /dev/null +++ b/pokemontools/vba/autoplayer.py @@ -0,0 +1,495 @@ +# -*- coding: utf-8 -*- +""" +Programmatic speedrun of Pokémon Crystal +""" +import os + +# bring in the emulator and basic tools +import vba + +def main(): + """ + Start the game. + """ + vba.load_rom() + + # get past the opening sequence + skip_intro() + + # walk to mom and handle her text + handle_mom() + + # walk outside into new bark town + walk_into_new_bark_town() + + # walk to elm and do whatever he wants + handle_elm("totodile") + + new_bark_level_grind(10, skip=False) + +def skippable(func): + """ + Makes a function skippable. + + Saves the state before and after the function runs. + Pass "skip=True" to the function to load the previous save + state from when the function finished. + """ + def wrapped_function(*args, **kwargs): + skip = True + + if "skip" in kwargs.keys(): + skip = kwargs["skip"] + del kwargs["skip"] + + # override skip if there's no save + if skip: + full_name = func.__name__ + "-end.sav" + if not os.path.exists(os.path.join(vba.save_state_path, full_name)): + skip = False + + return_value = None + + if not skip: + vba.save_state(func.__name__ + "-start", override=True) + return_value = func(*args, **kwargs) + vba.save_state(func.__name__ + "-end", override=True) + elif skip: + vba.set_state(vba.load_state(func.__name__ + "-end")) + + return return_value + return wrapped_function + +@skippable +def skip_intro(): + """ + Skip the game boot intro sequence. + """ + + # copyright sequence + vba.nstep(400) + + # skip the ditto sequence + vba.press("a") + vba.nstep(100) + + # skip the start screen + vba.press("start") + vba.nstep(100) + + # click "new game" + vba.press("a", holdsteps=50, aftersteps=1) + + # skip text up to "Are you a boy? Or are you a girl?" + vba.crystal.text_wait() + + # select "Boy" + vba.press("a", holdsteps=50, aftersteps=1) + + # text until "What time is it?" + vba.crystal.text_wait() + + # select 10 o'clock + vba.press("a", holdsteps=50, aftersteps=1) + + # yes i mean it + vba.press("a", holdsteps=50, aftersteps=1) + + # "How many minutes?" 0 min. + vba.press("a", holdsteps=50, aftersteps=1) + + # "Who! 0 min.?" yes/no select yes + vba.press("a", holdsteps=50, aftersteps=1) + + # read text until name selection + vba.crystal.text_wait() + + # select "Chris" + vba.press("d", holdsteps=10, aftersteps=1) + vba.press("a", holdsteps=50, aftersteps=1) + + def overworldcheck(): + """ + A basic check for when the game starts. + """ + return vba.get_memory_at(0xcfb1) != 0 + + # go until the introduction is done + vba.crystal.text_wait(callback=overworldcheck) + + return + +@skippable +def handle_mom(): + """ + Walk to mom. Handle her speech and questions. + """ + + vba.crystal.move("r") + vba.crystal.move("r") + vba.crystal.move("r") + vba.crystal.move("r") + + vba.crystal.move("u") + vba.crystal.move("u") + vba.crystal.move("u") + + vba.crystal.move("d") + vba.crystal.move("d") + + # move into mom's line of sight + vba.crystal.move("d") + + # let mom talk until "What day is it?" + vba.crystal.text_wait() + + # "What day is it?" Sunday + vba.press("a", holdsteps=10) # Sunday + + vba.crystal.text_wait() + + # "SUNDAY, is it?" yes/no + vba.press("a", holdsteps=10) # yes + + vba.crystal.text_wait() + + # "Is it Daylight Saving Time now?" yes/no + vba.press("a", holdsteps=10) # yes + + vba.crystal.text_wait() + + # "AM DST, is that OK?" yes/no + vba.press("a", holdsteps=10) # yes + + # text until "know how to use the PHONE?" yes/no + vba.crystal.text_wait() + + # press yes + vba.press("a", holdsteps=10) + + # wait until mom is done talking + vba.crystal.text_wait() + + # wait until the script is done running + vba.crystal.wait_for_script_running() + + return + +@skippable +def walk_into_new_bark_town(): + """ + Walk outside after talking with mom. + """ + + vba.crystal.move("d") + vba.crystal.move("d") + vba.crystal.move("d") + vba.crystal.move("l") + vba.crystal.move("l") + + # walk outside + vba.crystal.move("d") + +@skippable +def handle_elm(starter_choice): + """ + Walk to Elm's Lab and get a starter. + """ + + # walk to the lab + vba.crystal.move("l") + vba.crystal.move("l") + vba.crystal.move("l") + vba.crystal.move("l") + vba.crystal.move("l") + vba.crystal.move("l") + vba.crystal.move("l") + vba.crystal.move("u") + vba.crystal.move("u") + + # walk into the lab + vba.crystal.move("u") + + # talk to elm + vba.crystal.text_wait() + + # "that I recently caught." yes/no + vba.press("a", holdsteps=10) # yes + + # talk to elm some more + vba.crystal.text_wait() + + # talking isn't done yet.. + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + + # wait until the script is done running + vba.crystal.wait_for_script_running() + + # move toward the pokeballs + vba.crystal.move("r") + + # move to cyndaquil + vba.crystal.move("r") + + moves = 0 + + if starter_choice.lower() == "cyndaquil": + moves = 0 + if starter_choice.lower() == "totodile": + moves = 1 + else: + moves = 2 + + for each in range(0, moves): + vba.crystal.move("r") + + # face the pokeball + vba.crystal.move("u") + + # select it + vba.press("a", holdsteps=10, aftersteps=0) + + # wait for the image to pop up + vba.crystal.text_wait() + + # wait for the image to close + vba.crystal.text_wait() + + # wait for the yes/no box + vba.crystal.text_wait() + + # press yes + vba.press("a", holdsteps=10, aftersteps=0) + + # wait for elm to talk a bit + vba.crystal.text_wait() + + # TODO: why didn't that finish his talking? + vba.crystal.text_wait() + + # give a nickname? yes/no + vba.press("d", holdsteps=10, aftersteps=0) # move to "no" + vba.press("a", holdsteps=10, aftersteps=0) # no + + # TODO: why didn't this wait until he was completely done? + vba.crystal.text_wait() + vba.crystal.text_wait() + + # get the phone number + vba.crystal.text_wait() + + # talk with elm a bit more + vba.crystal.text_wait() + + # TODO: and again.. wtf? + vba.crystal.text_wait() + + # wait until the script is done running + vba.crystal.wait_for_script_running() + + # move down + vba.crystal.move("d") + vba.crystal.move("d") + vba.crystal.move("d") + vba.crystal.move("d") + + # move into the researcher's line of sight + vba.crystal.move("d") + + # get the potion from the person + vba.crystal.text_wait() + vba.crystal.text_wait() + + # wait for the script to end + vba.crystal.wait_for_script_running() + + vba.crystal.move("d") + vba.crystal.move("d") + vba.crystal.move("d") + + # go outside + vba.crystal.move("d") + + return + +@skippable +def new_bark_level_grind(level): + """ + Do level grinding in New Bark. + + Starting just outside of Elm's Lab, do some level grinding until the first + partymon level is equal to the given value.. + """ + + # walk to the grass area + new_bark_level_grind_walk_to_grass(skip=False) + + # TODO: walk around in grass, handle battles + walk = ["d", "d", "u", "d", "u", "d"] + for direction in walk: + vba.crystal.move(direction) + + # wait for wild battle to completely start + vba.crystal.text_wait() + + attacks = 5 + + while attacks > 0: + # FIGHT + vba.press("a", holdsteps=10, aftersteps=1) + + # wait to select a move + vba.crystal.text_wait() + + # SCRATCH + vba.press("a", holdsteps=10, aftersteps=1) + + # wait for the move to be over + vba.crystal.text_wait() + + hp = ((vba.get_memory_at(0xd218) << 8) | vba.get_memory_at(0xd217)) + print "enemy hp is: " + str(hp) + + if hp == 0: + print "enemy hp is zero, exiting" + break + else: + print "enemy hp is: " + str(hp) + + attacks = attacks - 1 + + while vba.get_memory_at(0xd22d) != 0: + vba.press("a", holdsteps=10, aftersteps=1) + + # wait for the map to finish loading + vba.nstep(50) + + print "okay, back in the overworld" + + # move up + vba.crystal.move("u") + vba.crystal.move("u") + vba.crystal.move("u") + vba.crystal.move("u") + + # move into new bark town + vba.crystal.move("r") + vba.crystal.move("r") + vba.crystal.move("r") + vba.crystal.move("r") + vba.crystal.move("r") + vba.crystal.move("r") + vba.crystal.move("r") + vba.crystal.move("r") + vba.crystal.move("r") + vba.crystal.move("r") + + # move up + vba.crystal.move("u") + vba.crystal.move("u") + vba.crystal.move("u") + vba.crystal.move("u") + vba.crystal.move("u") + + # move to the door + vba.crystal.move("r") + vba.crystal.move("r") + vba.crystal.move("r") + + # walk in + vba.crystal.move("u") + + # move up to the healing thing + vba.crystal.move("u") + vba.crystal.move("u") + vba.crystal.move("u") + vba.crystal.move("u") + vba.crystal.move("u") + vba.crystal.move("u") + vba.crystal.move("u") + vba.crystal.move("u") + vba.crystal.move("u") + vba.crystal.move("l") + vba.crystal.move("l") + + # face it + vba.crystal.move("u") + + # interact + vba.press("a", holdsteps=10, aftersteps=1) + + # wait for yes/no box + vba.crystal.text_wait() + + # press yes + vba.press("a", holdsteps=10, aftersteps=1) + + # TODO: when is healing done? + + # wait until the script is done running + vba.crystal.wait_for_script_running() + + # wait for it to be really really done + vba.nstep(50) + + vba.crystal.move("r") + vba.crystal.move("r") + + # move to the door + vba.crystal.move("d") + vba.crystal.move("d") + vba.crystal.move("d") + vba.crystal.move("d") + vba.crystal.move("d") + vba.crystal.move("d") + vba.crystal.move("d") + vba.crystal.move("d") + vba.crystal.move("d") + + # walk out + vba.crystal.move("d") + + # check partymon1 level + if vba.get_memory_at(0xdcfe) < level: + new_bark_level_grind(level, skip=False) + else: + return + +@skippable +def new_bark_level_grind_walk_to_grass(): + """ + Move to just above the grass from outside Elm's lab. + """ + + vba.crystal.move("d") + vba.crystal.move("d") + + vba.crystal.move("l") + vba.crystal.move("l") + + vba.crystal.move("d") + vba.crystal.move("d") + + vba.crystal.move("l") + vba.crystal.move("l") + + # move to route 29 past the trees + vba.crystal.move("l") + vba.crystal.move("l") + vba.crystal.move("l") + vba.crystal.move("l") + vba.crystal.move("l") + vba.crystal.move("l") + vba.crystal.move("l") + vba.crystal.move("l") + vba.crystal.move("l") + + # move to just above the grass + vba.crystal.move("d") + vba.crystal.move("d") + vba.crystal.move("d") + +if __name__ == "__main__": + main() diff --git a/pokemontools/vba/keyboard.data b/pokemontools/vba/keyboard.data new file mode 100644 index 0000000..2e9a669 --- /dev/null +++ b/pokemontools/vba/keyboard.data @@ -0,0 +1,501 @@ +A a select +A B r +A I l +A lower-upper-column-1 u +A J d + +B b select +B A l +B C r +B lower-upper-column-2 u +B K d + +C c select +C D r +C B l +C lower-upper-column-3 u +C L d + +D d select +D E r +D C l +D del-upper-column-1 u +D M d + +E e select +E del-upper-column-2 u +E N d +E D l +E F r + +F f select +F del-upper-column-3 u +F O d +F E l +F G r + +G g select +G end-upper-column-1 u +G P d +G F l +G H r + +H h select +H end-upper-column-2 u +H Q d +H G l +H I r + +I i select +I end-upper-column-3 u +I R d +I H l +I A r + +J j select +J A u +J S d +J R l +J K r + +K k select +K B u +K T d +K J l +K L r + +L l select +L C u +L U d +L K l +L M r + +M m select +M D u +M V d +M L l +M N r + +N n select +N E u +N W d +N M l +N O r + +O o select +O F u +O X d +O N l +O P r + +P p select +P G u +P Y d +P O l +P Q r + +Q q select +Q H u +Q Z d +Q P l +Q R r + +R r select +R I u +R space-upper-x8-y2 d +R Q l +R J r + +S s select +S J u +S - d +S space-upper-x8-y2 l + +T t select +T K u +T ? d +T S l +T U r + +U u select +U L u +U ! d +U T l +U V r + +V v select +V M u +V / d +V U l +V W r + +W w select +W N u +W . d +W V l +W X r + +X x select +X O u +X , d +X W l +X Y r + +Y y select +Y P u +Y space-upper-x6-y3 d +Y X l +Y Z r + +Z z select +Z Q u +Z space-upper-x7-y3 d +Z Y l +Z space-upper-x8-y2 r + +end-upper-column-1 lower-upper-column-1 r +end-upper-column-2 lower-upper-column-1 r +end-upper-column-3 lower-upper-column-1 r +end-upper-column-1 del-upper-column-1 l +end-upper-column-2 del-upper-column-1 l +end-upper-column-3 del-upper-column-1 l +lower-upper-column-1 end-upper-column-1 l +lower-upper-column-2 end-upper-column-1 l +lower-upper-column-3 end-upper-column-1 l +lower-upper-column-1 del-upper-column-1 r +lower-upper-column-2 del-upper-column-1 r +lower-upper-column-3 del-upper-column-1 r +del-upper-column-1 lower-upper-column-1 l +del-upper-column-2 lower-upper-column-1 l +del-upper-column-3 lower-upper-column-1 l +del-upper-column-1 end-upper-column-1 r +del-upper-column-2 end-upper-column-1 r +del-upper-column-3 end-upper-column-1 r + +lower-upper-column-1 A d +lower-upper-column-2 B d +lower-upper-column-3 C d +lower-upper-column-1 - u +lower-upper-column-2 ? u +lower-upper-column-3 ! u + +del-upper-column-1 D d +del-upper-column-2 E d +del-upper-column-3 F d +del-upper-column-1 / u +del-upper-column-2 . u +del-upper-column-3 , u + +end-upper-column-1 G d +end-upper-column-2 H d +end-upper-column-3 I d +end-upper-column-1 space-upper-x6-y3 u +end-upper-column-2 space-upper-x7-y3 u +end-upper-column-3 space-upper-x8-y3 u + +space-upper-x8-y2 space-lower-x8-y2 select +space-upper-x8-y2 R u +space-upper-x8-y2 space-upper-x8-y3 d +space-upper-x8-y2 Z l +space-upper-x8-y2 S r + +space-upper-x8-y3 MN select +space-upper-x8-y3 space-upper-x8-y2 u +space-upper-x8-y3 end-upper-column-3 d +space-upper-x8-y3 space-upper-x7-y3 l +space-upper-x8-y3 - r + +space-upper-x7-y3 PK select +space-upper-x7-y3 Z u +space-upper-x7-y3 end-upper-column-2 d +space-upper-x7-y3 space-upper-x6-y3 l +space-upper-x7-y3 space-upper-x8-y3 r + +space-upper-x6-y3 ] select +space-upper-x6-y3 Y u +space-upper-x6-y3 end-upper-column-1 d +space-upper-x6-y3 , l +space-upper-x6-y3 space-upper-x7-y3 r + +end-upper-column-1 end-lower-column-1 select +end-upper-column-2 end-lower-column-2 select +end-upper-column-3 end-lower-column-3 select +lower-upper-column-1 lower-lower-column-1 select +lower-upper-column-2 lower-lower-column-2 select +lower-upper-column-3 lower-lower-column-3 select +del-upper-column-1 del-lower-column-1 select +del-upper-column-2 del-lower-column-2 select +del-upper-column-3 del-lower-column-3 select + +lower-lower-column-1 × u +lower-lower-column-2 ( u +lower-lower-column-3 ) u +lower-lower-column-1 a d +lower-lower-column-2 b d +lower-lower-column-3 c d + +end-lower-column-1 ] u +end-lower-column-2 PK u +end-lower-column-3 MN u +end-lower-column-1 g d +end-lower-column-2 h d +end-lower-column-3 i d + +del-lower-column-1 : u +del-lower-column-2 ; u +del-lower-column-3 [ u +del-lower-column-1 d d +del-lower-column-2 e d +del-lower-column-3 f d + +- × select +- S u +- lower-upper-column-1 d +- space-upper-x8-y3 l +- ? r + +? ( select +? T u +? lower-upper-column-2 d +? - l +? ! r + +! ) select +! U u +! lower-upper-column-3 d +! ? l +! / r + +/ : select +/ V u +/ del-upper-column-1 d +/ ! l +/ . r + +. ; select +. W u +. del-upper-column-2 d +. / l +. , r + +, [ select +, X u +, del-upper-column-3 d +, . l +, space-upper-x6-y3 r + +× - select +× s u +× upper-lower-column-1 d +× MN l +× ( r + +( ? select +( t u +( upper-lower-column-2 d +( × l +( ) r + +) ! select +) u u +) upper-lower-column-3 d +) ( l +) : r + +: / select +: v u +: del-lower-column-1 d +: ) l +: ; r + +; . select +; w u +; del-lower-column-2 d +; : l +; [ r + +[ , select +[ x u +[ del-lower-column-3 d +[ ; l +[ ] r + +] space-upper-x6-y3 select +] y u +] end-lower-column-1 d +] [ l +] PK r + +PK space-upper-x7-y3 select +PK z u +PK end-lower-column-2 d +PK ] l +PK MN r + +MN space-upper-x8-y3 select +MN space-lower-x8-y2 u +MN end-lower-column-3 d +MN PK l +MN × r + +space-lower-x8-y2 space-upper-x8-y2 select +space-lower-x8-y2 r u +space-lower-x8-y2 MN d +space-lower-x8-y2 z l +space-lower-x8-y2 s r + +a A select +a upper-lower-column-1 u +a j d +a i l +a b r + +b B select +b upper-lower-column-2 u +b k d +b a l +b c r + +c C select +c upper-lower-column-3 u +c l d +c b l +c d r + +d D select +d del-lower-column-1 u +d m d +d c l +d e r + +e E select +e del-lower-column-2 u +e n d +e d l +e f r + +f F select +f del-lower-column-3 u +f o d +f e l +f g r + +g G select +g end-lower-column-1 u +g p d +g f l +g h r + +h H select +h end-lower-column-2 u +h q d +h g l +h i r + +i I select +i end-lower-column-3 u +i r d +i h l +i a r + +j J select +j a u +j s d +j r l +j k r + +k K select +k b u +k t d +k j l +k l r + +l L select +l c u +l u d +l k l +l m r + +m M select +m d u +m v d +m l l +m n r + +n N select +n e u +n w d +n m l +n o r + +o O select +o f u +o x d +o n l +o p r + +p P select +p g u +p y d +p o l +p q r + +q Q select +q h u +q z d +q p l +q r r + +r R select +r i u +r space-lower-x8-y2 d +r q l +r j r + +s S select +s j u +s × d +s space-lower-x8-y2 l +s t r + +t T select +t k u +t ( d +t s l +t u r + +u U select +u l u +u ) d +u t l +u v r + +v V select +v m u +v : d +v u l +v w r + +w W select +w n u +w ; d +w v l +w x r + +x X select +x o u +x [ d +x w l +x y r + +y Y select +y p u +y ] d +y x l +y z r + +z Z select +z q u +z PK d +z y l +z space-lower-x8-y2 r diff --git a/pokemontools/vba/keyboard.py b/pokemontools/vba/keyboard.py new file mode 100644 index 0000000..4a07e57 --- /dev/null +++ b/pokemontools/vba/keyboard.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +""" +This file constructs a networkx.DiGraph object called graph, which can be used +to find the shortest path of keypresses on the keyboard to type a word. +""" + +import os +import itertools +import networkx + +graph = networkx.DiGraph() + +# load graph data from file +data_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "keyboard.data") +graph_data = open(data_path, "r").read() + +for line in graph_data.split("\n"): + if line == "": + continue + elif line[0] == "#": + continue + + (node1, node2, edge_name) = line.split(" ") + graph.add_edge(node1, node2, key=edge_name) + + #print "Adding edge ("+edge_name+") "+node1+" -> "+node2 + +def shortest_path(node1, node2): + """ + Figures out the shortest list of button presses to move from one letter to + another. + """ + buttons = [] + last = None + path = networkx.shortest_path(graph, node1, node2) + for each in path: + if last != None: + buttons.append(convert_nodes_to_button_press(last, each)) + last = each + return buttons + #return [convert_nodes_to_button_press(node3, node4) for (node3, node4) in zip(*(iter(networkx.shortest_path(graph, node1, node2)),) * 2)] + +def convert_nodes_to_button_press(node1, node2): + """ + Determines the button necessary to switch from node1 to node2. + """ + print "getting button press for state transition: " + node1 + " -> " + node2 + return graph.get_edge_data(node1, node2)["key"] + +def plan_typing(text, current="A"): + """ + Plans a sequence of button presses to spell out the given text. + """ + buttons = [] + for target in text: + if target == current: + buttons.append("a") + else: + print "Finding the shortest path between " + current + " and " + target + more_buttons = shortest_path(current, target) + buttons.extend(more_buttons) + buttons.append("a") + current = target + return buttons diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py new file mode 100644 index 0000000..d7fdf1d --- /dev/null +++ b/pokemontools/vba/vba.py @@ -0,0 +1,527 @@ +# -*- coding: utf-8 -*- +""" +VBA automation +""" + +import os +import sys +import re +import string +from copy import copy + +import unittest + +# for converting bytes to readable text +from pokemontools.chars import chars + +from pokemontools.map_names import map_names + +import keyboard + +# just use a default config for now until the globals are removed completely +import pokemontools.config as conf +config = conf.Config() +project_path = config.path +save_state_path = config.save_state_path +rom_path = config.rom_path + +if not os.path.exists(rom_path): + raise Exception("rom_path is not configured properly; edit vba_config.py? " + str(rom_path)) + +import vba_wrapper + +vba = vba_wrapper.VBA(rom_path) +registers = vba_wrapper.core.registers.Registers(vba) + +button_masks = vba_wrapper.core.VBA.button_masks +button_combiner = vba_wrapper.core.VBA.button_combine + +def translate_chars(charz): + result = "" + for each in charz: + result += chars[each] + return result + +def press(buttons, holdsteps=1, aftersteps=1): + """ + Press a button. + + Use steplimit to say for how many steps you want to press + the button (try leaving it at the default, 1). + """ + if hasattr(buttons, "__len__"): + number = button_combiner(buttons) + elif isinstance(buttons, int): + number = buttons + else: + number = buttons + for step_counter in range(0, holdsteps): + Gb.step(number) + + # clear the button press + if aftersteps > 0: + for step_counter in range(0, aftersteps): + Gb.step(0) + +def call(bank, address): + """ + Jumps into a function at a certain address. + + Go into the start menu, pause the game and try call(1, 0x1078) to see a + string printed to the screen. + """ + push = [ + registers.pc, + registers.hl, + registers.de, + registers.bc, + registers.af, + 0x3bb7, + ] + + for value in push: + registers.sp -= 2 + set_memory_at(registers.sp + 1, value >> 8) + set_memory_at(registers.sp, value & 0xFF) + if get_memory_range(registers.sp, 2) != [value & 0xFF, value >> 8]: + print "desired memory values: " + str([value & 0xFF, value >> 8] ) + print "actual memory values: " + str(get_memory_range(registers.sp , 2)) + print "wrong value at " + hex(registers.sp) + " expected " + hex(value) + " but got " + hex(get_memory_at(registers.sp)) + + if bank != 0: + registers["af"] = (bank << 8) | (registers["af"] & 0xFF) + registers["hl"] = address + registers["pc"] = 0x2d63 # FarJump + else: + registers["pc"] = address + +def get_stack(): + """ + Return a list of functions on the stack. + """ + addresses = [] + sp = registers.sp + + for x in range(0, 11): + sp = sp - (2 * x) + hi = get_memory_at(sp + 1) + lo = get_memory_at(sp) + address = ((hi << 8) | lo) + addresses.append(address) + + return addresses + +class crystal: + """ + Just a simple namespace to store a bunch of functions for Pokémon Crystal. + """ + + @staticmethod + def text_wait(step_size=1, max_wait=200, sfx_limit=0, debug=False, callback=None): + """ + Presses the "A" button when text is done being drawn to screen. + + The `debug` parameter is only useful when debugging this function. It + enables the `max_wait` feature, which causes the function to exit + instead of hanging around. + + The `sfx_limit` parameter is useful for when the player is given an + item during the text. Set it to 1 to not treat the sound as the end of + text. The next loop around it will return to the normal behavior of the + function. + + :param step_size: number of steps per wait loop + :param max_wait: number of wait loops to perform + """ + while max_wait > 0: + hi = get_memory_at(registers.sp + 1) + lo = get_memory_at(registers.sp) + address = ((hi << 8) | lo) + + if address in range(0xa1b, 0xa46) + range(0xaaf, 0xaf5): # 0xaef: + print "pressing, then breaking.. address is: " + str(hex(address)) + + # set CurSFX + set_memory_at(0xc2bf, 0) + + press("a", holdsteps=10, aftersteps=1) + + # check if CurSFX is SFX_READ_TEXT_2 + if get_memory_at(0xc2bf) == 0x8: + print "cursfx is set to SFX_READ_TEXT_2, looping.." + return crystal.text_wait(step_size=step_size, max_wait=max_wait, debug=debug, callback=callback, sfx_limit=sfx_limit) + else: + if sfx_limit > 0: + sfx_limit = sfx_limit - 1 + print "decreasing sfx_limit" + else: + # probably the last textbox in a sequence + print "cursfx is not set to SFX_READ_TEXT_2, so: breaking" + + break + else: + stack = get_stack() + + # yes/no box or the name selection box + if address in range(0xa46, 0xaaf): + print "probably at a yes/no box.. exiting." + break + + # date/time box (day choice) + # 0x47ab is the one from the intro, 0x49ab is the one from mom. + elif 0x47ab in stack or 0x49ab in stack: # was any([x in stack for x in range(0x46EE, 0x47AB)]) + print "probably at a date/time box ? exiting." + break + + # "How many minutes?" selection box + elif 0x4826 in stack: + print "probably at a \"How many minutes?\" box ? exiting." + break + + else: + nstep(step_size) + + # if there is a callback, then call the callback and exit when the + # callback returns True. This is especially useful during the + # OakSpeech intro where textboxes are running constantly, and then + # suddenly the player can move around. One way to detect that is to + # set callback to a function that returns + # "vba.get_memory_at(0xcfb1) != 0". + if callback != None: + result = callback() + if result == True: + print "callback returned True, exiting" + return + + # only useful when debugging. When this is left on, text that + # takes a while to print to screen will cause this function to + # exit. + if debug == True: + max_wait = max_wait - 1 + + if max_wait == 0: + print "max_wait was hit" + + @staticmethod + def walk_through_walls_slow(): + memory = get_memory() + memory[0xC2FA] = 0 + memory[0xC2FB] = 0 + memory[0xC2FC] = 0 + memory[0xC2FD] = 0 + set_memory(memory) + + @staticmethod + def walk_through_walls(): + """ + Lets the player walk all over the map. + + These values are probably reset by some of the map/collision + functions when you move on to a new location, so this needs + to be executed each step/tick if continuous walk-through-walls + is desired. + """ + set_memory_at(0xC2FA, 0) + set_memory_at(0xC2FB, 0) + set_memory_at(0xC2FC, 0) + set_memory_at(0xC2FD, 0) + + #@staticmethod + #def set_enemy_level(level): + # set_memory_at(0xd213, level) + + @staticmethod + def nstep(steplimit=500): + """ + Steps the CPU forward and calls some functions in between each step. + + (For example, to manipulate memory.) This is pretty slow. + """ + for step_counter in range(0, steplimit): + crystal.walk_through_walls() + #call(0x1, 0x1078) + step() + + @staticmethod + def disable_triggers(): + set_memory_at(0x23c4, 0xAF) + set_memory_at(0x23d0, 0xAF); + + @staticmethod + def disable_callbacks(): + set_memory_at(0x23f2, 0xAF) + set_memory_at(0x23fe, 0xAF) + + @staticmethod + def get_map_group_id(): + """ + Returns the current map group. + """ + return get_memory_at(0xdcb5) + + @staticmethod + def get_map_id(): + """ + Returns the map number of the current map. + """ + return get_memory_at(0xdcb6) + + @staticmethod + def get_map_name(): + """ + Figures out the current map name. + """ + map_group_id = crystal.get_map_group_id() + map_id = crystal.get_map_id() + return map_names[map_group_id][map_id]["name"] + + @staticmethod + def get_xy(): + """ + (x, y) coordinates of player on map. + + Relative to top-left corner of map. + """ + x = get_memory_at(0xdcb8) + y = get_memory_at(0xdcb7) + return (x, y) + + @staticmethod + def menu_select(id=1): + """ + Sets the cursor to the given pokemon in the player's party. + + This is under Start -> PKMN. This is useful for selecting a + certain pokemon with fly or another skill. + + This probably works on other menus. + """ + set_memory_at(0xcfa9, id) + + @staticmethod + def is_in_battle(): + """ + Checks whether or not we're in a battle. + """ + return (get_memory_at(0xd22d) != 0) or crystal.is_in_link_battle() + + @staticmethod + def is_in_link_battle(): + return get_memory_at(0xc2dc) != 0 + + @staticmethod + def unlock_flypoints(): + """ + Unlocks different destinations for flying. + + Note: this might start at 0xDCA4 (minus one on all addresses), but not + sure. + """ + set_memory_at(0xDCA5, 0xFF) + set_memory_at(0xDCA6, 0xFF) + set_memory_at(0xDCA7, 0xFF) + set_memory_at(0xDCA8, 0xFF) + + @staticmethod + def get_gender(): + """ + Returns 'male' or 'female'. + """ + gender = get_memory_at(0xD472) + if gender == 0: + return "male" + elif gender == 1: + return "female" + else: + return gender + + @staticmethod + def get_player_name(): + """ + Returns the 7 characters making up the player's name. + """ + bytez = get_memory_range(0xD47D, 7) + name = translate_chars(bytez) + return name + + @staticmethod + def warp(map_group_id, map_id, x, y): + set_memory_at(0xdcb5, map_group_id) + set_memory_at(0xdcb6, map_id) + set_memory_at(0xdcb7, y) + set_memory_at(0xdcb8, x) + set_memory_at(0xd001, 0xFF) + set_memory_at(0xff9f, 0xF1) + set_memory_at(0xd432, 1) + set_memory_at(0xd434, 0 & 251) + + @staticmethod + def warp_pokecenter(): + crystal.warp(1, 1, 3, 3) + crystal.nstep(200) + + @staticmethod + def masterballs(): + # masterball + set_memory_at(0xd8d8, 1) + set_memory_at(0xd8d9, 99) + + # ultraball + set_memory_at(0xd8da, 2) + set_memory_at(0xd8db, 99) + + # pokeballs + set_memory_at(0xd8dc, 5) + set_memory_at(0xd8dd, 99) + + @staticmethod + def get_text(): + """ + Returns alphanumeric text on the screen. + + Other characters will not be shown. + """ + output = "" + tiles = get_memory_range(0xc4a0, 1000) + for each in tiles: + if each in chars.keys(): + thing = chars[each] + acceptable = False + + if len(thing) == 2: + portion = thing[1:] + else: + portion = thing + + if portion in string.printable: + acceptable = True + + if acceptable: + output += thing + + # remove extra whitespace + output = re.sub(" +", " ", output) + output = output.strip() + + return output + + @staticmethod + def keyboard_apply(button_sequence): + """ + Applies a sequence of buttons to the on-screen keyboard. + """ + for buttons in button_sequence: + press(buttons) + nstep(2) + press([]) + + @staticmethod + def write(something="TrAiNeR"): + """ + Types out a word. + + Uses a planning algorithm to do this in the most efficient way possible. + """ + button_sequence = keyboard.plan_typing(something) + crystal.keyboard_apply([[x] for x in button_sequence]) + + @staticmethod + def set_partymon2(): + """ + This causes corruption, so it's not working yet. + """ + memory = get_memory() + memory[0xdcd7] = 2 + memory[0xdcd9] = 0x7 + + memory[0xdd0f] = 0x7 + memory[0xdd10] = 0x1 + + # moves + memory[0xdd11] = 0x1 + memory[0xdd12] = 0x2 + memory[0xdd13] = 0x3 + memory[0xdd14] = 0x4 + + # id + memory[0xdd15] = 0x1 + memory[0xdd16] = 0x2 + + # experience + memory[0xdd17] = 0x2 + memory[0xdd18] = 0x3 + memory[0xdd19] = 0x4 + + # hp + memory[0xdd1a] = 0x5 + memory[0xdd1b] = 0x6 + + # current hp + memory[0xdd31] = 0x10 + memory[0xdd32] = 0x25 + + # max hp + memory[0xdd33] = 0x10 + memory[0xdd34] = 0x40 + + set_memory(memory) + + @staticmethod + def wait_for_script_running(debug=False, limit=1000): + """ + Wait until ScriptRunning isn't -1. + """ + while limit > 0: + if get_memory_at(0xd438) != 255: + print "script is done executing" + return + else: + step() + + if debug: + limit = limit - 1 + + if limit == 0: + print "limit ran out" + + @staticmethod + def move(cmd): + """ + Attempt to move the player. + """ + press(cmd, holdsteps=10, aftersteps=0) + press([]) + + memory = get_memory() + #while memory[0xd4e1] == 2 and memory[0xd042] != 0x3e: + while memory[0xd043] in [0, 1, 2, 3]: + #while memory[0xd043] in [0, 1, 2, 3] or memory[0xd042] != 0x3e: + nstep(10) + memory = get_memory() + +class TestEmulator(unittest.TestCase): + def test_PlaceString(self): + call(0, 0x1078) + + # where to draw the text + registers["hl"] = 0xc4a0 + + # what text to read from + registers["de"] = 0x1276 + + nstep(10) + + text = crystal.get_text() + + self.assertTrue("TRAINER" in text) + +class TestWriter(unittest.TestCase): + def test_very_basic(self): + button_sequence = keyboard.plan_typing("an") + expected_result = ["select", "a", "d", "r", "r", "r", "r", "a"] + + self.assertEqual(len(expected_result), len(button_sequence)) + self.assertEqual(expected_result, button_sequence) + +if __name__ == "__main__": + unittest.main() |