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