From 51ff03e95298fbb42f860e1fb896ea881a71a0d2 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 21 Sep 2013 15:42:54 -0500 Subject: fix up some import lines --- pokemontools/vba/vba.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index d7fdf1d..9569bc2 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -12,9 +12,13 @@ from copy import copy import unittest # for converting bytes to readable text -from pokemontools.chars import chars +from pokemontools.chars import ( + chars, +) -from pokemontools.map_names import map_names +from pokemontools.map_names import ( + map_names, +) import keyboard -- cgit v1.2.3 From ef1709a8bed631e1b01cffb65fb651d8f60938c5 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 21 Sep 2013 15:47:39 -0500 Subject: remove the custom press() implementation This is now handled in vba_wrapper. --- pokemontools/vba/vba.py | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 9569bc2..dbbe3c3 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -46,27 +46,6 @@ def translate_chars(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. @@ -148,7 +127,7 @@ class crystal: # set CurSFX set_memory_at(0xc2bf, 0) - press("a", holdsteps=10, aftersteps=1) + vba.press("a", hold=10, after=1) # check if CurSFX is SFX_READ_TEXT_2 if get_memory_at(0xc2bf) == 0x8: @@ -415,9 +394,9 @@ class crystal: Applies a sequence of buttons to the on-screen keyboard. """ for buttons in button_sequence: - press(buttons) + vba.press(buttons) nstep(2) - press([]) + vba.press([]) @staticmethod def write(something="TrAiNeR"): @@ -493,8 +472,8 @@ class crystal: """ Attempt to move the player. """ - press(cmd, holdsteps=10, aftersteps=0) - press([]) + vba.press(cmd, hold=10, after=0) + vba.press([]) memory = get_memory() #while memory[0xd4e1] == 2 and memory[0xd042] != 0x3e: -- cgit v1.2.3 From 115ce6781e2750d3b326e6a994677c80d0613029 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 21 Sep 2013 15:52:44 -0500 Subject: use vba.step() instead of step() --- pokemontools/vba/vba.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index dbbe3c3..b2c164d 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -162,7 +162,7 @@ class crystal: break else: - nstep(step_size) + vba.step(count=step_size) # if there is a callback, then call the callback and exit when the # callback returns True. This is especially useful during the @@ -223,7 +223,7 @@ class crystal: for step_counter in range(0, steplimit): crystal.walk_through_walls() #call(0x1, 0x1078) - step() + vba.step() @staticmethod def disable_triggers(): @@ -395,7 +395,7 @@ class crystal: """ for buttons in button_sequence: vba.press(buttons) - nstep(2) + vba.step(count=2) vba.press([]) @staticmethod @@ -479,7 +479,7 @@ class crystal: #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) + vba.step(count=10) memory = get_memory() class TestEmulator(unittest.TestCase): @@ -492,7 +492,7 @@ class TestEmulator(unittest.TestCase): # what text to read from registers["de"] = 0x1276 - nstep(10) + vba.step(count=10) text = crystal.get_text() -- cgit v1.2.3 From 48896b14568aac35955baee6d5f47a5701f7825b Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 21 Sep 2013 15:53:34 -0500 Subject: replace one more step() with vba.step() --- pokemontools/vba/vba.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index b2c164d..814c853 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -459,7 +459,7 @@ class crystal: print "script is done executing" return else: - step() + vba.step() if debug: limit = limit - 1 -- cgit v1.2.3 From 645cce62c8e7ea3784c7f493f7ad6413bcd64ba8 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 21 Sep 2013 15:55:37 -0500 Subject: set_memory_at -> vba.write_memory_at The set_memory_at function was moved into vba_wrapper. There's no reason for that one to be defined in pokemontools. --- pokemontools/vba/vba.py | 62 ++++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 814c853..75b3aad 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -64,8 +64,8 @@ def call(bank, address): for value in push: registers.sp -= 2 - set_memory_at(registers.sp + 1, value >> 8) - set_memory_at(registers.sp, value & 0xFF) + vba.write_memory_at(registers.sp + 1, value >> 8) + vba.write_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)) @@ -125,7 +125,7 @@ class crystal: print "pressing, then breaking.. address is: " + str(hex(address)) # set CurSFX - set_memory_at(0xc2bf, 0) + vba.write_memory_at(0xc2bf, 0) vba.press("a", hold=10, after=1) @@ -204,14 +204,14 @@ class crystal: 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) + vba.write_memory_at(0xC2FA, 0) + vba.write_memory_at(0xC2FB, 0) + vba.write_memory_at(0xC2FC, 0) + vba.write_memory_at(0xC2FD, 0) #@staticmethod #def set_enemy_level(level): - # set_memory_at(0xd213, level) + # vba.write_memory_at(0xd213, level) @staticmethod def nstep(steplimit=500): @@ -227,13 +227,13 @@ class crystal: @staticmethod def disable_triggers(): - set_memory_at(0x23c4, 0xAF) - set_memory_at(0x23d0, 0xAF); + vba.write_memory_at(0x23c4, 0xAF) + vba.write_memory_at(0x23d0, 0xAF); @staticmethod def disable_callbacks(): - set_memory_at(0x23f2, 0xAF) - set_memory_at(0x23fe, 0xAF) + vba.write_memory_at(0x23f2, 0xAF) + vba.write_memory_at(0x23fe, 0xAF) @staticmethod def get_map_group_id(): @@ -279,7 +279,7 @@ class crystal: This probably works on other menus. """ - set_memory_at(0xcfa9, id) + vba.write_memory_at(0xcfa9, id) @staticmethod def is_in_battle(): @@ -300,10 +300,10 @@ class crystal: 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) + vba.write_memory_at(0xDCA5, 0xFF) + vba.write_memory_at(0xDCA6, 0xFF) + vba.write_memory_at(0xDCA7, 0xFF) + vba.write_memory_at(0xDCA8, 0xFF) @staticmethod def get_gender(): @@ -329,14 +329,14 @@ class crystal: @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) + vba.write_memory_at(0xdcb5, map_group_id) + vba.write_memory_at(0xdcb6, map_id) + vba.write_memory_at(0xdcb7, y) + vba.write_memory_at(0xdcb8, x) + vba.write_memory_at(0xd001, 0xFF) + vba.write_memory_at(0xff9f, 0xF1) + vba.write_memory_at(0xd432, 1) + vba.write_memory_at(0xd434, 0 & 251) @staticmethod def warp_pokecenter(): @@ -346,16 +346,16 @@ class crystal: @staticmethod def masterballs(): # masterball - set_memory_at(0xd8d8, 1) - set_memory_at(0xd8d9, 99) + vba.write_memory_at(0xd8d8, 1) + vba.write_memory_at(0xd8d9, 99) # ultraball - set_memory_at(0xd8da, 2) - set_memory_at(0xd8db, 99) + vba.write_memory_at(0xd8da, 2) + vba.write_memory_at(0xd8db, 99) # pokeballs - set_memory_at(0xd8dc, 5) - set_memory_at(0xd8dd, 99) + vba.write_memory_at(0xd8dc, 5) + vba.write_memory_at(0xd8dd, 99) @staticmethod def get_text(): -- cgit v1.2.3 From fecf601fd9fa912bcd9c374eb7db8901f9e6e791 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 21 Sep 2013 15:57:24 -0500 Subject: get_memory_at -> vba.read_memory_at --- pokemontools/vba/vba.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 75b3aad..e0ebe61 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -69,7 +69,7 @@ def call(bank, address): 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)) + print "wrong value at " + hex(registers.sp) + " expected " + hex(value) + " but got " + hex(vba.read_memory_at(registers.sp)) if bank != 0: registers["af"] = (bank << 8) | (registers["af"] & 0xFF) @@ -87,8 +87,8 @@ def get_stack(): for x in range(0, 11): sp = sp - (2 * x) - hi = get_memory_at(sp + 1) - lo = get_memory_at(sp) + hi = vba.read_memory_at(sp + 1) + lo = vba.read_memory_at(sp) address = ((hi << 8) | lo) addresses.append(address) @@ -117,8 +117,8 @@ class crystal: :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) + hi = vba.read_memory_at(registers.sp + 1) + lo = vba.read_memory_at(registers.sp) address = ((hi << 8) | lo) if address in range(0xa1b, 0xa46) + range(0xaaf, 0xaf5): # 0xaef: @@ -130,7 +130,7 @@ class crystal: vba.press("a", hold=10, after=1) # check if CurSFX is SFX_READ_TEXT_2 - if get_memory_at(0xc2bf) == 0x8: + if vba.read_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: @@ -169,7 +169,7 @@ class crystal: # 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". + # "vba.read_memory_at(0xcfb1) != 0". if callback != None: result = callback() if result == True: @@ -240,14 +240,14 @@ class crystal: """ Returns the current map group. """ - return get_memory_at(0xdcb5) + return vba.read_memory_at(0xdcb5) @staticmethod def get_map_id(): """ Returns the map number of the current map. """ - return get_memory_at(0xdcb6) + return vba.read_memory_at(0xdcb6) @staticmethod def get_map_name(): @@ -265,8 +265,8 @@ class crystal: Relative to top-left corner of map. """ - x = get_memory_at(0xdcb8) - y = get_memory_at(0xdcb7) + x = vba.read_memory_at(0xdcb8) + y = vba.read_memory_at(0xdcb7) return (x, y) @staticmethod @@ -286,11 +286,11 @@ class crystal: """ Checks whether or not we're in a battle. """ - return (get_memory_at(0xd22d) != 0) or crystal.is_in_link_battle() + return (vba.read_memory_at(0xd22d) != 0) or crystal.is_in_link_battle() @staticmethod def is_in_link_battle(): - return get_memory_at(0xc2dc) != 0 + return vba.read_memory_at(0xc2dc) != 0 @staticmethod def unlock_flypoints(): @@ -310,7 +310,7 @@ class crystal: """ Returns 'male' or 'female'. """ - gender = get_memory_at(0xD472) + gender = vba.read_memory_at(0xD472) if gender == 0: return "male" elif gender == 1: @@ -455,7 +455,7 @@ class crystal: Wait until ScriptRunning isn't -1. """ while limit > 0: - if get_memory_at(0xd438) != 255: + if vba.read_memory_at(0xd438) != 255: print "script is done executing" return else: -- cgit v1.2.3 From d29f193037cad147fe99aca6216a2e35346f5780 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 21 Sep 2013 16:02:10 -0500 Subject: placeholder for get_memory_range But really, the old calls to get_memory_range should just be replaced with code that uses vba.memory[:] directly. --- pokemontools/vba/vba.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index e0ebe61..e107d90 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -40,6 +40,13 @@ registers = vba_wrapper.core.registers.Registers(vba) button_masks = vba_wrapper.core.VBA.button_masks button_combiner = vba_wrapper.core.VBA.button_combine +def get_memory_range(address, length): + """ + This is just a lame way to avoid converting some of the old + get_memory_range calls to use the vba.memory property. + """ + return list(vba.memory[address:address+length]) + def translate_chars(charz): result = "" for each in charz: -- cgit v1.2.3 From 55a436a1445027f7bf512cbf2b54392bbc0f99e0 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 21 Sep 2013 16:07:51 -0500 Subject: fix some memory manipulation to use vba_wrapper --- pokemontools/vba/vba.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index e107d90..8fdff08 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -194,12 +194,12 @@ class crystal: @staticmethod def walk_through_walls_slow(): - memory = get_memory() + memory = vba.memory memory[0xC2FA] = 0 memory[0xC2FB] = 0 memory[0xC2FC] = 0 memory[0xC2FD] = 0 - set_memory(memory) + vba.memory = memory @staticmethod def walk_through_walls(): @@ -420,7 +420,7 @@ class crystal: """ This causes corruption, so it's not working yet. """ - memory = get_memory() + memory = vba.memory memory[0xdcd7] = 2 memory[0xdcd9] = 0x7 @@ -454,7 +454,7 @@ class crystal: memory[0xdd33] = 0x10 memory[0xdd34] = 0x40 - set_memory(memory) + vba.memory = memory @staticmethod def wait_for_script_running(debug=False, limit=1000): @@ -482,12 +482,12 @@ class crystal: vba.press(cmd, hold=10, after=0) vba.press([]) - memory = get_memory() + memory = vba.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: vba.step(count=10) - memory = get_memory() + memory = vba.memory class TestEmulator(unittest.TestCase): def test_PlaceString(self): -- cgit v1.2.3 From 7f5151989b504d9561f041aaacff9916a9c68568 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 21 Sep 2013 19:29:52 -0500 Subject: pass vba into get_memory_range --- pokemontools/vba/vba.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 8fdff08..b403236 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -40,7 +40,7 @@ registers = vba_wrapper.core.registers.Registers(vba) button_masks = vba_wrapper.core.VBA.button_masks button_combiner = vba_wrapper.core.VBA.button_combine -def get_memory_range(address, length): +def get_memory_range(vba, address, length): """ This is just a lame way to avoid converting some of the old get_memory_range calls to use the vba.memory property. @@ -73,9 +73,9 @@ def call(bank, address): registers.sp -= 2 vba.write_memory_at(registers.sp + 1, value >> 8) vba.write_memory_at(registers.sp, value & 0xFF) - if get_memory_range(registers.sp, 2) != [value & 0xFF, value >> 8]: + if get_memory_range(vba, 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 "actual memory values: " + str(get_memory_range(vba, registers.sp, 2)) print "wrong value at " + hex(registers.sp) + " expected " + hex(value) + " but got " + hex(vba.read_memory_at(registers.sp)) if bank != 0: @@ -330,7 +330,7 @@ class crystal: """ Returns the 7 characters making up the player's name. """ - bytez = get_memory_range(0xD47D, 7) + bytez = get_memory_range(vba, 0xD47D, 7) name = translate_chars(bytez) return name @@ -372,7 +372,7 @@ class crystal: Other characters will not be shown. """ output = "" - tiles = get_memory_range(0xc4a0, 1000) + tiles = get_memory_range(vba, 0xc4a0, 1000) for each in tiles: if each in chars.keys(): thing = chars[each] -- cgit v1.2.3 From 975eb021754c3a1ad6a5aa447358212564382351 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 21 Sep 2013 19:31:44 -0500 Subject: pass vba and registers into call() --- pokemontools/vba/vba.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index b403236..f6a0f7b 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -53,7 +53,7 @@ def translate_chars(charz): result += chars[each] return result -def call(bank, address): +def call(bank, address, vba=vba, registers=registers): """ Jumps into a function at a certain address. @@ -491,7 +491,7 @@ class crystal: class TestEmulator(unittest.TestCase): def test_PlaceString(self): - call(0, 0x1078) + call(vba, 0, 0x1078) # where to draw the text registers["hl"] = 0xc4a0 -- cgit v1.2.3 From fc3b2ded897e9bb01180a2f5ea91b6f3ca037c09 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 21 Sep 2013 19:32:15 -0500 Subject: pass vba and registers into get_stack --- pokemontools/vba/vba.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index f6a0f7b..eafbaa1 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -85,7 +85,7 @@ def call(bank, address, vba=vba, registers=registers): else: registers["pc"] = address -def get_stack(): +def get_stack(vba=vba, registers=registers): """ Return a list of functions on the stack. """ -- cgit v1.2.3 From 2f9df7133f39b286afe222f635be32c0699c87bf Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 21 Sep 2013 19:34:46 -0500 Subject: move get_stack into vba.crystal --- pokemontools/vba/vba.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index eafbaa1..a4c7e27 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -85,26 +85,27 @@ def call(bank, address, vba=vba, registers=registers): else: registers["pc"] = address -def get_stack(vba=vba, registers=registers): +class crystal: """ - Return a list of functions on the stack. + Just a simple namespace to store a bunch of functions for Pokémon Crystal. """ - addresses = [] - sp = registers.sp - for x in range(0, 11): - sp = sp - (2 * x) - hi = vba.read_memory_at(sp + 1) - lo = vba.read_memory_at(sp) - address = ((hi << 8) | lo) - addresses.append(address) + @staticmethod + def get_stack(vba=vba, registers=registers): + """ + Return a list of functions on the stack. + """ + addresses = [] + sp = registers.sp - return addresses + for x in range(0, 11): + sp = sp - (2 * x) + hi = vba.read_memory_at(sp + 1) + lo = vba.read_memory_at(sp) + address = ((hi << 8) | lo) + addresses.append(address) -class crystal: - """ - Just a simple namespace to store a bunch of functions for Pokémon Crystal. - """ + return addresses @staticmethod def text_wait(step_size=1, max_wait=200, sfx_limit=0, debug=False, callback=None): @@ -150,7 +151,7 @@ class crystal: break else: - stack = get_stack() + stack = crystal.get_stack() # yes/no box or the name selection box if address in range(0xa46, 0xaaf): -- cgit v1.2.3 From b1c34e88d51a0d1f45659c75920a58e997346e54 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 21 Sep 2013 19:41:41 -0500 Subject: move call into vba.crystal --- pokemontools/vba/vba.py | 67 +++++++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 33 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index a4c7e27..e206d8a 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -53,43 +53,44 @@ def translate_chars(charz): result += chars[each] return result -def call(bank, address, vba=vba, registers=registers): - """ - 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 - vba.write_memory_at(registers.sp + 1, value >> 8) - vba.write_memory_at(registers.sp, value & 0xFF) - if get_memory_range(vba, registers.sp, 2) != [value & 0xFF, value >> 8]: - print "desired memory values: " + str([value & 0xFF, value >> 8] ) - print "actual memory values: " + str(get_memory_range(vba, registers.sp, 2)) - print "wrong value at " + hex(registers.sp) + " expected " + hex(value) + " but got " + hex(vba.read_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 - class crystal: """ Just a simple namespace to store a bunch of functions for Pokémon Crystal. """ + @staticmethod + def call(bank, address, vba=vba, registers=registers): + """ + 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 + vba.write_memory_at(registers.sp + 1, value >> 8) + vba.write_memory_at(registers.sp, value & 0xFF) + if get_memory_range(vba, registers.sp, 2) != [value & 0xFF, value >> 8]: + print "desired memory values: " + str([value & 0xFF, value >> 8] ) + print "actual memory values: " + str(get_memory_range(vba, registers.sp, 2)) + print "wrong value at " + hex(registers.sp) + " expected " + hex(value) + " but got " + hex(vba.read_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 + @staticmethod def get_stack(vba=vba, registers=registers): """ @@ -492,7 +493,7 @@ class crystal: class TestEmulator(unittest.TestCase): def test_PlaceString(self): - call(vba, 0, 0x1078) + crystal.call(0, 0x1078) # where to draw the text registers["hl"] = 0xc4a0 -- cgit v1.2.3 From 8ca4bcf37e5183455c9cc23e385597b92b4e6d29 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 21 Sep 2013 19:45:35 -0500 Subject: remove get_memory_range --- pokemontools/vba/vba.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index e206d8a..7d6a588 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -40,13 +40,6 @@ registers = vba_wrapper.core.registers.Registers(vba) button_masks = vba_wrapper.core.VBA.button_masks button_combiner = vba_wrapper.core.VBA.button_combine -def get_memory_range(vba, address, length): - """ - This is just a lame way to avoid converting some of the old - get_memory_range calls to use the vba.memory property. - """ - return list(vba.memory[address:address+length]) - def translate_chars(charz): result = "" for each in charz: @@ -79,9 +72,9 @@ class crystal: registers.sp -= 2 vba.write_memory_at(registers.sp + 1, value >> 8) vba.write_memory_at(registers.sp, value & 0xFF) - if get_memory_range(vba, registers.sp, 2) != [value & 0xFF, value >> 8]: + if list(vba.memory[registers.sp : registers.sp + 2]) != [value & 0xFF, value >> 8]: print "desired memory values: " + str([value & 0xFF, value >> 8] ) - print "actual memory values: " + str(get_memory_range(vba, registers.sp, 2)) + print "actual memory values: " + str(list(vba.memory[registers.sp : registers.sp + 2])) print "wrong value at " + hex(registers.sp) + " expected " + hex(value) + " but got " + hex(vba.read_memory_at(registers.sp)) if bank != 0: @@ -332,7 +325,7 @@ class crystal: """ Returns the 7 characters making up the player's name. """ - bytez = get_memory_range(vba, 0xD47D, 7) + bytez = vba.memory[0xD47D:0xD47D + 7] name = translate_chars(bytez) return name @@ -374,7 +367,7 @@ class crystal: Other characters will not be shown. """ output = "" - tiles = get_memory_range(vba, 0xc4a0, 1000) + tiles = vba.memory[0xc4a0:0xc4a0 + 1000] for each in tiles: if each in chars.keys(): thing = chars[each] -- cgit v1.2.3 From 7fdf53f162dc8027619803b36d4447cdc2f294bd Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 21 Sep 2013 21:24:38 -0500 Subject: switch vba.crystal to have instance methods --- pokemontools/vba/vba.py | 279 ++++++++++++++++++++++-------------------------- 1 file changed, 128 insertions(+), 151 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 7d6a588..7e3f0a7 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -23,20 +23,10 @@ from pokemontools.map_names import ( 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 pokemontools.configuration as configuration 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 @@ -46,13 +36,26 @@ def translate_chars(charz): result += chars[each] return result -class crystal: +class crystal(object): """ Just a simple namespace to store a bunch of functions for Pokémon Crystal. + There can only be one running instance of the emulator per process because + it's a poorly written shared library. """ - @staticmethod - def call(bank, address, vba=vba, registers=registers): + def __init__(self): + """ + Launch the VBA controller. + """ + self.config = configuration.Config() + + self.vba = vba_wrapper.VBA(self.config.rom_path) + self.registers = vba_wrapper.core.registers.Registers(self.vba) + + if not os.path.exists(rom_path): + raise Exception("rom_path is not configured properly; edit vba_config.py? " + str(rom_path)) + + def call(self, bank, address): """ Jumps into a function at a certain address. @@ -60,49 +63,47 @@ class crystal: string printed to the screen. """ push = [ - registers.pc, - registers.hl, - registers.de, - registers.bc, - registers.af, + self.registers.pc, + self.registers.hl, + self.registers.de, + self.registers.bc, + self.registers.af, 0x3bb7, ] for value in push: - registers.sp -= 2 - vba.write_memory_at(registers.sp + 1, value >> 8) - vba.write_memory_at(registers.sp, value & 0xFF) - if list(vba.memory[registers.sp : registers.sp + 2]) != [value & 0xFF, value >> 8]: + self.registers.sp -= 2 + self.vba.write_memory_at(registers.sp + 1, value >> 8) + self.vba.write_memory_at(registers.sp, value & 0xFF) + if list(self.vba.memory[self.registers.sp : self.registers.sp + 2]) != [value & 0xFF, value >> 8]: print "desired memory values: " + str([value & 0xFF, value >> 8] ) - print "actual memory values: " + str(list(vba.memory[registers.sp : registers.sp + 2])) - print "wrong value at " + hex(registers.sp) + " expected " + hex(value) + " but got " + hex(vba.read_memory_at(registers.sp)) + print "actual memory values: " + str(list(self.vba.memory[self.registers.sp : self.registers.sp + 2])) + print "wrong value at " + hex(self.registers.sp) + " expected " + hex(value) + " but got " + hex(self.vba.read_memory_at(self.registers.sp)) if bank != 0: - registers["af"] = (bank << 8) | (registers["af"] & 0xFF) - registers["hl"] = address - registers["pc"] = 0x2d63 # FarJump + self.registers["af"] = (bank << 8) | (self.registers["af"] & 0xFF) + self.registers["hl"] = address + self.registers["pc"] = 0x2d63 # FarJump else: - registers["pc"] = address + self.registers["pc"] = address - @staticmethod - def get_stack(vba=vba, registers=registers): + def get_stack(self): """ Return a list of functions on the stack. """ addresses = [] - sp = registers.sp + sp = self.registers.sp for x in range(0, 11): sp = sp - (2 * x) - hi = vba.read_memory_at(sp + 1) - lo = vba.read_memory_at(sp) + hi = self.vba.read_memory_at(sp + 1) + lo = self.vba.read_memory_at(sp) address = ((hi << 8) | lo) addresses.append(address) return addresses - @staticmethod - def text_wait(step_size=1, max_wait=200, sfx_limit=0, debug=False, callback=None): + def text_wait(self, 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. @@ -119,22 +120,22 @@ class crystal: :param max_wait: number of wait loops to perform """ while max_wait > 0: - hi = vba.read_memory_at(registers.sp + 1) - lo = vba.read_memory_at(registers.sp) + hi = self.vba.read_memory_at(registers.sp + 1) + lo = self.vba.read_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 - vba.write_memory_at(0xc2bf, 0) + self.vba.write_memory_at(0xc2bf, 0) - vba.press("a", hold=10, after=1) + self.vba.press("a", hold=10, after=1) # check if CurSFX is SFX_READ_TEXT_2 - if vba.read_memory_at(0xc2bf) == 0x8: + if self.vba.read_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) + return self.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 @@ -145,7 +146,7 @@ class crystal: break else: - stack = crystal.get_stack() + stack = self.get_stack() # yes/no box or the name selection box if address in range(0xa46, 0xaaf): @@ -164,7 +165,7 @@ class crystal: break else: - vba.step(count=step_size) + self.vba.step(count=step_size) # if there is a callback, then call the callback and exit when the # callback returns True. This is especially useful during the @@ -187,17 +188,15 @@ class crystal: if max_wait == 0: print "max_wait was hit" - @staticmethod - def walk_through_walls_slow(): - memory = vba.memory + def walk_through_walls_slow(self): + memory = self.vba.memory memory[0xC2FA] = 0 memory[0xC2FB] = 0 memory[0xC2FC] = 0 memory[0xC2FD] = 0 - vba.memory = memory + self.vba.memory = memory - @staticmethod - def walk_through_walls(): + def walk_through_walls(self): """ Lets the player walk all over the map. @@ -206,73 +205,65 @@ class crystal: to be executed each step/tick if continuous walk-through-walls is desired. """ - vba.write_memory_at(0xC2FA, 0) - vba.write_memory_at(0xC2FB, 0) - vba.write_memory_at(0xC2FC, 0) - vba.write_memory_at(0xC2FD, 0) + self.vba.write_memory_at(0xC2FA, 0) + self.vba.write_memory_at(0xC2FB, 0) + self.vba.write_memory_at(0xC2FC, 0) + self.vba.write_memory_at(0xC2FD, 0) #@staticmethod #def set_enemy_level(level): # vba.write_memory_at(0xd213, level) - @staticmethod - def nstep(steplimit=500): + def nstep(self, 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() + self.walk_through_walls() #call(0x1, 0x1078) - vba.step() + self.vba.step() - @staticmethod - def disable_triggers(): - vba.write_memory_at(0x23c4, 0xAF) - vba.write_memory_at(0x23d0, 0xAF); + def disable_triggers(self): + self.vba.write_memory_at(0x23c4, 0xAF) + self.vba.write_memory_at(0x23d0, 0xAF); - @staticmethod - def disable_callbacks(): - vba.write_memory_at(0x23f2, 0xAF) - vba.write_memory_at(0x23fe, 0xAF) + def disable_callbacks(self): + self.vba.write_memory_at(0x23f2, 0xAF) + self.vba.write_memory_at(0x23fe, 0xAF) - @staticmethod - def get_map_group_id(): + def get_map_group_id(self): """ Returns the current map group. """ - return vba.read_memory_at(0xdcb5) + return self.vba.read_memory_at(0xdcb5) - @staticmethod - def get_map_id(): + def get_map_id(self): """ Returns the map number of the current map. """ - return vba.read_memory_at(0xdcb6) + return self.vba.read_memory_at(0xdcb6) - @staticmethod - def get_map_name(): + def get_map_name(self, map_names=map_names): """ Figures out the current map name. """ - map_group_id = crystal.get_map_group_id() - map_id = crystal.get_map_id() + map_group_id = self.get_map_group_id() + map_id = self.get_map_id() return map_names[map_group_id][map_id]["name"] - @staticmethod - def get_xy(): + def get_xy(self): """ (x, y) coordinates of player on map. Relative to top-left corner of map. """ - x = vba.read_memory_at(0xdcb8) - y = vba.read_memory_at(0xdcb7) + x = self.vba.read_memory_at(0xdcb8) + y = self.vba.read_memory_at(0xdcb7) return (x, y) - @staticmethod - def menu_select(id=1): + def menu_select(self, id=1): """ Sets the cursor to the given pokemon in the player's party. @@ -281,38 +272,34 @@ class crystal: This probably works on other menus. """ - vba.write_memory_at(0xcfa9, id) + self.vba.write_memory_at(0xcfa9, id) - @staticmethod - def is_in_battle(): + def is_in_battle(self): """ Checks whether or not we're in a battle. """ - return (vba.read_memory_at(0xd22d) != 0) or crystal.is_in_link_battle() + return (self.vba.read_memory_at(0xd22d) != 0) or self.is_in_link_battle() - @staticmethod - def is_in_link_battle(): - return vba.read_memory_at(0xc2dc) != 0 + def is_in_link_battle(self): + return self.vba.read_memory_at(0xc2dc) != 0 - @staticmethod - def unlock_flypoints(): + def unlock_flypoints(self): """ Unlocks different destinations for flying. Note: this might start at 0xDCA4 (minus one on all addresses), but not sure. """ - vba.write_memory_at(0xDCA5, 0xFF) - vba.write_memory_at(0xDCA6, 0xFF) - vba.write_memory_at(0xDCA7, 0xFF) - vba.write_memory_at(0xDCA8, 0xFF) + self.vba.write_memory_at(0xDCA5, 0xFF) + self.vba.write_memory_at(0xDCA6, 0xFF) + self.vba.write_memory_at(0xDCA7, 0xFF) + self.vba.write_memory_at(0xDCA8, 0xFF) - @staticmethod - def get_gender(): + def get_gender(self): """ Returns 'male' or 'female'. """ - gender = vba.read_memory_at(0xD472) + gender = self.vba.read_memory_at(0xD472) if gender == 0: return "male" elif gender == 1: @@ -320,54 +307,49 @@ class crystal: else: return gender - @staticmethod - def get_player_name(): + def get_player_name(self): """ Returns the 7 characters making up the player's name. """ - bytez = vba.memory[0xD47D:0xD47D + 7] + bytez = self.vba.memory[0xD47D:0xD47D + 7] name = translate_chars(bytez) return name - @staticmethod - def warp(map_group_id, map_id, x, y): - vba.write_memory_at(0xdcb5, map_group_id) - vba.write_memory_at(0xdcb6, map_id) - vba.write_memory_at(0xdcb7, y) - vba.write_memory_at(0xdcb8, x) - vba.write_memory_at(0xd001, 0xFF) - vba.write_memory_at(0xff9f, 0xF1) - vba.write_memory_at(0xd432, 1) - vba.write_memory_at(0xd434, 0 & 251) - - @staticmethod - def warp_pokecenter(): - crystal.warp(1, 1, 3, 3) - crystal.nstep(200) - - @staticmethod - def masterballs(): + def warp(self, map_group_id, map_id, x, y): + self.vba.write_memory_at(0xdcb5, map_group_id) + self.vba.write_memory_at(0xdcb6, map_id) + self.vba.write_memory_at(0xdcb7, y) + self.vba.write_memory_at(0xdcb8, x) + self.vba.write_memory_at(0xd001, 0xFF) + self.vba.write_memory_at(0xff9f, 0xF1) + self.vba.write_memory_at(0xd432, 1) + self.vba.write_memory_at(0xd434, 0 & 251) + + def warp_pokecenter(self): + self.warp(1, 1, 3, 3) + self.nstep(200) + + def masterballs(self): # masterball - vba.write_memory_at(0xd8d8, 1) - vba.write_memory_at(0xd8d9, 99) + self.vba.write_memory_at(0xd8d8, 1) + self.vba.write_memory_at(0xd8d9, 99) # ultraball - vba.write_memory_at(0xd8da, 2) - vba.write_memory_at(0xd8db, 99) + self.vba.write_memory_at(0xd8da, 2) + self.vba.write_memory_at(0xd8db, 99) # pokeballs - vba.write_memory_at(0xd8dc, 5) - vba.write_memory_at(0xd8dd, 99) + self.vba.write_memory_at(0xd8dc, 5) + self.vba.write_memory_at(0xd8dd, 99) - @staticmethod - def get_text(): + def get_text(self, chars=chars): """ Returns alphanumeric text on the screen. Other characters will not be shown. """ output = "" - tiles = vba.memory[0xc4a0:0xc4a0 + 1000] + tiles = self.vba.memory[0xc4a0:0xc4a0 + 1000] for each in tiles: if each in chars.keys(): thing = chars[each] @@ -390,32 +372,29 @@ class crystal: return output - @staticmethod - def keyboard_apply(button_sequence): + def keyboard_apply(self, button_sequence): """ Applies a sequence of buttons to the on-screen keyboard. """ for buttons in button_sequence: - vba.press(buttons) - vba.step(count=2) - vba.press([]) + self.vba.press(buttons) + self.vba.step(count=2) + self.vba.press([]) - @staticmethod - def write(something="TrAiNeR"): + def write(self, 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]) + self.keyboard_apply([[x] for x in button_sequence]) - @staticmethod - def set_partymon2(): + def set_partymon2(self): """ This causes corruption, so it's not working yet. """ - memory = vba.memory + memory = self.vba.memory memory[0xdcd7] = 2 memory[0xdcd9] = 0x7 @@ -449,19 +428,18 @@ class crystal: memory[0xdd33] = 0x10 memory[0xdd34] = 0x40 - vba.memory = memory + self.vba.memory = memory - @staticmethod - def wait_for_script_running(debug=False, limit=1000): + def wait_for_script_running(self, debug=False, limit=1000): """ Wait until ScriptRunning isn't -1. """ while limit > 0: - if vba.read_memory_at(0xd438) != 255: + if self.vba.read_memory_at(0xd438) != 255: print "script is done executing" return else: - vba.step() + self.vba.step() if debug: limit = limit - 1 @@ -469,20 +447,19 @@ class crystal: if limit == 0: print "limit ran out" - @staticmethod - def move(cmd): + def move(self, cmd): """ Attempt to move the player. """ - vba.press(cmd, hold=10, after=0) - vba.press([]) + self.vba.press(cmd, hold=10, after=0) + self.vba.press([]) - memory = vba.memory + memory = self.vba.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: - vba.step(count=10) - memory = vba.memory + self.vba.step(count=10) + memory = self.vba.memory class TestEmulator(unittest.TestCase): def test_PlaceString(self): -- cgit v1.2.3 From e3a596e7d8c9de64162da2e0a05d5b17942d4694 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 21 Sep 2013 22:12:37 -0500 Subject: fix a vba.py test (test_PlaceString) --- pokemontools/vba/vba.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 7e3f0a7..b775970 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -462,18 +462,21 @@ class crystal(object): memory = self.vba.memory class TestEmulator(unittest.TestCase): + def setUp(self): + self.cry = crystal() + def test_PlaceString(self): - crystal.call(0, 0x1078) + self.cry.call(0, 0x1078) # where to draw the text - registers["hl"] = 0xc4a0 + self.cry.registers["hl"] = 0xc4a0 # what text to read from - registers["de"] = 0x1276 + self.cry.registers["de"] = 0x1276 - vba.step(count=10) + self.cry.vba.step(count=10) - text = crystal.get_text() + text = self.cry.get_text() self.assertTrue("TRAINER" in text) -- cgit v1.2.3 From 2a439694d6af7416732b76fe37e8ea9fe0e9faff Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 21 Sep 2013 23:12:38 -0500 Subject: combine some vba tests --- pokemontools/vba/vba.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index b775970..a308ea8 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -52,7 +52,7 @@ class crystal(object): self.vba = vba_wrapper.VBA(self.config.rom_path) self.registers = vba_wrapper.core.registers.Registers(self.vba) - if not os.path.exists(rom_path): + if not os.path.exists(self.config.rom_path): raise Exception("rom_path is not configured properly; edit vba_config.py? " + str(rom_path)) def call(self, bank, address): @@ -73,8 +73,8 @@ class crystal(object): for value in push: self.registers.sp -= 2 - self.vba.write_memory_at(registers.sp + 1, value >> 8) - self.vba.write_memory_at(registers.sp, value & 0xFF) + self.vba.write_memory_at(self.registers.sp + 1, value >> 8) + self.vba.write_memory_at(self.registers.sp, value & 0xFF) if list(self.vba.memory[self.registers.sp : self.registers.sp + 2]) != [value & 0xFF, value >> 8]: print "desired memory values: " + str([value & 0xFF, value >> 8] ) print "actual memory values: " + str(list(self.vba.memory[self.registers.sp : self.registers.sp + 2])) @@ -462,8 +462,12 @@ class crystal(object): memory = self.vba.memory class TestEmulator(unittest.TestCase): - def setUp(self): - self.cry = crystal() + @classmethod + def setUpClass(cls): + cls.cry = crystal() + + # advance it forward past the intro sequences + cls.cry.vba.step(count=3500) def test_PlaceString(self): self.cry.call(0, 0x1078) @@ -480,8 +484,7 @@ class TestEmulator(unittest.TestCase): self.assertTrue("TRAINER" in text) -class TestWriter(unittest.TestCase): - def test_very_basic(self): + def test_keyboard_planner(self): button_sequence = keyboard.plan_typing("an") expected_result = ["select", "a", "d", "r", "r", "r", "r", "a"] -- cgit v1.2.3 From f0e75972a119812ec37ec27fcdcd00afc45edf98 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 21 Sep 2013 23:14:58 -0500 Subject: move tests into test_vba.py They didn't belong in pokemontools/vba/vba.py in the first place. --- pokemontools/vba/vba.py | 35 ----------------------------------- 1 file changed, 35 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index a308ea8..863e16c 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -9,8 +9,6 @@ import re import string from copy import copy -import unittest - # for converting bytes to readable text from pokemontools.chars import ( chars, @@ -460,36 +458,3 @@ class crystal(object): #while memory[0xd043] in [0, 1, 2, 3] or memory[0xd042] != 0x3e: self.vba.step(count=10) memory = self.vba.memory - -class TestEmulator(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls.cry = crystal() - - # advance it forward past the intro sequences - cls.cry.vba.step(count=3500) - - def test_PlaceString(self): - self.cry.call(0, 0x1078) - - # where to draw the text - self.cry.registers["hl"] = 0xc4a0 - - # what text to read from - self.cry.registers["de"] = 0x1276 - - self.cry.vba.step(count=10) - - text = self.cry.get_text() - - self.assertTrue("TRAINER" in text) - - def test_keyboard_planner(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() -- cgit v1.2.3 From d0c53213c416a1c77891fb10f11e6316817101e7 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 21 Sep 2013 23:54:55 -0500 Subject: make the vba autoplayer use the new methods --- pokemontools/vba/autoplayer.py | 742 +++++++++++++++++++++-------------------- 1 file changed, 378 insertions(+), 364 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/autoplayer.py b/pokemontools/vba/autoplayer.py index 9aa8f4a..a63caa8 100644 --- a/pokemontools/vba/autoplayer.py +++ b/pokemontools/vba/autoplayer.py @@ -5,27 +5,7 @@ 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) +import vba as _vba def skippable(func): """ @@ -51,445 +31,479 @@ def skippable(func): return_value = None if not skip: - vba.save_state(func.__name__ + "-start", override=True) + _vba.save_state(func.__name__ + "-start", override=True) return_value = func(*args, **kwargs) - vba.save_state(func.__name__ + "-end", override=True) + _vba.save_state(func.__name__ + "-end", override=True) elif skip: - vba.set_state(vba.load_state(func.__name__ + "-end")) + _vba.set_state(vba.load_state(func.__name__ + "-end")) return return_value return wrapped_function -@skippable -def skip_intro(): +class Runner(object): """ - Skip the game boot intro sequence. + ``Runner`` is used to represent a set of functions that control an instance + of the emulator. This allows for automated runs of games. """ + pass - # copyright sequence - vba.nstep(400) +class SpeedRunner(Runner): + def setup(self): + self.cry = _vba.crystal() - # skip the ditto sequence - vba.press("a") - vba.nstep(100) + def main(self): + """ + Start the game. + """ + # get past the opening sequence + self.skip_intro() - # skip the start screen - vba.press("start") - vba.nstep(100) + # walk to mom and handle her text + self.handle_mom() - # click "new game" - vba.press("a", holdsteps=50, aftersteps=1) + # walk outside into new bark town + self.walk_into_new_bark_town() - # skip text up to "Are you a boy? Or are you a girl?" - vba.crystal.text_wait() + # walk to elm and do whatever he wants + self.handle_elm("totodile") - # select "Boy" - vba.press("a", holdsteps=50, aftersteps=1) + self.new_bark_level_grind(10, skip=False) - # text until "What time is it?" - vba.crystal.text_wait() + @skippable + def skip_intro(self): + """ + Skip the game boot intro sequence. + """ - # select 10 o'clock - vba.press("a", holdsteps=50, aftersteps=1) + # copyright sequence + self.cry.nstep(400) - # yes i mean it - vba.press("a", holdsteps=50, aftersteps=1) + # skip the ditto sequence + self.cry.press("a") + self.cry.nstep(100) - # "How many minutes?" 0 min. - vba.press("a", holdsteps=50, aftersteps=1) + # skip the start screen + self.cry.press("start") + self.cry.nstep(100) - # "Who! 0 min.?" yes/no select yes - vba.press("a", holdsteps=50, aftersteps=1) + # click "new game" + self.cry.press("a", holdsteps=50, aftersteps=1) - # read text until name selection - vba.crystal.text_wait() + # skip text up to "Are you a boy? Or are you a girl?" + self.cry.text_wait() - # select "Chris" - vba.press("d", holdsteps=10, aftersteps=1) - vba.press("a", holdsteps=50, aftersteps=1) + # select "Boy" + self.cry.press("a", holdsteps=50, aftersteps=1) - def overworldcheck(): - """ - A basic check for when the game starts. - """ - return vba.get_memory_at(0xcfb1) != 0 + # text until "What time is it?" + self.cry.text_wait() - # go until the introduction is done - vba.crystal.text_wait(callback=overworldcheck) + # select 10 o'clock + self.cry.press("a", holdsteps=50, aftersteps=1) - return + # yes i mean it + self.cry.press("a", holdsteps=50, aftersteps=1) -@skippable -def handle_mom(): - """ - Walk to mom. Handle her speech and questions. - """ + # "How many minutes?" 0 min. + self.cry.press("a", holdsteps=50, aftersteps=1) - vba.crystal.move("r") - vba.crystal.move("r") - vba.crystal.move("r") - vba.crystal.move("r") + # "Who! 0 min.?" yes/no select yes + self.cry.press("a", holdsteps=50, aftersteps=1) - vba.crystal.move("u") - vba.crystal.move("u") - vba.crystal.move("u") + # read text until name selection + self.cry.text_wait() - vba.crystal.move("d") - vba.crystal.move("d") + # select "Chris" + self.cry.press("d", holdsteps=10, aftersteps=1) + self.cry.press("a", holdsteps=50, aftersteps=1) - # move into mom's line of sight - vba.crystal.move("d") + def overworldcheck(): + """ + A basic check for when the game starts. + """ + return self.cry.vba.memory[0xcfb1] != 0 - # let mom talk until "What day is it?" - vba.crystal.text_wait() + # go until the introduction is done + self.cry.text_wait(callback=overworldcheck) - # "What day is it?" Sunday - vba.press("a", holdsteps=10) # Sunday + return - vba.crystal.text_wait() + @skippable + def handle_mom(self): + """ + Walk to mom. Handle her speech and questions. + """ - # "SUNDAY, is it?" yes/no - vba.press("a", holdsteps=10) # yes + self.cry.move("r") + self.cry.move("r") + self.cry.move("r") + self.cry.move("r") - vba.crystal.text_wait() + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") - # "Is it Daylight Saving Time now?" yes/no - vba.press("a", holdsteps=10) # yes + self.cry.move("d") + self.cry.move("d") - vba.crystal.text_wait() + # move into mom's line of sight + self.cry.move("d") - # "AM DST, is that OK?" yes/no - vba.press("a", holdsteps=10) # yes + # let mom talk until "What day is it?" + self.cry.text_wait() - # text until "know how to use the PHONE?" yes/no - vba.crystal.text_wait() + # "What day is it?" Sunday + self.cry.vba.press("a", holdsteps=10) # Sunday - # press yes - vba.press("a", holdsteps=10) + self.cry.text_wait() - # wait until mom is done talking - vba.crystal.text_wait() + # "SUNDAY, is it?" yes/no + self.cry.vba.press("a", holdsteps=10) # yes - # wait until the script is done running - vba.crystal.wait_for_script_running() + self.cry.text_wait() - return + # "Is it Daylight Saving Time now?" yes/no + self.cry.vba.press("a", holdsteps=10) # yes -@skippable -def walk_into_new_bark_town(): - """ - Walk outside after talking with mom. - """ + self.cry.text_wait() - vba.crystal.move("d") - vba.crystal.move("d") - vba.crystal.move("d") - vba.crystal.move("l") - vba.crystal.move("l") + # "AM DST, is that OK?" yes/no + self.cry.vba.press("a", holdsteps=10) # yes - # walk outside - vba.crystal.move("d") + # text until "know how to use the PHONE?" yes/no + self.cry.text_wait() -@skippable -def handle_elm(starter_choice): - """ - Walk to Elm's Lab and get a starter. - """ + # press yes + self.cry.vba.press("a", holdsteps=10) - # 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") + # wait until mom is done talking + self.cry.text_wait() - # walk into the lab - vba.crystal.move("u") + # wait until the script is done running + self.cry.wait_for_script_running() - # talk to elm - vba.crystal.text_wait() + return - # "that I recently caught." yes/no - vba.press("a", holdsteps=10) # yes + @skippable + def walk_into_new_bark_town(self): + """ + Walk outside after talking with mom. + """ - # talk to elm some more - vba.crystal.text_wait() + self.cry.move("d") + self.cry.move("d") + self.cry.move("d") + self.cry.move("l") + self.cry.move("l") - # talking isn't done yet.. - vba.crystal.text_wait() - vba.crystal.text_wait() - vba.crystal.text_wait() + # walk outside + self.cry.move("d") - # wait until the script is done running - vba.crystal.wait_for_script_running() + @skippable + def handle_elm(self, starter_choice): + """ + Walk to Elm's Lab and get a starter. + """ + + # walk to the lab + self.cry.move("l") + self.cry.move("l") + self.cry.move("l") + self.cry.move("l") + self.cry.move("l") + self.cry.move("l") + self.cry.move("l") + self.cry.move("u") + self.cry.move("u") + + # walk into the lab + self.cry.move("u") + + # talk to elm + self.cry.text_wait() + + # "that I recently caught." yes/no + self.cry.vba.press("a", holdsteps=10) # yes + + # talk to elm some more + self.cry.text_wait() + + # talking isn't done yet.. + self.cry.text_wait() + self.cry.text_wait() + self.cry.text_wait() - # move toward the pokeballs - vba.crystal.move("r") + # wait until the script is done running + self.cry.wait_for_script_running() - # move to cyndaquil - vba.crystal.move("r") + # move toward the pokeballs + self.cry.move("r") - moves = 0 + # move to cyndaquil + self.cry.move("r") - 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") + if starter_choice.lower() == "cyndaquil": + moves = 0 + if starter_choice.lower() == "totodile": + moves = 1 + else: + moves = 2 - # face the pokeball - vba.crystal.move("u") + for each in range(0, moves): + self.cry.move("r") - # select it - vba.press("a", holdsteps=10, aftersteps=0) + # face the pokeball + self.cry.move("u") - # wait for the image to pop up - vba.crystal.text_wait() + # select it + self.cry.vba.press("a", holdsteps=10, aftersteps=0) - # wait for the image to close - vba.crystal.text_wait() + # wait for the image to pop up + self.cry.text_wait() - # wait for the yes/no box - vba.crystal.text_wait() + # wait for the image to close + self.cry.text_wait() - # press yes - vba.press("a", holdsteps=10, aftersteps=0) + # wait for the yes/no box + self.cry.text_wait() - # wait for elm to talk a bit - vba.crystal.text_wait() + # press yes + self.cry.vba.press("a", holdsteps=10, aftersteps=0) - # TODO: why didn't that finish his talking? - vba.crystal.text_wait() + # wait for elm to talk a bit + self.cry.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 that finish his talking? + self.cry.text_wait() - # TODO: why didn't this wait until he was completely done? - vba.crystal.text_wait() - vba.crystal.text_wait() + # give a nickname? yes/no + self.cry.vba.press("d", holdsteps=10, aftersteps=0) # move to "no" + self.cry.vba.press("a", holdsteps=10, aftersteps=0) # no - # get the phone number - vba.crystal.text_wait() + # TODO: why didn't this wait until he was completely done? + self.cry.text_wait() + self.cry.text_wait() - # talk with elm a bit more - vba.crystal.text_wait() + # get the phone number + self.cry.text_wait() - # TODO: and again.. wtf? - vba.crystal.text_wait() + # talk with elm a bit more + self.cry.text_wait() - # wait until the script is done running - vba.crystal.wait_for_script_running() + # TODO: and again.. wtf? + self.cry.text_wait() - # move down - vba.crystal.move("d") - vba.crystal.move("d") - vba.crystal.move("d") - vba.crystal.move("d") + # wait until the script is done running + self.cry.wait_for_script_running() - # move into the researcher's line of sight - vba.crystal.move("d") + # move down + self.cry.move("d") + self.cry.move("d") + self.cry.move("d") + self.cry.move("d") - # get the potion from the person - vba.crystal.text_wait() - vba.crystal.text_wait() + # move into the researcher's line of sight + self.cry.move("d") - # wait for the script to end - vba.crystal.wait_for_script_running() + # get the potion from the person + self.cry.text_wait() + self.cry.text_wait() - vba.crystal.move("d") - vba.crystal.move("d") - vba.crystal.move("d") + # wait for the script to end + self.cry.wait_for_script_running() - # go outside - vba.crystal.move("d") + self.cry.move("d") + self.cry.move("d") + self.cry.move("d") - return + # go outside + self.cry.move("d") -@skippable -def new_bark_level_grind(level): - """ - Do level grinding in New Bark. + return - Starting just outside of Elm's Lab, do some level grinding until the first - partymon level is equal to the given value.. - """ + @skippable + def new_bark_level_grind(self, level): + """ + Do level grinding in New Bark. - # walk to the grass area - new_bark_level_grind_walk_to_grass(skip=False) + Starting just outside of Elm's Lab, do some level grinding until the + first partymon level is equal to the given value.. + """ - # TODO: walk around in grass, handle battles - walk = ["d", "d", "u", "d", "u", "d"] - for direction in walk: - vba.crystal.move(direction) + # walk to the grass area + self.new_bark_level_grind_walk_to_grass(skip=False) - # wait for wild battle to completely start - vba.crystal.text_wait() + # TODO: walk around in grass, handle battles + walk = ["d", "d", "u", "d", "u", "d"] + for direction in walk: + self.cry.move(direction) - attacks = 5 + # wait for wild battle to completely start + self.cry.text_wait() - while attacks > 0: - # FIGHT - vba.press("a", holdsteps=10, aftersteps=1) + attacks = 5 - # wait to select a move - vba.crystal.text_wait() + while attacks > 0: + # FIGHT + self.cry.vba.press("a", holdsteps=10, aftersteps=1) - # SCRATCH - vba.press("a", holdsteps=10, aftersteps=1) + # wait to select a move + self.cry.text_wait() - # wait for the move to be over - vba.crystal.text_wait() + # SCRATCH + self.cry.vba.press("a", holdsteps=10, aftersteps=1) - hp = ((vba.get_memory_at(0xd218) << 8) | vba.get_memory_at(0xd217)) - print "enemy hp is: " + str(hp) + # wait for the move to be over + self.cry.text_wait() - if hp == 0: - print "enemy hp is zero, exiting" - break - else: + hp = ((self.cry.vba.get_memory_at(0xd218) << 8) | self.cry.vba.get_memory_at(0xd217)) 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 + if hp == 0: + print "enemy hp is zero, exiting" + break + else: + print "enemy hp is: " + str(hp) + + attacks = attacks - 1 + + while self.cry.vba.get_memory_at(0xd22d) != 0: + self.cry.vba.press("a", holdsteps=10, aftersteps=1) + + # wait for the map to finish loading + self.cry.vba.nstep(50) + + print "okay, back in the overworld" + + # move up + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") + + # move into new bark town + self.cry.move("r") + self.cry.move("r") + self.cry.move("r") + self.cry.move("r") + self.cry.move("r") + self.cry.move("r") + self.cry.move("r") + self.cry.move("r") + self.cry.move("r") + self.cry.move("r") + + # move up + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") + + # move to the door + self.cry.move("r") + self.cry.move("r") + self.cry.move("r") + + # walk in + self.cry.move("u") + + # move up to the healing thing + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") + self.cry.move("u") + self.cry.move("l") + self.cry.move("l") + + # face it + self.cry.move("u") + + # interact + self.cry.vba.press("a", holdsteps=10, aftersteps=1) + + # wait for yes/no box + self.cry.text_wait() + + # press yes + self.cry.vba.press("a", holdsteps=10, aftersteps=1) + + # TODO: when is healing done? + + # wait until the script is done running + self.cry.wait_for_script_running() + + # wait for it to be really really done + self.cry.vba.nstep(50) + + self.cry.move("r") + self.cry.move("r") + + # move to the door + self.cry.move("d") + self.cry.move("d") + self.cry.move("d") + self.cry.move("d") + self.cry.move("d") + self.cry.move("d") + self.cry.move("d") + self.cry.move("d") + self.cry.move("d") + + # walk out + self.cry.move("d") + + # check partymon1 level + if self.cry.vba.get_memory_at(0xdcfe) < level: + self.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. - """ + @skippable + def new_bark_level_grind_walk_to_grass(self): + """ + Move to just above the grass from outside Elm's lab. + """ + + self.cry.move("d") + self.cry.move("d") - 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") + self.cry.move("l") + self.cry.move("l") + + self.cry.move("d") + self.cry.move("d") + + self.cry.move("l") + self.cry.move("l") + + # move to route 29 past the trees + self.cry.move("l") + self.cry.move("l") + self.cry.move("l") + self.cry.move("l") + self.cry.move("l") + self.cry.move("l") + self.cry.move("l") + self.cry.move("l") + self.cry.move("l") + + # move to just above the grass + self.cry.move("d") + self.cry.move("d") + self.cry.move("d") + +def main(): + runner = SpeedRunner() + runner.setup() + return runner.main() if __name__ == "__main__": main() -- cgit v1.2.3 From c44ab9b5565a46004b17d87fdfefcd515b0adbf9 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 22 Sep 2013 00:28:43 -0500 Subject: make the skippable decorator use config Use the pokemontools configuration to determine where to save the save states. --- pokemontools/vba/autoplayer.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/autoplayer.py b/pokemontools/vba/autoplayer.py index a63caa8..ee1a0c7 100644 --- a/pokemontools/vba/autoplayer.py +++ b/pokemontools/vba/autoplayer.py @@ -4,6 +4,8 @@ Programmatic speedrun of Pokémon Crystal """ import os +import pokemontools.configuration as configuration + # bring in the emulator and basic tools import vba as _vba @@ -11,11 +13,11 @@ 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. + 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): + self = args[0] skip = True if "skip" in kwargs.keys(): @@ -25,17 +27,17 @@ def skippable(func): # 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)): + if not os.path.exists(os.path.join(self.config.save_state_path, full_name)): skip = False return_value = None if not skip: - _vba.save_state(func.__name__ + "-start", override=True) + self.cry.save_state(func.__name__ + "-start", override=True) return_value = func(*args, **kwargs) - _vba.save_state(func.__name__ + "-end", override=True) + self.cry.save_state(func.__name__ + "-end", override=True) elif skip: - _vba.set_state(vba.load_state(func.__name__ + "-end")) + self.cry.vba.set_state(self.cry.vba.load_state(func.__name__ + "-end")) return return_value return wrapped_function @@ -48,8 +50,17 @@ class Runner(object): pass class SpeedRunner(Runner): + def __init__(self, cry=None, config=None): + self.cry = cry + + if not config: + config = configuration.Config() + + self.config = config + def setup(self): - self.cry = _vba.crystal() + if not self.cry: + self.cry = _vba.crystal(config=config) def main(self): """ -- cgit v1.2.3 From 6b56969ccf00262187071e95568cee89f637692b Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 22 Sep 2013 00:44:35 -0500 Subject: make vba.crystal accept config --- pokemontools/vba/vba.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 863e16c..d821999 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -41,11 +41,14 @@ class crystal(object): it's a poorly written shared library. """ - def __init__(self): + def __init__(self, config=None): """ Launch the VBA controller. """ - self.config = configuration.Config() + if not config: + config = configuration.Config() + + self.config = config self.vba = vba_wrapper.VBA(self.config.rom_path) self.registers = vba_wrapper.core.registers.Registers(self.vba) -- cgit v1.2.3 From dc63d8d51dd451b13fb5e1386edfc7bac0874ee6 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 22 Sep 2013 00:49:44 -0500 Subject: re-implement save_state This can be used to dump state to a file based on the current configuration of the running instance. --- pokemontools/vba/vba.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index d821999..d8ceebb 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -56,6 +56,27 @@ class crystal(object): if not os.path.exists(self.config.rom_path): raise Exception("rom_path is not configured properly; edit vba_config.py? " + str(rom_path)) + def save_state(self, name, state=None, override=False): + """ + Saves the given state to save_state_path. + + The file format must be ".sav", and this will be appended to your + string if necessary. + """ + if state == None: + state = self.vba.state + + if len(name) < 4 or name[-4:] != ".sav": + name += ".sav" + + save_path = os.path.join(self.config.save_state_path, name) + + if not override and os.path.exists(save_path): + raise Exception("oops, save state path already exists: {0}".format(save_path)) + + with open(save_path, "wb") as file_handler: + file_handler.write(state) + def call(self, bank, address): """ Jumps into a function at a certain address. -- cgit v1.2.3 From 6568c299fd59b72bf6bcb66df23b3631e2e0a88e Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 22 Sep 2013 00:59:19 -0500 Subject: fix how autoplayer calls hold/press on buttons --- pokemontools/vba/autoplayer.py | 50 +++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 25 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/autoplayer.py b/pokemontools/vba/autoplayer.py index ee1a0c7..a143f1e 100644 --- a/pokemontools/vba/autoplayer.py +++ b/pokemontools/vba/autoplayer.py @@ -90,43 +90,43 @@ class SpeedRunner(Runner): self.cry.nstep(400) # skip the ditto sequence - self.cry.press("a") + self.cry.vba.press("a") self.cry.nstep(100) # skip the start screen - self.cry.press("start") + self.cry.vba.press("start") self.cry.nstep(100) # click "new game" - self.cry.press("a", holdsteps=50, aftersteps=1) + self.cry.vba.press("a", hold=50, after=1) # skip text up to "Are you a boy? Or are you a girl?" self.cry.text_wait() # select "Boy" - self.cry.press("a", holdsteps=50, aftersteps=1) + self.cry.vba.press("a", hold=50, after=1) # text until "What time is it?" self.cry.text_wait() # select 10 o'clock - self.cry.press("a", holdsteps=50, aftersteps=1) + self.cry.vba.press("a", hold=50, after=1) # yes i mean it - self.cry.press("a", holdsteps=50, aftersteps=1) + self.cry.vba.press("a", hold=50, after=1) # "How many minutes?" 0 min. - self.cry.press("a", holdsteps=50, aftersteps=1) + self.cry.vba.press("a", hold=50, after=1) # "Who! 0 min.?" yes/no select yes - self.cry.press("a", holdsteps=50, aftersteps=1) + self.cry.vba.press("a", hold=50, after=1) # read text until name selection self.cry.text_wait() # select "Chris" - self.cry.press("d", holdsteps=10, aftersteps=1) - self.cry.press("a", holdsteps=50, aftersteps=1) + self.cry.vba.press("d", hold=10, after=1) + self.cry.vba.press("a", hold=50, after=1) def overworldcheck(): """ @@ -164,28 +164,28 @@ class SpeedRunner(Runner): self.cry.text_wait() # "What day is it?" Sunday - self.cry.vba.press("a", holdsteps=10) # Sunday + self.cry.vba.press("a", hold=10) # Sunday self.cry.text_wait() # "SUNDAY, is it?" yes/no - self.cry.vba.press("a", holdsteps=10) # yes + self.cry.vba.press("a", hold=10) # yes self.cry.text_wait() # "Is it Daylight Saving Time now?" yes/no - self.cry.vba.press("a", holdsteps=10) # yes + self.cry.vba.press("a", hold=10) # yes self.cry.text_wait() # "AM DST, is that OK?" yes/no - self.cry.vba.press("a", holdsteps=10) # yes + self.cry.vba.press("a", hold=10) # yes # text until "know how to use the PHONE?" yes/no self.cry.text_wait() # press yes - self.cry.vba.press("a", holdsteps=10) + self.cry.vba.press("a", hold=10) # wait until mom is done talking self.cry.text_wait() @@ -234,7 +234,7 @@ class SpeedRunner(Runner): self.cry.text_wait() # "that I recently caught." yes/no - self.cry.vba.press("a", holdsteps=10) # yes + self.cry.vba.press("a", hold=10) # yes # talk to elm some more self.cry.text_wait() @@ -269,7 +269,7 @@ class SpeedRunner(Runner): self.cry.move("u") # select it - self.cry.vba.press("a", holdsteps=10, aftersteps=0) + self.cry.vba.press("a", hold=10, after=0) # wait for the image to pop up self.cry.text_wait() @@ -281,7 +281,7 @@ class SpeedRunner(Runner): self.cry.text_wait() # press yes - self.cry.vba.press("a", holdsteps=10, aftersteps=0) + self.cry.vba.press("a", hold=10, after=0) # wait for elm to talk a bit self.cry.text_wait() @@ -290,8 +290,8 @@ class SpeedRunner(Runner): self.cry.text_wait() # give a nickname? yes/no - self.cry.vba.press("d", holdsteps=10, aftersteps=0) # move to "no" - self.cry.vba.press("a", holdsteps=10, aftersteps=0) # no + self.cry.vba.press("d", hold=10, after=0) # move to "no" + self.cry.vba.press("a", hold=10, after=0) # no # TODO: why didn't this wait until he was completely done? self.cry.text_wait() @@ -358,13 +358,13 @@ class SpeedRunner(Runner): while attacks > 0: # FIGHT - self.cry.vba.press("a", holdsteps=10, aftersteps=1) + self.cry.vba.press("a", hold=10, after=1) # wait to select a move self.cry.text_wait() # SCRATCH - self.cry.vba.press("a", holdsteps=10, aftersteps=1) + self.cry.vba.press("a", hold=10, after=1) # wait for the move to be over self.cry.text_wait() @@ -381,7 +381,7 @@ class SpeedRunner(Runner): attacks = attacks - 1 while self.cry.vba.get_memory_at(0xd22d) != 0: - self.cry.vba.press("a", holdsteps=10, aftersteps=1) + self.cry.vba.press("a", hold=10, after=1) # wait for the map to finish loading self.cry.vba.nstep(50) @@ -438,13 +438,13 @@ class SpeedRunner(Runner): self.cry.move("u") # interact - self.cry.vba.press("a", holdsteps=10, aftersteps=1) + self.cry.vba.press("a", hold=10, after=1) # wait for yes/no box self.cry.text_wait() # press yes - self.cry.vba.press("a", holdsteps=10, aftersteps=1) + self.cry.vba.press("a", hold=10, after=1) # TODO: when is healing done? -- cgit v1.2.3 From 8e0a4f922906a118bc0a7bcc2e8680c6ba9060df Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 22 Sep 2013 01:01:03 -0500 Subject: use self.registers in text_wait --- pokemontools/vba/vba.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index d8ceebb..0818b93 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -142,8 +142,8 @@ class crystal(object): :param max_wait: number of wait loops to perform """ while max_wait > 0: - hi = self.vba.read_memory_at(registers.sp + 1) - lo = self.vba.read_memory_at(registers.sp) + hi = self.vba.read_memory_at(self.registers.sp + 1) + lo = self.vba.read_memory_at(self.registers.sp) address = ((hi << 8) | lo) if address in range(0xa1b, 0xa46) + range(0xaaf, 0xaf5): # 0xaef: -- cgit v1.2.3 From f83fc26b499a55d5611e16726a356981b682dd63 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 22 Sep 2013 01:04:16 -0500 Subject: fix skippable decorator emulator state setter --- pokemontools/vba/autoplayer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/autoplayer.py b/pokemontools/vba/autoplayer.py index a143f1e..13565d4 100644 --- a/pokemontools/vba/autoplayer.py +++ b/pokemontools/vba/autoplayer.py @@ -37,7 +37,7 @@ def skippable(func): return_value = func(*args, **kwargs) self.cry.save_state(func.__name__ + "-end", override=True) elif skip: - self.cry.vba.set_state(self.cry.vba.load_state(func.__name__ + "-end")) + self.cry.vba.state = self.cry.vba.load_state(func.__name__ + "-end") return return_value return wrapped_function -- cgit v1.2.3 From 7bfbadc5687a1eb297db0ff5956478a8bd38178f Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 22 Sep 2013 01:10:46 -0500 Subject: implement a vba helper func for state loading This re-implements the load_state method that previously existed. I forget why it was removed, but basically a similar function is needed again, and it doesn't entirely belong in the emulator or in the emulator wrapper because these save states are game-specific. --- pokemontools/vba/vba.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 0818b93..7dc0d8f 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -77,6 +77,30 @@ class crystal(object): with open(save_path, "wb") as file_handler: file_handler.write(state) + def load_state(self, name, loadit=True): + """ + Read a state from file based on the name of the state. + + Looks in save_state_path for a file with this name (".sav" is + optional). + + @param loadit: whether or not to set the emulator to this state + """ + save_path = os.path.join(self.config.save_state_path, name) + + if not os.path.exists(save_path): + if len(name) < 4 or name[-4:] != ".sav": + name += ".sav" + save_path = os.path.join(save_state_path, name) + + with open(save_path, "rb") as file_handler: + state = file_handler.read() + + if loadit: + self.vba.state = state + + return state + def call(self, bank, address): """ Jumps into a function at a certain address. -- cgit v1.2.3 From 81504f340187fe6e3fff7ed13d947216fe36030c Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 22 Sep 2013 01:11:53 -0500 Subject: load_state was called on the wrong object --- pokemontools/vba/autoplayer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/autoplayer.py b/pokemontools/vba/autoplayer.py index 13565d4..6755db9 100644 --- a/pokemontools/vba/autoplayer.py +++ b/pokemontools/vba/autoplayer.py @@ -37,7 +37,7 @@ def skippable(func): return_value = func(*args, **kwargs) self.cry.save_state(func.__name__ + "-end", override=True) elif skip: - self.cry.vba.state = self.cry.vba.load_state(func.__name__ + "-end") + self.cry.vba.state = self.cry.load_state(func.__name__ + "-end") return return_value return wrapped_function -- cgit v1.2.3 From bf20b9982b19535044a7d40848bd46a04731694e Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 22 Sep 2013 01:12:55 -0500 Subject: save_state_path is only on self.config --- pokemontools/vba/vba.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 7dc0d8f..1b33ba7 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -91,7 +91,7 @@ class crystal(object): if not os.path.exists(save_path): if len(name) < 4 or name[-4:] != ".sav": name += ".sav" - save_path = os.path.join(save_state_path, name) + save_path = os.path.join(self.config.save_state_path, name) with open(save_path, "rb") as file_handler: state = file_handler.read() -- cgit v1.2.3 From a3bab14b2657582675b2a94c99e55ae3148e93eb Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 22 Sep 2013 01:28:06 -0500 Subject: weird, why was there no shutdown command? --- pokemontools/vba/vba.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 1b33ba7..3c8c084 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -56,6 +56,12 @@ class crystal(object): if not os.path.exists(self.config.rom_path): raise Exception("rom_path is not configured properly; edit vba_config.py? " + str(rom_path)) + def shutdown(self): + """ + Reset the emulator. + """ + self.vba.shutdown() + def save_state(self, name, state=None, override=False): """ Saves the given state to save_state_path. -- cgit v1.2.3 From 912efe7fd0127a0abd5c92c51990d732b18d32a6 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 22 Sep 2013 02:12:11 -0500 Subject: make SpeedRunner.setup use the right config ref --- pokemontools/vba/autoplayer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/autoplayer.py b/pokemontools/vba/autoplayer.py index 6755db9..aa57825 100644 --- a/pokemontools/vba/autoplayer.py +++ b/pokemontools/vba/autoplayer.py @@ -60,7 +60,7 @@ class SpeedRunner(Runner): def setup(self): if not self.cry: - self.cry = _vba.crystal(config=config) + self.cry = _vba.crystal(config=self.config) def main(self): """ -- cgit v1.2.3 From a04f6ceb4b98d9b37390905b7ab60bd5e35fcc21 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 22 Sep 2013 02:46:40 -0500 Subject: fix cyndaquil selection in Elm's Lab --- pokemontools/vba/autoplayer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/autoplayer.py b/pokemontools/vba/autoplayer.py index aa57825..5bbf91e 100644 --- a/pokemontools/vba/autoplayer.py +++ b/pokemontools/vba/autoplayer.py @@ -257,7 +257,7 @@ class SpeedRunner(Runner): if starter_choice.lower() == "cyndaquil": moves = 0 - if starter_choice.lower() == "totodile": + elif starter_choice.lower() == "totodile": moves = 1 else: moves = 2 -- cgit v1.2.3 From 50997c8b3849d70483266dce2661e3f076558cc6 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 22 Sep 2013 02:48:26 -0500 Subject: get rid of a text_wait when talking to Elm --- pokemontools/vba/autoplayer.py | 3 --- 1 file changed, 3 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/autoplayer.py b/pokemontools/vba/autoplayer.py index 5bbf91e..744b9e3 100644 --- a/pokemontools/vba/autoplayer.py +++ b/pokemontools/vba/autoplayer.py @@ -303,9 +303,6 @@ class SpeedRunner(Runner): # talk with elm a bit more self.cry.text_wait() - # TODO: and again.. wtf? - self.cry.text_wait() - # wait until the script is done running self.cry.wait_for_script_running() -- cgit v1.2.3 From a4c418bc7a2b99ad3da7f421bba8cf7415f9f021 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 22 Sep 2013 10:50:21 -0500 Subject: fix some func calls in the auto level grinder --- pokemontools/vba/autoplayer.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/autoplayer.py b/pokemontools/vba/autoplayer.py index 744b9e3..5ee2ac1 100644 --- a/pokemontools/vba/autoplayer.py +++ b/pokemontools/vba/autoplayer.py @@ -366,7 +366,7 @@ class SpeedRunner(Runner): # wait for the move to be over self.cry.text_wait() - hp = ((self.cry.vba.get_memory_at(0xd218) << 8) | self.cry.vba.get_memory_at(0xd217)) + hp = ((self.cry.vba.memory[0xd218] << 8) | self.cry.vba.memory[0xd217]) print "enemy hp is: " + str(hp) if hp == 0: @@ -377,11 +377,11 @@ class SpeedRunner(Runner): attacks = attacks - 1 - while self.cry.vba.get_memory_at(0xd22d) != 0: + while self.cry.vba.memory[0xd22d] != 0: self.cry.vba.press("a", hold=10, after=1) # wait for the map to finish loading - self.cry.vba.nstep(50) + self.cry.vba.step(count=50) print "okay, back in the overworld" @@ -449,7 +449,7 @@ class SpeedRunner(Runner): self.cry.wait_for_script_running() # wait for it to be really really done - self.cry.vba.nstep(50) + self.cry.vba.step(count=50) self.cry.move("r") self.cry.move("r") @@ -469,7 +469,7 @@ class SpeedRunner(Runner): self.cry.move("d") # check partymon1 level - if self.cry.vba.get_memory_at(0xdcfe) < level: + if self.cry.vba.memory[0xdcfe] < level: self.new_bark_level_grind(level, skip=False) else: return -- cgit v1.2.3 From 6a1fc607718e70a5e8c50309c51d6b2e4bfd54a1 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 22 Sep 2013 10:55:44 -0500 Subject: better IsInBattle detection for level grinding --- pokemontools/vba/autoplayer.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/autoplayer.py b/pokemontools/vba/autoplayer.py index 5ee2ac1..987395a 100644 --- a/pokemontools/vba/autoplayer.py +++ b/pokemontools/vba/autoplayer.py @@ -343,11 +343,19 @@ class SpeedRunner(Runner): # walk to the grass area self.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: + last_direction = "u" + + # walk around in the grass until a battle happens + while self.cry.vba.memory[0xd22d] == 0: + if last_direction == "u": + direction = "d" + else: + direction = "u" + self.cry.move(direction) + last_direction = direction + # wait for wild battle to completely start self.cry.text_wait() -- cgit v1.2.3 From d0b0b9c5f2e0d302b9150e6eeb5be9b2467fb792 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 22 Sep 2013 11:12:38 -0500 Subject: only heal if HP is low or move1 PP is low --- pokemontools/vba/autoplayer.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/autoplayer.py b/pokemontools/vba/autoplayer.py index 987395a..b47634e 100644 --- a/pokemontools/vba/autoplayer.py +++ b/pokemontools/vba/autoplayer.py @@ -332,7 +332,7 @@ class SpeedRunner(Runner): return @skippable - def new_bark_level_grind(self, level): + def new_bark_level_grind(self, level, walk_to_grass=True): """ Do level grinding in New Bark. @@ -341,7 +341,8 @@ class SpeedRunner(Runner): """ # walk to the grass area - self.new_bark_level_grind_walk_to_grass(skip=False) + if walk_to_grass: + self.new_bark_level_grind_walk_to_grass(skip=False) last_direction = "u" @@ -391,8 +392,24 @@ class SpeedRunner(Runner): # wait for the map to finish loading self.cry.vba.step(count=50) + # This is used to handle any additional textbox that might be up on the + # screen. The debug parameter is set to True so that max_wait is + # enabled. This might be a textbox that is still waiting around because + # of some faint during the battle. I am not completely sure why this + # happens. + self.cry.text_wait(max_wait=30, debug=True) + print "okay, back in the overworld" + cur_hp = ((self.cry.vba.memory[0xdd01] << 8) | self.cry.vba.memory[0xdd02]) + move_pp = self.cry.vba.memory[0xdcf6] # move 1 pp + + # if pokemon health is >20, just continue + # if move 1 PP is 0, just continue + if cur_hp > 20 and move_pp > 5: + self.cry.move("u") + return self.new_bark_level_grind(level, walk_to_grass=False, skip=False) + # move up self.cry.move("u") self.cry.move("u") -- cgit v1.2.3 From 54579ad69962d62a7af874635ff4e3e5fb4738b7 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 22 Sep 2013 11:38:35 -0500 Subject: make skippable decorator not always save state There are some runs where the "skippable" decorator should not save the state of the game before and after, like if the function is given different parameters and the after state should not be the canonical after state. --- pokemontools/vba/autoplayer.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/autoplayer.py b/pokemontools/vba/autoplayer.py index b47634e..c1e36bc 100644 --- a/pokemontools/vba/autoplayer.py +++ b/pokemontools/vba/autoplayer.py @@ -19,11 +19,16 @@ def skippable(func): def wrapped_function(*args, **kwargs): self = args[0] skip = True + override = True if "skip" in kwargs.keys(): skip = kwargs["skip"] del kwargs["skip"] + if "override" in kwargs.keys(): + override = kwargs["override"] + del kwargs["override"] + # override skip if there's no save if skip: full_name = func.__name__ + "-end.sav" @@ -33,9 +38,13 @@ def skippable(func): return_value = None if not skip: - self.cry.save_state(func.__name__ + "-start", override=True) + if override: + self.cry.save_state(func.__name__ + "-start", override=override) + return_value = func(*args, **kwargs) - self.cry.save_state(func.__name__ + "-end", override=True) + + if override: + self.cry.save_state(func.__name__ + "-end", override=override) elif skip: self.cry.vba.state = self.cry.load_state(func.__name__ + "-end") @@ -81,7 +90,7 @@ class SpeedRunner(Runner): self.new_bark_level_grind(10, skip=False) @skippable - def skip_intro(self): + def skip_intro(self, stop_at_name_selection=False): """ Skip the game boot intro sequence. """ @@ -124,6 +133,9 @@ class SpeedRunner(Runner): # read text until name selection self.cry.text_wait() + if stop_at_name_selection: + return + # select "Chris" self.cry.vba.press("d", hold=10, after=1) self.cry.vba.press("a", hold=50, after=1) -- cgit v1.2.3 From 6e4c7d5a0f1e5d416b204e83348df3a619631d8f Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 22 Sep 2013 12:14:49 -0500 Subject: pause a few frames before typing on a keyboard For names that weren't starting with a capletter, the "select" button to switch to downcase was happening too soon. So add in a small delay to get the keyboard writing to work. --- pokemontools/vba/vba.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 3c8c084..654292e 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -430,7 +430,12 @@ class crystal(object): """ for buttons in button_sequence: self.vba.press(buttons) - self.vba.step(count=2) + + if buttons == "select": + self.vba.step(count=5) + else: + self.vba.step(count=2) + self.vba.press([]) def write(self, something="TrAiNeR"): @@ -440,7 +445,9 @@ class crystal(object): Uses a planning algorithm to do this in the most efficient way possible. """ button_sequence = keyboard.plan_typing(something) + self.vba.step(count=10) self.keyboard_apply([[x] for x in button_sequence]) + return button_sequence def set_partymon2(self): """ -- cgit v1.2.3 From 0b7ed30a33fb9775b2b5e1f3245a2e28608c9fc6 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 22 Sep 2013 12:42:02 -0500 Subject: go heal if level reached the target --- pokemontools/vba/autoplayer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/autoplayer.py b/pokemontools/vba/autoplayer.py index c1e36bc..0049021 100644 --- a/pokemontools/vba/autoplayer.py +++ b/pokemontools/vba/autoplayer.py @@ -418,7 +418,7 @@ class SpeedRunner(Runner): # if pokemon health is >20, just continue # if move 1 PP is 0, just continue - if cur_hp > 20 and move_pp > 5: + if cur_hp > 20 and move_pp > 5 and self.cry.vba.memory[0xdcfe] < level: self.cry.move("u") return self.new_bark_level_grind(level, walk_to_grass=False, skip=False) -- cgit v1.2.3 From 3c4207d777a31914eeb76fb19e9ddd4ac34575f0 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 22 Sep 2013 17:26:56 -0500 Subject: basic level-up stats screen detection --- pokemontools/vba/vba.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 654292e..ce4bef2 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -424,6 +424,39 @@ class crystal(object): return output + def is_showing_stats_screen(self): + """ + This is meant to detect whether or not the stats screen is showing. + This is the menu that pops up after leveling up. + """ + # These words must be on the screen if the stats screen is currently + # displayed. + parts = [ + "ATTACK", + "DEFENSE", + "SPCL.ATK", + "SPCL.DEF", + "SPEED", + ] + + # get the current text on the screen + text = self.get_text() + + if all([part in text for part in parts]): + return True + else: + return False + + def handle_stats_screen(self, force=False): + """ + Attempts to bypass a stats screen. Set force=True if you want to make + the attempt regardless of whether or not the system thinks a stats + screen is showing. + """ + if self.is_showing_stats_screen() or force: + self.vba.press("a") + self.vba.step(count=20) + def keyboard_apply(self, button_sequence): """ Applies a sequence of buttons to the on-screen keyboard. -- cgit v1.2.3 From 38ecfa8705fb6140a00988ef8fc88272974cbe36 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 22 Sep 2013 17:27:35 -0500 Subject: use explicit skips in vba autoplayer --- pokemontools/vba/autoplayer.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/autoplayer.py b/pokemontools/vba/autoplayer.py index 0049021..479924c 100644 --- a/pokemontools/vba/autoplayer.py +++ b/pokemontools/vba/autoplayer.py @@ -76,18 +76,18 @@ class SpeedRunner(Runner): Start the game. """ # get past the opening sequence - self.skip_intro() + self.skip_intro(skip=True) # walk to mom and handle her text - self.handle_mom() + self.handle_mom(skip=True) # walk outside into new bark town - self.walk_into_new_bark_town() + self.walk_into_new_bark_town(skip=True) # walk to elm and do whatever he wants - self.handle_elm("totodile") + self.handle_elm("totodile", skip=True) - self.new_bark_level_grind(10, skip=False) + self.new_bark_level_grind(6, skip=False) @skippable def skip_intro(self, stop_at_name_selection=False): -- cgit v1.2.3 From 27ea6f652796a177ea4b102dbea504a459e11cf5 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 26 Sep 2013 01:03:58 -0500 Subject: grind to a higher level --- pokemontools/vba/autoplayer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/autoplayer.py b/pokemontools/vba/autoplayer.py index 479924c..6aa9494 100644 --- a/pokemontools/vba/autoplayer.py +++ b/pokemontools/vba/autoplayer.py @@ -87,7 +87,7 @@ class SpeedRunner(Runner): # walk to elm and do whatever he wants self.handle_elm("totodile", skip=True) - self.new_bark_level_grind(6, skip=False) + self.new_bark_level_grind(17, skip=False) @skippable def skip_intro(self, stop_at_name_selection=False): -- cgit v1.2.3 From c2d13dab14153c405657471d44e49d83f36e00e6 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 12 Oct 2013 16:17:57 -0500 Subject: write get_enemy_hp to calculate current hp --- pokemontools/vba/vba.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index ce4bef2..433df40 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -552,3 +552,10 @@ class crystal(object): #while memory[0xd043] in [0, 1, 2, 3] or memory[0xd042] != 0x3e: self.vba.step(count=10) memory = self.vba.memory + + def get_enemy_hp(self): + """ + Returns the HP of the current enemy. + """ + hp = ((self.cry.vba.memory[0xd218] << 8) | self.cry.vba.memory[0xd217]) + return hp -- cgit v1.2.3 From de5a585e5e1f1829ee26bca6e5b7d8aa9dceee25 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 12 Oct 2013 16:18:52 -0500 Subject: oops, made a mistake in get_enemy_hp --- pokemontools/vba/vba.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 433df40..007c132 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -557,5 +557,5 @@ class crystal(object): """ Returns the HP of the current enemy. """ - hp = ((self.cry.vba.memory[0xd218] << 8) | self.cry.vba.memory[0xd217]) + hp = ((self.vba.memory[0xd218] << 8) | self.vba.memory[0xd217]) return hp -- cgit v1.2.3 From 80fe5be90664faf03992c160055b9fa061eb74af Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 12 Oct 2013 16:22:53 -0500 Subject: use get_enemy_hp instead of a custom check --- pokemontools/vba/autoplayer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/autoplayer.py b/pokemontools/vba/autoplayer.py index 6aa9494..847ebbd 100644 --- a/pokemontools/vba/autoplayer.py +++ b/pokemontools/vba/autoplayer.py @@ -387,7 +387,7 @@ class SpeedRunner(Runner): # wait for the move to be over self.cry.text_wait() - hp = ((self.cry.vba.memory[0xd218] << 8) | self.cry.vba.memory[0xd217]) + hp = self.cry.get_enemy_hp() print "enemy hp is: " + str(hp) if hp == 0: -- cgit v1.2.3 From 2328b2bd3addc7034e2a7d14dc39e39b7627df82 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 12 Oct 2013 16:25:39 -0500 Subject: call super __init__() in SpeedRunner --- pokemontools/vba/autoplayer.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/autoplayer.py b/pokemontools/vba/autoplayer.py index 847ebbd..428b85d 100644 --- a/pokemontools/vba/autoplayer.py +++ b/pokemontools/vba/autoplayer.py @@ -60,6 +60,8 @@ class Runner(object): class SpeedRunner(Runner): def __init__(self, cry=None, config=None): + super(SpeedRunner, self).__init__() + self.cry = cry if not config: -- cgit v1.2.3 From 819d16726ec2e4cbe04c6803539b4c13014717b5 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 12 Oct 2013 16:29:03 -0500 Subject: improve some VBA-related docstrings --- pokemontools/vba/autoplayer.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/autoplayer.py b/pokemontools/vba/autoplayer.py index 428b85d..2d62433 100644 --- a/pokemontools/vba/autoplayer.py +++ b/pokemontools/vba/autoplayer.py @@ -70,12 +70,16 @@ class SpeedRunner(Runner): self.config = config def setup(self): + """ + Configure this ``Runner`` instance to contain a reference to an active + emulator session. + """ if not self.cry: self.cry = _vba.crystal(config=self.config) def main(self): """ - Start the game. + Main entry point for complete control of the game as the main player. """ # get past the opening sequence self.skip_intro(skip=True) @@ -548,6 +552,9 @@ class SpeedRunner(Runner): self.cry.move("d") def main(): + """ + Setup a basic ``SpeedRunner`` instance and then run the runner. + """ runner = SpeedRunner() runner.setup() return runner.main() -- cgit v1.2.3 From 49c51e03d059bb5a93b4dcd494bd145f5f96428a Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 12 Oct 2013 16:31:05 -0500 Subject: even more docstrings --- pokemontools/vba/vba.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 007c132..e30616f 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -368,6 +368,9 @@ class crystal(object): return name def warp(self, map_group_id, map_id, x, y): + """ + Warp into another map. + """ self.vba.write_memory_at(0xdcb5, map_group_id) self.vba.write_memory_at(0xdcb6, map_id) self.vba.write_memory_at(0xdcb7, y) @@ -378,10 +381,17 @@ class crystal(object): self.vba.write_memory_at(0xd434, 0 & 251) def warp_pokecenter(self): + """ + Warp straight into a pokecenter. + """ self.warp(1, 1, 3, 3) self.nstep(200) def masterballs(self): + """ + Deposit some pokeballs into the first few slots of the pack. This + overrides whatever items were previously there. + """ # masterball self.vba.write_memory_at(0xd8d8, 1) self.vba.write_memory_at(0xd8d9, 99) -- cgit v1.2.3 From e13cdeab1104260e2dcfd04aa9dbaa5688f357d0 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 12 Oct 2013 16:33:52 -0500 Subject: another minor docstring --- pokemontools/vba/vba.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index e30616f..81b27a3 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -29,6 +29,10 @@ button_masks = vba_wrapper.core.VBA.button_masks button_combiner = vba_wrapper.core.VBA.button_combine def translate_chars(charz): + """ + Translate a string from the in-game format to readable form. This is + accomplished through the same lookup table that the preprocessors use. + """ result = "" for each in charz: result += chars[each] -- cgit v1.2.3 From 5fdd27030e0268b6fa494b9219d3a1ac8c2a35cb Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 12 Oct 2013 16:41:50 -0500 Subject: move() can now take a list of movements to make --- pokemontools/vba/vba.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 81b27a3..81ffe4e 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -557,15 +557,19 @@ class crystal(object): """ Attempt to move the player. """ - self.vba.press(cmd, hold=10, after=0) - self.vba.press([]) + if isinstance(cmd, list): + for command in cmd: + self.move(cmd) + else: + self.vba.press(cmd, hold=10, after=0) + self.vba.press([]) - memory = self.vba.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: - self.vba.step(count=10) memory = self.vba.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: + self.vba.step(count=10) + memory = self.vba.memory def get_enemy_hp(self): """ -- cgit v1.2.3 From 0283856044c98792fd62db6672f01f81876d5e26 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 2 Nov 2013 00:05:57 -0500 Subject: a basic battle handling framework --- pokemontools/vba/battle.py | 105 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 pokemontools/vba/battle.py (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py new file mode 100644 index 0000000..3d4bb55 --- /dev/null +++ b/pokemontools/vba/battle.py @@ -0,0 +1,105 @@ +""" +Code that attempts to model a battle. +""" + +from pokemontools.vba.vba import crystal as emulator + +class BattleException(Exception): + """ + Something went terribly wrong in a battle. + """ + +class EmulatorController(object): + """ + Controls the emulator. I don't have a good reason for this. + """ + +class Battle(EmulatorController): + """ + Wrapper around the battle routine inside of the game. This object controls + the emulator and provides a sanitized interface for interacting with a + battle through python. + """ + + # Maybe this should be an instance variable instead, but since there can + # only be one emulator per instance (??) it doesn't really matter right + # now. + emulator = emulator + + def __init__(self, hook): + """ + Setup the battle. + + @param hook: object that implements handle_turn and handle_mandatory_switch + @type hook: BattleHook + """ + self.hook = hook + + @classmethod + def is_in_battle(cls): + """ + @rtype: bool + """ + return cls.emulator.is_in_battle() + + def run(self): + """ + Step through the entire battle. + """ + # xyz wants to battle + self.skip_start_text() + + while self.is_in_battle(): + self.skip_crap() + + if self.is_player_turn(): + self.hook.handle_turn() + elif self.is_mandatory_switch(): + self.hook.handle_mandatory_switch() + else: + raise BattleException("unknown state, aborting") + + # "how did i lose? wah" + self.skip_end_text() + +class BattleHook(object): + """ + Hooks that are called during a battle. + """ + + def __init__(self, battle): + """ + Makes references to some common objects. + """ + self.battle = battle + self.emulator = battle.emulator + + def handle_mandatory_switch(self): + """ + Something fainted, pick the next mon. + """ + for pokemon in self.emulator.party: + if pokemon.hp > 0: + break + else: + # the game failed to do a blackout.. not sure what to do now. + raise BattleException("No partymons left. wtf?") + + return pokemon.id + + def handle_turn(self): + """ + Take actions inside of a battle based on the game state. + """ + self.battle.throw_pokeball() + +class SimpleBattleHook(BattleHook): + """ + Attack the enemy with the first move. + """ + + def handle_turn(self): + """ + Always attack the enemy with the first move. + """ + self.battle.attack(self.battle.party[0].moves[0].name) -- cgit v1.2.3 From 9eb78d9c5a4e3c63960c7fb990df2c5bf859f591 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 2 Nov 2013 00:14:37 -0500 Subject: simplify the number of battle-related classes --- pokemontools/vba/battle.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index 3d4bb55..9546378 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -26,14 +26,11 @@ class Battle(EmulatorController): # now. emulator = emulator - def __init__(self, hook): + def __init__(self, emulator=emulator): """ Setup the battle. - - @param hook: object that implements handle_turn and handle_mandatory_switch - @type hook: BattleHook """ - self.hook = hook + self.emulator = emulator @classmethod def is_in_battle(cls): @@ -62,17 +59,22 @@ class Battle(EmulatorController): # "how did i lose? wah" self.skip_end_text() -class BattleHook(object): - """ - Hooks that are called during a battle. - """ + def handle_mandatory_switch(self): + """ + Something fainted, pick the next mon. + """ + raise NotImplementedError - def __init__(self, battle): + def handle_turn(self): """ - Makes references to some common objects. + Take actions inside of a battle based on the game state. """ - self.battle = battle - self.emulator = battle.emulator + raise NotImplementedError + +class BattleStrategy(Battle): + """ + Throw a pokeball until everyone dies. + """ def handle_mandatory_switch(self): """ @@ -93,7 +95,7 @@ class BattleHook(object): """ self.battle.throw_pokeball() -class SimpleBattleHook(BattleHook): +class SimpleBattleStrategy(BattleStrategy): """ Attack the enemy with the first move. """ @@ -102,4 +104,4 @@ class SimpleBattleHook(BattleHook): """ Always attack the enemy with the first move. """ - self.battle.attack(self.battle.party[0].moves[0].name) + self.attack(self.battle.party[0].moves[0].name) -- cgit v1.2.3 From 016d0b886444f864cf494c8d91f9de883ec9a57d Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 2 Nov 2013 13:54:35 -0500 Subject: fix some hook calls in Battle.run --- pokemontools/vba/battle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index 9546378..8897ccb 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -50,9 +50,9 @@ class Battle(EmulatorController): self.skip_crap() if self.is_player_turn(): - self.hook.handle_turn() + self.handle_turn() elif self.is_mandatory_switch(): - self.hook.handle_mandatory_switch() + self.handle_mandatory_switch() else: raise BattleException("unknown state, aborting") -- cgit v1.2.3 From 1f185185ca15389417474f6becfddde56e8e80eb Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 2 Nov 2013 14:00:04 -0500 Subject: autoplayer.bootstrap to call skip_intro There are situations other than just testing where making a bootstrapped game state is a useful ability. --- pokemontools/vba/autoplayer.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/autoplayer.py b/pokemontools/vba/autoplayer.py index 2d62433..a20744e 100644 --- a/pokemontools/vba/autoplayer.py +++ b/pokemontools/vba/autoplayer.py @@ -551,6 +551,26 @@ class SpeedRunner(Runner): self.cry.move("d") self.cry.move("d") +def bootstrap(runner=None, cry=None): + """ + Setup the initial game and return the state. This skips the intro and + performs some other actions to get the game to a reasonable starting state. + """ + if not runner: + runner = SpeedRunner(cry=cry) + runner.setup() + + # skip=False means always run the skip_intro function regardless of the + # presence of a saved after state. + runner.skip_intro(skip=True) + + # keep a reference of the current state + state = runner.cry.vba.state + + cry.vba.shutdown() + + return state + def main(): """ Setup a basic ``SpeedRunner`` instance and then run the runner. -- cgit v1.2.3 From 185856adb4a10c66a1fc206c13862ffa6958cec6 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 2 Nov 2013 14:01:01 -0500 Subject: fix a call to vba.shutdown in bootstrap() --- pokemontools/vba/autoplayer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/autoplayer.py b/pokemontools/vba/autoplayer.py index a20744e..af14d47 100644 --- a/pokemontools/vba/autoplayer.py +++ b/pokemontools/vba/autoplayer.py @@ -567,7 +567,7 @@ def bootstrap(runner=None, cry=None): # keep a reference of the current state state = runner.cry.vba.state - cry.vba.shutdown() + runner.cry.vba.shutdown() return state -- cgit v1.2.3 From 211da4dcb3ac2d8ca370d6b840e2368f59b5114d Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 2 Nov 2013 14:41:48 -0500 Subject: attempt to start a trainer battle --- pokemontools/vba/vba.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 81ffe4e..1af2364 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -577,3 +577,17 @@ class crystal(object): """ hp = ((self.vba.memory[0xd218] << 8) | self.vba.memory[0xd217]) return hp + + def start_trainer_battle(self, trainer_group, trainer_id): + memory = self.vba.memory + + # setup the battle + memory[0xd459] = 0x81 + memory[0xd22f] = trainer_group + memory[0xd231] = trainer_id + + self.vba.memory = memory + + Script_startbattle_address = 0x97436 + + self.call(0x25, Script_startbattle_address) -- cgit v1.2.3 From 0d4f9340b08766ae3a445b1428cbfe4b4a33031a Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 2 Nov 2013 20:56:40 -0500 Subject: a naive implementation of start_trainer_battle This is a really dumb way to start a battle, but the other methods aren't working yet. --- pokemontools/vba/vba.py | 91 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 76 insertions(+), 15 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 1af2364..bb468b8 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -127,14 +127,7 @@ class crystal(object): 0x3bb7, ] - for value in push: - self.registers.sp -= 2 - self.vba.write_memory_at(self.registers.sp + 1, value >> 8) - self.vba.write_memory_at(self.registers.sp, value & 0xFF) - if list(self.vba.memory[self.registers.sp : self.registers.sp + 2]) != [value & 0xFF, value >> 8]: - print "desired memory values: " + str([value & 0xFF, value >> 8] ) - print "actual memory values: " + str(list(self.vba.memory[self.registers.sp : self.registers.sp + 2])) - print "wrong value at " + hex(self.registers.sp) + " expected " + hex(value) + " but got " + hex(self.vba.read_memory_at(self.registers.sp)) + self.push_stack(push) if bank != 0: self.registers["af"] = (bank << 8) | (self.registers["af"] & 0xFF) @@ -143,6 +136,16 @@ class crystal(object): else: self.registers["pc"] = address + def push_stack(self, push): + for value in push: + self.registers["sp"] -= 2 + self.vba.write_memory_at(self.registers.sp + 1, value >> 8) + self.vba.write_memory_at(self.registers.sp, value & 0xFF) + if list(self.vba.memory[self.registers.sp : self.registers.sp + 2]) != [value & 0xFF, value >> 8]: + print "desired memory values: " + str([value & 0xFF, value >> 8] ) + print "actual memory values: " + str(list(self.vba.memory[self.registers.sp : self.registers.sp + 2])) + print "wrong value at " + hex(self.registers.sp) + " expected " + hex(value) + " but got " + hex(self.vba.read_memory_at(self.registers.sp)) + def get_stack(self): """ Return a list of functions on the stack. @@ -578,16 +581,74 @@ class crystal(object): hp = ((self.vba.memory[0xd218] << 8) | self.vba.memory[0xd217]) return hp - def start_trainer_battle(self, trainer_group, trainer_id): + def start_trainer_battle(self, map_group=0x1, map_id=0xc, x=6, y=8, direction="l", loop_limit=10): + """ + Starts a trainer battle by warping into a map at the designated + coordinates, pressing the direction button for a full walk step (which + ideally should be blocked, this is mainly to establish direction), and + then pressing "a" to initiate the trainer battle. + """ + self.warp(map_group, map_id, x, y) + + # finish loading the map, might not be necessary? + self.nstep(100) + + # face towards the trainer (or whatever direction was specified). If + # this direction is blocked, then this will only change which direction + # the character is facing. However, if this direction is not blocked by + # the map or by an npc, then this will cause an entire step to be + # taken. + self.vba.press([direction]) + + # talk to the trainer, don't assume line of sight will be triggered + self.vba.press(["a"]) + self.vba.press([]) + + # trainer might talk, skip any text until the player can choose moves + while not self.is_in_battle() and loop_limit > 0: + self.text_wait() + loop_limit -= 1 + + def broken_start_battle(self): + # go to a map with wildmons + self.warp(0x1, 0xc, 6, 8) + self.nstep(200) + memory = self.vba.memory + Script_startbattle_address = 0x97436 + CallScript_address = 0x261f + RockSmashBattleScript_address = 0x97cf9 + RockSmashEncounter_address = 0x97cc0 + StartBattle_address = 0x3f4c1 + ScriptRunning = 0xd438 + ScriptBank = 0xd439 + ScriptPos = 0xd43a + start_wild_battle = 0x3f4dd + script = 0x1a1dc6 + # setup the battle - memory[0xd459] = 0x81 - memory[0xd22f] = trainer_group - memory[0xd231] = trainer_id + #memory[0xd459] = 0x81 + #memory[0xd22f] = trainer_group + #memory[0xd231] = trainer_id - self.vba.memory = memory + #self.vba.memory = memory - Script_startbattle_address = 0x97436 + #self.call(0x25, Script_startbattle_address % 0x4000) + + #self.vba.registers["af"] = ((RockSmashBattleScript_address / 0x4000) << 8) | (self.vba.registers.af & 0xff) + #self.vba.registers["hl"] = RockSmashBattleScript_address % 0x4000 + #self.call(0x0, CallScript_address) + + #self.call(StartBattle_address / 0x4000, StartBattle_address % 0x4000) + #self.call(RockSmashEncounter_address / 0x4000, RockSmashEncounter_address % 0x4000) + + #self.push_stack([self.registers.pc]) + #memory[ScriptBank] = script / 0x4000 + #memory[ScriptPos] = ((script % 0x4000) & 0xff00) >> 8 + #memory[ScriptPos+1] = ((script % 0x4000) & 0xff) + #memory[ScriptRunning] = 0xff + + #self.call(start_wild_battle / 0x4000, start_wild_battle % 0x4000) - self.call(0x25, Script_startbattle_address) + #self.vba.memory = memory -- cgit v1.2.3 From a9a9568d142b81d07a6aa37721fe29ebbbb898ee Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 2 Nov 2013 23:12:14 -0500 Subject: a broken attempt at starting random battles --- pokemontools/vba/vba.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index bb468b8..a64a75a 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -609,6 +609,11 @@ class crystal(object): self.text_wait() loop_limit -= 1 + def broken_start_random_battle(self): + self.push_stack([self.registers.pc]) + self.registers["pc"] = address % 0x4000 + self.call(address / 0x4000, address % 0x4000) + def broken_start_battle(self): # go to a map with wildmons self.warp(0x1, 0xc, 6, 8) -- cgit v1.2.3 From a4179b5337ef767d215185d65a0f7af0f4cd4dbd Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 3 Nov 2013 01:41:07 -0500 Subject: attempting a few more trainer battles --- pokemontools/vba/vba.py | 63 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 19 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index a64a75a..05d4fad 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -581,7 +581,7 @@ class crystal(object): hp = ((self.vba.memory[0xd218] << 8) | self.vba.memory[0xd217]) return hp - def start_trainer_battle(self, map_group=0x1, map_id=0xc, x=6, y=8, direction="l", loop_limit=10): + def start_trainer_battle_lamely(self, map_group=0x1, map_id=0xc, x=6, y=8, direction="l", loop_limit=10): """ Starts a trainer battle by warping into a map at the designated coordinates, pressing the direction button for a full walk step (which @@ -614,36 +614,61 @@ class crystal(object): self.registers["pc"] = address % 0x4000 self.call(address / 0x4000, address % 0x4000) - def broken_start_battle(self): - # go to a map with wildmons - self.warp(0x1, 0xc, 6, 8) - self.nstep(200) + def start_trainer_battle(self, trainer_group=0x1, trainer_id=0x1): + """ + This will fail after the first mon is defeated. + """ + Script_startbattle_address = 0x97436 + # setup the battle memory = self.vba.memory + memory[0xd459] = 0x81 + memory[0xd22f] = trainer_group + memory[0xd231] = trainer_id + self.vba.memory = memory - Script_startbattle_address = 0x97436 + self.call(0x25, (Script_startbattle_address % 0x4000) + 0x4000) + + def broken_start_random_battle_by_rocksmash_battle_script(self): + """ + This doesn't start a battle. + """ CallScript_address = 0x261f RockSmashBattleScript_address = 0x97cf9 - RockSmashEncounter_address = 0x97cc0 - StartBattle_address = 0x3f4c1 ScriptRunning = 0xd438 ScriptBank = 0xd439 ScriptPos = 0xd43a - start_wild_battle = 0x3f4dd - script = 0x1a1dc6 - # setup the battle - #memory[0xd459] = 0x81 - #memory[0xd22f] = trainer_group - #memory[0xd231] = trainer_id + memory = self.vba.memory + memory[ScriptBank] = RockSmashBattleScript_address / 0x4000 + memory[ScriptPos] = ((RockSmashBattleScript_address % 0x4000 + 0x4000) & 0xff00) >> 8 + memory[ScriptPos+1] = ((RockSmashBattleScript_address % 0x4000 + 0x4000) & 0xff) + memory[ScriptRunning] = 0xff + self.vba.memory = memory - #self.vba.memory = memory + self.vba.registers["af"] = ((RockSmashBattleScript_address / 0x4000) << 8) | (self.vba.registers.af & 0xff) + self.vba.registers["hl"] = (RockSmashBattleScript_address % 0x4000) + 0x4000 + self.call(0x0, CallScript_address) - #self.call(0x25, Script_startbattle_address % 0x4000) + #def attempt_start_battle_by_startbattle(self): + # StartBattle_address = 0x3f4c1 + # self.call(StartBattle_address / 0x4000, (StartBattle_address % 0x4000) + 0x4000) - #self.vba.registers["af"] = ((RockSmashBattleScript_address / 0x4000) << 8) | (self.vba.registers.af & 0xff) - #self.vba.registers["hl"] = RockSmashBattleScript_address % 0x4000 - #self.call(0x0, CallScript_address) + #def attempt_start_random_battle_by_wild_battle(self): + # start_wild_battle = 0x3f4dd + # #self.call(start_wild_battle / 0x4000, start_wild_battle % 0x4000) + # #self.vba.registers["pc"] = ... + + def old_crap(self): + CallScript_address = 0x261f + RockSmashBattleScript_address = 0x97cf9 + RockSmashEncounter_address = 0x97cc0 + StartBattle_address = 0x3f4c1 + ScriptRunning = 0xd438 + ScriptBank = 0xd439 + ScriptPos = 0xd43a + start_wild_battle = 0x3f4dd + script = 0x1a1dc6 #self.call(StartBattle_address / 0x4000, StartBattle_address % 0x4000) #self.call(RockSmashEncounter_address / 0x4000, RockSmashEncounter_address % 0x4000) -- cgit v1.2.3 From 2b7e903f847e1c6de7e63549f915a89b0a5795e3 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 3 Nov 2013 01:53:04 -0500 Subject: implement more parts of the battler --- pokemontools/vba/battle.py | 58 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 9 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index 8897ccb..08d0b93 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -3,6 +3,7 @@ Code that attempts to model a battle. """ from pokemontools.vba.vba import crystal as emulator +import pokemontools.vba.vba as vba class BattleException(Exception): """ @@ -21,23 +22,62 @@ class Battle(EmulatorController): battle through python. """ - # Maybe this should be an instance variable instead, but since there can - # only be one emulator per instance (??) it doesn't really matter right - # now. - emulator = emulator - - def __init__(self, emulator=emulator): + def __init__(self, emulator=None): """ Setup the battle. """ self.emulator = emulator - @classmethod - def is_in_battle(cls): + def is_in_battle(self): """ @rtype: bool """ - return cls.emulator.is_in_battle() + return self.emulator.is_in_battle() + + def is_fight_pack_run_menu(self): + """ + Attempts to detect if the current menu is fight-pack-run. This is only + for whether or not the player needs to choose what to do next. + """ + signs = ["FIGHT", "PACK", "RUN"] + screentext = self.emulator.get_text() + return all([sign in screentext for sign in signs]) + + def is_player_turn(self): + return is_fight_pack_run_menu() + + def is_mandatory_switch(self): + return False # TODO + + def skip_start_text(self, max_loops=20): + """ + Skip any initial conversation until the battle has begun. + """ + if not self.is_in_battle(): + while not self.is_in_battle() and max_loops > 0: + self.emulator.text_wait() + max_loops -= 1 + + if max_loops <= 0: + raise Exception("Couldn't start the battle.") + else: + self.emulator.text_wait() + + def skip_end_text(self, loops=20): + if not self.is_in_battle(): + # TODO: keep talking until the character can move? + self.emulator.text_wait() + else: + while self.is_in_battle() and loops > 0: + self.emulator.text_wait() + loops -= 1 + + if loops <= 0: + raise Exception("Couldn't get out of the battle.") + + def skip_crap(self): + while not self.is_flight_pack_run_menu(): + self.emulator.text_wait() def run(self): """ -- cgit v1.2.3 From 1ac0e437d19963d46bd5360e0790b2294ade72b8 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 9 Nov 2013 14:44:36 -0600 Subject: add a bunch of docstrings in the battler --- pokemontools/vba/battle.py | 49 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 8 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index 08d0b93..1949fbb 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -34,6 +34,12 @@ class Battle(EmulatorController): """ return self.emulator.is_in_battle() + def is_input_required(self): + """ + Detects if the battle is waiting for player input. + """ + return self.is_player_turn() or self.is_mandatory_switch() + def is_fight_pack_run_menu(self): """ Attempts to detect if the current menu is fight-pack-run. This is only @@ -44,14 +50,28 @@ class Battle(EmulatorController): return all([sign in screentext for sign in signs]) def is_player_turn(self): - return is_fight_pack_run_menu() + """ + Detects if the battle is waiting for the player to choose an attack. + """ + return self.is_fight_pack_run_menu() def is_mandatory_switch(self): - return False # TODO + """ + Detects if the battle is waiting for the player to choose a next + pokemon. + """ + # TODO: detect the mandatry switch menu + # The following conditions are probably sufficient: + # 1) current pokemon hp is 0 + # 2) game is polling for input + return False def skip_start_text(self, max_loops=20): """ - Skip any initial conversation until the battle has begun. + Skip any initial conversation until the player can select an action. + This includes skipping any text that appears on a map from an NPC as + well as text that appears prior to the first time the action selection + menu appears. """ if not self.is_in_battle(): while not self.is_in_battle() and max_loops > 0: @@ -64,6 +84,9 @@ class Battle(EmulatorController): self.emulator.text_wait() def skip_end_text(self, loops=20): + """ + Skip through any text that appears after the final attack. + """ if not self.is_in_battle(): # TODO: keep talking until the character can move? self.emulator.text_wait() @@ -75,23 +98,31 @@ class Battle(EmulatorController): if loops <= 0: raise Exception("Couldn't get out of the battle.") - def skip_crap(self): - while not self.is_flight_pack_run_menu(): + def skip_until_input_required(self): + """ + Waits until the battle needs player input. + """ + while not self.is_input_required(): self.emulator.text_wait() def run(self): """ Step through the entire battle. """ - # xyz wants to battle + # Advance to the battle from either of these states: + # 1) the player is talking with an npc + # 2) the battle has already started but there's initial text + # xyz wants to battle, a wild foobar appeared self.skip_start_text() while self.is_in_battle(): - self.skip_crap() + self.skip_until_input_required() if self.is_player_turn(): + # battle hook provides input to handle this situation self.handle_turn() elif self.is_mandatory_switch(): + # battle hook provides input to handle this situation too self.handle_mandatory_switch() else: raise BattleException("unknown state, aborting") @@ -99,6 +130,8 @@ class Battle(EmulatorController): # "how did i lose? wah" self.skip_end_text() + # TODO: return should indicate win/loss (blackout) + def handle_mandatory_switch(self): """ Something fainted, pick the next mon. @@ -133,7 +166,7 @@ class BattleStrategy(Battle): """ Take actions inside of a battle based on the game state. """ - self.battle.throw_pokeball() + self.throw_pokeball() class SimpleBattleStrategy(BattleStrategy): """ -- cgit v1.2.3 From 6a1699d32e6fb88401bd3079a5c1ddcf4e8711f5 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 9 Nov 2013 14:50:55 -0600 Subject: even more comments in the battle routines --- pokemontools/vba/battle.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index 1949fbb..3bda1a5 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -60,10 +60,14 @@ class Battle(EmulatorController): Detects if the battle is waiting for the player to choose a next pokemon. """ - # TODO: detect the mandatry switch menu + # TODO: test when "no" fails to escape for wild battles. + # trainer battles: menu asks to select the next mon + # wild battles: yes/no box first # The following conditions are probably sufficient: # 1) current pokemon hp is 0 # 2) game is polling for input + + # TODO: detect the mandatory switch menu return False def skip_start_text(self, max_loops=20): @@ -88,7 +92,11 @@ class Battle(EmulatorController): Skip through any text that appears after the final attack. """ if not self.is_in_battle(): - # TODO: keep talking until the character can move? + # TODO: keep talking until the character can move? A battle can be + # triggered inside of a script, and after the battle is ver the + # player may not be able to move until the script is done. The + # script might only finish after other player input is given, so + # using "text_wait() until the player can move" is a bad idea here. self.emulator.text_wait() else: while self.is_in_battle() and loops > 0: -- cgit v1.2.3 From fd61c8f460d6e7453a79f945768f5952599ba724 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 9 Nov 2013 15:09:21 -0600 Subject: fix move() for lists of commands --- pokemontools/vba/vba.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 05d4fad..3367dee 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -562,7 +562,7 @@ class crystal(object): """ if isinstance(cmd, list): for command in cmd: - self.move(cmd) + self.move(command) else: self.vba.press(cmd, hold=10, after=0) self.vba.press([]) -- cgit v1.2.3 From b363e6cca00c05687c34d735abb93d5117f7b209 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 9 Nov 2013 15:18:55 -0600 Subject: fix a syntax error in battle.py --- pokemontools/vba/battle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index 3bda1a5..5aeb923 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -168,7 +168,7 @@ class BattleStrategy(Battle): # the game failed to do a blackout.. not sure what to do now. raise BattleException("No partymons left. wtf?") - return pokemon.id + return pokemon.id def handle_turn(self): """ -- cgit v1.2.3 From 982cff40af88af1b0c04d1e135ef8ec84007e41a Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 9 Nov 2013 17:55:30 -0600 Subject: draw the menu in skip_until_input_required Step forward 10 frames so that the menu actually draws. Otherwise the user will probably be confused about the actual state of the battle. --- pokemontools/vba/battle.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index 5aeb923..824f27a 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -113,6 +113,9 @@ class Battle(EmulatorController): while not self.is_input_required(): self.emulator.text_wait() + # let the text draw so that the state is more obvious + self.emulator.vba.step(count=10) + def run(self): """ Step through the entire battle. -- cgit v1.2.3 From a6118071a0b0f3c0a43754f3336693f198d2a6da Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 10 Nov 2013 13:53:24 -0600 Subject: broken attempt at calling givepoke --- pokemontools/vba/vba.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index cb0070d..515ac8d 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -636,6 +636,36 @@ class crystal(object): self.call(0x25, (Script_startbattle_address % 0x4000) + 0x4000) + def set_script(self, address): + """ + Sets the current script in wram to whatever address. + """ + ScriptBank = 0xd439 + ScriptPos = 0xd43a + + memory = self.vba.memory + memory[ScriptBank] = address / 0x4000 + memory[ScriptPos] = (((address % 0x4000) + 0x4000) & 0xff00) >> 8 + memory[ScriptPos] = ((address % 0x4000) + 0x4000) & 0xff + + # TODO: determine if this is necessary + #memory[ScriptRunning] = 0xff + + self.vba.memory = memory + + def attempt_call_givepoke(self): + """ + An attempt at calling the givepoke command directly. + """ + givepoke_address = 0x97932 + + # 0, 50, 0, 0 + givepoke_data_address = 0x6ca5 + + self.set_script(givepoke_data_address) + + self.call(givepoke_address / 0x4000, (givepoke_address % 0x4000) + 0x4000) + def broken_start_random_battle_by_rocksmash_battle_script(self): """ This doesn't start a battle. -- cgit v1.2.3 From 271cce531902e381f1207122060f7fef898a2afc Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 10 Nov 2013 13:56:29 -0600 Subject: make call() calculate bank addresses --- pokemontools/vba/vba.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 515ac8d..3249ce3 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -118,13 +118,16 @@ class crystal(object): return state - def call(self, bank, address): + def call(self, 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. """ + bank = address / 0x4000 + address = (address % 0x4000) + 0x4000 + push = [ self.registers.pc, self.registers.hl, @@ -288,7 +291,7 @@ class crystal(object): """ for step_counter in range(0, steplimit): self.walk_through_walls() - #call(0x1, 0x1078) + #call(0x1078) self.vba.step() def disable_triggers(self): @@ -617,9 +620,10 @@ class crystal(object): loop_limit -= 1 def broken_start_random_battle(self): + address = None self.push_stack([self.registers.pc]) self.registers["pc"] = address % 0x4000 - self.call(address / 0x4000, address % 0x4000) + self.call(address) def start_trainer_battle(self, trainer_group=0x1, trainer_id=0x1): """ @@ -634,7 +638,7 @@ class crystal(object): memory[0xd231] = trainer_id self.vba.memory = memory - self.call(0x25, (Script_startbattle_address % 0x4000) + 0x4000) + self.call(Script_startbattle_address) def set_script(self, address): """ @@ -664,7 +668,7 @@ class crystal(object): self.set_script(givepoke_data_address) - self.call(givepoke_address / 0x4000, (givepoke_address % 0x4000) + 0x4000) + self.call(givepoke_address) def broken_start_random_battle_by_rocksmash_battle_script(self): """ @@ -685,15 +689,15 @@ class crystal(object): self.vba.registers["af"] = ((RockSmashBattleScript_address / 0x4000) << 8) | (self.vba.registers.af & 0xff) self.vba.registers["hl"] = (RockSmashBattleScript_address % 0x4000) + 0x4000 - self.call(0x0, CallScript_address) + self.call(CallScript_address) #def attempt_start_battle_by_startbattle(self): # StartBattle_address = 0x3f4c1 - # self.call(StartBattle_address / 0x4000, (StartBattle_address % 0x4000) + 0x4000) + # self.call(StartBattle_address) #def attempt_start_random_battle_by_wild_battle(self): # start_wild_battle = 0x3f4dd - # #self.call(start_wild_battle / 0x4000, start_wild_battle % 0x4000) + # #self.call(start_wild_battle) # #self.vba.registers["pc"] = ... def old_crap(self): @@ -707,8 +711,8 @@ class crystal(object): start_wild_battle = 0x3f4dd script = 0x1a1dc6 - #self.call(StartBattle_address / 0x4000, StartBattle_address % 0x4000) - #self.call(RockSmashEncounter_address / 0x4000, RockSmashEncounter_address % 0x4000) + #self.call(StartBattle_address) + #self.call(RockSmashEncounter_address) #self.push_stack([self.registers.pc]) #memory[ScriptBank] = script / 0x4000 @@ -716,6 +720,6 @@ class crystal(object): #memory[ScriptPos+1] = ((script % 0x4000) & 0xff) #memory[ScriptRunning] = 0xff - #self.call(start_wild_battle / 0x4000, start_wild_battle % 0x4000) + #self.call(start_wild_battle) #self.vba.memory = memory -- cgit v1.2.3 From b36e42494df301452594534a87bdebc2296d4cc0 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 10 Nov 2013 14:13:20 -0600 Subject: stop writing 0x4000 everywhere --- pokemontools/vba/vba.py | 48 ++++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 20 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 3249ce3..b3224bb 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -35,6 +35,21 @@ import vba_wrapper button_masks = vba_wrapper.core.VBA.button_masks button_combiner = vba_wrapper.core.VBA.button_combine +def calculate_bank(address): + """ + Which bank does this address exist in? + """ + return address / 0x4000 + +def calculate_address(address): + """ + Gives the relative address once the bank is loaded. + + This is not the same as the calculate_pointer in the + pokemontools.crystal.pointers module. + """ + return (address % 0x4000) + 0x4000 + def translate_chars(charz): """ Translate a string from the in-game format to readable form. This is @@ -118,15 +133,15 @@ class crystal(object): return state - def call(self, address): + def call(self, address, bank=None): """ 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. """ - bank = address / 0x4000 - address = (address % 0x4000) + 0x4000 + if not bank: + bank = calculate_bank(address) push = [ self.registers.pc, @@ -619,12 +634,6 @@ class crystal(object): self.text_wait() loop_limit -= 1 - def broken_start_random_battle(self): - address = None - self.push_stack([self.registers.pc]) - self.registers["pc"] = address % 0x4000 - self.call(address) - def start_trainer_battle(self, trainer_group=0x1, trainer_id=0x1): """ This will fail after the first mon is defeated. @@ -648,9 +657,10 @@ class crystal(object): ScriptPos = 0xd43a memory = self.vba.memory - memory[ScriptBank] = address / 0x4000 - memory[ScriptPos] = (((address % 0x4000) + 0x4000) & 0xff00) >> 8 - memory[ScriptPos] = ((address % 0x4000) + 0x4000) & 0xff + memory[ScriptBank] = calculate_bank(address) + pointer = calculate_address(address) + memory[ScriptPos] = (calculate_address(address) & 0xff00) >> 8 + memory[ScriptPos] = calculate_address(address) & 0xff # TODO: determine if this is necessary #memory[ScriptRunning] = 0xff @@ -681,14 +691,14 @@ class crystal(object): ScriptPos = 0xd43a memory = self.vba.memory - memory[ScriptBank] = RockSmashBattleScript_address / 0x4000 - memory[ScriptPos] = ((RockSmashBattleScript_address % 0x4000 + 0x4000) & 0xff00) >> 8 - memory[ScriptPos+1] = ((RockSmashBattleScript_address % 0x4000 + 0x4000) & 0xff) + memory[ScriptBank] = calculate_bank(RockSmashBattleScript_address) + memory[ScriptPos] = (calculate_address(RockSmashBattleScript_address) & 0xff00) >> 8 + memory[ScriptPos+1] = calculate_address(RockSmashBattleScript_address) & 0xff memory[ScriptRunning] = 0xff self.vba.memory = memory - self.vba.registers["af"] = ((RockSmashBattleScript_address / 0x4000) << 8) | (self.vba.registers.af & 0xff) - self.vba.registers["hl"] = (RockSmashBattleScript_address % 0x4000) + 0x4000 + self.vba.registers["af"] = (calculate_bank(RockSmashBattleScript_address) << 8) | (self.vba.registers.af & 0xff) + self.vba.registers["hl"] = calculate_address(RockSmashBattleScript_address) self.call(CallScript_address) #def attempt_start_battle_by_startbattle(self): @@ -715,9 +725,7 @@ class crystal(object): #self.call(RockSmashEncounter_address) #self.push_stack([self.registers.pc]) - #memory[ScriptBank] = script / 0x4000 - #memory[ScriptPos] = ((script % 0x4000) & 0xff00) >> 8 - #memory[ScriptPos+1] = ((script % 0x4000) & 0xff) + #self.set_script(script) #memory[ScriptRunning] = 0xff #self.call(start_wild_battle) -- cgit v1.2.3 From 699ed8c6c1fa58a7d3b3edf58542541e2e6f34af Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 10 Nov 2013 14:18:54 -0600 Subject: fix a few call() calls --- pokemontools/vba/vba.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index b3224bb..c32e8bd 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -647,7 +647,7 @@ class crystal(object): memory[0xd231] = trainer_id self.vba.memory = memory - self.call(Script_startbattle_address) + self.call(calculate_address(Script_startbattle_address), bank=calculate_bank(Script_startbattle_address,)) def set_script(self, address): """ @@ -678,7 +678,7 @@ class crystal(object): self.set_script(givepoke_data_address) - self.call(givepoke_address) + self.call(calculate_address(givepoke_address), bank=calculate_bank(givepoke_address)) def broken_start_random_battle_by_rocksmash_battle_script(self): """ @@ -699,11 +699,11 @@ class crystal(object): self.vba.registers["af"] = (calculate_bank(RockSmashBattleScript_address) << 8) | (self.vba.registers.af & 0xff) self.vba.registers["hl"] = calculate_address(RockSmashBattleScript_address) - self.call(CallScript_address) + self.call(calculate_address(CallScript_address), bank=calculate_bank(CallScript_address)) #def attempt_start_battle_by_startbattle(self): # StartBattle_address = 0x3f4c1 - # self.call(StartBattle_address) + # self.call(calculate_address(StartBattle_address), bank=calculate_bank(StartBattle_address)) #def attempt_start_random_battle_by_wild_battle(self): # start_wild_battle = 0x3f4dd -- cgit v1.2.3 From 8b637503a560c7028ac066d50b7aa3923e49082a Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 10 Nov 2013 14:19:26 -0600 Subject: allow bank=0 in call() --- pokemontools/vba/vba.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index c32e8bd..2a2c4ca 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -140,7 +140,7 @@ class crystal(object): Go into the start menu, pause the game and try call(1, 0x1078) to see a string printed to the screen. """ - if not bank: + if bank is None: bank = calculate_bank(address) push = [ -- cgit v1.2.3 From cab54cc723ee66f962d36d2e7c6d844a3df9b3b0 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 10 Nov 2013 15:04:31 -0600 Subject: crude attempt at injecting asm into wram --- pokemontools/vba/vba.py | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 2a2c4ca..bd737da 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -187,6 +187,73 @@ class crystal(object): return addresses + def inject_asm(self, asm, address=0xc6d4): + """ + Writes asm to memory. Makes the emulator run the asm. + + This function will append "ret" to the list of bytes. Before returning, + it updates the value at the first byte to indicate that the function + has executed. + + The first byte at the given address is reserved for whether the asm has + finished executing. + """ + memory = self.vba.memory + + # the first byte is reserved for whether the script has finished + has_finished = address + memory[has_finished] = 0 + + # the second byte is where the script will be stored + script_address = address + 1 + + # TODO: error checking; make sure the last byte doesn't already return. + # Use some functions from gbz80disasm to perform this check. + + # set a value to indicate that the script has executed + set_has_finished = [ + # push af + 0xf5, + + # ld a, 1 + 0xfa, 1, + + # ld [has_finished], a + 0x77, has_finished & 0xff, has_finished >> 8, + + # pop af + 0xf1, + + # ret + 0xc9, + ] + + # append the last opcodes to the script + asm.extend(set_has_finished) + + memory[script_address : script_address + len(asm)] = asm + self.vba.memory = memory + + # make the emulator call the script + self.call(script_address, bank=0) + + # make the emulator step forward + self.vba.step(count=1) + + # check if the script has executed + # TODO: should this raise an exception if the script didn't finish? + if self.vba.memory[has_finished] == 0: + return False + elif self.vba.memory[has_finished] == 1: + return True + else: + raise Exception( + "has_finished at {has_finished} was overwritten with an unexpected value {value}".format( + has_finished=hex(has_finished), + value=self.vba.memory[has_finished], + ) + ) + def text_wait(self, 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. -- cgit v1.2.3 From e700c37844142b5017feddce6f25dbbba5e89f04 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 10 Nov 2013 15:19:15 -0600 Subject: a working inject_asm implementation --- pokemontools/vba/vba.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index bd737da..605a69d 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -187,7 +187,7 @@ class crystal(object): return addresses - def inject_asm(self, asm, address=0xc6d4): + def inject_asm(self, asm=[], address=0xdfcf): """ Writes asm to memory. Makes the emulator run the asm. @@ -198,7 +198,7 @@ class crystal(object): The first byte at the given address is reserved for whether the asm has finished executing. """ - memory = self.vba.memory + memory = list(self.vba.memory) # the first byte is reserved for whether the script has finished has_finished = address @@ -216,10 +216,10 @@ class crystal(object): 0xf5, # ld a, 1 - 0xfa, 1, + 0x3e, 1, # ld [has_finished], a - 0x77, has_finished & 0xff, has_finished >> 8, + 0xea, has_finished & 0xff, has_finished >> 8, # pop af 0xf1, @@ -229,7 +229,7 @@ class crystal(object): ] # append the last opcodes to the script - asm.extend(set_has_finished) + asm = bytearray(asm) + bytearray(set_has_finished) memory[script_address : script_address + len(asm)] = asm self.vba.memory = memory @@ -238,7 +238,7 @@ class crystal(object): self.call(script_address, bank=0) # make the emulator step forward - self.vba.step(count=1) + self.vba.step(count=50) # check if the script has executed # TODO: should this raise an exception if the script didn't finish? -- cgit v1.2.3 From b71fbf3852fb1274b13b548e13b27303e155658c Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 10 Nov 2013 15:53:12 -0600 Subject: write inject_asm_into_rom This method injects asm straight into the ROM loaded in the emulator. It does not overwrite the ROM on the file system. This method is much slower than the wram version because it involves copying memory multiples and copying the entire ROM into python and then sending it back to the emulator. --- pokemontools/vba/vba.py | 88 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 605a69d..f69595b 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -187,7 +187,93 @@ class crystal(object): return addresses - def inject_asm(self, asm=[], address=0xdfcf): + def inject_asm_into_rom(self, asm=[], address=0x75 * 0x4000, has_finished_address=0xdb75): + """ + Writes asm to the loaded ROM. Calls the asm. + + :param address: ROM address for where to store the injected asm script. + The default value is an address in pokecrystal that isn't used for + anything. + + :param has_finished_address: address for where to store whether the + script executed or not. This value is restored when the script has been + confirmed to work. It's conceivable that some injected asm might need + to change that address if the asm needs to access the original wram + value itself. + """ + if len(asm) > 0x4000: + raise Exception("too much asm") + + # temporarily use wram + cached_wram_value = self.vba.memory[has_finished_address] + + # set the value at has_finished_address to 0 + reset_wram_mem = list(self.vba.memory) + reset_wram_mem[has_finished_address] = 0 + self.vba.memory = reset_wram_mem + + # set a value to indicate that the script has executed + set_has_finished = [ + # push af + 0xf5, + + # ld a, 1 + 0x3e, 1, + + # ld [has_finished], a + 0xea, has_finished_address & 0xff, has_finished_address >> 8, + + # pop af + 0xf1, + + # ret + 0xc9, + ] + + # TODO: check if asm ends with a byte that causes a return or call or + # other "ender". Raise an exception if it already returns on its own. + + # combine the given asm with the setter bytes + total_asm = asm + set_has_finished + + # get a copy of the current rom + rom = list(self.vba.rom) + + # inject the asm + rom[address : address + len(total_asm)] = total_asm + + # set the rom with the injected asm + self.vba.rom = rom + + # call the injected asm + self.call(calculate_address(address), bank=calculate_bank(address)) + + # make the emulator step forward + self.vba.step(count=20) + + # check if the script has executed (see below) + current_mem = self.vba.memory + + # reset the wram value to its original value + another_mem = list(self.vba.memory) + another_mem[has_finished_address] = cached_wram_value + self.vba.memory = another_mem + + # check if the script has actually executed + # TODO: should this raise an exception if the script didn't finish? + if current_mem[has_finished_address] == 0: + return False + elif current_mem[has_finished_address] == 1: + return True + else: + raise Exception( + "has_finished_address at {has_finished_address} was overwritten with an unexpected value {value}".format( + has_finished_address=hex(has_finished_address), + value=current_mem[has_finished_address], + ) + ) + + def inject_asm_into_wram(self, asm=[], address=0xdfcf): """ Writes asm to memory. Makes the emulator run the asm. -- cgit v1.2.3 From 6b0d8ee855af252b48709c88a46c748c956af740 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 10 Nov 2013 16:29:23 -0600 Subject: implement call_script to call CallScript This is the entry point for calling in-game scripts. --- pokemontools/vba/vba.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index f69595b..42f5e5b 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -340,6 +340,54 @@ class crystal(object): ) ) + def call_script(self, address, bank=None, wram=False, force=False): + """ + Sets wram values so that the engine plays a script. + + :param address: address of the map script + :param bank: override for bank calculation (based on address) + :param wram: force bank to 0 + :param force: override an already-running script + """ + + ScriptFlags = 0xd434 + ScriptMode = 0xd437 + ScriptRunning = 0xd438 + ScriptBank = 0xd439 + ScriptPos = 0xd43a + NumScriptParents = 0xd43c + ScriptParents = 0xd43d + + num_possible_parents = 4 + len_parent = 3 + + mem = list(self.vba.memory) + + if mem[ScriptRunning] == 0xff: + if force: + # wipe the parent routine array + mem[NumScriptParents] = 0 + for i in xrange(num_possible_parents * len_parent): + mem[ScriptParents + i] = 0 + else: + raise Exception("a script is already running, use force=True") + + if wram: + bank = 0 + elif not bank: + bank = calculate_bank(address) + address = address % 0x4000 + 0x4000 * bool(bank) + + mem[ScriptFlags] |= 4 + mem[ScriptMode] = 1 + mem[ScriptRunning] = 0xff + + mem[ScriptBank] = bank + mem[ScriptPos] = address % 0x100 + mem[ScriptPos+1] = address / 0x100 + + self.vba.memory = mem + def text_wait(self, 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. -- cgit v1.2.3 From c76156f3f0d48a6eed324c7f99e20a365f2da416 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 10 Nov 2013 16:29:55 -0600 Subject: method to lower enemy hp during battle --- pokemontools/vba/vba.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 42f5e5b..a3c8b7b 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -495,9 +495,12 @@ class crystal(object): self.vba.write_memory_at(0xC2FC, 0) self.vba.write_memory_at(0xC2FD, 0) - #@staticmethod - #def set_enemy_level(level): - # vba.write_memory_at(0xd213, level) + def lower_enemy_hp(self): + """ + Dramatically lower the enemy's HP. + """ + self.vba.write_memory_at(0xd216, 0) + self.vba.write_memory_at(0xd217, 1) def nstep(self, steplimit=500): """ -- cgit v1.2.3 From 4fab1088d9718ef94bb72caa3463a0648aff848a Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 10 Nov 2013 16:32:16 -0600 Subject: function to start a battle by rocksmash 01:04 < padz> u cunt --- pokemontools/vba/vba.py | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index a3c8b7b..60cd0ab 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -884,26 +884,13 @@ class crystal(object): self.call(calculate_address(givepoke_address), bank=calculate_bank(givepoke_address)) - def broken_start_random_battle_by_rocksmash_battle_script(self): + def start_random_battle_by_rocksmash_battle_script(self): """ - This doesn't start a battle. + Initiates a wild battle using the same function that using rocksmash + would call. """ - CallScript_address = 0x261f RockSmashBattleScript_address = 0x97cf9 - ScriptRunning = 0xd438 - ScriptBank = 0xd439 - ScriptPos = 0xd43a - - memory = self.vba.memory - memory[ScriptBank] = calculate_bank(RockSmashBattleScript_address) - memory[ScriptPos] = (calculate_address(RockSmashBattleScript_address) & 0xff00) >> 8 - memory[ScriptPos+1] = calculate_address(RockSmashBattleScript_address) & 0xff - memory[ScriptRunning] = 0xff - self.vba.memory = memory - - self.vba.registers["af"] = (calculate_bank(RockSmashBattleScript_address) << 8) | (self.vba.registers.af & 0xff) - self.vba.registers["hl"] = calculate_address(RockSmashBattleScript_address) - self.call(calculate_address(CallScript_address), bank=calculate_bank(CallScript_address)) + self.call_script(RockSmashBattleScript_address) #def attempt_start_battle_by_startbattle(self): # StartBattle_address = 0x3f4c1 -- cgit v1.2.3 From bc30cd1e9a95342cbb77c1a05a2656f2505e817a Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 10 Nov 2013 17:38:55 -0600 Subject: an attempt at givepoke --- pokemontools/vba/vba.py | 66 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 7 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 60cd0ab..62c16da 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -871,18 +871,70 @@ class crystal(object): self.vba.memory = memory - def attempt_call_givepoke(self): + def givepoke(self, pokemon_id, level, nickname): """ - An attempt at calling the givepoke command directly. + Give the player a pokemon. """ - givepoke_address = 0x97932 + if isinstance(nickname, str): + if len(nickname) == 0: + raise Exception("invalid nickname") + elif len(nickname) > 11: + raise Exception("nickname too long") + else: + if not nickname: + nickname = False + else: + raise Exception("nickname needs to be a string, False or None") + + # script to inject into wram + script = [ + 0x47, # loadfont + #0x55, # keeptextopen + + # givepoke pokemon_id, level, 0, 0 + 0x2d, pokemon_id, level, 0, 0, + + #0x54, # closetext + 0x49, # loadmovesprites + 0x91, # end + ] + + #address = 0xd073 + #address = 0xc000 + address = 0xd8f1 + + mem = list(self.vba.memory) + backup_wram = mem[address : address + len(script)] + mem[address : address + len(script)] = script + self.vba.memory = mem + + self.call_script(address, wram=True) + + # "would you like to give it a nickname?" + self.text_wait() - # 0, 50, 0, 0 - givepoke_data_address = 0x6ca5 + if nickname: + # yes + self.vba.press("a", hold=10) + + # wait for the keyboard to appear + # TODO: this wait should be moved into write() + self.vba.step(count=20) - self.set_script(givepoke_data_address) + # type the requested nicknameb + self.write(nickname) - self.call(calculate_address(givepoke_address), bank=calculate_bank(givepoke_address)) + self.vba.press("start", hold=5, after=10) + self.vba.press("a", hold=5, after=50) + else: + # no nickname + self.vba.press("d", hold=10, after=20) + self.vba.press("a", hold=5, after=30) + + # reset whatever was in wram before this script was called + mem = list(self.vba.memory) + mem[address : address + len(script)] = backup_wram + self.vba.memory = mem def start_random_battle_by_rocksmash_battle_script(self): """ -- cgit v1.2.3 From d1da18652d96212c05be10a9dfb22bc02071f84e Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 10 Nov 2013 17:53:06 -0600 Subject: make givepoke work (h/t padz) --- pokemontools/vba/vba.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 62c16da..9d2be23 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -871,7 +871,7 @@ class crystal(object): self.vba.memory = memory - def givepoke(self, pokemon_id, level, nickname): + def givepoke(self, pokemon_id, level, nickname=None): """ Give the player a pokemon. """ @@ -899,9 +899,12 @@ class crystal(object): 0x91, # end ] + # picked this region of wram because it looks like it's probably unused + # in situations where givepoke will work. #address = 0xd073 #address = 0xc000 - address = 0xd8f1 + #address = 0xd8f1 + address = 0xd280 mem = list(self.vba.memory) backup_wram = mem[address : address + len(script)] @@ -928,8 +931,11 @@ class crystal(object): self.vba.press("a", hold=5, after=50) else: # no nickname - self.vba.press("d", hold=10, after=20) - self.vba.press("a", hold=5, after=30) + self.vba.press("b", hold=10, after=20) + + # Wait for the script to end in the engine before copying the original + # wram values back in. + self.vba.step(count=100) # reset whatever was in wram before this script was called mem = list(self.vba.memory) -- cgit v1.2.3 From 9310699565243442a8c363012b8bb09f000ddf29 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 10 Nov 2013 19:49:39 -0600 Subject: write script to ROM and execute This handles all of the usual tasks that will be required for injecting and running custom scripts. --- pokemontools/vba/vba.py | 82 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 9d2be23..0d678da 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -838,7 +838,7 @@ class crystal(object): self.text_wait() loop_limit -= 1 - def start_trainer_battle(self, trainer_group=0x1, trainer_id=0x1): + def broken_start_trainer_battle(self, trainer_group=0x1, trainer_id=0x1): """ This will fail after the first mon is defeated. """ @@ -871,6 +871,86 @@ class crystal(object): self.vba.memory = memory + def inject_script_into_rom(self, asm=[0x91], rom_address=0x75 * 0x4000, wram_address=0xd280, limit=50): + """ + Writes a script to the ROM in a blank location. Calls call_script to + make the game engine aware of the script. Then executes the script and + looks for confirmation thta the script has started to run. + + The script must end itself. + + :param asm: scripting command bytes + :param rom_address: rom location to write asm to + :param wram_address: temporary storage for indicating if the script has + started yet + :param limit: number of frames to emulate before giving up on the start + script + """ + execution_pending = 0 + execution_started = 1 + valid_execution_states = (execution_pending, execution_started) + + # location for byte for whether script has started executing + execution_indicator_address = wram_address + + # backup whatever exists at the current wram location + backup_wram = self.vba.read_memory_at(execution_indicator_address) + + # .. and set it to "pending" + self.vba.write_memory_at(execution_indicator_address, execution_pending) + + # initial script that runs first to tell python that execution started + execution_indicator_script = [ + # writebyte to say that the script has executed + 0x15, execution_started, + + # copyvartobyte + 0x1a, execution_indicator_address & 0xff, execution_indicator_address >> 8, + ] + + # make the indicator script run before the user script + full_script = execution_indicator_script + asm + + # inject the asm + rom = list(self.vba.rom) + rom[rom_address : rom_address + len(full_script)] = full_script + + # set the rom with the injected bytes + self.vba.rom = rom + + # setup the script for execution + self.call_script(rom_address) + + status = execution_pending + while status != execution_started and limit > 0: + # emulator time travel + self.vba.step(count=1) + + # get latest wram + status = self.vba.read_memory_at(execution_indicator_address) + if status not in valid_execution_states: + raise Exception( + "The execution indicator at {addr} has invalid state {value}".format( + addr=hex(execution_indicator_address), + value=status, + ) + ) + elif status == execution_started: + break # hooray + + limit -= 1 + + if status == execution_pending and limit == 0: + raise Exception( + "Emulation timeout while waiting for script to start." + ) + + # The script has started so it's okay to reset wram back to whatever it + # was. + self.vba.write_memory_at(execution_indicator_address, backup_wram) + + return True + def givepoke(self, pokemon_id, level, nickname=None): """ Give the player a pokemon. -- cgit v1.2.3 From 9bf214f963113a40b2a6e3037e06c71abae2e69a Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 10 Nov 2013 19:56:03 -0600 Subject: simplify givepoke by using inject_script_into_rom --- pokemontools/vba/vba.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 0d678da..9376c6a 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -951,7 +951,7 @@ class crystal(object): return True - def givepoke(self, pokemon_id, level, nickname=None): + def givepoke(self, pokemon_id, level, nickname=None, wram=False): """ Give the player a pokemon. """ @@ -986,12 +986,15 @@ class crystal(object): #address = 0xd8f1 address = 0xd280 - mem = list(self.vba.memory) - backup_wram = mem[address : address + len(script)] - mem[address : address + len(script)] = script - self.vba.memory = mem + if not wram: + self.inject_script_into_rom(asm=script, wram_address=address) + else: + mem = list(self.vba.memory) + backup_wram = mem[address : address + len(script)] + mem[address : address + len(script)] = script + self.vba.memory = mem - self.call_script(address, wram=True) + self.call_script(address, wram=True) # "would you like to give it a nickname?" self.text_wait() @@ -1013,14 +1016,15 @@ class crystal(object): # no nickname self.vba.press("b", hold=10, after=20) - # Wait for the script to end in the engine before copying the original - # wram values back in. - self.vba.step(count=100) - - # reset whatever was in wram before this script was called - mem = list(self.vba.memory) - mem[address : address + len(script)] = backup_wram - self.vba.memory = mem + if wram: + # Wait for the script to end in the engine before copying the original + # wram values back in. + self.vba.step(count=100) + + # reset whatever was in wram before this script was called + mem = list(self.vba.memory) + mem[address : address + len(script)] = backup_wram + self.vba.memory = mem def start_random_battle_by_rocksmash_battle_script(self): """ -- cgit v1.2.3 From 409eab17163b2a85f07d4fe3ea58ed7e4a39b00b Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 10 Nov 2013 22:43:48 -0600 Subject: start_trainer_battle spawns a battle --- pokemontools/vba/vba.py | 102 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 97 insertions(+), 5 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 9376c6a..17472f0 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -60,6 +60,22 @@ def translate_chars(charz): result += chars[each] return result +def translate_text(text, chars=chars): + """ + Converts text to the in-game byte coding. + """ + output = [] + for given_char in text: + for (byte, char) in chars.iteritems(): + if char == given_char: + output.append(byte) + break + else: + raise Exception( + "no match for {0}".format(given_char) + ) + return output + class crystal(object): """ Just a simple namespace to store a bunch of functions for Pokémon Crystal. @@ -853,6 +869,85 @@ class crystal(object): self.call(calculate_address(Script_startbattle_address), bank=calculate_bank(Script_startbattle_address,)) + def start_trainer_battle(self, trainer_group=0x1, trainer_id=0x1, text_win="YOU WIN", text_address=0xdb90): + """ + Start a trainer battle with the trainer located by trainer_group and + trainer_id. + + :param trainer_group: trainer group id + :param trainer_id: trainer id within the group + :param text_win: text to show if player wins + :param text_address: where to store text_win in wram + """ + # where the script will be written + rom_address = 0x75 * 0x4000 + + # battle win message + translated_text = translate_text(text_win) + + # also include the first and last bytes needed for text + translated_text = [0] + translated_text + [0x57] + + mem = self.vba.memory + + # create a backup of the current data + wram_backup = mem[text_address : text_address + len(translated_text)] + + # manipulate the memory + mem[text_address : text_address + len(translated_text)] = translated_text + self.vba.memory = mem + + text_pointer_hi = text_address / 0x100 + text_pointer_lo = text_address % 0x100 + + script = [ + # loadfont + #0x47, + + # winlosstext address, address + 0x64, text_pointer_lo, text_pointer_hi, 0, 0, + + # loadtrainer group, id + 0x5e, trainer_group, trainer_id, + + # startbattle + 0x5f, + + # returnafterbattle + 0x60, + + # reloadmapmusic + 0x83, + + # reloadmap + 0x7B, + ] + + # Now make the script restore wram at the end (after the text has been + # used). The assumption here is that this particular subset of wram + # data would not be needed during the bulk of the script. + address = text_address + for byte in wram_backup: + address_hi = address / 0x100 + address_lo = address % 0x100 + + script += [ + # loadvar + 0x1b, address_lo, address_hi, byte, + ] + + address += 1 + + script += [ + # end + 0x91, + ] + + # Use a different wram address because the default is something related + # to trainers. + # use a higher loop limit because otherwise it doesn't start fast enough? + self.inject_script_into_rom(asm=script, rom_address=rom_address, wram_address=0xdb75, limit=1000) + def set_script(self, address): """ Sets the current script in wram to whatever address. @@ -901,11 +996,8 @@ class crystal(object): # initial script that runs first to tell python that execution started execution_indicator_script = [ - # writebyte to say that the script has executed - 0x15, execution_started, - - # copyvartobyte - 0x1a, execution_indicator_address & 0xff, execution_indicator_address >> 8, + # loadvar address, value + 0x1b, execution_indicator_address & 0xff, execution_indicator_address >> 8, execution_started, ] # make the indicator script run before the user script -- cgit v1.2.3 From 0729ed3554f3d79bd59b626c82ccedb88b063860 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 10 Nov 2013 22:45:12 -0600 Subject: remove a dead function --- pokemontools/vba/vba.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 17472f0..a564ac2 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -832,6 +832,8 @@ class crystal(object): coordinates, pressing the direction button for a full walk step (which ideally should be blocked, this is mainly to establish direction), and then pressing "a" to initiate the trainer battle. + + Consider using start_trainer_battle instead. """ self.warp(map_group, map_id, x, y) @@ -854,21 +856,6 @@ class crystal(object): self.text_wait() loop_limit -= 1 - def broken_start_trainer_battle(self, trainer_group=0x1, trainer_id=0x1): - """ - This will fail after the first mon is defeated. - """ - Script_startbattle_address = 0x97436 - - # setup the battle - memory = self.vba.memory - memory[0xd459] = 0x81 - memory[0xd22f] = trainer_group - memory[0xd231] = trainer_id - self.vba.memory = memory - - self.call(calculate_address(Script_startbattle_address), bank=calculate_bank(Script_startbattle_address,)) - def start_trainer_battle(self, trainer_group=0x1, trainer_id=0x1, text_win="YOU WIN", text_address=0xdb90): """ Start a trainer battle with the trainer located by trainer_group and -- cgit v1.2.3 From 44ef6852f4d24f075ae58373d329e681151ccb62 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 10 Nov 2013 22:49:19 -0600 Subject: remove old functions --- pokemontools/vba/vba.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index a564ac2..3e3d66d 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -1113,15 +1113,12 @@ class crystal(object): RockSmashBattleScript_address = 0x97cf9 self.call_script(RockSmashBattleScript_address) - #def attempt_start_battle_by_startbattle(self): - # StartBattle_address = 0x3f4c1 - # self.call(calculate_address(StartBattle_address), bank=calculate_bank(StartBattle_address)) - #def attempt_start_random_battle_by_wild_battle(self): # start_wild_battle = 0x3f4dd # #self.call(start_wild_battle) # #self.vba.registers["pc"] = ... + # why is this here? def old_crap(self): CallScript_address = 0x261f RockSmashBattleScript_address = 0x97cf9 @@ -1132,14 +1129,3 @@ class crystal(object): ScriptPos = 0xd43a start_wild_battle = 0x3f4dd script = 0x1a1dc6 - - #self.call(StartBattle_address) - #self.call(RockSmashEncounter_address) - - #self.push_stack([self.registers.pc]) - #self.set_script(script) - #memory[ScriptRunning] = 0xff - - #self.call(start_wild_battle) - - #self.vba.memory = memory -- cgit v1.2.3 From b94aaa38829dc96804ca35b01d1db92fe77f914b Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 10 Nov 2013 23:05:31 -0600 Subject: remove more dead code --- pokemontools/vba/vba.py | 17 ----------------- 1 file changed, 17 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 3e3d66d..0dac63f 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -1112,20 +1112,3 @@ class crystal(object): """ RockSmashBattleScript_address = 0x97cf9 self.call_script(RockSmashBattleScript_address) - - #def attempt_start_random_battle_by_wild_battle(self): - # start_wild_battle = 0x3f4dd - # #self.call(start_wild_battle) - # #self.vba.registers["pc"] = ... - - # why is this here? - def old_crap(self): - CallScript_address = 0x261f - RockSmashBattleScript_address = 0x97cf9 - RockSmashEncounter_address = 0x97cc0 - StartBattle_address = 0x3f4c1 - ScriptRunning = 0xd438 - ScriptBank = 0xd439 - ScriptPos = 0xd43a - start_wild_battle = 0x3f4dd - script = 0x1a1dc6 -- cgit v1.2.3 From 966985411f01b799fa71f4823da7a8cd6d9cc47b Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Mon, 11 Nov 2013 00:55:54 -0600 Subject: detect the "mandatory switch" menu This requires a slightly slower text_wait function. There is probably a way to refactor that function in a way that doesn't cause cancer. --- pokemontools/vba/battle.py | 6 ++++-- pokemontools/vba/vba.py | 11 ++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) (limited to 'pokemontools/vba') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index 824f27a..87cd7b1 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -67,8 +67,10 @@ class Battle(EmulatorController): # 1) current pokemon hp is 0 # 2) game is polling for input - # TODO: detect the mandatory switch menu - return False + if "CANCEL Which ?" in self.emulator.get_text(): + return True + else: + return False def skip_start_text(self, max_loops=20): """ diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 0dac63f..10513c6 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -431,12 +431,17 @@ class crystal(object): # set CurSFX self.vba.write_memory_at(0xc2bf, 0) - self.vba.press("a", hold=10, after=1) + self.vba.press("a", hold=10, after=50) # check if CurSFX is SFX_READ_TEXT_2 if self.vba.read_memory_at(0xc2bf) == 0x8: - print "cursfx is set to SFX_READ_TEXT_2, looping.." - return self.text_wait(step_size=step_size, max_wait=max_wait, debug=debug, callback=callback, sfx_limit=sfx_limit) + if "CANCEL Which" in self.get_text(): + print "probably the 'switch pokemon' menu" + return + else: + print "cursfx is set to SFX_READ_TEXT_2, looping.." + print self.get_text() + return self.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 -- cgit v1.2.3