-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Adding hard mode
Today we are going to do a tutorial of how to add a hard mode to the game. This means that we will force set mode, disable the use of items in battle, and add a hard level cap to the game, if the player chooses the mode.
To start, we define the difficulty in ram/wram.asm
. This will ensure that our scripts will be understood.
...
wRoute18Gate1FCurScript:: db
- ds 78
+ ; ds 78
wGameProgressFlagsEnd::
+wDifficulty::
+ ; $00 = normal
+ ; $01 = hard
+ ds 1
...
Let's also define wGameStage
for checking if it's post game or not, as level caps will be disabled post game.
...
wFirstLockTrashCanIndex:: db
wSecondLockTrashCanIndex:: db
- ds 2
+ ds 1
+wGameStage:: db
+ ; $00 = before champion fight
+ ; $01 = post game
...
We will force the player to play on Set Mode when on Hard Mode. Open engine/battle/core.asm
and make the following changes.
...
cp LINK_STATE_BATTLING
jr z, .next4
+ ld a, [wDifficulty] ; Check if player is on hard mode
+ and a
+ jr z, .DontForceSetMode
- jr z, .next4
+ jr .next4 ; skip switch request if on hard mode
+ .DontForceSetMode
ld a, [wOptions]
bit BIT_BATTLE_SHIFT, a
...
We'll prevent the player from using items in battle, and it will say in atrainer battle "Items can't be used right now."To do this, stay in engine/battle/core.asm
and make the following changes:
...
; normal battle
call DrawHUDsAndHPBars
.next
ld a, [wBattleType]
- dec a ; is it the old man tutorial?
+ cp BATTLE_TYPE_OLD_MAN ; is it the old man battle?
+ jr z, .simulatedInputBattle
+ ld a, [wDifficulty] ; Check if player is on hard mode
+ and a
+ jr z, .NormalMode
+ ld a, [wIsInBattle] ; Check if this is a wild battle or trainer battle
+ dec a
+ jr z, .NormalMode ; Not a trainer battle
+ ld hl, ItemsCantBeUsedHereText ; items can't be used during trainer battles in hard mode
+ call PrintText
+ jp DisplayBattleMenu
+.NormalMode
jr DisplayPlayerBag ; no, it is a normal battle
+.simulatedInputBattle
ld hl, OldManItemList
ld a, l
ld [wListPointer], a
ld a, h
ld [wListPointer + 1], a
jr DisplayBagMenu
OldManItemList:
db 1 ; # items
+ ; optional: changes the number of poke balls from 50 to 1 to maintain logic
- db POKE_BALL, 50
+ db POKE_BALL, 1
db -1 ; end
...
The last one is optional, but I made the old man only have 1 Poke ball for logical consistency. You can keep it at 50 if you want to.
We will level cap the gyms so that you can't overlevel past them with a Rare Candy, Gaining Exp via battles, or Daycare. Start by staying in engine/battle/core.asm
, to make gaining EXP via battles impossible.
...
ld a, [wPlayerID]
cp [hl]
jr nz, .monIsTraded
+ ld a, [wDifficulty] ; Check if player is on hard mode
+ and a
+ jr z, .NormalMode2
+; what level might disobey?
+ ld a, [wGameStage] ; Check if player has beat the game
+ and a
+ ld a, 101
+ jr nz, .next
+ farcall GetBadgesObtained
+ ld a, [wNumSetBits]
+ cp 8
+ ld a, 65 ; Venusaur/Charizard/Blastoise's level
+ jr nc, .next
+ cp 7
+ ld a, 50 ; Rhydon's level
+ jr nc, .next
+ cp 6
+ ld a, 47 ; Arcanine's level
+ jr nc, .next
+ cp 5
+ ld a, 43 ; Alakazam's level
+ jr nc, .next
+ cp 4
+ ld a, 43 ; Weezing's level
+ jr nc, .next
+ cp 3
+ ld a, 29 ; Vileplume's level
+ jr nc, .next
+ cp 2
+ ld a, 24 ; Raichu's level
+ jr nc, .next
+ cp 1
+ ld a, 21 ; Starmie's level
+ jr nc, .next
+ ld a, 14 ; Onix's level
+ jp .next
+.NormalMode2
+ inc hl
+ ld a, [wPlayerID + 1]
+ cp [hl]
+ jp z, .canUseMove ; on normal mode non traded pokemon will always obey
+ ; it was traded
...
Open engine/battle/experience.asm
and add the following lines. This will reset gained EXP back to 0 if on hard mode and above the level cap
...
ld [wd0b5], a
call GetMonHeader
ld d, MAX_LEVEL
+ ld a, [wDifficulty] ; Check if player is on hard mode
+ and a
+ jr z, .next1 ; no level caps if not on hard mode
+ ld a, [wGameStage] ; Check if player has beat the game
+ and a
+ ld d, 100
+ jr nz, .next1
+ call GetBadgesObtained
+ ld a, [wNumSetBits]
+ cp 8
+ ld d, 65 ; Venusaur/Charizard/Blastoise's level
+ jr nc, .next1
+ cp 7
+ ld d, 50 ; Rhydon's level
+ jr nc, .next1
+ cp 6
+ ld d, 47 ; Arcanine's level
+ jr nc, .next1
+ cp 5
+ ld d, 43 ; Alakazam's level
+ jr nc, .next1
+ cp 4
+ ld d, 43 ; Weezing's level
+ jr nc, .next1
+ cp 3
+ ld d, 29 ; Vileplume's level
+ jr nc, .next1
+ cp 2
+ ld d, 24 ; Raichu's level
+ jr nc, .next1
+ cp 1
+ ld d, 21 ; Starmie's level
+ jr nc, .next1
+ ld d, 14 ; Onix's level
+.next1
callfar CalcExperience ; get max exp
...
At the end of experience.asm
add these lines to make the function GetBadgesObtained
...
+; function to count the set bits in wObtainedBadges
+; OUTPUT:
+; a = set bits in wObtainedBadges
+GetBadgesObtained::
+ push hl
+ push bc
+ push de
+ ld hl, wObtainedBadges
+ ld b, $1
+ call CountSetBits
+ pop de
+ pop bc
+ pop hl
+ ld a, [wNumSetBits]
+ ret
...
Now go to engine/items/item_effects.asm
to make the Rare Candy not work on Pokémon that are above the level cap.
...
.useRareCandy
push hl
ld bc, wPartyMon1Level - wPartyMon1
add hl, bc ; hl now points to level
ld a, [hl] ; a = level
- cp MAX_LEVEL
+ ld b, MAX_LEVEL
+ ld a, [wDifficulty] ; Check if player is on hard mode
+ and a
+ jr z, .next1 ; no level caps if not on hard mode
+
+ ld a, [wGameStage] ; Check if player has beat the game
+ and a
+ jr nz, .next1
+ farcall GetBadgesObtained
+ ld a, [wNumSetBits]
+ cp 8
+ ld b, 65 ; Venusaur/Charizard/Blastoise's level
+ jr nc, .next1
+ cp 7
+ ld b, 50 ; Rhydon's level
+ jr nc, .next1
+ cp 6
+ ld b, 47 ; Arcanine's level
+ jr nc, .next1
+ cp 5
+ ld b, 43 ; Alakazam's level
+ jr nc, .next1
+ cp 4
+ ld b, 43 ; Weezing's level
+ jr nc, .next1
+ cp 3
+ ld b, 29 ; Vileplume's level
+ jr nc, .next1
+ cp 2
+ ld b, 24 ; Raichu's level
+ jr nc, .next1
+ cp 1
+ ld b, 21 ; Starmie's level
+ jr nc, .next1
+ ld b, 14 ; Onix's level
+.next1
+
+ pop hl
+ ld a, [hl] ; a = level
+ cp b ; MAX_LEVEL on normal mode, level cap on hard mode
jr z, .vitaminNoEffect ; can't raise level above 100
inc a
ld [hl], a ; store incremented level
...
Now for the daycare. Open scripts/Daycare.asm
and make the following edits. This will make the daycare unable to level up your pokemon past the level cap.
...
.daycareInUse
xor a
ld hl, wDayCareMonName
call GetPartyMonName
ld a, DAYCARE_DATA
ld [wMonDataLocation], a
call LoadMonData
callfar CalcLevelFromExperience
- ld a, d
- cp MAX_LEVEL
- jr c, .skipCalcExp
- ld d, MAX_LEVEL
- callfar CalcExperience
- ld hl, wDayCareMonExp
- ldh a, [hExperience]
- ld [hli], a
- ldh a, [hExperience + 1]
- ld [hli], a
- ldh a, [hExperience + 2]
- ld [hl], a
- ld d, MAX_LEVEL
+ push bc
+ ld a, [wDifficulty] ; Check if player is on hard mode
+ and a
+ ld b, MAX_LEVEL
+ jr z, .next1 ; no level caps if not on hard mode
+
+ ld a, [wGameStage] ; Check if player has beat the game
+ and a
+ jr nz, .next1
+ farcall GetBadgesObtained
+ ld a, [wNumSetBits]
+ cp 8
+ ld b, 65 ; Venusaur/Charizard/Blastoise's level
+ jr nc, .next1
+ cp 7
+ ld b, 50 ; Rhydon's level
+ jr nc, .next1
+ cp 6
+ ld b, 47 ; Arcanine's level
+ jr nc, .next1
+ cp 5
+ ld b, 43 ; Alakazam's level
+ jr nc, .next1
+ cp 4
+ ld b, 43 ; Weezing's level
+ jr nc, .next1
+ cp 3
+ ld b, 29 ; Vileplume's level
+ jr nc, .next1
+ cp 2
+ ld b, 24 ; Raichu's level
+ jr nc, .next1
+ cp 1
+ ld b, 21 ; Starmie's level
+ jr nc, .next1
+ ld b, 14 ; Onix's level
+.next1
+ ld a, b
+ ld [wMaxDaycareLevel], a
+ ld a, d
+ cp b
+ pop bc
+ jr c, .skipCalcExp
+
+ ld a, [wMaxDaycareLevel]
+ ld d, a
+ callfar CalcExperience
+ ld hl, wDayCareMonExp
+ ldh a, [hExperience]
+ ld [hli], a
+ ldh a, [hExperience + 1]
+ ld [hli], a
+ ldh a, [hExperience + 2]
+ ld [hl], a
+ ld a, [wMaxDaycareLevel]
+ ld d, a
...
We will ask the player the difficulty to give them a choice between Normal and Hard Mode. Open constants/menu_constants.asm
and add these lines:
...
const TRADE_CANCEL_MENU ; 5
const HEAL_CANCEL_MENU ; 6
const NO_YES_MENU ; 7
+ const DIFFICULTY_SELECTION_MENU ; 8
...
Now go to data/yes_no_menu_strings.asm
and add this line:
...
two_option_menu 7, 4, TRUE, .HealCancelMenu
two_option_menu 4, 3, FALSE, .NoYesMenu
+ two_option_menu 7, 3, FALSE, .DifficultyMenu
...
And at the end, add this line:
...
.HealCancelMenu:
db "HEAL"
next "CANCEL@"
+.DifficultyMenu:
+ db "NORMAL"
+ next "HARD@"
...
In data/text/text_2.asm
, add these at the end:
...
+_DifficultyText::
+ text "Select Difficulty"
+ done
+_AreYouSureText::
+ text "Are you sure?"
+ done
...
In data/text/text_3.asm
, add these:
...
+_NormalModeText::
+ text "Are you sure?"
+ para "Classic #MON"
+ line "rules."
+ done
+_HardModeText::
+ text "Are you sure?"
+ para "Set mode, no"
+ line "items in battle,"
+ cont "gym level caps."
+ done
...
Finally, let's open engine/movie/oak_speech/oak_speech.asm
and make the following changes. This will activate the difficulty menu when starting a new file.
...
ld a, [wStatusFlags6]
bit BIT_DEBUG_MODE, a
jp nz, .skipSpeech
+.MenuCursorLoop ; difficulty menu
+ ld hl, DifficultyText
+ call PrintText
+ call DifficultyChoice
+ ld a, [wCurrentMenuItem]
+ ld [wDifficulty], a
+ cp 0 ; normal
+ jr z, .SelectedNormalMode
+ cp 1 ; hard
+ jr z, .SelectedHardMode
+ ; space for more game modes down the line
+.SelectedNormalMode
+ ld hl, NormalModeText
+ call PrintText
+ jp .YesNoNormalHard
+.SelectedHardMode
+ ld hl, HardModeText
+ call PrintText
+.YesNoNormalHard ; Give the player a brief description of each game mode and make sure that's what they want
+ call YesNoNormalHardChoice
+ ld a, [wCurrentMenuItem]
+ cp 0
+ jr z, .doneLoop
+ jp .MenuCursorLoop ; If player says no, back to difficulty selection
+.doneLoop
+ call ClearScreen ; clear the screen before resuming normal intro
...
Go a little bit more down and add these to import the text from the text files:
...
+NormalModeText:
+ text_far _NormalModeText
+ text_end
+HardModeText:
+ text_far _HardModeText
+ text_end
+DifficultyText:
+ text_far _DifficultyText
+ text_end
+YesNoNormalHardText:
+ text_far _AreYouSureText
+ text_end
...
Finally, go a bit more down and add these lines to make the menus:
...
+; displays difficulty choice
+DifficultyChoice::
+ call SaveScreenTilesToBuffer1
+ call InitDifficultyTextBoxParameters
+ jr DisplayDifficultyChoice
+
+InitDifficultyTextBoxParameters::
+ ld a, $8 ; loads the value for the difficulty menu
+ ld [wTwoOptionMenuID], a
+ coord hl, 5, 5
+ ld bc, $606 ; Cursor Pos
+ ret
+
+DisplayDifficultyChoice::
+ ld a, $14
+ ld [wTextBoxID], a
+ call DisplayTextBoxID
+ jp LoadScreenTilesFromBuffer1
+
+; display yes/no choice
+YesNoNormalHardChoice::
+ call SaveScreenTilesToBuffer1
+ call InitYesNoNormalHardTextBoxParameters
+ jr DisplayYesNoNormalHardChoice
+
+InitYesNoNormalHardTextBoxParameters::
+ ld a, $0 ; loads the value for the difficulty menu
+ ld [wTwoOptionMenuID], a
+ coord hl, 7, 5
+ ld bc, $608 ; Cursor Pos
+ ret
+
+DisplayYesNoNormalHardChoice::
+ ld a, $14
+ ld [wTextBoxID], a
+ call DisplayTextBoxID
+ jp LoadScreenTilesFromBuffer1
...
If you've done this, feel free to update. Thanks for seeing this and hope it works for you.