blob: 87cd7b1c0fd3ecfc729e45101d2d0db6975c93be (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
|
"""
Code that attempts to model a battle.
"""
from pokemontools.vba.vba import crystal as emulator
import pokemontools.vba.vba as vba
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.
"""
def __init__(self, emulator=None):
"""
Setup the battle.
"""
self.emulator = emulator
def is_in_battle(self):
"""
@rtype: bool
"""
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
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):
"""
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):
"""
Detects if the battle is waiting for the player to choose a next
pokemon.
"""
# 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
if "CANCEL Which ?" in self.emulator.get_text():
return True
else:
return False
def skip_start_text(self, max_loops=20):
"""
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:
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):
"""
Skip through any text that appears after the final attack.
"""
if not self.is_in_battle():
# 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:
self.emulator.text_wait()
loops -= 1
if loops <= 0:
raise Exception("Couldn't get out of the battle.")
def skip_until_input_required(self):
"""
Waits until the battle needs player input.
"""
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.
"""
# 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_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")
# "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.
"""
raise NotImplementedError
def handle_turn(self):
"""
Take actions inside of a battle based on the game state.
"""
raise NotImplementedError
class BattleStrategy(Battle):
"""
Throw a pokeball until everyone dies.
"""
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.throw_pokeball()
class SimpleBattleStrategy(BattleStrategy):
"""
Attack the enemy with the first move.
"""
def handle_turn(self):
"""
Always attack the enemy with the first move.
"""
self.attack(self.battle.party[0].moves[0].name)
|