summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--vba.py41
-rw-r--r--vba_autoplayer.py464
2 files changed, 500 insertions, 5 deletions
diff --git a/vba.py b/vba.py
index 317f705..f85f838 100644
--- a/vba.py
+++ b/vba.py
@@ -159,15 +159,21 @@ def button_combiner(buttons):
# recognized as "s" and "t" etc..
if isinstance(buttons, str):
if "restart" in buttons:
- buttons.replace("restart", "")
+ buttons = buttons.replace("restart", "")
result |= button_masks["restart"]
if "start" in buttons:
- buttons.replace("start", "")
+ buttons = buttons.replace("start", "")
result |= button_masks["start"]
if "select" in buttons:
- buttons.replace("select", "")
+ buttons = buttons.replace("select", "")
result |= button_masks["select"]
+ # allow for the "a, b" and "a b" formats
+ if ", " in buttons:
+ buttons = buttons.split(", ")
+ elif " " in buttons:
+ buttons = buttons.split(" ")
+
if isinstance(buttons, list):
if len(buttons) > 9:
raise Exception("can't combine more than 9 buttons at a time")
@@ -429,7 +435,7 @@ def set_memory_at(address, value):
"""
Gb.setMemoryAt(address, value)
-def press(buttons, steplimit=1):
+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).
@@ -440,9 +446,14 @@ def press(buttons, steplimit=1):
number = buttons
else:
number = buttons
- for step_counter in range(0, steplimit):
+ 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 get_buttons():
"""
Returns the currentButtons[0] value (an integer with bits set for which
@@ -702,6 +713,26 @@ class crystal:
"""
@staticmethod
+ def text_wait(step_size=10, max_wait=500):
+ """
+ Watches for a sign that text is done being drawn to screen, then
+ presses the "A" button.
+
+ :param step_size: number of steps per wait loop
+ :param max_wait: number of wait loops to perform
+ """
+ for x in range(0, max_wait):
+ hi = get_memory_at(registers.sp + 1)
+ lo = get_memory_at(registers.sp)
+ address = ((hi << 8) | lo)
+ if address == 0xaef:
+ break
+ else:
+ nstep(step_size)
+
+ press("a", holdsteps=50, aftersteps=1)
+
+ @staticmethod
def walk_through_walls_slow():
memory = get_memory()
memory[0xC2FA] = 0
diff --git a/vba_autoplayer.py b/vba_autoplayer.py
new file mode 100644
index 0000000..eafbff1
--- /dev/null
+++ b/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()