summaryrefslogtreecommitdiff
path: root/Hard-coded-logic.md
blob: 88127e2f8525290761f2d9dac12ccfea4c8eef09 (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
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
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)
- [Some high values for maps' music IDs play incorrectly](#some-high-values-for-maps-music-ids-play-incorrectly)
- [Trainer classes with different battle music](#trainer-classes-with-different-battle-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 ; 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))


## `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
```


## Some high values for maps' music IDs play incorrectly

If a map's music ID in [data/maps/maps.asm](../blob/master/data/maps/maps.asm) is $64 (the value of `MUSIC_MAHOGANY_MART` or `MUSIC_SUICUNE_BATTLE`) it will play either `MUSIC_ROCKET_HIDEOUT` or `MUSIC_CHERRYGROVE_CITY`. Moreover, if a map's music ID is $80 or above (the value of `RADIO_TOWER_MUSIC`) it might play `MUSIC_ROCKET_OVERTURE` or something else.

This is caused by `GetMapMusic` in [home/map.asm](../blob/master/home/map.asm):

```asm
GetMapMusic::
	push hl
	push bc
	ld de, MAP_MUSIC
	call GetMapField
	ld a, c
	cp MUSIC_MAHOGANY_MART
	jr z, .mahoganymart
	bit RADIO_TOWER_MUSIC_F, c
	jr nz, .radiotower
	farcall Function8b342
	ld e, c
	ld d, 0
.done
	pop bc
	pop hl
	ret

.radiotower
	ld a, [wStatusFlags2]
	bit STATUSFLAGS2_ROCKETS_IN_RADIO_TOWER_F, a
	jr z, .clearedradiotower
	ld de, MUSIC_ROCKET_OVERTURE
	jr .done

.clearedradiotower
	; the rest of the byte
	ld a, c
	and RADIO_TOWER_MUSIC - 1
	ld e, a
	ld d, 0
	jr .done

.mahoganymart
	ld a, [wStatusFlags2]
	bit STATUSFLAGS2_ROCKETS_IN_MAHOGANY_F, a
	jr z, .clearedmahogany
	ld de, MUSIC_ROCKET_HIDEOUT
	jr .done

.clearedmahogany
	ld de, MUSIC_CHERRYGROVE_CITY
	jr .done
```

This can cause problems if you add too many new songs, or rearrange the existing ones. A solution would be to redefine the special music constants in [constants/music_constants.asm](../blob/master/constants/music_constants.asm):

```diff
-; GetMapMusic picks music for this value (see home/map.asm)
-MUSIC_MAHOGANY_MART EQU $64
+; GetMapMusic picks music for these values (see home/map.asm)
+MUSIC_MAHOGANY_MART EQU $fc
+MUSIC_RADIO_TOWER   EQU $fd

 ; ExitPokegearRadio_HandleMusic uses these values
 RESTART_MAP_MUSIC EQU $fe
 ENTER_MAP_MUSIC   EQU $ff
-
-; GetMapMusic picks music for this bit flag
-RADIO_TOWER_MUSIC_F EQU 7
-RADIO_TOWER_MUSIC EQU 1 << RADIO_TOWER_MUSIC_F
```

And then edit `GetMapMusic`:

```diff
 GetMapMusic::
 	push hl
 	push bc
 	ld de, MAP_MUSIC
 	call GetMapField
 	ld a, c
 	cp MUSIC_MAHOGANY_MART
 	jr z, .mahoganymart
-	bit RADIO_TOWER_MUSIC_F, c
-	jr nz, .radiotower
+	cp MUSIC_RADIO_TOWER
+	jr z, .radiotower
 	farcall Function8b342
 	ld e, c
 	ld d, 0
 .done
 	pop bc
 	pop hl
 	ret

 .radiotower
 	ld a, [wStatusFlags2]
 	bit STATUSFLAGS2_ROCKETS_IN_RADIO_TOWER_F, a
 	jr z, .clearedradiotower
 	ld de, MUSIC_ROCKET_OVERTURE
 	jr .done

 .clearedradiotower
-	; the rest of the byte
-	ld a, c
-	and RADIO_TOWER_MUSIC - 1
-	ld e, a
-	ld d, 0
+	ld de, MUSIC_GOLDENROD_CITY
 	jr .done

 .mahoganymart
 	ld a, [wStatusFlags2]
 	bit STATUSFLAGS2_ROCKETS_IN_MAHOGANY_F, a
 	jr z, .clearedmahogany
 	ld de, MUSIC_ROCKET_HIDEOUT
 	jr .done

 .clearedmahogany
 	ld de, MUSIC_CHERRYGROVE_CITY
 	jr .done
```

You'll also need to edit [data/maps/maps.asm](../blob/master/data/maps/maps.asm) so the Radio Tower maps use `MUSIC_RADIO_TOWER` instead of `RADIO_TOWER_MUSIC | MUSIC_GOLDENROD_CITY`.


## 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, play `MUSIC_KANTO_WILD_BATTLE` if we're in Kanto, `MUSIC_JOHTO_WILD_BATTLE_NIGHT` in Johto at night, or `MUSIC_JOHTO_WILD_BATTLE` during morning and day.
3. If `[wOtherTrainerClass]` is `CHAMPION` or `RED`, play `MUSIC_CHAMPION_BATTLE`.
4. If `[wOtherTrainerClass]` is `GRUNTM` or `GRUNTF`, play `MUSIC_ROCKET_BATTLE`. (They should have included `EXECUTIVEM`, `EXECUTIVEF`, and `SCIENTIST` too…)
5. If `[wOtherTrainerClass]` is listed under `KantoGymLeaders` in [data/trainers/leaders.asm](../blob/master/data/trainers/leaders.asm), play `MUSIC_KANTO_GYM_LEADER_BATTLE`.
6. 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 have already been accounted for at this point.)
7. 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`.
8. If `[wOtherTrainerClass]` is `RIVAL1` or `RIVAL2`, play `MUSIC_RIVAL_BATTLE`.
9. If it's a link battle, play `MUSIC_JOHTO_TRAINER_BATTLE`.
10. Play `MUSIC_KANTO_TRAINER_BATTLE` if we're in Kanto or `MUSIC_JOHTO_TRAINER_BATTLE` if we're in Johto.


## `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
```