-
Notifications
You must be signed in to change notification settings - Fork 47
/
rock_smash.py
341 lines (294 loc) · 13.4 KB
/
rock_smash.py
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
import contextlib
from typing import Generator
from modules.context import context
from modules.debug import debug
from modules.encounter import handle_encounter
from modules.gui.multi_select_window import Selection, ask_for_choice
from modules.items import get_item_bag, get_item_by_name
from modules.map_data import MapRSE
from modules.map_path import calculate_path
from modules.memory import get_event_flag, get_event_var
from modules.player import TileTransitionState, get_player, get_player_avatar, AvatarFlags, get_player_location
from modules.pokemon import get_opponent
from modules.runtime import get_sprites_path
from modules.save_data import get_save_data
from modules.tasks import task_is_active, get_global_script_context
from . import BattleAction
from ._asserts import SavedMapLocation, assert_has_pokemon_with_move, assert_save_game_exists, assert_saved_on_map
from ._interface import BotMode, BotModeError
from .util import (
navigate_to,
follow_waypoints,
RanOutOfRepels,
apply_repel,
apply_white_flute_if_available,
ensure_facing_direction,
follow_path,
replenish_repel,
soft_reset,
wait_for_player_avatar_to_be_standing_still,
wait_for_script_to_start_and_finish,
wait_for_unique_rng_value,
wait_until_task_is_not_active,
walk_one_tile,
)
class RockSmashMode(BotMode):
@staticmethod
def name() -> str:
return "Rock Smash"
@staticmethod
def is_selectable() -> bool:
if not context.rom.is_rse:
return False
return get_player_avatar().map_group_and_number in (
MapRSE.GRANITE_CAVE_B2F,
MapRSE.ROUTE121_SAFARI_ZONE_ENTRANCE,
MapRSE.SAFARI_ZONE_SOUTH,
MapRSE.SAFARI_ZONE_NORTHEAST,
MapRSE.SAFARI_ZONE_SOUTHEAST,
)
def __init__(self):
super().__init__()
self._in_safari_zone = False
self._using_repel = False
self._using_mach_bike = False
def on_safari_zone_timeout(self) -> bool:
self._in_safari_zone = False
return True
def on_battle_started(self) -> BattleAction | None:
return handle_encounter(get_opponent(), disable_auto_battle=True)
def on_repel_effect_ended(self) -> None:
if self._using_repel:
try:
replenish_repel()
except RanOutOfRepels:
context.controller_stack.insert(len(context.controller_stack) - 1, self.reset_and_wait())
def run(self) -> Generator:
self._in_safari_zone = False
self._using_repel = False
if not get_event_flag("BADGE03_GET"):
raise BotModeError(
"You do not have the Dynamo Badge, which is necessary to use Rock Smash outside of battle."
)
assert_has_pokemon_with_move(
"Rock Smash", "None of your party Pokémon know the move Rock Smash. Please teach it to someone."
)
if get_player_avatar().map_group_and_number in (
MapRSE.ROUTE121_SAFARI_ZONE_ENTRANCE,
MapRSE.SAFARI_ZONE_SOUTH,
MapRSE.SAFARI_ZONE_NORTHEAST,
MapRSE.SAFARI_ZONE_SOUTHEAST,
):
assert_save_game_exists("There is no saved game. Cannot soft reset.")
assert_saved_on_map(
SavedMapLocation(MapRSE.ROUTE121_SAFARI_ZONE_ENTRANCE),
"In order to rock smash for Shuckle you should save in the entrance building to the Safari Zone.",
)
if get_player_avatar().map_group_and_number == MapRSE.GRANITE_CAVE_B2F and get_item_bag().number_of_repels > 0:
mode = ask_for_choice(
[
Selection("Use Repel", get_sprites_path() / "items" / "Repel.png"),
Selection("No Repel", get_sprites_path() / "other" / "No Repel.png"),
],
window_title="Use Repel?",
)
if mode == "Use Repel":
assert_save_game_exists("There is no saved game. Cannot soft reset.")
assert_saved_on_map(
SavedMapLocation(MapRSE.GRANITE_CAVE_B2F),
"In order to use Repel, you need to save on this map.",
)
save_data = get_save_data()
party = save_data.get_party()
if len(party) == 0 or party[0].is_egg or party[0].level < 13 or party[0].level > 16:
raise BotModeError(
"In order to use Repel, you must have a lead Pokémon with level 13-16. "
"For best encounter rates, use Level 13!"
)
if save_data.get_item_bag().number_of_repels == 0:
raise BotModeError("In your saved game, you do not have any Repels.")
self._using_repel = True
yield from self.reset_and_wait()
self._using_mach_bike = get_player().registered_item == get_item_by_name("Mach Bike")
starting_cash = get_player().money
while True:
match get_player_avatar().map_group_and_number:
case MapRSE.GRANITE_CAVE_B2F:
starting_frame = context.emulator.get_frame_count()
for _ in self.granite_cave():
# Detect reset
if context.emulator.get_frame_count() < starting_frame:
break
yield
case MapRSE.ROUTE121_SAFARI_ZONE_ENTRANCE:
current_cash = get_player().money
if current_cash < 500 or starting_cash - current_cash > 25000:
yield from soft_reset()
yield from wait_for_unique_rng_value()
for _ in range(5):
yield
starting_cash = get_player().money
yield from self.enter_safari_zone()
case MapRSE.SAFARI_ZONE_NORTHEAST:
self._in_safari_zone = True
for _ in self.safari_zone():
if self._in_safari_zone:
yield
else:
break
case MapRSE.SAFARI_ZONE_SOUTH | MapRSE.SAFARI_ZONE_SOUTHEAST:
self._in_safari_zone = True
while (
get_player_avatar().map_group_and_number == MapRSE.SAFARI_ZONE_SOUTH
and get_player_avatar().local_coordinates in ((32, 33), (32, 34))
) or get_global_script_context().is_active:
yield
yield from wait_for_player_avatar_to_be_standing_still()
if self._using_mach_bike:
yield from self.mount_bicycle()
yield from navigate_to(MapRSE.SAFARI_ZONE_NORTHEAST, (15, 7))
yield from self.unmount_bicycle()
else:
yield from navigate_to(MapRSE.SAFARI_ZONE_NORTHEAST, (12, 7))
@staticmethod
@debug.track
def reset_and_wait():
context.emulator.reset_held_buttons()
yield from soft_reset()
yield from wait_for_unique_rng_value()
yield from wait_for_player_avatar_to_be_standing_still()
@staticmethod
@debug.track
def smash(flag_name):
if not get_event_flag(flag_name):
yield from wait_for_script_to_start_and_finish("EventScript_RockSmash", "A")
while get_player_avatar().tile_transition_state != TileTransitionState.NOT_MOVING:
yield
if task_is_active("Task_ReturnToFieldNoScript"):
yield from wait_until_task_is_not_active("Task_ReturnToFieldNoScript")
if task_is_active("Task_ExitNonDoor"):
yield from wait_until_task_is_not_active("Task_ExitNonDoor")
yield
@debug.track
def mount_bicycle(self) -> Generator:
while AvatarFlags.OnMachBike not in get_player_avatar().flags:
context.emulator.press_button("Select")
yield
yield
@debug.track
def unmount_bicycle(self) -> Generator:
while AvatarFlags.OnMachBike in get_player_avatar().flags:
context.emulator.press_button("Select")
yield
yield
@debug.track
def granite_cave(self) -> Generator:
if self._using_repel and get_event_var("REPEL_STEP_COUNT") <= 0:
with contextlib.suppress(RanOutOfRepels):
yield from apply_repel()
yield from navigate_to(MapRSE.GRANITE_CAVE_B2F, (6, 21))
yield from ensure_facing_direction("Down")
# With Repel active, White Flute boosts encounters by 30-40%, but without Repel it
# actually _decreases_ encounter rates (due to so many regular encounters popping up
# while walking around.) So we only enable White Flute if Repel is also active.
if self._using_repel:
yield from apply_white_flute_if_available()
yield from self.smash("TEMP_16")
yield from follow_path([(4, 21)])
yield from ensure_facing_direction("Left")
yield from self.smash("TEMP_17")
yield from ensure_facing_direction("Down")
yield from self.smash("TEMP_15")
yield from follow_path([(4, 16), (3, 16)])
yield from ensure_facing_direction("Left")
yield from self.smash("TEMP_13")
yield from follow_path([(3, 15)])
yield from ensure_facing_direction("Up")
yield from self.smash("TEMP_12")
yield from follow_path([(5, 15)])
yield from ensure_facing_direction("Up")
yield from self.smash("TEMP_11")
yield from follow_path([(7, 15), (7, 13)])
yield from ensure_facing_direction("Up")
yield from self.smash("TEMP_14")
yield from navigate_to(MapRSE.GRANITE_CAVE_B2F, (29, 13))
yield from walk_one_tile("Up")
yield from walk_one_tile("Down")
yield from navigate_to(MapRSE.GRANITE_CAVE_B2F, (7, 13))
yield from ensure_facing_direction("Up")
if self._using_repel:
yield from apply_white_flute_if_available()
yield from self.smash("TEMP_14")
yield from follow_path([(7, 14), (6, 14)])
yield from ensure_facing_direction("Left")
yield from self.smash("TEMP_11")
yield from follow_path([(4, 14)])
yield from ensure_facing_direction("Left")
yield from self.smash("TEMP_12")
yield from follow_path([(4, 16), (3, 16)])
yield from ensure_facing_direction("Left")
yield from self.smash("TEMP_13")
yield from follow_path([(4, 16), (4, 21)])
yield from ensure_facing_direction("Down")
yield from self.smash("TEMP_15")
yield from ensure_facing_direction("Left")
yield from self.smash("TEMP_17")
yield from follow_path([(6, 21)])
yield from ensure_facing_direction("Down")
yield from self.smash("TEMP_16")
yield from navigate_to(MapRSE.GRANITE_CAVE_B2F, (28, 21))
yield from wait_for_player_avatar_to_be_standing_still()
yield from walk_one_tile("Down")
yield from walk_one_tile("Up")
@debug.track
def enter_safari_zone(self):
if get_player().money < 500:
raise BotModeError("You do not have enough cash to re-enter the Safari Zone.")
yield from navigate_to(MapRSE.ROUTE121_SAFARI_ZONE_ENTRANCE, (9, 4))
yield from ensure_facing_direction("Left")
context.emulator.hold_button("Left")
for _ in range(10):
yield
context.emulator.release_button("Left")
yield
yield from wait_for_script_to_start_and_finish(
"Route121_SafariZoneEntrance_EventScript_EntranceCounterTrigger", "A"
)
yield from wait_for_script_to_start_and_finish(
"Route121_SafariZoneEntrance_EventScript_TryEnterSafariZone", "A"
)
while (
get_player_avatar().local_coordinates != (32, 35)
or get_player_avatar().tile_transition_state != TileTransitionState.NOT_MOVING
):
yield
@debug.track
def safari_zone(self):
yield from navigate_to(MapRSE.SAFARI_ZONE_NORTHEAST, (12, 7))
yield from ensure_facing_direction("Down")
yield from self.smash("TEMP_12")
yield from follow_path([(10, 7)])
yield from ensure_facing_direction("Left")
yield from self.smash("TEMP_11")
yield from follow_path([(10, 11)])
yield from ensure_facing_direction("Right")
yield from self.smash("TEMP_15")
yield from follow_path([(8, 11)])
yield from ensure_facing_direction("Up")
yield from self.smash("TEMP_14")
yield from follow_path([(8, 12)])
yield from ensure_facing_direction("Down")
yield from self.smash("TEMP_13")
if self._using_mach_bike:
yield from self.mount_bicycle()
def bike_waypoints():
point_a = (MapRSE.SAFARI_ZONE_SOUTHEAST, (8, 0))
point_b = (MapRSE.SAFARI_ZONE_SOUTHEAST, (7, 0))
point_c = (MapRSE.SAFARI_ZONE_NORTHEAST, (15, 7))
yield from calculate_path(get_player_location(), point_a)
yield from calculate_path(point_a, point_b)
yield from calculate_path(point_b, point_c)
yield from follow_waypoints(bike_waypoints())
yield from self.unmount_bicycle()
else:
yield from navigate_to(MapRSE.SAFARI_ZONE_SOUTHEAST, (8, 0))