diff --git a/extras/vba_autoplayer.py b/extras/vba_autoplayer.py new file mode 100644 index 000000000..eafbff134 --- /dev/null +++ b/extras/vba_autoplayer.py @@ -0,0 +1,464 @@ +# -*- 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 to elm and do whatever he wants + handle_elm("totodile") + + new_bark_level_grind(10, skip=False) + +def skippable(func): + """ + Makes a function skippable by saving 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) + + # Are you a boy? Or are you a girl? + vba.nstep(145) + + # pick "boy" + vba.press("a", holdsteps=50, aftersteps=1) + + # .... + vba.crystal.text_wait() + + vba.crystal.text_wait() + + # What time is it? + vba.crystal.text_wait() + + # DAY 10 o'clock + vba.press("a", holdsteps=50, aftersteps=1) + + # WHAT? DAY 10 o'clock? YES/NO. + vba.press("a", holdsteps=50, aftersteps=1) + + # How many minutes? 0 min. + vba.press("a", holdsteps=50, aftersteps=1) + + # Whoa! 0 min.? YES/NO. + vba.press("a", holdsteps=50, aftersteps=1) + + vba.crystal.text_wait() + vba.crystal.text_wait() + + # People call me + vba.crystal.text_wait() + vba.crystal.text_wait() + + # tures that we call + vba.crystal.text_wait() + + vba.crystal.text_wait() + vba.crystal.text_wait() + + vba.crystal.text_wait() + vba.crystal.text_wait() + + vba.crystal.text_wait() + + # everything about pokemon yet. + vba.crystal.text_wait() + vba.crystal.text_wait() + + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + + # Now, what did you say your name was? + vba.crystal.text_wait() + + # move down to "CHRIS" + vba.press("d") + vba.nstep(50) + + vba.press("a", holdsteps=50, aftersteps=1) + + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + + # wait until playable + # could be 150, but it sometimes takes longer?? + vba.nstep(200) + + return + +@skippable +def handle_mom(): + """ + Walk to mom. Handle her speech and questions. + """ + vba.press("r"); vba.nstep(50) + vba.press("r"); vba.nstep(50) + vba.press("r"); vba.nstep(50) + vba.press("r"); vba.nstep(50) + vba.press("r"); vba.nstep(50) + vba.press("u"); vba.nstep(50) + vba.press("u"); vba.nstep(50) + vba.press("u"); vba.nstep(50) + vba.press("u"); vba.nstep(50) + + # wait for next map to load + vba.nstep(50) + + vba.press("d"); vba.nstep(50) + vba.press("d"); vba.nstep(50) + vba.press("d"); vba.nstep(50) + + # walk into mom's line of sight + vba.press("d"); vba.nstep(50) + + vba.nstep(50) + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + + # What day is it? SUNDAY. + vba.press("a", holdsteps=50, aftersteps=1) + + # SUNDAY, is it? YES/NO + vba.press("a", holdsteps=50, aftersteps=1) + + vba.nstep(200) + + # is it DST now? YES/NO. + vba.press("a", holdsteps=50, aftersteps=1) + + # 10:06 AM DST, is that OK? YES/NO. + vba.press("a", holdsteps=50, aftersteps=1) + + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + + # know how to use the PHONE? YES/NO. + vba.press("a", holdsteps=50, aftersteps=1) + + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + + vba.press("a", holdsteps=50, aftersteps=1) + + # have to wait for her to move back :( + vba.nstep(50) + + # face down + vba.press("d"); vba.nstep(50) + + return + +@skippable +def handle_elm(starter_choice): + """ + Walk to Elm's Lab and get a starter. + """ + + # walk down + vba.press("d"); vba.nstep(50) + vba.press("d"); vba.nstep(50) + + # face left + vba.press("l"); vba.nstep(50) + + # walk left + vba.press("l"); vba.nstep(50) + vba.press("l"); vba.nstep(50) + + # face down + vba.press("d"); vba.nstep(50) + + # walk down + vba.press("d"); vba.nstep(50) + + # walk down to warp to outside + vba.press("d"); vba.nstep(50) + vba.nstep(10) + + vba.press("l", holdsteps=10, aftersteps=50) + vba.press("l", holdsteps=10, aftersteps=50) + vba.press("l", holdsteps=10, aftersteps=50) + + vba.press("l", holdsteps=10, aftersteps=50) + vba.press("l", holdsteps=10, aftersteps=50) + vba.press("l", holdsteps=10, aftersteps=50) + vba.press("l", holdsteps=10, aftersteps=50) + + vba.press("u", holdsteps=10, aftersteps=50) + vba.press("u", holdsteps=10, aftersteps=50) + + # warp into elm's lab (bottom left warp) + vba.press("u", holdsteps=5, aftersteps=50) + + # let the script play + vba.nstep(200) + + vba.crystal.text_wait() + # I needed to ask you a fa + + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + + vba.nstep(50) + + # YES/NO. + vba.press("a") + + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + + vba.press("a") + + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.press("a") + vba.crystal.text_wait() + vba.crystal.text_wait() + + vba.crystal.text_wait() + + for x in range(0, 8): # was 15 + vba.crystal.text_wait() + + vba.nstep(50) + vba.press("a") + vba.nstep(100) + + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + + # Go on! Pick one. + vba.nstep(100) + vba.press("a"); vba.nstep(50) + + vba.press("r"); vba.nstep(50) + vba.press("r"); vba.nstep(50) + + right = 0 + if starter_choice in [1, "cyndaquil"]: + right = 0 + elif starter_choice in [2, "totodile"]: + right = 1 + elif starter_choice in [3, "chikorita"]: + right = 2 + else: + raise Exception("bad starter") + + for each in range(0, right): + vba.press("r"); vba.nstep(50) + + # look up + vba.press("u", holdsteps=5, aftersteps=50) + + # get menu + vba.press("a", holdsteps=5, aftersteps=50) + + # let the image show + vba.nstep(100) + + vba.crystal.text_wait() + vba.crystal.text_wait() + + # YES/NO. + vba.press("a") + + vba.crystal.text_wait() + vba.crystal.text_wait() + + # received.. music is playing. + vba.press("a") + vba.crystal.text_wait() + + # YES/NO (nickname) + vba.crystal.text_wait() + + vba.press("b") + + # get back to elm + vba.nstep(100) + vba.nstep(100) + + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + + # phone number.. + vba.press("a") + vba.crystal.text_wait() + vba.nstep(100) + vba.press("a") + vba.nstep(300) + vba.press("a") + vba.nstep(100) + + vba.crystal.text_wait() + vba.crystal.text_wait() + vba.crystal.text_wait() + + # I'm counting on you! + vba.nstep(200) + vba.press("a") + vba.nstep(50) + + # look down + vba.press("d", holdsteps=5, aftersteps=50) + + # walk down + vba.press("d", holdsteps=10, aftersteps=50) + + vba.press("d", holdsteps=10, aftersteps=50) + vba.press("d", holdsteps=10, aftersteps=50) + vba.press("d", holdsteps=10, aftersteps=50) + vba.press("d", holdsteps=10, aftersteps=50) + + vba.crystal.text_wait() + vba.crystal.text_wait() + + vba.press("a") + vba.nstep(50) + vba.press("a") + + vba.crystal.text_wait() + vba.crystal.text_wait() + + vba.press("a") + vba.nstep(50) + + vba.crystal.text_wait() + vba.press("a") + + vba.nstep(100) + + vba.press("d", holdsteps=10, aftersteps=50) + vba.press("d", holdsteps=10, aftersteps=50) + vba.press("d", holdsteps=10, aftersteps=50) + vba.press("d", holdsteps=10, aftersteps=50) + + # step outside + vba.nstep(40) + +@skippable +def new_bark_level_grind(level): + """ + 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 + # TODO: heal at the lab, then repeat entire function + +@skippable +def new_bark_level_grind_travel_to_grass(): + """ + Move to just above the grass. + """ + vba.press("d", holdsteps=10, aftersteps=50) + vba.press("d", holdsteps=10, aftersteps=50) + vba.press("d", holdsteps=10, aftersteps=50) + + vba.press("l", holdsteps=10, aftersteps=50) + vba.press("l", holdsteps=10, aftersteps=50) + vba.press("l", holdsteps=10, aftersteps=50) + vba.press("l", holdsteps=10, aftersteps=50) + + vba.press("d", holdsteps=10, aftersteps=50) + vba.press("d", holdsteps=10, aftersteps=50) + +if __name__ == "__main__": + main()