summaryrefslogtreecommitdiff
path: root/pokemontools/vba
diff options
context:
space:
mode:
Diffstat (limited to 'pokemontools/vba')
-rw-r--r--pokemontools/vba/battle.py129
-rw-r--r--pokemontools/vba/vba.py57
2 files changed, 172 insertions, 14 deletions
diff --git a/pokemontools/vba/battle.py b/pokemontools/vba/battle.py
index 87cd7b1..b1ac0fe 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_trainer_switch_prompt()
def is_fight_pack_run_menu(self):
"""
@@ -49,12 +49,86 @@ 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.
"""
return self.is_fight_pack_run_menu()
+ 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_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
@@ -134,6 +208,10 @@ class Battle(EmulatorController):
if self.is_player_turn():
# battle hook provides input to handle this situation
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()
@@ -141,6 +219,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)
@@ -151,6 +230,20 @@ class Battle(EmulatorController):
"""
raise NotImplementedError
+ 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.
+ """
+ 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.
@@ -159,35 +252,43 @@ 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?")
+ raise NotImplementedError
+
+ 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.
+ """
+ raise NotImplementedError
- return pokemon.id
+ 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.
"""
- self.throw_pokeball()
+ raise NotImplementedError
-class SimpleBattleStrategy(BattleStrategy):
+class SpamBattleStrategy(BattleStrategy):
"""
- Attack the enemy with the first move.
+ A really simple battle strategy that always picks the first move of the
+ first pokemon to attack the enemy.
"""
def handle_turn(self):
"""
- Always attack the enemy with the first move.
+ Always picks the first move of the current pokemon.
"""
- self.attack(self.battle.party[0].moves[0].name)
+ pass
diff --git a/pokemontools/vba/vba.py b/pokemontools/vba/vba.py
index 02fd99d..57e5e1b 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.
@@ -592,6 +599,50 @@ class crystal(object):
def is_in_link_battle(self):
return self.vba.read_memory_at(0xc2dc) != 0
+ 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
+ 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()
+
+ requirements = [
+ "YES",
+ "NO",
+ "Will ",
+ "change POKMON?",
+ ]
+
+ 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.
+ """
+ # 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):
+ """
+ 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.
@@ -604,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'.