-
Notifications
You must be signed in to change notification settings - Fork 831
Hard coded logic
Much of the game logic can be changed via the files in data/, but some things are hard-coded and can be tricky to find. This page lists things that may trip you up when hacking.
- Tilesets that have per-mapgroup roofs
- Maps that don't display a location sign
- Outdoor maps within indoor maps don't confuse Dig or Escape Rope
- Landmark limits when scrolling in the Town Map
- Trainer classes with different battle music
RIVAL1
's first Pokémon has no held itemRIVAL1
andRIVAL2
don't print their trainer class in battle- Vital Throw always goes last
This is caused by LoadTilesetGFX
in home/map.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
This is caused by ReturnFromMapSetupScript.CheckSpecialMap
in engine/events/map_name_sign.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
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:
; 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
This is caused by PokegearMap_KantoMap
and PokegearMap_JohtoMap
in engine/pokegear/pokegear.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)
This is caused by InitEnemyTrainer
in engine/battle/core.asm:
; RIVAL1's first mon has no held item
ld a, [wTrainerClass]
cp RIVAL1
jr nz, .ok
xor a
ld [wOTPartyMon1Item], a
.ok
This is caused by PlayBattleMusic
in engine/battle/start_battle.asm. The routine's logic is:
- If
[wBattleType]
isBATTLETYPE_SUICUNE
orBATTLETYPE_ROAMING
, playMUSIC_SUICUNE_BATTLE
. - 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, orMUSIC_JOHTO_WILD_BATTLE
during morning and day. - If
[wOtherTrainerClass]
isCHAMPION
orRED
, playMUSIC_CHAMPION_BATTLE
. - If
[wOtherTrainerClass]
isGRUNTM
orGRUNTF
, playMUSIC_ROCKET_BATTLE
. (They should have includedEXECUTIVEM
,EXECUTIVEF
, andSCIENTIST
too…) - If
[wOtherTrainerClass]
is listed underKantoGymLeaders
in data/trainers/leaders.asm, playMUSIC_KANTO_GYM_LEADER_BATTLE
. - If
[wOtherTrainerClass]
is listed underGymLeaders
in data/trainers/leaders.asm, playMUSIC_JOHTO_GYM_LEADER_BATTLE
. (CHAMPION
,RED
, and the Kanto Gym leaders are listed but have already been accounted for at this point.) - If
[wOtherTrainerClass]
isRIVAL2
and[wOtherTrainerID]
is at leastRIVAL2_2_CHIKORITA
(i.e. we're battling our rival in Indigo Plateau), playMUSIC_CHAMPION_BATTLE
. - If
[wOtherTrainerClass]
isRIVAL1
orRIVAL2
, playMUSIC_RIVAL_BATTLE
. - If it's a link battle, play
MUSIC_JOHTO_TRAINER_BATTLE
. - Play
MUSIC_KANTO_TRAINER_BATTLE
if we're in Kanto orMUSIC_JOHTO_TRAINER_BATTLE
if we're in Johto.
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:
ld a, [wTrainerClass]
cp RIVAL1
jr z, .rival
cp RIVAL2
jr z, .rival
Most move effects' priorities are specified in MoveEffectPriorities
in 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:
GetMovePriority:
; Return the priority (0-3) of move a.
ld b, a
; Vital Throw goes last.
cp VITAL_THROW
ld a, 0
ret z