-
Notifications
You must be signed in to change notification settings - Fork 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
...
Lastly, let's define wMaxDaycareLevel
to prevent the player from overleveling in the daycare.
...
; the address of the menu cursor's current location within wTileMap
wMenuCursorLocation:: dw
+; index in party of currently battling mon
+wMaxDaycareLevel:: db
+ ds 1
...
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
...
NOTE: Use make
after this change to check, do you get the the "jr target out of reach" error on line 2169? If you do, follow this and if no, skip this.
...
.throwSafariBallWasSelected
ld a, SAFARI_BALL
ld [wCurItem], a
- jr UseBagItem
+ jp UseBagItem
...
I got this error while making this, and if you get the error here's how to fix it.
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 to make a constant for the difficulty selection menu:
...
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 to make the menu. (7, 3) means the size of the window, and FALSE
means that the first option will be false.
...
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 to create selecting text and adding "Are you sure?" to make sure they don't make it accidentally:
...
+_DifficultyText::
+ text "Select Difficulty"
+ done
+_AreYouSureText::
+ text "Are you sure?"
+ done
...
In data/text/text_3.asm
, add these to describe the difficulties after choice:
...
+_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, add these at the bottom to make the functions from the first script we did to this file:
...
+; 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.