-
Notifications
You must be signed in to change notification settings - Fork 993
Nuzlocke Mode
This tutorial will help you get a rudimentary Nuzlocke mode running. The following changes will be implemented
- Pokemon cannot be resurrected when fainted.
- You may only catch/obtain the first Pokemon you encounter/find per area.
Note, this tutorial will not cover the so-called "dupes clause".
DISCLAIMER: This tutorial is currently under construction. Let me know on Discord (@Xillicis) if you discover any bugs or have suggestions.
- Handling Fainted Pokemon
- Catch First Encounters
- Cannot Trade Dead Pokemon
- Edge Cases
- Game over
- Balancing
We start by removing the effects of revive and max revive. The plan is to simply make these items unusable. You could do more work by completely removing them, but that is beyond the scope of this tutorial. Open the file engine/items/item_effects.asm and make the following modification.
...
.checkItemType
ld a, [wcf91]
cp REVIVE
- jr nc, .healHP ; if it's a Revive or Max Revive
+ jp nc, .healingItemNoEffect
cp FULL_HEAL
jr z, .cureStatusAilment ; if it's a Full Heal
cp HP_UP
...
Head down a little bit further and make the changes.
...
.fainted
ld a, [wcf91]
cp REVIVE
- jr z, .updateInBattleFaintedData
+ jp .healingItemNoEffect
cp MAX_REVIVE
- jr z, .updateInBattleFaintedData
+ jp .healingItemNoEffect
.updateInBattleFaintedData
ld a, [wIsInBattle]
...
Find the next MAX_REVIVE
...
cp HYPER_POTION
jr c, .setCurrentHPToMaxHp ; if using a Full Restore or Max Potion
cp MAX_REVIVE
- jr z, .setCurrentHPToMaxHp ; if using a Max Revive
+ jr z, .healingItemNoEffect
jr .updateInBattleData
.setCurrentHPToHalfMaxHP
dec hl
...
Finding the last REVIVE
and MAX_REVIVE
, we make the changes.
...
ld a, REVIVE_MSG
ld [wPartyMenuTypeOrMessageID], a
ld a, [wcf91]
cp REVIVE
- jr z, .showHealingItemMessage
+ jr z, .healingItemNoEffect
cp MAX_REVIVE
- jr z, .showHealingItemMessage
+ jr z, .healingItemNoEffect
ld a, POTION_MSG
...
This does it for revive type items. Next we need to stop the Pokemon centers (or other locations) from fully reviving fainted Pokemon.
Next we need to make sure the heal events will not heal a fainted Pokemon. We do this by first checking if the Pokemon has zero HP, if it does, then we skip the healing process. This is all be done in the file engine/events/heal_party.asm. We need to modify the HealParty:
subroutine for this. The HealParty:
subroutine is a bit complicated, as it needs to loop through each Pokemon and apply the healing affects. However, we just need to modify a small part of this subroutine. Make the following changes to HealParty:
,
...
.nextmove
dec b
jr nz, .pp
pop de
ld hl, wPartyMon1MaxHP - wPartyMon1HP
add hl, de
+ ; Check if current mon is fainted
+ push hl ; need to work on the hl register
+ ld h, d
+ ld l, e
+ ld a, [hli] ; load current HP
+ or a, [hl] ; check if zero HP
+ pop hl
+ jr z, .noHealIfFainted
+ ; heal current mon if not fainted
ld a, [hli]
ld [de], a
inc de
ld a, [hl]
ld [de], a
+.noHealIfFainted
pop de
pop hl
push hl
ld bc, wPartyMon2 - wPartyMon1
ld h, d
...
There is a small trick to check if the value stored across 2-bytes (pointed to by hl
in this case), is zero. That is, the instructions, ld a, [hli]
and or a, [hl]
. These two instructions first load the most significant byte of data pointed to by hl
into a
, increments hl
to the least significant byte, and finally executes a binary OR
operation. The result of the OR
operation is zero if and only if those two bytes of data are both zero. Hence the Pokemon would have fainted.
A common rule adopted by Nuzlocke players, is that losing the first
rival fight doesn't mean you must restart the game. After the rival fight,
HealParty
is called, but we've changed it to not heal fainted Pokemon.
Therefore, we need to manually restore the Pokemon's health. Head to
scripts/OaksLab.asm and make the following changes,
...
xor a ; SPRITE_FACING_DOWN
ldh [hSpriteFacingDirection], a
call SetSpriteFacingDirectionAndDelay
+; Restore the health of the Pokemon after 1st rival fight
+ ld hl, wPartyMon1MaxHP
+ ld de, wPartyMon1HP
+ ld a, [hli]
+ ld [de], a
+
+ inc de
+ ld a, [hl]
+ ld [de], a
- predef HealParty
SetEvent EVENT_BATTLED_RIVAL_IN_OAKS_LAB
ld a, $d
ld [wOaksLabCurScript], a
ret
...
Note, we don't restore the PP, but its not really necessary. That should cover all possible circumstances for handling fainted Pokemon.
The idea for this section to set a flag for each encounter area. We can only catch the Pokemon if the flag is set to 0, but the flag will be set to 1 immediately after the encounter. This makes it so that every subsequent encounter in that region will not be capturable.
The first thing we'll do is to allocate space for these Nuzlocke flags. In ram/wram.asm add the following line
...
wCurMapScript:: db
- ds 7
+ ds 1
+
+wNuzlockeRegions:: ds 6
wPlayTimeHours:: db
...
We will need 6 bytes of data as as there are 43 locations for encountering Pokemon
(43 < 6 * 8 = 48). Note the location in wram.asm
to define these bytes is up to you.
Depending on the modifications you've made to your code already, you may have to define
these bytes else where.
In order to better keep track of these locations we define some constants add the end of the file constants/battle_constants.asm.
...
+; wNuzlockeRegions
+ const_def ; First byte
+ const RESET_ROUTES_NUZ ; 0
+ const PALLET_TOWN_NUZ ; 1
+ const VIRIDIAN_CITY_NUZ ; 2
+ const CERULEAN_CITY_NUZ ; 3
+ const VERMILION_CITY_NUZ ; 4
+ const CELADON_CITY_NUZ ; 5
+ const FUCHSIA_CITY_NUZ ; 6
+ const CINNABAR_ISLAND_NUZ ; 7
+ const_def ; Second byte
+ const SAFFRON_CITY_NUZ ; 0
+ const ROUTE_1_NUZ ; 1
+ const ROUTE_2_NUZ ; 2
+ const ROUTE_3_NUZ ; 3
+ const ROUTE_4_NUZ ; 4
+ const ROUTE_5_NUZ ; 5
+ const ROUTE_6_NUZ ; 6
+ const ROUTE_7_NUZ ; 7
+ const_def ; Third byte
+ const ROUTE_8_NUZ ; 0
+ const ROUTE_9_NUZ ; 1
+ const ROUTE_10_NUZ ; 2
+ const ROUTE_11_NUZ ; 3
+ const ROUTE_12_NUZ ; 4
+ const ROUTE_13_NUZ ; 5
+ const ROUTE_14_NUZ ; 6
+ const ROUTE_15_NUZ ; 7
+ const_def ; Fourth byte
+ const ROUTE_16_NUZ ; 0
+ const ROUTE_17_NUZ ; 1
+ const ROUTE_18_NUZ ; 2
+ const ROUTE_19_NUZ ; 3
+ const ROUTE_20_NUZ ; 4
+ const ROUTE_21_NUZ ; 5
+ const ROUTE_22_NUZ ; 6
+ const ROUTE_23_NUZ ; 7
+ const_def ; Fifth byte
+ const ROUTE_24_NUZ ; 0
+ const ROUTE_25_NUZ ; 1
+ const VIRIDIAN_FOREST_NUZ ; 2
+ const MT_MOON_NUZ ; 3
+ const ROCK_TUNNEL_NUZ ; 4
+ const POWER_PLANT_NUZ ; 5
+ const VICTORY_ROAD_NUZ ; 6
+ const POKEMON_TOWER_NUZ ; 7
+ const_def ; Sixth byte
+ const SEAFOAM_ISLANDS_NUZ ; 0
+ const DIGLETTS_CAVE_NUZ ; 1
+ const POKEMON_MANSION_NUZ ; 2
+ const SAFARI_ZONE_NUZ ; 3
These constant values will be used to reference the different bits of the different bytes of wNuzlockeRegions
.
Now we need to set the appropriate Nuzlocke flag after we encounter a Pokemon and also to
check the flag to determine if a Pokemon is catchable. We add two subroutines to do this:
setNuzlockeFlag
and checkNuzlockeStatus
. These two functions are added into a new
file, nuzlocke.asm
in the engine/
directory. The file is long and cumbersome, so I
recommend just copy pasting it in. Is it elegent? No. Is there a better way to do this?
Probably. Feel free to suggest a better implementation if you have one.
setNuzlockeFlag::
ld hl, wNuzlockeRegions
ld a, [wCurMap]
cp INDIGO_PLATEAU
jr nc, .routeRegion1
cp PALLET_TOWN
jr z, .nuzPalletTownFlag
cp VIRIDIAN_CITY
jr z, .nuzViridianCityFlag
cp CERULEAN_CITY
jr z, .nuzCeruleanCityFlag
cp VERMILION_CITY
jr z, .nuzVermilionCityFlag
cp VERMILION_DOCK
jr z, .nuzVermilionCityFlag
cp CELADON_CITY
jr z, .nuzCeladonCityFlag
cp FUCHSIA_CITY
jr z, .nuzFuchsiaCityFlag
cp CINNABAR_ISLAND
jr z, .nuzCinnabarIslandFlag
.nuzPalletTownFlag
set PALLET_TOWN_NUZ, [hl]
ret
.nuzViridianCityFlag
set VIRIDIAN_CITY_NUZ, [hl]
ret
.nuzCeruleanCityFlag
set CERULEAN_CITY_NUZ, [hl]
ret
.nuzVermilionCityFlag
set VERMILION_CITY_NUZ, [hl]
ret
.nuzCeladonCityFlag
set CELADON_CITY_NUZ, [hl]
ret
.nuzFuchsiaCityFlag
set FUCHSIA_CITY_NUZ, [hl]
ret
.nuzCinnabarIslandFlag
set CINNABAR_ISLAND_NUZ, [hl]
ret
.routeRegion1
inc hl
cp ROUTE_8
jr nc, .routeRegion2
cp SAFFRON_CITY
jr z, .nuzSaffronCityFlag
cp ROUTE_1
jr z, .nuzRoute1Flag
cp ROUTE_2
jr z, .nuzRoute2Flag
cp ROUTE_3
jr z, .nuzRoute3Flag
cp ROUTE_4
jr z, .nuzRoute4Flag
cp ROUTE_5
jr z, .nuzRoute5Flag
cp ROUTE_6
jr z, .nuzRoute6Flag
cp ROUTE_7
jr z, .nuzRoute7Flag
.nuzSaffronCityFlag
set SAFFRON_CITY_NUZ, [hl]
ret
.nuzRoute1Flag
set ROUTE_1_NUZ, [hl]
ret
.nuzRoute2Flag
set ROUTE_2_NUZ, [hl]
ret
.nuzRoute3Flag
set ROUTE_3_NUZ, [hl]
ret
.nuzRoute4Flag
set ROUTE_4_NUZ, [hl]
ret
.nuzRoute5Flag
set ROUTE_5_NUZ, [hl]
ret
.nuzRoute6Flag
set ROUTE_6_NUZ, [hl]
ret
.nuzRoute7Flag
set ROUTE_7_NUZ, [hl]
ret
.routeRegion2
inc hl
cp ROUTE_16
jr nc, .routeRegion3
cp ROUTE_8
jr z, .nuzRoute8Flag
cp ROUTE_9
jr z, .nuzRoute9Flag
cp ROUTE_10
jr z, .nuzRoute10Flag
cp ROUTE_11
jr z, .nuzRoute11Flag
cp ROUTE_12
jr z, .nuzRoute12Flag
cp ROUTE_13
jr z, .nuzRoute13Flag
cp ROUTE_14
jr z, .nuzRoute14Flag
cp ROUTE_15
jr z, .nuzRoute15Flag
.nuzRoute8Flag
set ROUTE_8_NUZ, [hl]
ret
.nuzRoute9Flag
set ROUTE_9_NUZ, [hl]
ret
.nuzRoute10Flag
set ROUTE_10_NUZ, [hl]
ret
.nuzRoute11Flag
set ROUTE_11_NUZ, [hl]
ret
.nuzRoute12Flag
set ROUTE_12_NUZ, [hl]
ret
.nuzRoute13Flag
set ROUTE_13_NUZ, [hl]
ret
.nuzRoute14Flag
set ROUTE_14_NUZ, [hl]
ret
.nuzRoute15Flag
set ROUTE_15_NUZ, [hl]
ret
.routeRegion3
inc hl
cp ROUTE_24
jr nc, .routeRegion4
cp ROUTE_16
jr z, .nuzRoute16Flag
cp ROUTE_17
jr z, .nuzRoute17Flag
cp ROUTE_18
jr z, .nuzRoute18Flag
cp ROUTE_19
jr z, .nuzRoute19Flag
cp ROUTE_20
jr z, .nuzRoute20Flag
cp ROUTE_21
jr z, .nuzRoute21Flag
cp ROUTE_22
jr z, .nuzRoute22Flag
cp ROUTE_23
jr z, .nuzRoute23Flag
.nuzRoute16Flag
set ROUTE_16_NUZ, [hl]
ret
.nuzRoute17Flag
set ROUTE_17_NUZ, [hl]
ret
.nuzRoute18Flag
set ROUTE_18_NUZ, [hl]
ret
.nuzRoute19Flag
set ROUTE_19_NUZ, [hl]
ret
.nuzRoute20Flag
set ROUTE_20_NUZ, [hl]
ret
.nuzRoute21Flag
set ROUTE_21_NUZ, [hl]
ret
.nuzRoute22Flag
set ROUTE_22_NUZ, [hl]
ret
.nuzRoute23Flag
set ROUTE_23_NUZ, [hl]
ret
.routeRegion4
inc hl
cp MR_FUJIS_HOUSE
jr nc, .finalRegion
cp ROUTE_24
jr z, .nuzRoute24Flag
cp ROUTE_25
jr z, .nuzRoute25Flag
cp VIRIDIAN_FOREST
jr z, .nuzViridianForestFlag
cp CERULEAN_TRASHED_HOUSE ; This will check all the Mt. Moon floors
jr c, .nuzMtMoonFlag
CERULEAN_GYM ; The gym counts for Cerulean city
jp z, .nuzCeruleanCityFlag
CP ROCK_TUNNEL_1F
jr z, .nuzRockTunnelFlag
cp POWER_PLANT
jr z, .nuzPowerPlantFlag
cp VICTORY_ROAD_1F
jr z, .nuzVictoryRoadFlag
jr .nuzPokemonTowerFlag ; Last remaining option is Pokemon Tower
.nuzRoute24Flag
set ROUTE_24_NUZ, [hl]
ret
.nuzRoute25Flag
set ROUTE_25_NUZ, [hl]
ret
.nuzViridianForestFlag
set VIRIDIAN_FOREST_NUZ, [hl]
ret
.nuzMtMoonFlag
set MT_MOON_NUZ, [hl]
ret
.nuzRockTunnelFlag
set ROCK_TUNNEL_NUZ, [hl]
ret
.nuzPowerPlantFlag
set POWER_PLANT_NUZ, [hl]
ret
.nuzVictoryRoadFlag
set VICTORY_ROAD_NUZ, [hl]
ret
.nuzPokemonTowerFlag
set POKEMON_TOWER_NUZ, [hl]
ret
.finalRegion
inc hl
cp VERMILION_OLD_ROD_HOUSE
jr c, .nuzSeafoamIslandsFlag ; This checks B1F, B2F, B3F, and B4F
cp POKEMON_MANSION_1F
jr z, .nuzPokemonMansionFlag
cp SEAFOAM_ISLANDS_1F
jr z, .nuzSeafoamIslandsFlag
cp DIGLETTS_CAVE
jr z, .nuzDiglettsCaveFlag
cp ROCKET_HIDEOUT_B1F ; Checks for victory road 2F and 3F
jr c, .nuzVictoryRoadFlag
cp SAFARI_ZONE_EAST ; Check pokemon mansion 2F, 3F, and B1F
jr c, .nuzPokemonMansionFlag
cp SAFARI_ZONE_CENTER_REST_HOUSE
jr c, .nuzSafariZoneFlag
jr .nuzRockTunnelFlag ; Last remaining zone for nuzlocke is rock tunnel B1F
.nuzSeafoamIslandsFlag
set SEAFOAM_ISLANDS_NUZ, [hl]
ret
.nuzDiglettsCaveFlag
set DIGLETTS_CAVE_NUZ, [hl]
ret
.nuzPokemonMansionFlag
set POKEMON_MANSION_NUZ, [hl]
ret
.nuzSafariZoneFlag
set SAFARI_ZONE_NUZ, [hl]
ret
checkNuzlockeStatus::
ld hl, wNuzlockeRegions
ld a, [wCurMap]
cp INDIGO_PLATEAU
jr nc, .routeRegion1
cp PALLET_TOWN
jr z, .nuzPalletTown
cp VIRIDIAN_CITY
jr z, .nuzViridianCity
cp CERULEAN_CITY
jr z, .nuzCeruleanCity
cp VERMILION_CITY
jr z, .nuzVermilionCity
cp VERMILION_DOCK
jr z, .nuzVermilionCity
cp CELADON_CITY
jr z, .nuzCeladonCity
cp FUCHSIA_CITY
jr z, .nuzFuchsiaCity
cp CINNABAR_ISLAND
jr z, .nuzCinnabarIsland
.nuzPalletTown
bit PALLET_TOWN_NUZ, [hl]
ret
.nuzViridianCity
bit VIRIDIAN_CITY_NUZ, [hl]
ret
.nuzCeruleanCity
bit CERULEAN_CITY_NUZ, [hl]
ret
.nuzVermilionCity
bit VERMILION_CITY_NUZ, [hl]
ret
.nuzCeladonCity
bit CELADON_CITY_NUZ, [hl]
ret
.nuzFuchsiaCity
bit FUCHSIA_CITY_NUZ, [hl]
ret
.nuzCinnabarIsland
bit CINNABAR_ISLAND_NUZ, [hl]
ret
.routeRegion1
inc hl
cp ROUTE_8
jr nc, .routeRegion2
cp SAFFRON_CITY
jr z, .nuzSaffronCity
cp ROUTE_1
jr z, .nuzRoute1
cp ROUTE_2
jr z, .nuzRoute2
cp ROUTE_3
jr z, .nuzRoute3
cp ROUTE_4
jr z, .nuzRoute4
cp ROUTE_5
jr z, .nuzRoute5
cp ROUTE_6
jr z, .nuzRoute6
cp ROUTE_7
jr z, .nuzRoute7
.nuzSaffronCity
bit SAFFRON_CITY_NUZ, [hl]
ret
.nuzRoute1
bit ROUTE_1_NUZ, [hl]
ret
.nuzRoute2
bit ROUTE_2_NUZ, [hl]
ret
.nuzRoute3
bit ROUTE_3_NUZ, [hl]
ret
.nuzRoute4
bit ROUTE_4_NUZ, [hl]
ret
.nuzRoute5
bit ROUTE_5_NUZ, [hl]
ret
.nuzRoute6
bit ROUTE_6_NUZ, [hl]
ret
.nuzRoute7
bit ROUTE_7_NUZ, [hl]
ret
.routeRegion2
inc hl
cp ROUTE_16
jr nc, .routeRegion3
cp ROUTE_8
jr z, .nuzRoute8
cp ROUTE_9
jr z, .nuzRoute9
cp ROUTE_10
jr z, .nuzRoute10
cp ROUTE_11
jr z, .nuzRoute11
cp ROUTE_12
jr z, .nuzRoute12
cp ROUTE_13
jr z, .nuzRoute13
cp ROUTE_14
jr z, .nuzRoute14
cp ROUTE_15
jr z, .nuzRoute15
.nuzRoute8
bit ROUTE_8_NUZ, [hl]
ret
.nuzRoute9
bit ROUTE_9_NUZ, [hl]
ret
.nuzRoute10
bit ROUTE_10_NUZ, [hl]
ret
.nuzRoute11
bit ROUTE_11_NUZ, [hl]
ret
.nuzRoute12
bit ROUTE_12_NUZ, [hl]
ret
.nuzRoute13
bit ROUTE_13_NUZ, [hl]
ret
.nuzRoute14
bit ROUTE_14_NUZ, [hl]
ret
.nuzRoute15
bit ROUTE_15_NUZ, [hl]
ret
.routeRegion3
inc hl
cp ROUTE_24
jr nc, .routeRegion4
cp ROUTE_16
jr z, .nuzRoute16
cp ROUTE_17
jr z, .nuzRoute17
cp ROUTE_18
jr z, .nuzRoute18
cp ROUTE_19
jr z, .nuzRoute19
cp ROUTE_20
jr z, .nuzRoute20
cp ROUTE_21
jr z, .nuzRoute21
cp ROUTE_22
jr z, .nuzRoute22
cp ROUTE_23
jr z, .nuzRoute23
.nuzRoute16
bit ROUTE_16_NUZ, [hl]
ret
.nuzRoute17
bit ROUTE_17_NUZ, [hl]
ret
.nuzRoute18
bit ROUTE_18_NUZ, [hl]
ret
.nuzRoute19
bit ROUTE_19_NUZ, [hl]
ret
.nuzRoute20
bit ROUTE_20_NUZ, [hl]
ret
.nuzRoute21
bit ROUTE_21_NUZ, [hl]
ret
.nuzRoute22
bit ROUTE_22_NUZ, [hl]
ret
.nuzRoute23
bit ROUTE_23_NUZ, [hl]
ret
.routeRegion4
inc hl
cp MR_FUJIS_HOUSE
jr nc, .finalRegion
cp ROUTE_24
jr z, .nuzRoute24
cp ROUTE_25
jr z, .nuzRoute25
cp VIRIDIAN_FOREST
jr z, .nuzViridianForest
cp CERULEAN_TRASHED_HOUSE ; This will check all the Mt. Moon floors
jr c, .nuzMtMoon
CERULEAN_GYM ; The gym counts for Cerulean city
jp z, .nuzCeruleanCity
CP ROCK_TUNNEL_1F
jr z, .nuzRockTunnel
cp POWER_PLANT
jr z, .nuzPowerPlant
cp VICTORY_ROAD_1F
jr z, .nuzVictoryRoad
jr .nuzPokemonTower ; Last remaining option is Pokemon Tower
.nuzRoute24
bit ROUTE_24_NUZ, [hl]
ret
.nuzRoute25
bit ROUTE_25_NUZ, [hl]
ret
.nuzViridianForest
bit VIRIDIAN_FOREST_NUZ, [hl]
ret
.nuzMtMoon
bit MT_MOON_NUZ, [hl]
ret
.nuzRockTunnel
bit ROCK_TUNNEL_NUZ, [hl]
ret
.nuzPowerPlant
bit POWER_PLANT_NUZ, [hl]
ret
.nuzVictoryRoad
bit VICTORY_ROAD_NUZ, [hl]
ret
.nuzPokemonTower
bit POKEMON_TOWER_NUZ, [hl]
ret
.finalRegion
inc hl
cp VERMILION_OLD_ROD_HOUSE
jr c, .nuzSeafoamIslands ; This checks B1F, B2F, B3F, and B4F
cp POKEMON_MANSION_1F
jr z, .nuzPokemonMansion
cp SEAFOAM_ISLANDS_1F
jr z, .nuzSeafoamIslands
cp DIGLETTS_CAVE
jr z, .nuzDiglettsCave
cp ROCKET_HIDEOUT_B1F ; Checks for victory road 2F and 3F
jr c, .nuzVictoryRoad
cp SAFARI_ZONE_EAST ; Check pokemon mansion 2F, 3F, and B1F
jr c, .nuzPokemonMansion
cp SAFARI_ZONE_CENTER_REST_HOUSE
jr c, .nuzSafariZone
jr .nuzRockTunnel ; Last remaining zone for nuzlocke is rock tunnel B1F
.nuzSeafoamIslands
bit SEAFOAM_ISLANDS_NUZ, [hl]
ret
.nuzDiglettsCave
bit DIGLETTS_CAVE_NUZ, [hl]
ret
.nuzPokemonMansion
bit POKEMON_MANSION_NUZ, [hl]
ret
.nuzSafariZone
bit SAFARI_ZONE_NUZ, [hl]
ret
Since we are adding a new file, we also need to include it in main.asm
. So head to
main.asm and add the line
...
INCLUDE "engine/menus/players_pc.asm"
INCLUDE "engine/pokemon/remove_mon.asm"
INCLUDE "engine/events/display_pokedex.asm"
+INCLUDE "engine/nuzlocke.asm"
...
There are few things to mention about this code. The ordering of the different locations
is based on the ordering in constants/map_constants.asm
. The code also handles regions
like MT_MOON
; that is, it considers all the different floors that you could be on and
treats it as a single location.
The second thing you may notice is that the very first bit in wNuzlockeRegions
is not
specified. This bit will be used in order to initialize the Nuzlocke mode after acquiring
Pokeballs. I personally, believe a Nuzlocke doesn't start until one acquires a Pokeball,
so we will add some more changes to reset the Nuzlocke flags for ROUTE 1
and ROUTE 2
as these two regions can be explored before acquiring Pokeballs.
Head to engine/items/inventory.asm and make the following changes:
...
ld c, a
ld b, 0
add hl, bc ; hl = address to store the item
ld a, [wcf91]
+
+ cp POKE_BALL
+ push hl
+ jr nz, .noNuzlockeReset
+
+.startNuzlocke
+ ld hl, wNuzlockeRegions
+ bit RESET_ROUTES_NUZ, [hl] ; Check if we have already aquired pokeballs
+ jr nz, .noNuzlockeReset
+ set RESET_ROUTES_NUZ, [hl] ; Set the flag declaring the start of the Nuzlocke
+ inc hl ; Increment to the next byte which points to the routes
+ res ROUTE_1_NUZ, [hl] ; Reset Route 1 Nuzlocke flag
+ res ROUTE_2_NUZ, [hl] ; Reset Route 2 Nuzlocke flag
+
+.noNuzlockeReset
+ pop hl
ld [hli], a ; store item ID
ld a, [wItemQuantity]
ld [hli], a ; store item quantity
ld [hl], $ff ; store terminator
...
This code checks to see if we are adding a Pokeball to our inventory (which would happen at the first Pokemart in Viridian City). It then checks to see if this is the first time we are getting a Pokeball and if it is the first time, then we reset Routes 1 and 2.
That does it for all the initializing; we now need to handle the Pokemon encounters and a few edge cases.
For wild Nuzlocke encounters there are essentially four cases to handle.
- The wild Pokemon flees.
- You defeat the wild Pokemon.
- You run away from the wild Pokemon.
- You or the wild Pokemon casts Roar, Whirlwind, or Teleport.
For the first case head to engine/battle/core.asm and set the Nuzlocke flag,
...
EnemyRan:
call LoadScreenTilesFromBuffer1
+ callfar setNuzlockeFlag
ld a, [wLinkState]
cp LINK_STATE_BATTLING
ld hl, WildRanText
jr nz, .printText
; link battle
xor a
ld [wBattleResult], a
ld hl, EnemyRanText
...
Head down a little bit further
and add the "set Nuzlocke flag" for defeating the wild Pokemon in the subroutine FaintEnemyPokemon:
,
...
.wild_win
call EndLowHealthAlarm
+ callfar setNuzlockeFlag
ld a, MUSIC_DEFEATED_WILD_MON
call PlayBattleVictoryMusic
...
For the third case, in the same
file, head down to the subroutine TryRunningFromBattle:
and make the following change,
...
and a ; reset carry
ret
.canEscape
+ callfar setNuzlockeFlag
ld a, [wLinkState]
cp LINK_STATE_BATTLING
...
This handles the case for fleeing from battle. Finally, we need to
account for the case when roar, whirlwind, or teleport is used. We
make the following change in
engine/battle/effects.asm, to the subroutine SwitchAndTeleportEffect:
,
...
cp ROAR
jr z, .printText
ld hl, WasBlownAwayText
.printText
+ push hl
+ callfar setNuzlockeFlag
+ pop hl
jp PrintText
...
Note we push hl
and pop hl
since the callfar
instruction modifies the hl
register and we need that for printing the text.
We need to let the player know that a Pokemon is or is not catchable, as the player may not be keeping track of all the locations. To do this, we will simply display some extra text letting the player know that they can catch the Pokemon.
First, we will create the text that is displayed when the Pokemon is catchable. Open up data/text/text_2.asm and add the following,
...
+_WildMonCatchableText::
+ text "@"
+ text_ram wEnemyMonNick
+ text_start
+ line "is catchable!"
+ prompt
_HookedMonAttackedText::
text "The hooked"
...
Now we display this text if the Pokemon is catchable, otherwise, no additional text is displayed. Open up engine/battle/common_text.asm and add the following,
...
callfar DrawAllPokeballs
pop hl
call PrintText
+ ld a, [wIsInBattle]
+ dec a
+ jr nz, .done ; Check if trainer battle or not
+ callfar checkNuzlockeStatus
+ jr nz, .done
+ ld hl, WildMonCatchableText
+ call PrintText
jr .done
.pokemonTower
ld b, SILPH_SCOPE
...
and a bit further down add
...
WildMonAppearedText:
text_far _WildMonAppearedText
text_end
+WildMonCatchableText:
+ text_far _WildMonCatchableText
+ text_end
...
And that covers it for this section. I'd prefer a visual cue, like a some kind of Pokeball next to the Pokemon name or health bar for indicating the catchable status. But currently that is beyond my ability. Edit this wiki if you have a way to do that.
We now need to account for the in-game trades. Without making any modifications, the player would be able to trade dead Pokemon and receive a new fully healed Pokemon. We will modify the in-game trades to check that the Pokemon is not dead, if it is, then we will display some appropriate text. The modifications must be made in engine/events/in_game_trades.asm. Make the following change to the subroutine InGameTrade_DoTrade:
,
InGameTrade_DoTrade:
xor a ; NORMAL_PARTY_MENU
ld [wPartyMenuTypeOrMessageID], a
dec a
ld [wUpdateSpritesEnabled], a
call DisplayPartyMenu
push af
call InGameTrade_RestoreScreen
pop af
ld a, $1
jp c, .tradeFailed ; jump if the player didn't select a pokemon
ld a, [wInGameTradeGiveMonSpecies]
ld b, a
ld a, [wcf91]
cp b
ld a, $2
jp nz, .tradeFailed ; jump if the selected mon's species is not the required one
+ ; check for dead mon
+ ld a, [wWhichPokemon]
+ ld hl, wPartyMon1HP
+ ld bc, wPartyMon2 - wPartyMon1
+ call AddNTimes ; iterate to the selected mon
+ ld a, [hli] ; load current HP
+ or a, [hl] ; check if zero HP
+ ld a, $5 ; load indicator for dead mon dialog
+ jr z, .tradeFailed
ld a, [wWhichPokemon]
ld hl, wPartyMon1Level
ld bc, wPartyMon2 - wPartyMon1
call AddNTimes
...
The variable wWhichPokemon
stores a number between 0 and 5 and indicates which of the 6 Pokemon we are considering. Because of the data structure for Pokemon's stats, we start by loading the first Pokemon's HP, wPartyMon1HP
and then adding wPartyMon2 - wPartyMon1
to wPartyMon1HP
, wWhichPokemon
times. Doing this allows hl
to point to the HP of the selected Pokemon, whether it's the first Pokemon in our part or the last. For checking the HP, we repeat the same process just like when we modified the healing event. We check if the current Pokemon is fainted by the instructions, ld a, [hli]
and or a, [hl]
. Lastly, we load $5
into a
which is used to load the dialog for trying to trade a dead Pokemon.
Head down a bit further and we will add the text pointers for the dead Pokemon dialog,
...
TradeTextPointers1:
dw WannaTrade1Text
dw NoTrade1Text
dw WrongMon1Text
dw Thanks1Text
dw AfterTrade1Text
+ dw DeadMon1Text
TradeTextPointers2:
dw WannaTrade2Text
dw NoTrade2Text
dw WrongMon2Text
dw Thanks2Text
dw AfterTrade2Text
+ dw DeadMon2Text
TradeTextPointers3:
dw WannaTrade3Text
dw NoTrade3Text
dw WrongMon3Text
dw Thanks3Text
dw AfterTrade3Text
+ dw DeadMon3Text
...
and also add the text_far
's for loading the dialog,
...
AfterTrade1Text:
text_far _AfterTrade1Text
text_end
+DeadMon1Text:
+ text_far _DeadMon1Text
+ text_end
+DeadMon2Text:
+ text_far _DeadMon2Text
+ text_end
+DeadMon3Text:
+ text_far _DeadMon3Text
+ text_end
WannaTrade2Text:
text_far _WannaTrade2Text
text_end
...
Finally, we can add the dialog in data/text/text_7.asm
...
+_DeadMon1Text::
+ text "Come on man."
+ para "Trying to trade"
+ line "a dead #MON?"
+ done
+_DeadMon2Text::
+ text "Wow. You don't"
+ line "have any shame!"
+ para "Trading dead"
+ line "#MON!"
+ done
+_DeadMon3Text::
+ text "I can't believe"
+ line "you would try to"
+ cont "trade a dead"
+ cont "#MON!"
+ para "You don't deserve"
+ line "those badges!"
+ done
...
You can add these dialogs at the bottom of the file if you wish. The reason there are three different dialogs is because, out of all the in-game trades you can make, they only have 3 different sets of dialog. You can see these three different dialog types in data/events/trades.asm.
This section mainly concerns gifted Pokemon or Pokemon attained through the Gamecorner. Some people allow these Pokemon to be attained independently of the Nuzlocke rules, but we will not be giving any freebies. This section details how to actually handle these cases for the Nuzlocke rules.
For the Magikarp salesman, we will check if the Pokemon has been encountered already on Route 3. If the player has, then they will be unable to purchase the Magikarp and a different text will be displayed.
First, lets add the alternative text for when the Route 3 flag has been set. Make the following changes to text/MtMoonPokecenter.asm
...
+_MagikarpSalesmanNoPokemon::
+ text "What do I look"
+ line "like? A MAGIKARP"
+ cont "salesman?"
+
+ para "Get outa' here!"
+ done
...
You're free to make him say whatever. Also this text can be added anywhere in this file (except in the middle of a subroutine!)
Now open up scripts/MtMoonPokecenter.asm and make the following changes,
...
MagikarpSalesmanText:
text_asm
CheckEvent EVENT_BOUGHT_MAGIKARP, 1
jp c, .alreadyBoughtMagikarp
+ ; Since our current position is not in one of the regions defined
+ ; in checkNuzlockeStatus, we manually check the status.
+ ld hl, wNuzlockeRegions
+ inc hl
+ bit ROUTE_3_NUZ, [hl]
+ jr nz, .noPokemon
ld hl, .Text1
call PrintText
...
and
...
jr .printText
.enoughMoney
lb bc, MAGIKARP, 5
+ ; Hard set Route 3 when purchasing Pokemon.
+ ld hl, wNuzlockeRegions
+ inc hl
+ set ROUTE_3_NUZ, [hl]
call GivePokemon
jr nc, .done
...
I also recommend changing the Pokemon from MAGIKARP
to something like
GOLDEEN
as Gyarados is often considered overpowered in Nuzlockes. But
that's entirely up to you.
In Celadon City, one can aquire an Eevee at the top of the Celadon mansion or one can purchase a Pokemon from the Game Corner using coins.
We only want to allow the player one of these two options.
Starting with the Game Corner, open up engine/events/prize_menu.asm
and make the following change at the beginning,
CeladonPrizeMenu::
+ ld hl, wNuzlockeRegions
+ bit CELADON_CITY, [hl]
+ jr z, .nuzGameCornerOkay
+ ld hl, NuzlockeNoPrizeTextPtr
+ jp PrintText
+.nuzGameCornerOkay
ld b, COIN_CASE
call IsItemInBag
jr nz, .havingCoinCase
...
cp 3 ; "NO,THANKS" choice
jr z, .noChoice
call HandlePrizeChoice
.noChoice
ld hl, wd730
res 6, [hl]
ret
+NuzlockeNoPrizeTextPtr:
+ text_far _NuzlockeNoPrizeText
+ text_waitbutton
+ text_end
RequireCoinCaseTextPtr:
text_far _RequireCoinCaseText
text_waitbutton
text_end
...
This change checks if we have already aquired a Pokemon in Celadon City, if we have, then we will print some text saying we can't purchase a Pokemon. The last thing to modify in the file, is to set the Nuzlocke flag once we have purchased a Pokemon. In the subroutine HandlePrizeChocie
add the following,
...
call z, WaitForTextScrollButtonPress
pop af
; If the mon couldn't be given to the player (because both the party and box
; were full), return without subtracting coins.
ret nc
+ ld hl, wNuzlockeRegions
+ set CELADON_CITY, [hl]
.subtractCoins
...
Next we need to add the text that is displayed at the Game Corner if the Nuzlocke flag has been set. Open data/text/text_2.asm
and add the following,
...
_MonWasReleasedText::
text_ram wStringBuffer
text " was"
line "released outside."
cont "Bye @"
text_ram wStringBuffer
text "!"
prompt
+_NuzlockeNoPrizeText::
+ text "Sorry, prizes are"
+ line "unavailable.@"
+ text_end
_RequireCoinCaseText::
text "A COIN CASE is"
line "required!@"
text_end
...
Now to handl the Eevee at the top of the Celadon Mansion. Open scripts/CeladMansionRoofHouse.asm
and make the change,
...
CeladonMansion5Text2:
text_asm
+ ld hl, wNuzlockeRegions
+ bit CELADON_CITY, [hl]
+ jr nz, .nuzNoGift
lb bc, EEVEE, 25
call GivePokemon
jr nc, .party_full
+ ld hl, wNuzlockeRegions
+ set CELADON_CITY, [hl]
ld a, HS_CELADON_MANSION_EEVEE_GIFT
ld [wMissableObjectIndex], a
predef HideObject
.party_full
jp TextScriptEnd
+.nuzNoGift
+ ld hl, CeladonMansion5Text3
+ call PrintText
+ jp TextScriptEnd
+CeladonMansion5Text3:
+ text_far _CeladonMansion5Text3
+ text_end
Just as before, if the Nuzlocke flag for Celadon City has been set, they we display some text saying we can't take the Pokemon, otherwise, evertyhing follows normally. Lastly, we need to add the text that is displayed when we can't take the Eevee. Open text/CeladonMansionRoofHouse.asm
and add the following,
_CeladonMansion5Text1::
text "I know everything"
line "about the world"
cont "of #MON in"
cont "your GAME BOY!"
para "Get together with"
line "your friends and"
cont "trade #MON!"
done
+_CeladonMansion5Text3::
+ text "One #MON is"
+ line "enough from this"
+ cont "city."
+ done
That does it for Celadon City. Feel free to change the text to whatever you'd like.
For Saffron City, the player can either receive Hitmonlee or Hitmonchan from the fighting dojo and they can also receive a free Lapras on the 7th floor of Silph Co. To keep with the rules they can either get the Lapras or Hitmonlee or Hitmonchan. Let's start with Silph Co. Open up scripts/SilphCo7F.asm and make the following changes,
...
SilphCo7Text1:
; lapras guy
text_asm
ld a, [wd72e]
bit 0, a ; got lapras?
jr z, .givelapras
+.noLapras
CheckEvent EVENT_BEAT_SILPH_CO_GIOVANNI
jr nz, .savedsilph
ld hl, .LaprasGuyText
call PrintText
jr .done
.givelapras
+ ld hl, wNuzlockeRegions
+ inc hl ; Saffron City is in byte 2
+ bit SAFFRON_CITY_NUZ, [hl]
+ jr nz, .noLapras
ld hl, .MeetLaprasGuyText
call PrintText
lb bc, LAPRAS, 15
call GivePokemon
jr nc, .done
ld a, [wSimulatedJoypadStatesEnd]
and a
call z, WaitForTextScrollButtonPress
call EnableAutoTextBoxDrawing
+ ld hl, wNuzlockeRegions
+ inc hl ; Saffron city is in byte 2
+ set SAFFRON_CITY_NUZ, [hl]
ld hl, .HeresYourLaprasText
call PrintText
ld hl, wd72e
set 0, [hl]
jr .done
.savedsilph
ld hl, .LaprasGuySavedText
call PrintText
.done
jp TextScriptEnd
...
As usual we check if the Nuzlocke flag has been set for this region and if so, we don't give the Pokemon otherwise, we do give the Pokemon and set the corresponding bit to be 1. Note, the text for talking to the Lapras guy after receiving the Lapras is the same text as if we had already received Hitmonlee or Hitmonchan. I've gone ahead and changed to the text to bit a more informative, you can find it in text/SilphCo7F.asm
...
_LaprasGuyText::
- text "TEAM ROCKET's"
- line "BOSS went to the"
- cont "boardroom! Is our"
- cont "PRESIDENT OK?"
+ text "Sorry, I'm all"
+ line "out of #MON."
done
...
Now for the Fighting Dojo. Open up scripts/FightingDojo.asm and make the following modifications for choosing Hitmonlee:
...
FightingDojoText6:
; Hitmonlee Poké Ball
text_asm
+ ld hl, wNuzlockeRegions
+ inc hl
+ bit SAFFRON_CITY_NUZ, [hl]
+ jr z, .checkHitmonEvents
+ ld hl, NuzlockeDojoText
+ call PrintText
+ jr .done
+.checkHitmonEvents
CheckEitherEventSet EVENT_GOT_HITMONLEE, EVENT_GOT_HITMONCHAN
jr z, .GetMon
ld hl, OtherHitmonText
call PrintText
jr .done
.GetMon
ld a, HITMONLEE
call DisplayPokedex
ld hl, WantHitmonleeText
call PrintText
call YesNoChoice
ld a, [wCurrentMenuItem]
and a
jr nz, .done
ld a, [wcf91]
ld b, a
ld c, 30
call GivePokemon
jr nc, .done
+ ld hl, wNuzlockeRegions
+ inc hl
+ set SAFFRON_CITY_NUZ, [hl]
; once Poké Ball is taken, hide sprite
ld a, HS_FIGHTING_DOJO_GIFT_1
ld [wMissableObjectIndex], a
predef HideObject
SetEvents EVENT_GOT_HITMONLEE, EVENT_DEFEATED_FIGHTING_DOJO
.done
jp TextScriptEnd
...
and same thing for Hitmonchan. We also add in the jump to the Nuzlocke text.
...
FightingDojoText7:
; Hitmonchan Poké Ball
text_asm
+ ld hl, wNuzlockeRegions
+ inc hl
+ bit SAFFRON_CITY_NUZ, [hl]
+ jr z, .checkHitmonEvents
+ ld hl, NuzlockeDojoText
+ call PrintText
+ jr .done
+.checkHitmonEvents
CheckEitherEventSet EVENT_GOT_HITMONLEE, EVENT_GOT_HITMONCHAN
jr z, .GetMon
ld hl, OtherHitmonText
call PrintText
jr .done
.GetMon
ld a, HITMONCHAN
call DisplayPokedex
ld hl, WantHitmonchanText
call PrintText
call YesNoChoice
ld a, [wCurrentMenuItem]
and a
jr nz, .done
ld a, [wcf91]
ld b, a
ld c, 30
call GivePokemon
jr nc, .done
+ ld hl, wNuzlockeRegions
+ inc hl
+ set SAFFRON_CITY_NUZ, [hl]
SetEvents EVENT_GOT_HITMONCHAN, EVENT_DEFEATED_FIGHTING_DOJO
; once Poké Ball is taken, hide sprite
ld a, HS_FIGHTING_DOJO_GIFT_2
ld [wMissableObjectIndex], a
predef HideObject
.done
jp TextScriptEnd
WantHitmonchanText:
text_far _WantHitmonchanText
text_end
OtherHitmonText:
text_far _OtherHitmonText
text_end
+NuzlockeDojoText:
+ text_far _NuzlockeDojoText
+ text_end
Lastly, we need to write out the comment if we have already received the Lapras and can no longer get a new Pokemon. Open up text/FightingDojo.asm and add the Nuzlocke text:
...
_OtherHitmonText::
text "Better not get"
line "greedy..."
done
+_NuzlockeDojoText::
+ text "Sorry #MON"
+ line "prizes have been"
+ cont "suspended for the"
+ cont "time being."
+ done
I'd also suggest increasing the Lapras' level so you don't torture the player with grinding. Level 15 is just too low at this point in the game. That's it for Saffron City.
I'm not actually sure how others handle the fossil Pokemon for their Nuzlockes, but I'm going to treat it in this way. If the player catches a Pokemon in Cinnabar Island (by fishing/surfing) then they cannot get the fossil Pokemon and vice versa. Go ahead and open up scripts/CinnabarLabFossilRoom.asm and make the following changes,
...
Lab4Text1:
text_asm
+ ld hl, wNuzlockeRegions
+ bit CINNABAR_ISLAND_NUZ, [hl]
+ jr nz, .nuzlockeCinnabarText
CheckEvent EVENT_GAVE_FOSSIL_TO_LAB
jr nz, .asm_75d96
...
call GivePokemon
jr nc, .asm_75d93
+ ld hl, wNuzlockeRegions
+ set CINNABAR_ISLAND_NUZ, [hl]
ResetEvents EVENT_GAVE_FOSSIL_TO_LAB, EVENT_LAB_STILL_REVIVING_FOSSIL, EVENT_LAB_HANDING_OVER_FOSSIL_MON
jr .asm_75d93
+.nuzlockeCinnabarText
+ ld hl, NuzlockeCinnabarLabText
+ call PrintText
+ jr .exitLab4Text1
+NuzlockeCinnabarLabText:
+ text_far _NuzlockeCinnabarLabText
+ text_end
...
This is very similar to the other edge cases. Note that, if the player gives the fossil and then trys to cheat by fishing up another Pokemon, they won't be able to get the fossil Pokemon. Lastly, we need to include the text that is displayed if we can't get the fossil Pokemon. Open up text/CinnabarLabFossilRoom.asm and add the following text,
...
+_NuzlockeCinnabarLabText::
+ text "Sorry, the"
+ line "Resurrection"
+ cont "machine is out of"
+ cont "order."
+ done
There is a bug in the game where the player can fish inside some of the statues. This would allow the player to fish up an unlimited number of Pokemon in, say, Vermillion Gym. Check out Disable fishing in statues in the Bug Fixes tutorials.
There are many possible ways that you could end the game when the player blacks out. I won't go into any specific way of doing this. For my own personal ROM hack, it is set up so when you lose the game, you're teleported to a graveyard that you can't escape from. You can read tombstones there and look at the PC to see all your Pokemon. Another possible option would be to just role the credits when you lose.
In this section, I present a few ideas and solutions for balancing the Nuzlocke game mode.
This is an easy change. Open up engine/items/item_effects.asm and make the following change,
ItemUseOldRod:
call FishingInit
jp c, ItemUseNotTime
- lb bc, 5, MAGIKARP
+ lb bc, 5, GOLDEEN
ld a, $1 ; set bite
jr RodResponse
Snorlax is another very powerful Pokemon which the player can (essentially) get for free in Routes 12 and 16.
A simple fix without hard removing the Snorlax's blocking the road is to simply replace the encounter with a Ditto.
This fix keeps the theme of Pokemon intact (and trolls the player). Open up scripts/Route12.asm and scripts/Route16.asm
and change SNORLAX
to DITTO
.
...
call DisplayTextID
- ld a, SNORLAX
+ ld a, DITTO
ld [wCurOpponent], a
...
Open up data/maps/hide_show_data.asm and switch Zapdos, Moltres, and Articuno from SHOW
to HIDE
. For example,
...
SeafoamIslandsB4FHS:
db SEAFOAM_ISLANDS_B4F, SEAFOAMISLANDSB4F_BOULDER1, HIDE
db SEAFOAM_ISLANDS_B4F, SEAFOAMISLANDSB4F_BOULDER2, HIDE
- db SEAFOAM_ISLANDS_B4F, SEAFOAMISLANDSB4F_ARTICUNO, SHOW
+ db SEAFOAM_ISLANDS_B4F, SEAFOAMISLANDSB4F_ARTICUNO, HIDE
db $FF, $01, SHOW ; end
...
A common rule for balancing Nuzlocke runs is to impose a level cap based on the next gym leaders highest level Pokemon. I've personally chosen to implement a soft level cap. That is to repurpose the "loafing around" mechanic for traded Pokemon. Head to engine/battle/core.asm and make the following changes,
...
call AddNTimes
ld a, [wPlayerID]
cp [hl]
- jr nz, .monIsTraded
- inc hl
- ld a, [wPlayerID + 1]
- cp [hl]
- jp z, .canUseMove
; it was traded
.monIsTraded
; what level might disobey?
+ CheckEvent EVENT_BEAT_CHAMPION_RIVAL
+ ld a, 101
+ jr nz, .next
ld hl, wObtainedBadges
bit BIT_EARTHBADGE, [hl]
- ld a, 101
+ ld a, 65 ; Venasaur/Charizard/Blastoise's level
jr nz, .next
+ bit BIT_VOLCANOBADGE, [hl]
+ ld a, 50 ; Rhydon's level
+ jr nz, .next
bit BIT_MARSHBADGE, [hl]
- ld a, 70
+ ld a, 47 ; Arcanine's level
jr nz, .next
+ bit BIT_SOULBADGE, [hl]
+ ld a, 43 ; Alakazam's level
+ jr nz, .next
bit BIT_RAINBOWBADGE, [hl]
- ld a, 50
+ ld a, 43 ; Weezing's level
jr nz, .next
+ bit BIT_THUNDERBADGE, [hl]
+ ld a, 29 ; Vileplume's level
+ jr nz, .next
bit BIT_CASCADEBADGE, [hl]
- ld a, 30
+ ld a, 24 ; Raichu's level
jr nz, .next
+ bit BIT_BOULDERBADGE, [hl]
+ ld a, 21 ; Starmie's level
+ jr nz, .next
- ld a, 10
+ ld a, 14 ; Onix's level
.next
ld b, a
...
At the beginning we remove the check to see if the Pokemon is traded so that the level check applys to all Pokemon. Then as might be evident, we check for which badge we have and set the level requirement accordingly. You are free to change the level cap to whatever you'd like. I've chosen the values to be the highest levels of the gym leaders' Pokemon.
An alternative to this would be a strict level cap. That could be implemented by stopping experience gain once the Pokemon has attained the restricted level. We could do this by implementing this bitwise check in the routine where experience is awarded. Feel free to update this tutorial if you've done this.