-
Notifications
You must be signed in to change notification settings - Fork 806
Replace the Freeze status with Frostbite
Pokémon: Legends Arceus made new changes to the battle system, like the implementation of Agile/Strong style moves; among those changes, the Freeze status was replaced with "Frostbite", which acts similarly to the Burn status, but it halves the opponent's Special Atttack instead of its Attack.
Just like in Legends Arceus, this tutorial will teach you how to replace Freeze with Frostbite.
- Edit the battle text
- Frostbite deals damage every turn
- Halve Special Attack when frostbitten
- Don't immobilize a Pokémon suffering from Frostbite
- Change more Freeze behavior
- Adjust the AI properly
- Edit more miscellaneous text
Before creating the necessary routines, let's make some preparation work by creating, editing and deleting the battle text related to Freeze/Frostbite. Go to data/text/battle.asm:
HurtByBurnText:
text "<USER>'s"
line "hurt by its burn!"
prompt
+HurtByFrostbiteText:
+ text "<USER>'s"
+ line "hurt by frostbite!"
+ prompt
...
-FrozenSolidText:
- text "<USER>"
- line "is frozen solid!"
- prompt
...
-WasFrozenText:
+GotAFrostbiteText:
text "<TARGET>"
- line "was frozen solid!"
+ line "got a frostbite!"
prompt
We added two new texts for when the opponent is inflicted with Frostbite and when it's hurt by it, along with removing the FrozenSolidText
. Now it's time to program the status per se.
Just like the Burn status, Frostbite damages the inflicted Pokémon every turn, and so we have to edit the responsible routine for it. Let's go to engine/battle/core.asm:
ResidualDamage:
; Return z if the user fainted before
; or as a result of residual damage.
; For Sandstorm damage, see HandleWeather.
call HasUserFainted
ret z
ld a, BATTLE_VARS_STATUS
call GetBattleVar
- and 1 << PSN | 1 << BRN
+ and 1 << PSN | 1 << BRN | 1 << FRZ
jr z, .did_psn_brn
ld hl, HurtByPoisonText
ld de, ANIM_PSN
- and 1 << BRN
- jr z, .got_anim
+ bit PSN, a
+ jr nz, .got_anim
ld hl, HurtByBurnText
ld de, ANIM_BRN
+ bit BRN, a
+ jr nz, .got_anim
+
+ ld hl, HurtByFrostbiteText
+ ld de, ANIM_FRZ
.got_anim
...
Now when checking the user's status it'll also check for Frostbite and load the corresponding "Hurt by" text along with its animation by checking which status bit is set. In this example, Frostbite is using the same animation as Freeze since we haven't made a new one (creating entirely new animations is out of the scope of this tutorial).
Another trait of Frostbite is also halving the inflicted Pokémon's Special Attack (unlike Burn which halves Attack), so we'll create a new routine for it. In the same file as the previous step:
+ApplyFrbEffectOnSpclAttack:
+ ldh a, [hBattleTurn]
+ and a
+ jr z, .enemy
+ ld a, [wBattleMonStatus]
+ and 1 << FRZ
+ ret z
+ ld hl, wBattleMonSpclAtk + 1
+ jr .proceed
+
+.enemy
+ ld a, [wEnemyMonStatus]
+ and 1 << FRZ
+ ret z
+ ld hl, wEnemyMonSpclAtk + 1
+.proceed
+ ld a, [hld]
+ ld b, a
+ ld a, [hl]
+ srl a
+ rr b
+ ld [hli], a
+ or b
+ jr nz, .ok
+ ld b, $1 ; min special attack
+
+.ok
+ ld [hl], b
+ ret
This routine is based on ApplyBrnEffectOnAttack
(but slightly optimized), with the difference of affecting Special Attack instead of Attack. It checks whether the user is affected by Frostbite and, if so, applies the debuff.
Note: For organization reasons I suggest placing it after ApplyBrnEffectOnAttack
, but you can also place it at the end of the file.
Anyways, it's time to start using our newly made routine! First, we need to use it whenever the status effects are applied on both the player's and opponent's stats, so let's edit ApplyStatusEffectOnStats
in engine/battle/core.asm:
ApplyStatusEffectOnStats:
ldh [hBattleTurn], a
call ApplyPrzEffectOnSpeed
+ call ApplyFrbEffectOnSpclAttack
jp ApplyBrnEffectOnAttack
And let's go to engine/battle/effect_commands.asm to use it whenever the player's and opponent's stats are calculated:
CalcPlayerStats:
...
ld hl, ApplyPrzEffectOnSpeed
call CallBattleCore
ld hl, ApplyBrnEffectOnAttack
call CallBattleCore
+ ld hl, ApplyFrbEffectOnSpclAttack
+ call CallBattleCore
jp BattleCommand_SwitchTurn
CalcEnemyStats:
...
ld hl, ApplyPrzEffectOnSpeed
call CallBattleCore
ld hl, ApplyBrnEffectOnAttack
call CallBattleCore
+ ld hl, ApplyFrbEffectOnSpclAttack
+ call CallBattleCore
jp BattleCommand_SwitchTurn
Finally, we'll have to use the routine when initially applying the Frostbite status on the opponent. Still, in engine/battle/effect_commands.asm:
BattleCommand_FreezeTarget:
...
set FRZ, [hl]
call UpdateOpponentInParty
+ ld hl, ApplyFrbEffectOnSpclAttack
+ call CallBattleCore
ld de, ANIM_FRZ
call PlayOpponentBattleAnim
call RefreshBattleHuds
Here ApplyFrbEffectOnSpclAttack
is being used similarly to how BattleCommand_BurnTarget
uses ApplyBrnEffectOnAttack
, calling it from the "Battle Core" bank (where engine/battle/core.asm) is placed.
There are various routines responsible for applying the Freeze status, restarting some substatus when it happens (like flying and digging) and thawing you with a 10% chance every turn.
First we'll get rid of the one which stops both the inflicted Pokémon from moving. Let's edit engine/battle/effect_commands.asm:
BattleCommand_CheckTurn:
...
.not_asleep
-
- ld hl, wBattleMonStatus
- bit FRZ, [hl]
- jr z, .not_frozen
-
- ; Flame Wheel and Sacred Fire thaw the user.
- ld a, [wCurPlayerMove]
- cp FLAME_WHEEL
- jr z, .not_frozen
- cp SACRED_FIRE
- jr z, .not_frozen
-
- ld hl, FrozenSolidText
- call StdBattleTextbox
-
- call CantMove
- jp EndTurn
-
-.not_frozen
ld hl, wPlayerSubStatus3
bit SUBSTATUS_FLINCHED, [hl]
jr z, .not_flinched
...
CheckEnemyTurn:
...
.not_asleep
-
- ld hl, wEnemyMonStatus
- bit FRZ, [hl]
- jr z, .not_frozen
-
- ; Flame Wheel and Sacred Fire thaw the user.
- ld a, [wCurEnemyMove]
- cp FLAME_WHEEL
- jr z, .not_frozen
- cp SACRED_FIRE
- jr z, .not_frozen
-
- ld hl, FrozenSolidText
- call StdBattleTextbox
- call CantMove
- jp EndTurn
-
-.not_frozen
ld hl, wEnemySubStatus3
bit SUBSTATUS_FLINCHED, [hl]
jr z, .not_flinched
...
Now the frostbitten Pokémon can move, but some of their substatus will reset when inflicted by it, which makes sense for a frozen Pokémon but not for a frostbitten one. Let's change this behavior:
BattleCommand_FreezeTarget:
...
- ld hl, WasFrozenText
+ ld hl, GotAFrostbiteText
call StdBattleTextbox
farcall UseHeldStatusHealingItem
- ret nz
-
- call OpponentCantMove
- call EndRechargeOpp
- ld hl, wEnemyJustGotFrozen
- ldh a, [hBattleTurn]
- and a
- jr z, .finish
- ld hl, wPlayerJustGotFrozen
-.finish
- ld [hl], $1
ret
We also used the chance to replace the old WasFrozenText
with the new one we edited.
Great! Frostbite works! But there are some leftover routines from the old Freeze status that we need to clean up; to be more specific, HandleDefrost
is a routine that acts between turns to determine whether the frozen Pokémon should defrost itself or not. We don't need it anymore, so we can safely delete it along with the instances where it's called. For this, go to engine/battle/core.asm:
HandleBetweenTurnEffects:
...
.NoMoreFaintingConditions:
call HandleLeftovers
call HandleMysteryberry
- call HandleDefrost
call HandleSafeguard
...
-HandleDefrost:
- ldh a, [hSerialConnectionStatus]
- cp USING_EXTERNAL_CLOCK
- jr z, .enemy_first
- call .do_player_turn
- jr .do_enemy_turn
- ...
-.wild
-
- call UpdateBattleHuds
- call SetPlayerTurn
- ld hl, DefrostedOpponentText
- jp StdBattleTextbox
Also, wPlayerJustGotFrozen
and wEnemyJustGotFrozen
were labels used to store a value to indicate that the player/enemy was recently frozen and stop HandleDefrost
from thawing them in the same turn they were statused, but they're not useful anymore, so we can also delete them. Still in engine/battle/core.asm:
BattleTurn:
.loop
call Stubbed_Increments5_a89a
call CheckContestBattleOver
jp c, .quit
xor a
ld [wPlayerIsSwitching], a
ld [wEnemyIsSwitching], a
ld [wBattleHasJustStarted], a
- ld [wPlayerJustGotFrozen], a
- ld [wEnemyJustGotFrozen], a
ld [wCurDamage], a
ld [wCurDamage + 1], a
And now let's delete the labels from ram/wram.asm and allocate empty bytes instead (this is to avoid shifting all the other memory addresses and desync data):
wAmuletCoin:: db
wSomeoneIsRampaging:: db
-wPlayerJustGotFrozen:: db
-wEnemyJustGotFrozen:: db
+ ds 2
+
wBattleEnd::
A frozen/sleeping wild Pokémon can't run from battle, but it doesn't make sense for frostbitten Pokémon, so we'll edit this in engine/battle/core.asm:
TryEnemyFlee:
ld a, [wBattleMode]
dec a
jr nz, .Stay
ld a, [wPlayerSubStatus5]
bit SUBSTATUS_CANT_RUN, a
jr nz, .Stay
ld a, [wEnemyWrapCount]
and a
jr nz, .Stay
ld a, [wEnemyMonStatus]
- and 1 << FRZ | SLP_MASK
+ and SLP_MASK
jr nz, .Stay
Now it'll only check for the Sleep status instead of also checking for Freeze (or Frostbite in this case).
In the next case, you cannot flinch a target that's either frozen or sleeping, but again, it doesn't make sense for frostbitten Pokémon. Go to engine/battle/effect_commands.asm:
BattleCommand_FakeOut:
ld a, [wAttackMissed]
and a
ret nz
call CheckSubstituteOpp
jr nz, .fail
ld a, BATTLE_VARS_STATUS_OPP
call GetBattleVar
- and 1 << FRZ | SLP_MASK
+ and SLP_MASK
jr nz, .fail
call CheckOpponentWentFirst
jr z, FlinchTarget
.fail
ld a, 1
ld [wAttackMissed], a
ret
BattleCommand_FlinchTarget:
call CheckSubstituteOpp
ret nz
ld a, BATTLE_VARS_STATUS_OPP
call GetBattleVar
- and 1 << FRZ | SLP_MASK
+ and SLP_MASK
ret nz
call CheckOpponentWentFirst
ret nz
ld a, [wEffectFailed]
and a
ret nz
CheckFaintedFrzSlep
is used for muting a Pokémon's cry whenever they're sent into battle or seen in the status screen. When evolving, it won't animate the Pokémon either. Just like before, we'll edit the check in engine/pokemon/stats_screen.asm:
CheckFaintedFrzSlp:
ld hl, MON_HP
add hl, bc
ld a, [hli]
or [hl]
- jr z, .fainted_frz_slp
+ jr z, .fainted_slp
ld hl, MON_STATUS
add hl, bc
ld a, [hl]
- and 1 << FRZ | SLP_MASK
- jr nz, .fainted_frz_slp
+ and SLP _MASK
+ jr nz, .fainted_slp
and a
ret
-.fainted_frz_slp
+.fainted_slp
scf
ret
We've been working from the player's perspective, but now we need to modify the AI to act properly.
If the AI has the CONTEXT_USE_F
item flag, possesses either a Full Heal or Full Restore, and their Pokémon is frozen/sleeping, they'll always use their item. It makes sense for frozen Pokémon since they can't move and can only heal themselves by using either Flame Wheel or Sacred Fire, but a frostbitten Pokémon can still act, so it's not a high priority anymore. Let's go to engine/battle/ai/items.asm:
AI_Items:
...
.FailToxicCheck:
ld a, [wEnemyMonStatus]
- and 1 << FRZ | SLP_MASK
+ and SLP_MASK
jp z, .DontUse
jp .Use
And also let's go to engine/battle/ai/scoring.asm to adjust the AI intelligence (and edit some comments to be more accurate):
AI_Smart_DefrostOpponent:
-; Greatly encourage this move if enemy is frozen.
+; Greatly encourage this move if enemy is frostbitten.
; No move has EFFECT_DEFROST_OPPONENT, so this layer is unused.
...
AI_Smart_HealBell:
; Dismiss this move if none of the opponent's Pokemon is statused.
; Encourage this move if the enemy is statused.
-; 50% chance to greatly encourage this move if the enemy is fast asleep or frozen.
+; 50% chance to greatly encourage this move if the enemy is fast asleep.
...
.ok
- and 1 << FRZ | SLP_MASK
+ and SLP_MASK
ret z
call AI_50_50
ret c
dec [hl]
dec [hl]
ret
...
AI_Smart_FlameWheel:
-; Use this move if the enemy is frozen.
+; Use this move if the enemy is frostbitten.
...
Now what's left is some text related to items/moves and other things to reflect the new status. Let's go step by step. First in data/items/descriptions.asm:
IceHealDesc:
- db "Defrosts frozen"
- next "#MON.@
+ db "Heals a frostbit-"
+ next "ten #MON.@"
...
BurntBerryDesc:
db "A self-cure for"
- next "freezing. (HOLD)@"
+ next "frostbite. (HOLD)@"
Then in data/moves/descriptions.asm:
IcePunchDescription:
db "An icy punch. May"
- next "cause freezing.@"
+ next "cause frostbite.@"
IceBeamDescription:
db "An attack that may"
- next "freeze the foe.@"
+ next "cause frostbite.@"
BlizzardDescription:
db "An attack that may"
- next "freeze the foe.@"
+ next "cause frostbite.@"
PowderSnowDescription:
db "An attack that may"
- next "cause freezing.@"
+ next "cause frostbite.@"
Next we'll edit the "FRZ" acronym in engine/pokemon/mon_stats.asm:
PlaceNonFaintStatus:
push de
ld a, [de]
ld de, PsnString
bit PSN, a
jr nz, .place
ld de, BrnString
bit BRN, a
jr nz, .place
- ld de, FrzString
+ ld de, FrbString
bit FRZ, a
...
SlpString: db "SLP@"
PsnString: db "PSN@"
BrnString: db "BRN@"
-FrzString: db "FRZ@"
+FrbString: db "FRB@"
ParString: db "PAR@"
Finally, there are two maps with dialogue talking about the Frozen status that we'll edit. The first map is maps/EarlsPokemonAcademy.asm:
AcademyBlackboard:
opentext
writetext AcademyBlackboardText
.Loop:
loadmenu .BlackboardMenuHeader
_2dmenu
closewindow
ifequal 1, .Poison
ifequal 2, .Paralysis
ifequal 3, .Sleep
ifequal 4, .Burn
- ifequal 5, .Freeze
+ ifequal 5, .Frostbite
closetext
end
...
-.Freeze:
- writetext AcademyFreezeText
+.Frostbite:
+ writetext AcademyFrostbiteText
waitbutton
sjump .Loop
...
-AcademyFreezeText:
+AcademyFrostbiteText:
text "If your #MON is"
- line "frozen, it can't"
- cont "do a thing."
+ line "frostbitten, it'll"
+ cont "gradually lose HP."
- para "It remains frozen"
- line "after battle."
+ para "Its SPCL.ATK will"
+ line "also be halved."
para "Thaw it out with"
line "an ICE HEAL."
done
And the second one is maps/MahoganyGym.asm:
BoarderRonaldSeenText:
- text "I'll freeze your"
- line "#MON, so you"
+ text "I'll frostbite"
+ line "your #MON, so you"
cont "can't do a thing!"
done
...
BoarderRonaldAfterBattleText:
text "I think there's a"
- line "move a #MON"
-
- para "can use while it's"
- line "frozen."
+ line "move a frostbitten"
+
+ para "#MON can use to"
+ line "cure itself."
done
And with this we're finally done! Frostbite is completely implemented into the game!