From 4c6a904db191c182c3b9cecdcbc0d5ea3b5c2173 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Mon, 11 Nov 2013 21:22:15 -0600 Subject: a quick function to set pokemon hp in battle --- pokemontools/vba/vba.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'pokemontools') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 10513c6..47cadd7 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -523,6 +523,13 @@ class crystal(object): self.vba.write_memory_at(0xd216, 0) self.vba.write_memory_at(0xd217, 1) + def set_battle_mon_hp(self, hp): + """ + Set the BattleMonHP variable to the given hp. + """ + self.vba.write_memory_at(0xc63c, hp / 0x100) + self.vba.write_memory_at(0xc63c + 1, hp % 0x100) + def nstep(self, steplimit=500): """ Steps the CPU forward and calls some functions in between each step. -- cgit v1.2.3 From 18449eea21096cf8b90e21ee7bcdc6da5e047df4 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Mon, 11 Nov 2013 21:27:28 -0600 Subject: function to check if it's the yes/no prompt This is the prompt that appears during battles for whether or not to switch pokemon when the other trainer is sending something else out. --- pokemontools/vba/battle.py | 7 +++++++ pokemontools/vba/vba.py | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) (limited to 'pokemontools') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index 87cd7b1..17c0d5d 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -55,6 +55,13 @@ class Battle(EmulatorController): """ return self.is_fight_pack_run_menu() + def is_switch_prompt(self): + """ + Detects if the battle is waiting for the player to choose whether or + not to switch pokemon. + """ + return self.emulator.is_battle_switch_prompt() + def is_mandatory_switch(self): """ Detects if the battle is waiting for the player to choose a next diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 47cadd7..79eb77e 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -599,6 +599,23 @@ class crystal(object): def is_in_link_battle(self): return self.vba.read_memory_at(0xc2dc) != 0 + def is_battle_switch_prompt(self): + """ + Checks if the game is currently displaying the yes/no prompt for + whether or not to switch pokemon. + """ + # get on-screen text + text = self.get_text() + + requirements = [ + "YES", + "NO", + "Will ", + "change POKMON?", + ] + + return all([requirement in text for requirement in requirements]) + def unlock_flypoints(self): """ Unlocks different destinations for flying. -- cgit v1.2.3 From 2801a3545ffd4cde947cb6fee212fe3a4f759543 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Mon, 11 Nov 2013 21:48:01 -0600 Subject: update docstrings about the switch prompt --- pokemontools/vba/battle.py | 4 +++- pokemontools/vba/vba.py | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'pokemontools') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index 17c0d5d..a5d9d52 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -58,7 +58,9 @@ class Battle(EmulatorController): def is_switch_prompt(self): """ Detects if the battle is waiting for the player to choose whether or - not to switch pokemon. + not to switch pokemon. This is the prompt that asks yes/no for whether + to switch pokemon, like if the trainer is switching pokemon at the end + of a turn set. """ return self.emulator.is_battle_switch_prompt() diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 79eb77e..58ed1bd 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -602,8 +602,12 @@ class crystal(object): def is_battle_switch_prompt(self): """ Checks if the game is currently displaying the yes/no prompt for - whether or not to switch pokemon. + whether or not to switch pokemon. This happens when the trainer is + switching pokemon out. """ + # TODO: this method should return False if the game options have been + # set to not use the battle switching style. + # get on-screen text text = self.get_text() -- cgit v1.2.3 From cc4c24816767caaaf1a6c2d280184505565eb688 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Mon, 11 Nov 2013 21:52:12 -0600 Subject: add is_switch_prompt to the input poll detector --- pokemontools/vba/battle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pokemontools') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index a5d9d52..a453478 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -38,7 +38,7 @@ class Battle(EmulatorController): """ Detects if the battle is waiting for player input. """ - return self.is_player_turn() or self.is_mandatory_switch() + return self.is_player_turn() or self.is_mandatory_switch() or self.is_switch_prompt() def is_fight_pack_run_menu(self): """ -- cgit v1.2.3 From 9f72be1a0f4186ca49e3bf9c6148afbe6477e391 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Mon, 11 Nov 2013 21:54:06 -0600 Subject: handle the yes/no prompt in the battle run loop --- pokemontools/vba/battle.py | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'pokemontools') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index a453478..8f4e5eb 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -143,6 +143,8 @@ class Battle(EmulatorController): if self.is_player_turn(): # battle hook provides input to handle this situation self.handle_turn() + elif self.is_switch_prompt(): + self.handle_switch_prompt() elif self.is_mandatory_switch(): # battle hook provides input to handle this situation too self.handle_mandatory_switch() @@ -160,6 +162,13 @@ class Battle(EmulatorController): """ raise NotImplementedError + def handle_switch_prompt(self): + """ + The trainer is switching pokemon. The game asks yes/no for whether or + not the player would like to switch. + """ + raise NotImplementedError + def handle_turn(self): """ Take actions inside of a battle based on the game state. -- cgit v1.2.3 From d9bb01f2c0aef6d270bc4e2da93af94ea210e8db Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Mon, 11 Nov 2013 21:56:34 -0600 Subject: rename the switch prompt detector --- pokemontools/vba/battle.py | 12 ++++++------ pokemontools/vba/vba.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'pokemontools') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index 8f4e5eb..9fe4a84 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -38,7 +38,7 @@ class Battle(EmulatorController): """ Detects if the battle is waiting for player input. """ - return self.is_player_turn() or self.is_mandatory_switch() or self.is_switch_prompt() + return self.is_player_turn() or self.is_mandatory_switch() or self.is_trainer_switch_prompt() def is_fight_pack_run_menu(self): """ @@ -55,14 +55,14 @@ class Battle(EmulatorController): """ return self.is_fight_pack_run_menu() - def is_switch_prompt(self): + def is_trainer_switch_prompt(self): """ Detects if the battle is waiting for the player to choose whether or not to switch pokemon. This is the prompt that asks yes/no for whether to switch pokemon, like if the trainer is switching pokemon at the end of a turn set. """ - return self.emulator.is_battle_switch_prompt() + return self.emulator.is_trainer_switch_prompt() def is_mandatory_switch(self): """ @@ -143,8 +143,8 @@ class Battle(EmulatorController): if self.is_player_turn(): # battle hook provides input to handle this situation self.handle_turn() - elif self.is_switch_prompt(): - self.handle_switch_prompt() + elif self.is_trainer_switch_prompt(): + self.handle_trainer_switch_prompt() elif self.is_mandatory_switch(): # battle hook provides input to handle this situation too self.handle_mandatory_switch() @@ -162,7 +162,7 @@ class Battle(EmulatorController): """ raise NotImplementedError - def handle_switch_prompt(self): + def handle_trainer_switch_prompt(self): """ The trainer is switching pokemon. The game asks yes/no for whether or not the player would like to switch. diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 58ed1bd..67d3cf1 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -599,7 +599,7 @@ class crystal(object): def is_in_link_battle(self): return self.vba.read_memory_at(0xc2dc) != 0 - def is_battle_switch_prompt(self): + def is_trainer_switch_prompt(self): """ Checks if the game is currently displaying the yes/no prompt for whether or not to switch pokemon. This happens when the trainer is -- cgit v1.2.3 From d6cf3454c44fab60c6f83bc8fa3109f0c793fd2f Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Mon, 11 Nov 2013 22:21:23 -0600 Subject: prepare for a wild-based detector This one isn't implemented yet but might as well get the wrapper functions out of the way. --- pokemontools/vba/battle.py | 15 +++++++++++++++ pokemontools/vba/vba.py | 15 +++++++++++++++ 2 files changed, 30 insertions(+) (limited to 'pokemontools') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index 9fe4a84..6b52bce 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -64,6 +64,21 @@ class Battle(EmulatorController): """ return self.emulator.is_trainer_switch_prompt() + def is_wild_switch_prompt(self): + """ + Detects if the battle is waiting for the player to choose whether or + not to continue to fight the wild pokemon. + """ + return self.emulator.is_wild_switch_prompt() + + def is_switch_prompt(self): + """ + Detects both trainer and wild switch prompts (for prompting whether to + switch pokemon). This is a yes/no box and not the actual pokemon + selection menu. + """ + return self.is_trainer_switch_prompt() or self.is_wild_switch_prompt() + def is_mandatory_switch(self): """ Detects if the battle is waiting for the player to choose a next diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 67d3cf1..6f863b6 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -620,6 +620,21 @@ class crystal(object): return all([requirement in text for requirement in requirements]) + def is_wild_switch_prompt(self): + """ + Detects if the battle is waiting for the player to choose whether or + not to continue to fight the wild pokemon. + """ + # TODO: make a better implementation + return False + + def is_switch_prompt(self): + """ + Detects both the trainer-style switch prompt and the wild-style switch + prompt. This is the yes/no prompt for whether to switch pokemon. + """ + return self.is_trainer_switch_prompt() or self.is_wild_switch_prompt() + def unlock_flypoints(self): """ Unlocks different destinations for flying. -- cgit v1.2.3 From 4087851f7c9c92791182ffa695073827c5433829 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Mon, 11 Nov 2013 22:39:37 -0600 Subject: wild switch prompt detector --- pokemontools/vba/vba.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'pokemontools') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 6f863b6..17d3be6 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -625,8 +625,16 @@ class crystal(object): Detects if the battle is waiting for the player to choose whether or not to continue to fight the wild pokemon. """ - # TODO: make a better implementation - return False + # get on-screen text + screen_text = self.get_text() + + requirements = [ + "YES", + "NO", + "Use next POKMON?", + ] + + return all([requirement in text for requirement in requirements]) def is_switch_prompt(self): """ -- cgit v1.2.3 From fd5544f79094ddddce59754de7bbe3738622e8fe Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Mon, 11 Nov 2013 23:04:31 -0600 Subject: handle wild yes/no prompt during battle --- pokemontools/vba/battle.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'pokemontools') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index 6b52bce..c0c886e 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -160,6 +160,8 @@ class Battle(EmulatorController): self.handle_turn() elif self.is_trainer_switch_prompt(): self.handle_trainer_switch_prompt() + elif self.is_wild_switch_prompt(): + self.handle_wild_switch_prompt() elif self.is_mandatory_switch(): # battle hook provides input to handle this situation too self.handle_mandatory_switch() @@ -167,6 +169,7 @@ class Battle(EmulatorController): raise BattleException("unknown state, aborting") # "how did i lose? wah" + # TODO: this doesn't happen for wild battles self.skip_end_text() # TODO: return should indicate win/loss (blackout) @@ -184,6 +187,13 @@ class Battle(EmulatorController): """ raise NotImplementedError + def handle_wild_switch_prompt(self): + """ + The wild pokemon defeated the party pokemon. This is the yes/no box for + whether to switch pokemon or not. + """ + raise NotImplementedError + def handle_turn(self): """ Take actions inside of a battle based on the game state. -- cgit v1.2.3 From 5b35595c7a13792db782f8ebc21dccfcc1b56b9a Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Mon, 11 Nov 2013 23:04:44 -0600 Subject: minor function to set battle type This needs to be replaced with something that loads variable names from wram.asm instead of manually repeating everything in python. --- pokemontools/vba/vba.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'pokemontools') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 17d3be6..e339f57 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -655,6 +655,12 @@ class crystal(object): self.vba.write_memory_at(0xDCA7, 0xFF) self.vba.write_memory_at(0xDCA8, 0xFF) + def set_battle_type(self, battle_type): + """ + Changes the battle type value. + """ + self.vba.write_memory_at(0xd230, battle_type) + def get_gender(self): """ Returns 'male' or 'female'. -- cgit v1.2.3 From 4d5696251ce7feac89cb15d2c875e10e6cc8b831 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Mon, 11 Nov 2013 23:22:09 -0600 Subject: make a class that shows off the handler methods It doesn't do anything except show some of the methods that need to be implemented. --- pokemontools/vba/battle.py | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) (limited to 'pokemontools') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index c0c886e..5d05d8c 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -202,35 +202,31 @@ class Battle(EmulatorController): class BattleStrategy(Battle): """ - Throw a pokeball until everyone dies. + This class shows the relevant methods to make a battle handler. """ 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 + raise NotImplementedError - def handle_turn(self): + def handle_trainer_switch_prompt(self): """ - Take actions inside of a battle based on the game state. + The trainer is switching pokemon. The game asks yes/no for whether or + not the player would like to switch. """ - self.throw_pokeball() + raise NotImplementedError -class SimpleBattleStrategy(BattleStrategy): - """ - Attack the enemy with the first move. - """ + def handle_wild_switch_prompt(self): + """ + The wild pokemon defeated the party pokemon. This is the yes/no box for + whether to switch pokemon or not. + """ + raise NotImplementedError def handle_turn(self): """ - Always attack the enemy with the first move. + Take actions inside of a battle based on the game state. """ - self.attack(self.battle.party[0].moves[0].name) + raise NotImplementedError -- cgit v1.2.3 From d193e02a11ea5c4b86d9ac42a473b55e255c67ff Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Tue, 12 Nov 2013 02:11:09 -0600 Subject: select a battle menu option --- pokemontools/vba/battle.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) (limited to 'pokemontools') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index 5d05d8c..b1ede97 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -49,6 +49,56 @@ class Battle(EmulatorController): screentext = self.emulator.get_text() return all([sign in screentext for sign in signs]) + def select_battle_menu_action(self, action, execute=True): + """ + Moves the cursor to the requested action and selects it. + + :param action: fight, pkmn, pack, run + """ + if not self.is_fight_pack_run_menu(): + raise Exception( + "This isn't the fight-pack-run menu." + ) + + action = action.lower() + + action_map = { + "fight": (1, 1), + "pkmn": (1, 2), + "pack": (2, 1), + "run": (2, 2), + } + + if action not in action_map.keys(): + raise Exception( + "Unexpected requested action {0}".format(action) + ) + + current_row = self.vba.read_memory_at(0xcfa9) + current_column = self.vba.read_memory_at(0xcfaa) + + direction = None + if current_row != action_map[action][0]: + if current_row > action_map[action][0]: + direction = "u" + elif current_row < action_map[action][0]: + direction = "d" + + self.vba.press(direction, hold=5, after=10) + + direction = None + if current_column != action_map[action][1]: + if current_column > action_map[action][1]: + direction = "l" + elif current_column < action_map[action][1]: + direction = "r" + + self.vba.press(direction, hold=5, after=10) + + # now select the action + if execute: + self.vba.press(a, hold=5, after=100) + def is_player_turn(self): """ Detects if the battle is waiting for the player to choose an attack. -- cgit v1.2.3 From b44e7b6dee19c9029e68747315e4fc84e741a86b Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Tue, 12 Nov 2013 02:11:23 -0600 Subject: incomplete simple battle strategy --- pokemontools/vba/battle.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'pokemontools') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index b1ede97..b1ac0fe 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -280,3 +280,15 @@ class BattleStrategy(Battle): Take actions inside of a battle based on the game state. """ raise NotImplementedError + +class SpamBattleStrategy(BattleStrategy): + """ + A really simple battle strategy that always picks the first move of the + first pokemon to attack the enemy. + """ + + def handle_turn(self): + """ + Always picks the first move of the current pokemon. + """ + pass -- cgit v1.2.3 From 84c106e118e809884319a871972b61f038540a83 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Wed, 13 Nov 2013 19:43:47 -0600 Subject: battle move selection --- pokemontools/vba/battle.py | 74 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) (limited to 'pokemontools') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index b1ac0fe..c2cb5c3 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -99,6 +99,80 @@ class Battle(EmulatorController): if execute: self.vba.press(a, hold=5, after=100) + def select_attack(self, move_number=1, hold=5, after=10): + """ + Moves the cursor to the correct attack in the menu and presses the + button. + + :param move_number: the attack number on the FIGHT menu. Note that this + starts from 1. + :param hold: how many frames to hold each button press + :param after: how many frames to wait after each button press + """ + # TODO: detect fight menu and make sure it's detected here. + + pp_address = 0xc634 + (move_number - 1) + pp = self.vba.read_memory_at(pp_address) + + # detect zero pp because i don't want to write a way to inform the + # caller that there was no more pp. Just check the pp yourself. + if pp == 0: + raise BattleException( + "Move {num} has no more PP.".format( + num=move_number, + ) + ) + + valid_selection_states = (1, 2, 3, 4) + + selection = self.vba.read_memory_at(0xcfa9) + + while selection != move_number: + if selection not in valid_selection_states: + raise BattleException( + "The current selected attack is out of bounds: {num}".format( + num=selection, + ) + ) + + direction = None + + if selection > move_number: + direction = "d" + elif selection < move_number: + direction = "u" + else: + # probably never happens + raise BattleException( + "Not sure what to do here." + ) + + # press the arrow button + self.vba.press(direction, hold=hold, after=after) + + # let's see what the current selection is + selection = self.vba.read_memory_at(0xcfa9) + + # press to choose the attack + self.vba.press("a", hold=hold, after=after) + + def fight(self, move_number): + """ + Select FIGHT from the flight-pack-run menu and select the move + identified by move_number. + """ + # make sure the menu is detected + if not self.is_fight_pack_run_menu(): + raise BattleException( + "Wrong menu. Can't press FIGHT here." + ) + + # select FIGHT + self.select_battle_menu_action("fight") + + # select the requested attack + self.select_attack(move_number) + def is_player_turn(self): """ Detects if the battle is waiting for the player to choose an attack. -- cgit v1.2.3 From e8371152c9071c1e8966512c22ec63d61b7c8b8b Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Wed, 13 Nov 2013 19:52:40 -0600 Subject: self.vba -> self.emulator.vba --- pokemontools/vba/battle.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'pokemontools') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index c2cb5c3..180230d 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -74,8 +74,8 @@ class Battle(EmulatorController): "Unexpected requested action {0}".format(action) ) - current_row = self.vba.read_memory_at(0xcfa9) - current_column = self.vba.read_memory_at(0xcfaa) + current_row = self.emulator.vba.read_memory_at(0xcfa9) + current_column = self.emulator.vba.read_memory_at(0xcfaa) direction = None if current_row != action_map[action][0]: @@ -84,7 +84,7 @@ class Battle(EmulatorController): elif current_row < action_map[action][0]: direction = "d" - self.vba.press(direction, hold=5, after=10) + self.emulator.vba.press(direction, hold=5, after=10) direction = None if current_column != action_map[action][1]: @@ -93,11 +93,11 @@ class Battle(EmulatorController): elif current_column < action_map[action][1]: direction = "r" - self.vba.press(direction, hold=5, after=10) + self.emulator.vba.press(direction, hold=5, after=10) # now select the action if execute: - self.vba.press(a, hold=5, after=100) + self.emulator.vba.press(a, hold=5, after=100) def select_attack(self, move_number=1, hold=5, after=10): """ @@ -112,7 +112,7 @@ class Battle(EmulatorController): # TODO: detect fight menu and make sure it's detected here. pp_address = 0xc634 + (move_number - 1) - pp = self.vba.read_memory_at(pp_address) + pp = self.emulator.vba.read_memory_at(pp_address) # detect zero pp because i don't want to write a way to inform the # caller that there was no more pp. Just check the pp yourself. @@ -125,7 +125,7 @@ class Battle(EmulatorController): valid_selection_states = (1, 2, 3, 4) - selection = self.vba.read_memory_at(0xcfa9) + selection = self.emulator.vba.read_memory_at(0xcfa9) while selection != move_number: if selection not in valid_selection_states: @@ -148,13 +148,13 @@ class Battle(EmulatorController): ) # press the arrow button - self.vba.press(direction, hold=hold, after=after) + self.emulator.vba.press(direction, hold=hold, after=after) # let's see what the current selection is - selection = self.vba.read_memory_at(0xcfa9) + selection = self.emulator.vba.read_memory_at(0xcfa9) # press to choose the attack - self.vba.press("a", hold=hold, after=after) + self.emulator.vba.press("a", hold=hold, after=after) def fight(self, move_number): """ -- cgit v1.2.3 From 257ddde525c4cbe6c4623a444c5d82ef8039bb45 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Wed, 13 Nov 2013 21:02:34 -0600 Subject: press the "a" button (fix typo) --- pokemontools/vba/battle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pokemontools') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index 180230d..a0e111f 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -97,7 +97,7 @@ class Battle(EmulatorController): # now select the action if execute: - self.emulator.vba.press(a, hold=5, after=100) + self.emulator.vba.press("a", hold=5, after=100) def select_attack(self, move_number=1, hold=5, after=10): """ -- cgit v1.2.3 From 28393a5342a3f6ad3c1511f81ff86ab4fe9216a2 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Wed, 13 Nov 2013 21:44:21 -0600 Subject: fix a variable typo in wild prompt detector --- pokemontools/vba/vba.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pokemontools') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 57e5e1b..d221fef 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -634,7 +634,7 @@ class crystal(object): "Use next POKMON?", ] - return all([requirement in text for requirement in requirements]) + return all([requirement in screen_text for requirement in requirements]) def is_switch_prompt(self): """ -- cgit v1.2.3 From 79bc5088895d087b5338cb4519d7e5d748ce1faf Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Wed, 13 Nov 2013 21:45:05 -0600 Subject: better "is input required" detector --- pokemontools/vba/battle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pokemontools') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index a0e111f..409d9a8 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -38,7 +38,7 @@ class Battle(EmulatorController): """ Detects if the battle is waiting for player input. """ - return self.is_player_turn() or self.is_mandatory_switch() or self.is_trainer_switch_prompt() + return self.is_player_turn() or self.is_mandatory_switch() or self.is_switch_prompt() def is_fight_pack_run_menu(self): """ -- cgit v1.2.3 From dddc61e026163808da93ae3ca7121ec64b2bba8f Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Wed, 13 Nov 2013 22:43:35 -0600 Subject: a really simple, broken battle strategy --- pokemontools/vba/battle.py | 50 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 4 deletions(-) (limited to 'pokemontools') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index 409d9a8..1b582dd 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -260,8 +260,12 @@ class Battle(EmulatorController): """ Waits until the battle needs player input. """ - while not self.is_input_required(): - self.emulator.text_wait() + # callback causes text_wait to exit when the callback returns True + def is_in_battle_checker(): + return (self.emulator.vba.read_memory_at(0xd22d) == 0) + + while not self.is_input_required() and self.is_in_battle(): + self.emulator.text_wait(callback=is_in_battle_checker) # let the text draw so that the state is more obvious self.emulator.vba.step(count=10) @@ -276,9 +280,14 @@ class Battle(EmulatorController): # xyz wants to battle, a wild foobar appeared self.skip_start_text() + wild = (self.emulator.vba.read_memory_at(0xd22d) == 1) + while self.is_in_battle(): self.skip_until_input_required() + if not self.is_in_battle(): + continue + if self.is_player_turn(): # battle hook provides input to handle this situation self.handle_turn() @@ -294,7 +303,8 @@ class Battle(EmulatorController): # "how did i lose? wah" # TODO: this doesn't happen for wild battles - self.skip_end_text() + if not wild: + self.skip_end_text() # TODO: return should indicate win/loss (blackout) @@ -365,4 +375,36 @@ class SpamBattleStrategy(BattleStrategy): """ Always picks the first move of the current pokemon. """ - pass + self.fight(1) + + def handle_trainer_switch_prompt(self): + """ + The trainer is switching pokemon. The game asks yes/no for whether or + not the player would like to switch. + """ + # decline + self.emulator.vba.press(["b"], hold=5, after=10) + + def handle_wild_switch_prompt(self): + """ + The wild pokemon defeated the party pokemon. This is the yes/no box for + whether to switch pokemon or not. + """ + # why not just make a battle strategy that doesn't lose? + # TODO: Note that the longer "after" value is required here. + self.emulator.vba.press("a", hold=5, after=30) + + self.handle_mandatory_switch() + + def handle_mandatory_switch(self): + """ + Something fainted, pick the next mon. + """ + + # TODO: make a better selector for which pokemon. + + # now scroll down + self.emulator.vba.press("d", hold=5, after=10) + + # select this mon + self.emulator.vba.press("a", hold=5, after=30) -- cgit v1.2.3 From 367485303a0b40a4aec1ead83adf7f90fce59834 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Wed, 13 Nov 2013 22:44:04 -0600 Subject: improve text_wait for in-battle situations --- pokemontools/vba/vba.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'pokemontools') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index d221fef..5106004 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -462,16 +462,17 @@ class crystal(object): # date/time box (day choice) # 0x47ab is the one from the intro, 0x49ab is the one from mom. elif 0x47ab in stack or 0x49ab in stack: # was any([x in stack for x in range(0x46EE, 0x47AB)]) - print "probably at a date/time box ? exiting." - break + # if not in battle + if self.vba.read_memory_at(0xd22d) == 0: + print "probably at a date/time box ? exiting." + break # "How many minutes?" selection box elif 0x4826 in stack: print "probably at a \"How many minutes?\" box ? exiting." break - else: - self.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 -- cgit v1.2.3 From 811630a5dd82c571836c03131b00ff2608ea5d25 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Wed, 13 Nov 2013 23:07:26 -0600 Subject: a better in-battle check for text_wait --- pokemontools/vba/battle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pokemontools') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index 1b582dd..7c788b9 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -262,7 +262,7 @@ class Battle(EmulatorController): """ # callback causes text_wait to exit when the callback returns True def is_in_battle_checker(): - return (self.emulator.vba.read_memory_at(0xd22d) == 0) + return (self.emulator.vba.read_memory_at(0xd22d) == 0) and (self.emulator.vba.read_memory_at(0xc734) != 0) while not self.is_input_required() and self.is_in_battle(): self.emulator.text_wait(callback=is_in_battle_checker) -- cgit v1.2.3 From d8ab944d4354546d44d098b2c30ba82b6ff509ee Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Wed, 13 Nov 2013 23:07:44 -0600 Subject: simplify the date/clock check in text_wait --- pokemontools/vba/vba.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'pokemontools') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index 5106004..c7a0bf9 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -462,8 +462,7 @@ class crystal(object): # date/time box (day choice) # 0x47ab is the one from the intro, 0x49ab is the one from mom. elif 0x47ab in stack or 0x49ab in stack: # was any([x in stack for x in range(0x46EE, 0x47AB)]) - # if not in battle - if self.vba.read_memory_at(0xd22d) == 0: + if not self.is_in_battle(): print "probably at a date/time box ? exiting." break -- cgit v1.2.3 From 106f45ac7acd657834c5ac2cd5a7d1198ddea95c Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 14 Nov 2013 00:43:35 -0600 Subject: a really slow way to check for stats screen --- pokemontools/vba/battle.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) (limited to 'pokemontools') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index 7c788b9..898a594 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -220,6 +220,22 @@ class Battle(EmulatorController): else: return False + def is_levelup_screen(self): + """ + Detects the levelup stats screen. + """ + requirements = [ + "ATTACK ", + "DEFENSE ", + "SPCL.ATK ", + "SPCL.DEF ", + "SPEED ", + ] + + screen_text = self.emulator.get_text() + + return all([requirement in screen_text for requirement in requirements]) + def skip_start_text(self, max_loops=20): """ Skip any initial conversation until the player can select an action. @@ -262,7 +278,12 @@ class Battle(EmulatorController): """ # callback causes text_wait to exit when the callback returns True def is_in_battle_checker(): - return (self.emulator.vba.read_memory_at(0xd22d) == 0) and (self.emulator.vba.read_memory_at(0xc734) != 0) + result = (self.emulator.vba.read_memory_at(0xd22d) == 0) and (self.emulator.vba.read_memory_at(0xc734) != 0) + + # but also, jump out if it's the stats screen + result = result or self.is_levelup_screen() + + return result while not self.is_input_required() and self.is_in_battle(): self.emulator.text_wait(callback=is_in_battle_checker) @@ -298,6 +319,8 @@ class Battle(EmulatorController): elif self.is_mandatory_switch(): # battle hook provides input to handle this situation too self.handle_mandatory_switch() + elif self.is_levelup_screen(): + self.emulator.vba.press("a", hold=5, after=30) else: raise BattleException("unknown state, aborting") -- cgit v1.2.3 From dc0a7ac670915a089d1b1ba27df0b5daca2b56f3 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 14 Nov 2013 00:43:52 -0600 Subject: make get_text slightly more configurable Add params to the get_text() function to dump text tiles from screen. --- pokemontools/vba/vba.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'pokemontools') diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py index c7a0bf9..204e102 100644 --- a/pokemontools/vba/vba.py +++ b/pokemontools/vba/vba.py @@ -718,14 +718,14 @@ class crystal(object): self.vba.write_memory_at(0xd8dc, 5) self.vba.write_memory_at(0xd8dd, 99) - def get_text(self, chars=chars): + def get_text(self, chars=chars, offset=0, bounds=1000): """ Returns alphanumeric text on the screen. Other characters will not be shown. """ output = "" - tiles = self.vba.memory[0xc4a0:0xc4a0 + 1000] + tiles = self.vba.memory[0xc4a0 + offset:0xc4a0 + offset + bounds] for each in tiles: if each in chars.keys(): thing = chars[each] -- cgit v1.2.3 From 1bbfb2869fd011416d4ca4883105d7308a562655 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 14 Nov 2013 01:08:08 -0600 Subject: a faster way to detect the stats screen in battles The other way was way too slow since it had to parse 1000 characters every frame. --- pokemontools/vba/battle.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'pokemontools') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index 898a594..8ba534c 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -38,7 +38,7 @@ class Battle(EmulatorController): """ Detects if the battle is waiting for player input. """ - return self.is_player_turn() or self.is_mandatory_switch() or self.is_switch_prompt() + return self.is_player_turn() or self.is_mandatory_switch() or self.is_switch_prompt() or self.is_levelup_screen() def is_fight_pack_run_menu(self): """ @@ -224,17 +224,17 @@ class Battle(EmulatorController): """ Detects the levelup stats screen. """ - requirements = [ - "ATTACK ", - "DEFENSE ", - "SPCL.ATK ", - "SPCL.DEF ", - "SPEED ", - ] + # This is implemented as reading some text on the screen instead of + # using get_text() because checking every loop is really slow. - screen_text = self.emulator.get_text() + address = 0xc50f + values = [146, 143, 130, 139] - return all([requirement in screen_text for requirement in requirements]) + for (index, value) in enumerate(values): + if self.emulator.vba.read_memory_at(address + index) != value: + return False + else: + return True def skip_start_text(self, max_loops=20): """ -- cgit v1.2.3 From 038a9d8ec093e7c73f0a596d0cc996e3971d7d0f Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 14 Nov 2013 01:44:40 -0600 Subject: a quick method to detect the "is evolving" message --- pokemontools/vba/battle.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'pokemontools') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index 8ba534c..b800df0 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -236,6 +236,27 @@ class Battle(EmulatorController): else: return True + def is_evolution_screen(self): + """ + What? MEW is evolving! + """ + address = 0xc5e4 + + values = [164, 181, 174, 171, 181, 168, 173, 166, 231] + + for (index, value) in enumerate(values): + if self.emulator.vba.read_memory_at(address + index) != value: + return False + else: + # also check "What?" + what_address = 0xc5b9 + what_values = [150, 167, 160, 179, 230] + for (index, value) in enumerate(what_values): + if self.emulator.vba.read_memory_at(what_address + index) != value: + return False + else: + return True + def skip_start_text(self, max_loops=20): """ Skip any initial conversation until the player can select an action. @@ -283,6 +304,9 @@ class Battle(EmulatorController): # but also, jump out if it's the stats screen result = result or self.is_levelup_screen() + # stay in text_wait if it's the evolution screen + result = result and not self.is_evolution_screen() + return result while not self.is_input_required() and self.is_in_battle(): -- cgit v1.2.3 From 05faa7784ad03cb057e9ac478606d211c4cd88eb Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 14 Nov 2013 19:57:49 -0600 Subject: quick check for the evolved screen This is the message that appears after the pokemon evolved. --- pokemontools/vba/battle.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'pokemontools') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index b800df0..3339a9f 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -257,6 +257,26 @@ class Battle(EmulatorController): else: return True + def is_evolved_screen(self): + """ + Checks if the screen is the "evolved into METAPOD!" screen. Note that + this only works inside of a battle. This is because there may be other + text boxes that have the same text when outside of battle. But within a + battle, this is probably the only time the text "evolved into ... !" is + seen. + """ + if not self.is_in_battle(): + return False + + address = 0x4bb1 + values = [164, 181, 174, 171, 181, 164, 163, 127, 168, 173, 179, 174, 79] + + for (index, value) in enumerate(values): + if self.emulator.vba.read_memory_at(address + index) != value: + return False + else: + return True + def skip_start_text(self, max_loops=20): """ Skip any initial conversation until the player can select an action. -- cgit v1.2.3 From 3bf53fa87facc5471ee0b06c496fee6e8eaceefb Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 14 Nov 2013 20:00:59 -0600 Subject: detect the evolved screen text --- pokemontools/vba/battle.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'pokemontools') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index 3339a9f..4b7fb68 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -365,6 +365,8 @@ class Battle(EmulatorController): self.handle_mandatory_switch() elif self.is_levelup_screen(): self.emulator.vba.press("a", hold=5, after=30) + elif self.is_evolved_screen(): + self.emulator.vba.step(count=30) else: raise BattleException("unknown state, aborting") -- cgit v1.2.3 From b2785948d16acd06197db5fcffea45d54778bdba Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 14 Nov 2013 20:04:22 -0600 Subject: fast check for the "move to make room for" text --- pokemontools/vba/battle.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'pokemontools') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index 4b7fb68..f8457c9 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -277,6 +277,22 @@ class Battle(EmulatorController): else: return True + def is_make_room_for_move_prompt(self): + """ + Detects the prompt that asks whether to make room for a move. + """ + if not self.is_in_battle(): + return False + + address = 0xc5b9 + values = [172, 174, 181, 164, 127, 179, 174, 127, 172, 160, 170, 164, 127, 177, 174, 174, 172] + + for (index, value) in enumerate(values): + if self.emulator.vba.read_memory_at(address + index) != value: + return False + else: + return True + def skip_start_text(self, max_loops=20): """ Skip any initial conversation until the player can select an action. -- cgit v1.2.3 From 1b9212a7e47a8eca382249bd7bb116c69fc789d3 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 14 Nov 2013 20:16:35 -0600 Subject: never learn a new move --- pokemontools/vba/battle.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) (limited to 'pokemontools') diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py index f8457c9..39d7047 100644 --- a/pokemontools/vba/battle.py +++ b/pokemontools/vba/battle.py @@ -38,7 +38,7 @@ class Battle(EmulatorController): """ Detects if the battle is waiting for player input. """ - return self.is_player_turn() or self.is_mandatory_switch() or self.is_switch_prompt() or self.is_levelup_screen() + return self.is_player_turn() or self.is_mandatory_switch() or self.is_switch_prompt() or self.is_levelup_screen() or self.is_make_room_for_move_prompt() def is_fight_pack_run_menu(self): """ @@ -340,6 +340,9 @@ class Battle(EmulatorController): # but also, jump out if it's the stats screen result = result or self.is_levelup_screen() + # jump out if it's the "make room for a new move" screen + result = result or self.is_make_room_for_move_prompt() + # stay in text_wait if it's the evolution screen result = result and not self.is_evolution_screen() @@ -361,6 +364,9 @@ class Battle(EmulatorController): # xyz wants to battle, a wild foobar appeared self.skip_start_text() + # skip a few hundred frames + self.emulator.vba.step(count=100) + wild = (self.emulator.vba.read_memory_at(0xd22d) == 1) while self.is_in_battle(): @@ -383,6 +389,8 @@ class Battle(EmulatorController): self.emulator.vba.press("a", hold=5, after=30) elif self.is_evolved_screen(): self.emulator.vba.step(count=30) + elif self.is_make_room_for_move_prompt(): + self.handle_make_room_for_move() else: raise BattleException("unknown state, aborting") @@ -450,6 +458,12 @@ class BattleStrategy(Battle): """ raise NotImplementedError + def handle_make_room_for_move(self): + """ + Choose yes/no then handle learning the move. + """ + raise NotImplementedError + class SpamBattleStrategy(BattleStrategy): """ A really simple battle strategy that always picks the first move of the @@ -493,3 +507,15 @@ class SpamBattleStrategy(BattleStrategy): # select this mon self.emulator.vba.press("a", hold=5, after=30) + + def handle_make_room_for_move(self): + """ + Choose yes/no then handle learning the move. + """ + # make room? no + self.emulator.vba.press("b", hold=5, after=100) + + # stop learning? yes + self.emulator.vba.press("a", hold=5, after=20) + + self.emulator.text_wait() -- cgit v1.2.3