Allow to fly to any map and from any map - pret/pokered GitHub Wiki

In vanilla, we can fly only from certain maps (the ones in the "open": cities, routes, but for example from the top of the Celadon Condominiums or from the top of the Celadon Store) and to certain maps (namely, only cities, even though Route 4 and Route 10 have Pokémon Centers). We can modify both of these behaviours.

Fly TO any map

Let's use Route 4's and Route 10's Pokecenters as example destinations, if nothing else because that makes a lot of sense and they are already "prepared" in vanilla code (one could speculate that GF intended to indeed be able to fly there). The following procedure can be applied to any destination map, as long as you add the appropriate entries in data/maps/special_warps.asm. The idea is to expand the allowed destinations. This is achieved by editing the code that handles the fly action.

Edit constants/map_constants.asm

 ; Indoor maps, such as houses, use this as the Map ID in their exit warps
 ; This map ID takes the player back to the last outdoor map they were on, stored in wLastMap
 DEF LAST_MAP EQU -1
+
+DEF NUM_FLY_LOCATIONS EQU NUM_CITY_MAPS + 2
+
+	const_def NUM_CITY_MAPS
+	const FLYLOC_ROUTE_4_CENTER
+	const FLYLOC_ROUTE_10_CENTER

The +2 allows to hold 2 more destinations. Modify it according to your needs; as a reminder, this tutorial covers how to add exactly 2 fly destinations.

Edit engine/items/town_map.asm

.wrapToEndOfList
-	ld hl, wFlyLocationsList + NUM_CITY_MAPS
+	ld hl, wFlyLocationsList + NUM_FLY_LOCATIONS
	jr .pressedDown
BuildFlyLocationsList:
	ld hl, wFlyAnimUsingCoordList
	ld [hl], $ff
-	inc hl
+	inc hl ; it's wFlyLocationsList
	ld a, [wTownVisitedFlag]
	ld e, a
	ld a, [wTownVisitedFlag + 1]
	ld d, a
-	lb bc, 0, NUM_CITY_MAPS
+	lb bc, 0, NUM_FLY_LOCATIONS
 .loop
 	srl d
 	rr e
 	ld a, NOT_VISITED
-	jr nc, .notVisited
+	jr nc, .gotValue
 	ld a, b ; store the map number of the town if it has been visited
-.notVisited
+	cp FLYLOC_ROUTE_4_CENTER
+	jr c, .gotValue
+	ld a, ROUTE_4
+	jr z, .gotValue
+	ld a, ROUTE_10
+.gotValue
 	ld [hl], a
 	inc hl
 	inc b
	dec c
	jr nz, .loop
	ld [hl], $ff
	ret

What's happening here is that we are looping over "visited" locations to add new targets. This list gets created every time we open the map. The vanilla code expects only city-like entries, hence the value of b corresponds exactly to the index value of the city we want to add. This is NOT the case for ROUTE_4 and ROUTE_10. Thus, we need some more hacky way to handle these two entries. There are for sure other options, but this is what I came up with. Feel free to improve on this approach! Btw, the very first edit is of course not necessary since it's a mere comment, but I personally think it helps a lot for clarity. The .notVisited label was renamed to match its new usage.

Edit ram/wram.asm

-wFlyLocationsList:: ds NUM_CITY_MAPS + 2
+wFlyLocationsList:: ds NUM_FLY_LOCATIONS + 2 ; edited, to allow fly to Route 4 and Route 10

We need to allocate 2 more bytes for the wram variable we use in the code above. Again, if you add more than 2 destinations, modify this 2 accordingly.

-wTownVisitedFlag:: flag_array NUM_CITY_MAPS
+wTownVisitedFlag:: flag_array NUM_FLY_LOCATIONS

This is not terribly necessary, but it's good habit to have it. What happens here is that flag_array allocates a number of bytes equal to the number on its right divided by 8. In our specific case, adding 2 more bits does not make us require 1 extra byte. If that was the case for you because you add more fly destinations, remember you'll end up with 1 more byte here, so be careful.

Mark the pokecenters as visited

You have multiple choices to do that.

  1. When talking to the nurse
  2. When entering the pokecenter

Option 1: Edit engine/events/pokecenter.asm

DisplayPokemonCenterDialogue_::
+; new, for setting Route 4 and Route 10 Pokecenters fly locations
+	ld a, [wCurMap]
+	cp MT_MOON_POKECENTER
+	jr nz, .checkRockTunnelPokecenter
+	lb bc, FLAG_SET, FLYLOC_ROUTE_4_CENTER
+	ld hl, wTownVisitedFlag   ; mark town as visited (for flying)
+	predef FlagActionPredef
+	jr .regularCenter
+.checkRockTunnelPokecenter
+	cp ROCK_TUNNEL_POKECENTER
+	jr nz, .regularCenter
+	lb bc, FLAG_SET, FLYLOC_ROUTE_10_CENTER
+	ld hl, wTownVisitedFlag   ; mark town as visited (for flying)
+	predef FlagActionPredef
+.regularCenter
+; back to vanilla
	call SaveScreenTilesToBuffer1 ; save screen
	ld hl, PokemonCenterWelcomeText
	call PrintText

We're done! We need to make these locations being registered. There are many ways to do so, but what I ended up with (just in case FLY was acquired earlier in the game, to avoid cheesing a route), is to have these fly destinations registered upon talking with the nurse in their respective Pokecenters. Btw, some of the code above it inspired from pokeyellow, so you can take a look there for other special behaviours you may want to give to your nurses.

Option 2: Edit the pokecenters scripts

Edit scripts/MtMoonPokecenter.asm:

 MtMoonPokecenter_Script:
+	call .markAsVisited
 	call Serial_TryEstablishingExternallyClockedConnection
 	jp EnableAutoTextBoxDrawing
+.markAsVisited
+	ld hl, wCurrentMapScriptFlags
+	bit BIT_CUR_MAP_LOADED_1, [hl]
+	res BIT_CUR_MAP_LOADED_1, [hl]
+	ret z
+	lb bc, FLAG_SET, FLYLOC_ROUTE_4_CENTER
+	ld hl, wTownVisitedFlag
+	predef_jump FlagActionPredef

Edit scripts/RockTunnelPokecenter.asm:

 RockTunnelPokecenter_Script:
+	call .markAsVisited
 	call Serial_TryEstablishingExternallyClockedConnection
 	jp EnableAutoTextBoxDrawing
+.markAsVisited
+	ld hl, wCurrentMapScriptFlags
+	bit BIT_CUR_MAP_LOADED_1, [hl]
+	res BIT_CUR_MAP_LOADED_1, [hl]
+	ret z
+	lb bc, FLAG_SET, FLYLOC_ROUTE_10_CENTER
+	ld hl, wTownVisitedFlag
+	predef_jump FlagActionPredef

We're done! We need to make these locations being registered. There are many ways to do so, but what I ended up with (just in case FLY was acquired earlier in the game, to avoid cheesing a route), is to have these fly destinations registered upon entering the pokecenter.

Fly FROM any map

Why can't we fly from the top of the Celadon Store Center? Or from the top of the Celadon Condominiums? Or, well, Viridian Forest? Let's handle this! (be careful if you make the Safari Zone flyable from, as it may mess up with the Safari Game) It's as simple as it follows.

Edit engine/menus/start_sub_menus.asm

.fly
	bit BIT_THUNDERBADGE, a
	jp z, .newBadgeRequired
	call CheckIfInOutsideMap
	jr z, .canFly
+; new block to make "open-air" maps flyable
+	ld a, [wCurMap]
+	cp CELADON_MART_ROOF
+	jr z, .canFly
+	cp CELADON_MANSION_ROOF
+	jr z, .canFly
+	cp VERMILION_DOCK
+	jr z, .canFly
+	cp SS_ANNE_BOW
+	jr z, .canFly
+; end of new block to make "open-air" maps flyable
	ld a, [wWhichPokemon]
	ld hl, wPartyMonNicks
	call GetPartyMonName
	ld hl, .cannotFlyHereText
	call PrintText
	jp .loop
.canFly

And we are done!