summaryrefslogtreecommitdiff
path: root/engine/battle/move_effects
diff options
context:
space:
mode:
Diffstat (limited to 'engine/battle/move_effects')
-rw-r--r--engine/battle/move_effects/conversion.asm35
-rw-r--r--engine/battle/move_effects/drain_hp.asm104
-rw-r--r--engine/battle/move_effects/focus_energy.asm22
-rw-r--r--engine/battle/move_effects/haze.asm81
-rw-r--r--engine/battle/move_effects/heal.asm120
-rw-r--r--engine/battle/move_effects/leech_seed.asm40
-rw-r--r--engine/battle/move_effects/mist.asm19
-rw-r--r--engine/battle/move_effects/one_hit_ko.asm38
-rw-r--r--engine/battle/move_effects/paralyze.asm47
-rw-r--r--engine/battle/move_effects/pay_day.asm45
-rw-r--r--engine/battle/move_effects/recoil.asm70
-rw-r--r--engine/battle/move_effects/reflect_light_screen.asm45
-rw-r--r--engine/battle/move_effects/substitute.asm77
-rw-r--r--engine/battle/move_effects/transform.asm141
14 files changed, 884 insertions, 0 deletions
diff --git a/engine/battle/move_effects/conversion.asm b/engine/battle/move_effects/conversion.asm
new file mode 100644
index 00000000..1f64ec5b
--- /dev/null
+++ b/engine/battle/move_effects/conversion.asm
@@ -0,0 +1,35 @@
+ConversionEffect_:
+ ld hl, wEnemyMonType1
+ ld de, wBattleMonType1
+ ldh a, [hWhoseTurn]
+ and a
+ ld a, [wEnemyBattleStatus1]
+ jr z, .conversionEffect
+ push hl
+ ld h, d
+ ld l, e
+ pop de
+ ld a, [wPlayerBattleStatus1]
+.conversionEffect
+ bit INVULNERABLE, a ; is mon immune to typical attacks (dig/fly)
+ jr nz, PrintButItFailedText
+; copy target's types to user
+ ld a, [hli]
+ ld [de], a
+ inc de
+ ld a, [hl]
+ ld [de], a
+ ld hl, PlayCurrentMoveAnimation
+ call CallBankF
+ ld hl, ConvertedTypeText
+ jp PrintText
+
+ConvertedTypeText:
+ text_far _ConvertedTypeText
+ text_end
+
+PrintButItFailedText:
+ ld hl, PrintButItFailedText_
+CallBankF:
+ ld b, BANK(PrintButItFailedText_)
+ jp Bankswitch
diff --git a/engine/battle/move_effects/drain_hp.asm b/engine/battle/move_effects/drain_hp.asm
new file mode 100644
index 00000000..04a585cc
--- /dev/null
+++ b/engine/battle/move_effects/drain_hp.asm
@@ -0,0 +1,104 @@
+DrainHPEffect_:
+ ld hl, wDamage
+ ld a, [hl]
+ srl a ; divide damage by 2
+ ld [hli], a
+ ld a, [hl]
+ rr a
+ ld [hld], a
+ or [hl] ; is damage 0?
+ jr nz, .getAttackerHP
+; if damage is 0, increase to 1 so that the attacker gains at least 1 HP
+ inc hl
+ inc [hl]
+.getAttackerHP
+ ld hl, wBattleMonHP
+ ld de, wBattleMonMaxHP
+ ldh a, [hWhoseTurn]
+ and a
+ jp z, .addDamageToAttackerHP
+ ld hl, wEnemyMonHP
+ ld de, wEnemyMonMaxHP
+.addDamageToAttackerHP
+ ld bc, wHPBarOldHP+1
+; copy current HP to wHPBarOldHP
+ ld a, [hli]
+ ld [bc], a
+ ld a, [hl]
+ dec bc
+ ld [bc], a
+; copy max HP to wHPBarMaxHP
+ ld a, [de]
+ dec bc
+ ld [bc], a
+ inc de
+ ld a, [de]
+ dec bc
+ ld [bc], a
+; add damage to attacker's HP and copy new HP to wHPBarNewHP
+ ld a, [wDamage + 1]
+ ld b, [hl]
+ add b
+ ld [hld], a
+ ld [wHPBarNewHP], a
+ ld a, [wDamage]
+ ld b, [hl]
+ adc b
+ ld [hli], a
+ ld [wHPBarNewHP+1], a
+ jr c, .capToMaxHP ; if HP > 65,535, cap to max HP
+; compare HP with max HP
+ ld a, [hld]
+ ld b, a
+ ld a, [de]
+ dec de
+ sub b
+ ld a, [hli]
+ ld b, a
+ ld a, [de]
+ inc de
+ sbc b
+ jr nc, .next
+.capToMaxHP
+ ld a, [de]
+ ld [hld], a
+ ld [wHPBarNewHP], a
+ dec de
+ ld a, [de]
+ ld [hli], a
+ ld [wHPBarNewHP+1], a
+ inc de
+.next
+ ldh a, [hWhoseTurn]
+ and a
+ hlcoord 10, 9
+ ld a, $1
+ jr z, .next2
+ hlcoord 2, 2
+ xor a
+.next2
+ ld [wHPBarType], a
+ predef UpdateHPBar2
+ predef DrawPlayerHUDAndHPBar
+ predef DrawEnemyHUDAndHPBar
+ callfar ReadPlayerMonCurHPAndStatus
+ ld hl, SuckedHealthText
+ ldh a, [hWhoseTurn]
+ and a
+ ld a, [wPlayerMoveEffect]
+ jr z, .next3
+ ld a, [wEnemyMoveEffect]
+.next3
+ cp DREAM_EATER_EFFECT
+ jr nz, .printText
+ ld hl, DreamWasEatenText
+.printText
+ jp PrintText
+
+SuckedHealthText:
+ text_far _SuckedHealthText
+ text_end
+
+DreamWasEatenText:
+ text_far _DreamWasEatenText
+ text_end
diff --git a/engine/battle/move_effects/focus_energy.asm b/engine/battle/move_effects/focus_energy.asm
new file mode 100644
index 00000000..1fafe920
--- /dev/null
+++ b/engine/battle/move_effects/focus_energy.asm
@@ -0,0 +1,22 @@
+FocusEnergyEffect_:
+ ld hl, wPlayerBattleStatus2
+ ldh a, [hWhoseTurn]
+ and a
+ jr z, .notEnemy
+ ld hl, wEnemyBattleStatus2
+.notEnemy
+ bit GETTING_PUMPED, [hl] ; is mon already using focus energy?
+ jr nz, .alreadyUsing
+ set GETTING_PUMPED, [hl] ; mon is now using focus energy
+ callfar PlayCurrentMoveAnimation
+ ld hl, GettingPumpedText
+ jp PrintText
+.alreadyUsing
+ ld c, 50
+ call DelayFrames
+ jpfar PrintButItFailedText_
+
+GettingPumpedText:
+ text_pause
+ text_far _GettingPumpedText
+ text_end
diff --git a/engine/battle/move_effects/haze.asm b/engine/battle/move_effects/haze.asm
new file mode 100644
index 00000000..915eeed8
--- /dev/null
+++ b/engine/battle/move_effects/haze.asm
@@ -0,0 +1,81 @@
+HazeEffect_:
+ ld a, $7
+; store 7 on every stat mod
+ ld hl, wPlayerMonAttackMod
+ call ResetStatMods
+ ld hl, wEnemyMonAttackMod
+ call ResetStatMods
+; copy unmodified stats to battle stats
+ ld hl, wPlayerMonUnmodifiedAttack
+ ld de, wBattleMonAttack
+ call ResetStats
+ ld hl, wEnemyMonUnmodifiedAttack
+ ld de, wEnemyMonAttack
+ call ResetStats
+; cure non-volatile status, but only for the target
+ ld hl, wEnemyMonStatus
+ ld de, wEnemySelectedMove
+ ldh a, [hWhoseTurn]
+ and a
+ jr z, .cureStatuses
+ ld hl, wBattleMonStatus
+ dec de ; wPlayerSelectedMove
+
+.cureStatuses
+ ld a, [hl]
+ ld [hl], $0
+ and SLP | (1 << FRZ)
+ jr z, .cureVolatileStatuses
+; prevent the Pokemon from executing a move if it was asleep or frozen
+ ld a, $ff
+ ld [de], a
+
+.cureVolatileStatuses
+ xor a
+ ld [wPlayerDisabledMove], a
+ ld [wEnemyDisabledMove], a
+ ld hl, wPlayerDisabledMoveNumber
+ ld [hli], a
+ ld [hl], a
+ ld hl, wPlayerBattleStatus1
+ call CureVolatileStatuses
+ ld hl, wEnemyBattleStatus1
+ call CureVolatileStatuses
+ ld hl, PlayCurrentMoveAnimation
+ call CallBankF
+ ld hl, StatusChangesEliminatedText
+ jp PrintText
+
+CureVolatileStatuses:
+ res CONFUSED, [hl]
+ inc hl ; BATTSTATUS2
+ ld a, [hl]
+ ; clear USING_X_ACCURACY, PROTECTED_BY_MIST, GETTING_PUMPED, and SEEDED statuses
+ and $ff ^((1 << USING_X_ACCURACY) | (1 << PROTECTED_BY_MIST) | (1 << GETTING_PUMPED) | (1 << SEEDED))
+ ld [hli], a ; BATTSTATUS3
+ ld a, [hl]
+ and %11110000 | (1 << TRANSFORMED) ; clear Bad Poison, Reflect and Light Screen statuses
+ ld [hl], a
+ ret
+
+ResetStatMods:
+ ld b, $8
+.loop
+ ld [hli], a
+ dec b
+ jr nz, .loop
+ ret
+
+ResetStats:
+ ld b, $8
+.loop
+ ld a, [hli]
+ ld [de], a
+ inc de
+ dec b
+ jr nz, .loop
+ ret
+
+StatusChangesEliminatedText:
+ text_far _StatusChangesEliminatedText
+ text_end
diff --git a/engine/battle/move_effects/heal.asm b/engine/battle/move_effects/heal.asm
new file mode 100644
index 00000000..80923a29
--- /dev/null
+++ b/engine/battle/move_effects/heal.asm
@@ -0,0 +1,120 @@
+HealEffect_:
+ ldh a, [hWhoseTurn]
+ and a
+ ld de, wBattleMonHP
+ ld hl, wBattleMonMaxHP
+ ld a, [wPlayerMoveNum]
+ jr z, .healEffect
+ ld de, wEnemyMonHP
+ ld hl, wEnemyMonMaxHP
+ ld a, [wEnemyMoveNum]
+.healEffect
+ ld b, a
+ ld a, [de]
+ cp [hl] ; most significant bytes comparison is ignored
+ ; causes the move to miss if max HP is 255 or 511 points higher than the current HP
+ inc de
+ inc hl
+ ld a, [de]
+ sbc [hl]
+ jp z, .failed ; no effect if user's HP is already at its maximum
+ ld a, b
+ cp REST
+ jr nz, .healHP
+ push hl
+ push de
+ push af
+ ld c, 50
+ call DelayFrames
+ ld hl, wBattleMonStatus
+ ldh a, [hWhoseTurn]
+ and a
+ jr z, .restEffect
+ ld hl, wEnemyMonStatus
+.restEffect
+ ld a, [hl]
+ and a
+ ld [hl], 2 ; clear status and set number of turns asleep to 2
+ ld hl, StartedSleepingEffect ; if mon didn't have an status
+ jr z, .printRestText
+ ld hl, FellAsleepBecameHealthyText ; if mon had an status
+.printRestText
+ call PrintText
+ pop af
+ pop de
+ pop hl
+.healHP
+ ld a, [hld]
+ ld [wHPBarMaxHP], a
+ ld c, a
+ ld a, [hl]
+ ld [wHPBarMaxHP+1], a
+ ld b, a
+ jr z, .gotHPAmountToHeal
+; Recover and Softboiled only heal for half the mon's max HP
+ srl b
+ rr c
+.gotHPAmountToHeal
+; update HP
+ ld a, [de]
+ ld [wHPBarOldHP], a
+ add c
+ ld [de], a
+ ld [wHPBarNewHP], a
+ dec de
+ ld a, [de]
+ ld [wHPBarOldHP+1], a
+ adc b
+ ld [de], a
+ ld [wHPBarNewHP+1], a
+ inc hl
+ inc de
+ ld a, [de]
+ dec de
+ sub [hl]
+ dec hl
+ ld a, [de]
+ sbc [hl]
+ jr c, .playAnim
+; copy max HP to current HP if an overflow occurred
+ ld a, [hli]
+ ld [de], a
+ ld [wHPBarNewHP+1], a
+ inc de
+ ld a, [hl]
+ ld [de], a
+ ld [wHPBarNewHP], a
+.playAnim
+ ld hl, PlayCurrentMoveAnimation
+ call Bankswitch3DtoF
+ ldh a, [hWhoseTurn]
+ and a
+ hlcoord 10, 9
+ ld a, $1
+ jr z, .updateHPBar
+ hlcoord 2, 2
+ xor a
+.updateHPBar
+ ld [wHPBarType], a
+ predef UpdateHPBar2
+ ld hl, DrawHUDsAndHPBars
+ call Bankswitch3DtoF
+ ld hl, RegainedHealthText
+ jp PrintText
+.failed
+ ld c, 50
+ call DelayFrames
+ ld hl, PrintButItFailedText_
+ jp Bankswitch3DtoF
+
+StartedSleepingEffect:
+ text_far _StartedSleepingEffect
+ text_end
+
+FellAsleepBecameHealthyText:
+ text_far _FellAsleepBecameHealthyText
+ text_end
+
+RegainedHealthText:
+ text_far _RegainedHealthText
+ text_end
diff --git a/engine/battle/move_effects/leech_seed.asm b/engine/battle/move_effects/leech_seed.asm
new file mode 100644
index 00000000..61bd982a
--- /dev/null
+++ b/engine/battle/move_effects/leech_seed.asm
@@ -0,0 +1,40 @@
+LeechSeedEffect_:
+ callfar MoveHitTest
+ ld a, [wMoveMissed]
+ and a
+ jr nz, .moveMissed
+ ld hl, wEnemyBattleStatus2
+ ld de, wEnemyMonType1
+ ldh a, [hWhoseTurn]
+ and a
+ jr z, .leechSeedEffect
+ ld hl, wPlayerBattleStatus2
+ ld de, wBattleMonType1
+.leechSeedEffect
+; miss if the target is grass-type or already seeded
+ ld a, [de]
+ cp GRASS
+ jr z, .moveMissed
+ inc de
+ ld a, [de]
+ cp GRASS
+ jr z, .moveMissed
+ bit SEEDED, [hl]
+ jr nz, .moveMissed
+ set SEEDED, [hl]
+ callfar PlayCurrentMoveAnimation
+ ld hl, WasSeededText
+ jp PrintText
+.moveMissed
+ ld c, 50
+ call DelayFrames
+ ld hl, EvadedAttackText
+ jp PrintText
+
+WasSeededText:
+ text_far _WasSeededText
+ text_end
+
+EvadedAttackText:
+ text_far _EvadedAttackText
+ text_end
diff --git a/engine/battle/move_effects/mist.asm b/engine/battle/move_effects/mist.asm
new file mode 100644
index 00000000..163d386f
--- /dev/null
+++ b/engine/battle/move_effects/mist.asm
@@ -0,0 +1,19 @@
+MistEffect_:
+ ld hl, wPlayerBattleStatus2
+ ldh a, [hWhoseTurn]
+ and a
+ jr z, .mistEffect
+ ld hl, wEnemyBattleStatus2
+.mistEffect
+ bit PROTECTED_BY_MIST, [hl] ; is mon protected by mist?
+ jr nz, .mistAlreadyInUse
+ set PROTECTED_BY_MIST, [hl] ; mon is now protected by mist
+ callfar PlayCurrentMoveAnimation
+ ld hl, ShroudedInMistText
+ jp PrintText
+.mistAlreadyInUse
+ jpfar PrintButItFailedText_
+
+ShroudedInMistText:
+ text_far _ShroudedInMistText
+ text_end
diff --git a/engine/battle/move_effects/one_hit_ko.asm b/engine/battle/move_effects/one_hit_ko.asm
new file mode 100644
index 00000000..7e5db0f7
--- /dev/null
+++ b/engine/battle/move_effects/one_hit_ko.asm
@@ -0,0 +1,38 @@
+OneHitKOEffect_:
+ ld hl, wDamage
+ xor a
+ ld [hli], a
+ ld [hl], a ; set the damage output to zero
+ dec a
+ ld [wCriticalHitOrOHKO], a
+ ld hl, wBattleMonSpeed + 1
+ ld de, wEnemyMonSpeed + 1
+ ldh a, [hWhoseTurn]
+ and a
+ jr z, .compareSpeed
+ ld hl, wEnemyMonSpeed + 1
+ ld de, wBattleMonSpeed + 1
+.compareSpeed
+; set damage to 65535 and OHKO flag is the user's current speed is higher than the target's
+ ld a, [de]
+ dec de
+ ld b, a
+ ld a, [hld]
+ sub b
+ ld a, [de]
+ ld b, a
+ ld a, [hl]
+ sbc b
+ jr c, .userIsSlower
+ ld hl, wDamage
+ ld a, $ff
+ ld [hli], a
+ ld [hl], a
+ ld a, $2
+ ld [wCriticalHitOrOHKO], a
+ ret
+.userIsSlower
+; keep damage at 0 and set move missed flag if target's current speed is higher instead
+ ld a, $1
+ ld [wMoveMissed], a
+ ret
diff --git a/engine/battle/move_effects/paralyze.asm b/engine/battle/move_effects/paralyze.asm
new file mode 100644
index 00000000..dbaa0fb8
--- /dev/null
+++ b/engine/battle/move_effects/paralyze.asm
@@ -0,0 +1,47 @@
+ParalyzeEffect_:
+ ld hl, wEnemyMonStatus
+ ld de, wPlayerMoveType
+ ldh a, [hWhoseTurn]
+ and a
+ jp z, .next
+ ld hl, wBattleMonStatus
+ ld de, wEnemyMoveType
+.next
+ ld a, [hl]
+ and a ; does the target already have a status ailment?
+ jr nz, .didntAffect
+; check if the target is immune due to types
+ ld a, [de]
+ cp ELECTRIC
+ jr nz, .hitTest
+ ld b, h
+ ld c, l
+ inc bc
+ ld a, [bc]
+ cp GROUND
+ jr z, .doesntAffect
+ inc bc
+ ld a, [bc]
+ cp GROUND
+ jr z, .doesntAffect
+.hitTest
+ push hl
+ callfar MoveHitTest
+ pop hl
+ ld a, [wMoveMissed]
+ and a
+ jr nz, .didntAffect
+ set PAR, [hl]
+ callfar QuarterSpeedDueToParalysis
+ ld c, 30
+ call DelayFrames
+ callfar PlayCurrentMoveAnimation
+ jpfar PrintMayNotAttackText
+.didntAffect
+ ld c, 50
+ call DelayFrames
+ jpfar PrintDidntAffectText
+.doesntAffect
+ ld c, 50
+ call DelayFrames
+ jpfar PrintDoesntAffectText
diff --git a/engine/battle/move_effects/pay_day.asm b/engine/battle/move_effects/pay_day.asm
new file mode 100644
index 00000000..fa373038
--- /dev/null
+++ b/engine/battle/move_effects/pay_day.asm
@@ -0,0 +1,45 @@
+PayDayEffect_:
+ xor a
+ ld hl, wcd6d
+ ld [hli], a
+ ldh a, [hWhoseTurn]
+ and a
+ ld a, [wBattleMonLevel]
+ jr z, .payDayEffect
+ ld a, [wEnemyMonLevel]
+.payDayEffect
+; level * 2
+ add a
+ ldh [hDividend + 3], a
+ xor a
+ ldh [hDividend], a
+ ldh [hDividend + 1], a
+ ldh [hDividend + 2], a
+; convert to BCD
+ ld a, 100
+ ldh [hDivisor], a
+ ld b, $4
+ call Divide
+ ldh a, [hQuotient + 3]
+ ld [hli], a
+ ldh a, [hRemainder]
+ ldh [hDividend + 3], a
+ ld a, 10
+ ldh [hDivisor], a
+ ld b, $4
+ call Divide
+ ldh a, [hQuotient + 3]
+ swap a
+ ld b, a
+ ldh a, [hRemainder]
+ add b
+ ld [hl], a
+ ld de, wTotalPayDayMoney + 2
+ ld c, $3
+ predef AddBCDPredef
+ ld hl, CoinsScatteredText
+ jp PrintText
+
+CoinsScatteredText:
+ text_far _CoinsScatteredText
+ text_end
diff --git a/engine/battle/move_effects/recoil.asm b/engine/battle/move_effects/recoil.asm
new file mode 100644
index 00000000..85110d50
--- /dev/null
+++ b/engine/battle/move_effects/recoil.asm
@@ -0,0 +1,70 @@
+RecoilEffect_:
+ ldh a, [hWhoseTurn]
+ and a
+ ld a, [wPlayerMoveNum]
+ ld hl, wBattleMonMaxHP
+ jr z, .recoilEffect
+ ld a, [wEnemyMoveNum]
+ ld hl, wEnemyMonMaxHP
+.recoilEffect
+ ld d, a
+ ld a, [wDamage]
+ ld b, a
+ ld a, [wDamage + 1]
+ ld c, a
+ srl b
+ rr c
+ ld a, d
+ cp STRUGGLE ; struggle deals 50% recoil damage
+ jr z, .gotRecoilDamage
+ srl b
+ rr c
+.gotRecoilDamage
+ ld a, b
+ or c
+ jr nz, .updateHP
+ inc c ; minimum recoil damage is 1
+.updateHP
+; subtract HP from user due to the recoil damage
+ ld a, [hli]
+ ld [wHPBarMaxHP+1], a
+ ld a, [hl]
+ ld [wHPBarMaxHP], a
+ push bc
+ ld bc, wBattleMonHP - wBattleMonMaxHP
+ add hl, bc
+ pop bc
+ ld a, [hl]
+ ld [wHPBarOldHP], a
+ sub c
+ ld [hld], a
+ ld [wHPBarNewHP], a
+ ld a, [hl]
+ ld [wHPBarOldHP+1], a
+ sbc b
+ ld [hl], a
+ ld [wHPBarNewHP+1], a
+ jr nc, .getHPBarCoords
+; if recoil damage is higher than the Pokemon's HP, set its HP to 0
+ xor a
+ ld [hli], a
+ ld [hl], a
+ ld hl, wHPBarNewHP
+ ld [hli], a
+ ld [hl], a
+.getHPBarCoords
+ hlcoord 10, 9
+ ldh a, [hWhoseTurn]
+ and a
+ ld a, $1
+ jr z, .updateHPBar
+ hlcoord 2, 2
+ xor a
+.updateHPBar
+ ld [wHPBarType], a
+ predef UpdateHPBar2
+ ld hl, HitWithRecoilText
+ jp PrintText
+HitWithRecoilText:
+ text_far _HitWithRecoilText
+ text_end
diff --git a/engine/battle/move_effects/reflect_light_screen.asm b/engine/battle/move_effects/reflect_light_screen.asm
new file mode 100644
index 00000000..c05055fa
--- /dev/null
+++ b/engine/battle/move_effects/reflect_light_screen.asm
@@ -0,0 +1,45 @@
+ReflectLightScreenEffect_:
+ ld hl, wPlayerBattleStatus3
+ ld de, wPlayerMoveEffect
+ ldh a, [hWhoseTurn]
+ and a
+ jr z, .reflectLightScreenEffect
+ ld hl, wEnemyBattleStatus3
+ ld de, wEnemyMoveEffect
+.reflectLightScreenEffect
+ ld a, [de]
+ cp LIGHT_SCREEN_EFFECT
+ jr nz, .reflect
+ bit HAS_LIGHT_SCREEN_UP, [hl] ; is mon already protected by light screen?
+ jr nz, .moveFailed
+ set HAS_LIGHT_SCREEN_UP, [hl] ; mon is now protected by light screen
+ ld hl, LightScreenProtectedText
+ jr .playAnim
+.reflect
+ bit HAS_REFLECT_UP, [hl] ; is mon already protected by reflect?
+ jr nz, .moveFailed
+ set HAS_REFLECT_UP, [hl] ; mon is now protected by reflect
+ ld hl, ReflectGainedArmorText
+.playAnim
+ push hl
+ ld hl, PlayCurrentMoveAnimation
+ call Bankswitch3DtoF
+ pop hl
+ jp PrintText
+.moveFailed
+ ld c, 50
+ call DelayFrames
+ ld hl, PrintButItFailedText_
+ jp Bankswitch3DtoF
+
+LightScreenProtectedText:
+ text_far _LightScreenProtectedText
+ text_end
+
+ReflectGainedArmorText:
+ text_far _ReflectGainedArmorText
+ text_end
+
+Bankswitch3DtoF:
+ ld b, BANK(BattleCore)
+ jp Bankswitch
diff --git a/engine/battle/move_effects/substitute.asm b/engine/battle/move_effects/substitute.asm
new file mode 100644
index 00000000..860b76b6
--- /dev/null
+++ b/engine/battle/move_effects/substitute.asm
@@ -0,0 +1,77 @@
+SubstituteEffect_:
+ ld c, 50
+ call DelayFrames
+ ld hl, wBattleMonMaxHP
+ ld de, wPlayerSubstituteHP
+ ld bc, wPlayerBattleStatus2
+ ldh a, [hWhoseTurn]
+ and a
+ jr z, .notEnemy
+ ld hl, wEnemyMonMaxHP
+ ld de, wEnemySubstituteHP
+ ld bc, wEnemyBattleStatus2
+.notEnemy
+ ld a, [bc]
+ bit HAS_SUBSTITUTE_UP, a ; user already has substitute?
+ jr nz, .alreadyHasSubstitute
+; quarter health to remove from user
+; assumes max HP is 1023 or lower
+ push bc
+ ld a, [hli]
+ ld b, [hl]
+ srl a
+ rr b
+ srl a
+ rr b ; max hp / 4
+ push de
+ ld de, wBattleMonHP - wBattleMonMaxHP
+ add hl, de ; point hl to current HP low byte
+ pop de
+ ld a, b
+ ld [de], a ; save copy of HP to subtract in wPlayerSubstituteHP/wEnemySubstituteHP
+ ld a, [hld]
+; subtract [max hp / 4] to current HP
+ sub b
+ ld d, a
+ ld a, [hl]
+ sbc 0
+ pop bc
+ jr c, .notEnoughHP ; underflow means user would be left with negative health
+ ; bug: since it only branches on carry, it will possibly leave user with 0 HP
+.userHasZeroOrMoreHP
+ ldi [hl], a ; save resulting HP after subtraction into current HP
+ ld [hl], d
+ ld h, b
+ ld l, c
+ set HAS_SUBSTITUTE_UP, [hl]
+ ld a, [wOptions]
+ bit 7, a ; battle animation is enabled?
+ ld hl, PlayCurrentMoveAnimation
+ ld b, BANK(PlayCurrentMoveAnimation)
+ jr z, .animationEnabled
+ ld hl, AnimationSubstitute
+ ld b, BANK(AnimationSubstitute)
+.animationEnabled
+ call Bankswitch ; jump to routine depending on animation setting
+ ld hl, SubstituteText
+ call PrintText
+ jpfar DrawHUDsAndHPBars
+.alreadyHasSubstitute
+ ld hl, HasSubstituteText
+ jr .printText
+.notEnoughHP
+ ld hl, TooWeakSubstituteText
+.printText
+ jp PrintText
+
+SubstituteText:
+ text_far _SubstituteText
+ text_end
+
+HasSubstituteText:
+ text_far _HasSubstituteText
+ text_end
+
+TooWeakSubstituteText:
+ text_far _TooWeakSubstituteText
+ text_end
diff --git a/engine/battle/move_effects/transform.asm b/engine/battle/move_effects/transform.asm
new file mode 100644
index 00000000..d37bd94d
--- /dev/null
+++ b/engine/battle/move_effects/transform.asm
@@ -0,0 +1,141 @@
+TransformEffect_:
+ ld hl, wBattleMonSpecies
+ ld de, wEnemyMonSpecies
+ ld bc, wEnemyBattleStatus3
+ ld a, [wEnemyBattleStatus1]
+ ldh a, [hWhoseTurn]
+ and a
+ jr nz, .hitTest
+ ld hl, wEnemyMonSpecies
+ ld de, wBattleMonSpecies
+ ld bc, wPlayerBattleStatus3
+ ld [wPlayerMoveListIndex], a
+ ld a, [wPlayerBattleStatus1]
+.hitTest
+ bit INVULNERABLE, a ; is mon invulnerable to typical attacks? (fly/dig)
+ jp nz, .failed
+ push hl
+ push de
+ push bc
+ ld hl, wPlayerBattleStatus2
+ ldh a, [hWhoseTurn]
+ and a
+ jr z, .transformEffect
+ ld hl, wEnemyBattleStatus2
+.transformEffect
+; animation(s) played are different if target has Substitute up
+ bit HAS_SUBSTITUTE_UP, [hl]
+ push af
+ ld hl, HideSubstituteShowMonAnim
+ ld b, BANK(HideSubstituteShowMonAnim)
+ call nz, Bankswitch
+ ld a, [wOptions]
+ add a
+ ld hl, PlayCurrentMoveAnimation
+ ld b, BANK(PlayCurrentMoveAnimation)
+ jr nc, .gotAnimToPlay
+ ld hl, AnimationTransformMon
+ ld b, BANK(AnimationTransformMon)
+.gotAnimToPlay
+ call Bankswitch
+ ld hl, ReshowSubstituteAnim
+ ld b, BANK(ReshowSubstituteAnim)
+ pop af
+ call nz, Bankswitch
+ pop bc
+ ld a, [bc]
+ set TRANSFORMED, a ; mon is now transformed
+ ld [bc], a
+ pop de
+ pop hl
+ push hl
+; transform user into opposing Pokemon
+; species
+ ld a, [hl]
+ ld [de], a
+; type 1, type 2, catch rate, and moves
+ ld bc, $5
+ add hl, bc
+ inc de
+ inc de
+ inc de
+ inc de
+ inc de
+ inc bc
+ inc bc
+ call CopyData
+ ldh a, [hWhoseTurn]
+ and a
+ jr z, .next
+; save enemy mon DVs at wTransformedEnemyMonOriginalDVs
+ ld a, [de]
+ ld [wTransformedEnemyMonOriginalDVs], a
+ inc de
+ ld a, [de]
+ ld [wTransformedEnemyMonOriginalDVs + 1], a
+ dec de
+.next
+; DVs
+ ld a, [hli]
+ ld [de], a
+ inc de
+ ld a, [hli]
+ ld [de], a
+ inc de
+; Attack, Defense, Speed, and Special stats
+ inc hl
+ inc hl
+ inc hl
+ inc de
+ inc de
+ inc de
+ ld bc, $8
+ call CopyData
+ ld bc, wBattleMonMoves - wBattleMonPP
+ add hl, bc ; ld hl, wBattleMonMoves
+ ld b, NUM_MOVES
+.copyPPLoop
+; 5 PP for all moves
+ ld a, [hli]
+ and a
+ jr z, .lessThanFourMoves
+ ld a, $5
+.lessThanFourMoves
+ ld [de], a
+ inc de
+ dec b
+ jr nz, .copyPPLoop
+.copyStats
+; original (unmodified) stats and stat mods
+ pop hl
+ ld a, [hl]
+ ld [wd11e], a
+ call GetMonName
+ ld hl, wEnemyMonUnmodifiedAttack
+ ld de, wPlayerMonUnmodifiedAttack
+ call .copyBasedOnTurn ; original (unmodified) stats
+ ld hl, wEnemyMonStatMods
+ ld de, wPlayerMonStatMods
+ call .copyBasedOnTurn ; stat mods
+ ld hl, TransformedText
+ jp PrintText
+
+.copyBasedOnTurn
+ ldh a, [hWhoseTurn]
+ and a
+ jr z, .gotStatsOrModsToCopy
+ push hl
+ ld h, d
+ ld l, e
+ pop de
+.gotStatsOrModsToCopy
+ ld bc, $8
+ jp CopyData
+
+.failed
+ ld hl, PrintButItFailedText_
+ jp Bankswitch3DtoF
+
+TransformedText:
+ text_far _TransformedText
+ text_end