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
|
Much of the game logic can be changed via the files in [data/](../blob/master/data/), but some things are hard-coded and can be tricky to find. This page lists things that may trip you up when hacking.
## Contents
- [Tilesets that have per-mapgroup roofs](#tilesets-that-have-per-mapgroup-roofs)
- [Maps that don't display a location sign](#maps-that-dont-display-a-location-sign)
- [Outdoor maps within indoor maps don't confuse Dig or Escape Rope](#outdoor-maps-within-indoor-maps-dont-confuse-dig-or-escape-rope)
- [Landmark limits when scrolling in the Town Map](#landmark-limits-when-scrolling-in-the-town-map)
- [Spawn points when you start and finish the game](#spawn-points-when-you-start-and-finish-the-game)
- [Trainer classes with different battle music](#trainer-classes-with-different-battle-music)
- [Trainer classes with different victory music](#trainer-classes-with-different-victory-music)
- [`RIVAL1`'s first Pokémon has no held item](#rival1s-first-pokémon-has-no-held-item)
- [`RIVAL1` and `RIVAL2` don't print their trainer class in battle](#rival1-and-rival2-dont-print-their-trainer-class-in-battle)
- [Vital Throw always goes last](#vital-throw-always-goes-last)
## Tilesets that have per-mapgroup roofs
This is caused by `LoadTilesetGFX` in [home/map.asm](../blob/master/home/map.asm):
```asm
; These tilesets support dynamic per-mapgroup roof tiles.
ld a, [wTileset]
cp TILESET_JOHTO
jr z, .load_roof
cp TILESET_JOHTO_MODERN
jr z, .load_roof
cp TILESET_BATTLE_TOWER_OUTSIDE
jr z, .load_roof
jr .skip_roof
.load_roof
farcall LoadMapGroupRoof
.skip_roof
```
## Maps that don't display a location sign
This is caused by `ReturnFromMapSetupScript.CheckSpecialMap` in [engine/events/map_name_sign.asm](../blob/master/engine/events/map_name_sign.asm):
```asm
.CheckSpecialMap:
; These landmarks do not get pop-up signs.
cp -1
ret z
cp SPECIAL_MAP
ret z
cp RADIO_TOWER
ret z
cp LAV_RADIO_TOWER
ret z
cp UNDERGROUND_PATH
ret z
cp INDIGO_PLATEAU
ret z
cp POWER_PLANT
ret z
ld a, 1
and a
ret
```
## Outdoor maps within indoor maps don't confuse Dig or Escape Rope
Dig and Escape Rope take you out of a dungeon and back to the entrance you used. However, some dungeons are designed with an enclosed outdoor portion, and it would be bad if visiting those portions made Dig or Escape Rope take you back to *them* instead of properly outside the dungeon.
There's no "outdoor-within-indoor" map environment, so the few maps in this situation have to be hard-coded. It's caused by `LoadWarpData.SaveDigWarp` in [engine/overworld/warp_connection.asm](../blob/master/engine/overworld/warp_connection.asm):
```asm
; MOUNT_MOON_SQUARE and TIN_TOWER_ROOF are outdoor maps within indoor maps.
; Dig and Escape Rope should not take you to them.
ld a, [wPrevMapGroup]
cp GROUP_MOUNT_MOON_SQUARE ; aka GROUP_TIN_TOWER_ROOF
jr nz, .not_mt_moon_or_tin_tower
ld a, [wPrevMapNumber]
cp MAP_MOUNT_MOON_SQUARE
ret z
cp MAP_TIN_TOWER_ROOF
ret z
.not_mt_moon_or_tin_tower
```
## Landmark limits when scrolling in the Town Map
This is caused by `PokegearMap_KantoMap` and `PokegearMap_JohtoMap` in [engine/pokegear/pokegear.asm](../blob/master/engine/pokegear/pokegear.asm):
```asm
PokegearMap_KantoMap:
call TownMap_GetKantoLandmarkLimits
jr PokegearMap_ContinueMap
PokegearMap_JohtoMap:
ld d, SILVER_CAVE
ld e, NEW_BARK_TOWN
PokegearMap_ContinueMap:
...
TownMap_GetKantoLandmarkLimits:
ld a, [wStatusFlags]
bit STATUSFLAGS_HALL_OF_FAME_F, a
jr z, .not_hof
ld d, ROUTE_28
ld e, PALLET_TOWN
ret
.not_hof
ld d, ROUTE_28
ld e, VICTORY_ROAD
ret
```
If you access a map that's outside the limits, then scrolling through the Town Map can underflow and go past the defined landmark data, displaying garbage. ([Video](https://www.youtube.com/watch?v=r32agevxdw4))
## Spawn points when you start and finish the game
These are defined in [engine/menus/intro_menu.asm](../blob/master/engine/menus/intro_menu.asm):
```asm
ld a, LANDMARK_NEW_BARK_TOWN
ld [wPrevLandmark], a
ld a, SPAWN_HOME
ld [wDefaultSpawnpoint], a
```
```asm
.SpawnAfterE4:
ld a, SPAWN_NEW_BARK
ld [wDefaultSpawnpoint], a
call PostCreditsSpawn
jp FinishContinueFunction
SpawnAfterRed:
ld a, SPAWN_MT_SILVER
ld [wDefaultSpawnpoint], a
```
(The maps and coordinates that correspond to those spawn points are not hard-coded; they're in the `SpawnPoints` table in [data/maps/spawn_points.asm](../blob/master/data/maps/spawn_points.asm).)
## `RIVAL1`'s first Pokémon has no held item
This is caused by `InitEnemyTrainer` in [engine/battle/core.asm](../blob/master/engine/battle/core.asm):
```asm
; RIVAL1's first mon has no held item
ld a, [wTrainerClass]
cp RIVAL1
jr nz, .ok
xor a
ld [wOTPartyMon1Item], a
.ok
```
## Trainer classes with different battle music
This is caused by `PlayBattleMusic` in [engine/battle/start_battle.asm](../blob/master/engine/battle/start_battle.asm). The routine's logic is:
1. If `[wBattleType]` is `BATTLETYPE_SUICUNE` or `BATTLETYPE_ROAMING`, play `MUSIC_SUICUNE_BATTLE`.
2. If it's a wild battle, check the region and time.
1. If we're in Kanto, play `MUSIC_KANTO_WILD_BATTLE`.
2. If it's night (and we must be in Johto), play `MUSIC_JOHTO_WILD_BATTLE_NIGHT`.
3. We must be in Johto during morning or day; play `MUSIC_JOHTO_WILD_BATTLE`.
3. It must be a trainer battle; check the values of `[wOtherTrainerClass]` and `[wOtherTrainerID]`:
1. If `[wOtherTrainerClass]` is `CHAMPION` or `RED`, play `MUSIC_CHAMPION_BATTLE`.
2. If `[wOtherTrainerClass]` is `GRUNTM` or `GRUNTF`, play `MUSIC_ROCKET_BATTLE`. (They should have included `EXECUTIVEM`, `EXECUTIVEF`, and `SCIENTIST` too…)
3. If `[wOtherTrainerClass]` is listed under `KantoGymLeaders` in [data/trainers/leaders.asm](../blob/master/data/trainers/leaders.asm), play `MUSIC_KANTO_GYM_LEADER_BATTLE`.
4. If `[wOtherTrainerClass]` is listed under `GymLeaders` in [data/trainers/leaders.asm](../blob/master/data/trainers/leaders.asm), play `MUSIC_JOHTO_GYM_LEADER_BATTLE`. (`CHAMPION`, `RED`, and the Kanto Gym leaders are listed but were already handled in step 3.i.)
5. If `[wOtherTrainerClass]` is `RIVAL2` and `[wOtherTrainerID]` is at least `RIVAL2_2_CHIKORITA` (i.e. we're battling our rival in Indigo Plateau), play `MUSIC_CHAMPION_BATTLE`.
6. If `[wOtherTrainerClass]` is `RIVAL1` or `RIVAL2`, play `MUSIC_RIVAL_BATTLE`.
4. If it's a link battle, play `MUSIC_JOHTO_TRAINER_BATTLE`.
5. If we're in Kanto, play `MUSIC_KANTO_TRAINER_BATTLE`.
6. We must be in Johto; play `MUSIC_JOHTO_TRAINER_BATTLE`.
## Trainer classes with different victory music
This is caused by `PlayVictoryMusic` in [engine/battle/core.asm](../blob/master/engine/battle/core.asm). The routine's logic is:
1. Play `MUSIC_NONE`, silencing the battle music.
2. If `[wBattleMode]` is not `WILD_BATTLE` (and so must be `TRAINER_BATTLE`):
1. If `[wOtherTrainerClass]` is listed under `GymLeaders` in [data/trainers/leaders.asm](../blob/master/data/trainers/leaders.asm), play `MUSIC_GYM_VICTORY`.
2. It must be a regular trainer battle; play `MUSIC_TRAINER_VICTORY`.
3. It must a wild battle. If any mon is holding an Exp. Share, or we collect money from Pay Day, or we have not lost the battle, play `MUSIC_WILD_VICTORY`.
4. Do not play any victory music.
## `RIVAL1` and `RIVAL2` don't print their trainer class in battle
Both of these classes are named "RIVAL", but battles just print "SILVER wants to battle!", not "RIVAL SILVER wants to battle!"
This is caused by `PlaceEnemysName` in [home/text.asm](../blob/master/home/text.asm):
```asm
ld a, [wTrainerClass]
cp RIVAL1
jr z, .rival
cp RIVAL2
jr z, .rival
```
## Vital Throw always goes last
Most move effects' priorities are specified in `MoveEffectPriorities` in [data/moves/effects_priorities.asm](../blob/master/data/moves/effects_priorities.asm).
...except for Vital Throw. This move shares its effect with a lot of other moves, and they couldn't be bothered to make a new move effect ID for it like `EFFECT_PRIORITY_HIT`, so they hard-coded this case, in `GetMovePriority` of [engine/battle/core.asm](../blob/master/engine/battle/core.asm):
```asm
GetMovePriority:
; Return the priority (0-3) of move a.
ld b, a
; Vital Throw goes last.
cp VITAL_THROW
ld a, 0
ret z
```
|