Skip to content
Rangi edited this page Jun 3, 2020 · 24 revisions

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.

Contents

Tilesets that have per-mapgroup roofs

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

Maps that don't display a location sign

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

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:

; 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:

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)

RIVAL1's first Pokémon has no held item

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

Trainer classes with different battle music

This is caused by PlayBattleMusic in 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...
    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. ...if 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, play MUSIC_KANTO_GYM_LEADER_BATTLE.
    4. If [wOtherTrainerClass] is listed under GymLeaders in 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.

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:

	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. ...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
Clone this wiki locally