summaryrefslogtreecommitdiff
path: root/engine/battle/move_effects
diff options
context:
space:
mode:
authorRangi <35663410+Rangi42@users.noreply.github.com>2020-07-03 09:38:52 -0400
committerGitHub <noreply@github.com>2020-07-03 09:38:52 -0400
commitc85050497c1bd062e9cd40bf5b32fa3beca366cc (patch)
tree9593ddd3ab820223ab580d5fc0ae133b485b8315 /engine/battle/move_effects
parent5559d51c863b6fb529ea0494d857950a36fe85b7 (diff)
parent87ef75c173b5d5f227912860487600b6f53d1d1f (diff)
Merge pull request #256 from Rangi42/master
Add subdirectories to engine/ similar to pokecrystal
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.asm148
14 files changed, 891 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..f23c3d70
--- /dev/null
+++ b/engine/battle/move_effects/conversion.asm
@@ -0,0 +1,35 @@
+ConversionEffect_:
+ ld hl, wEnemyMonType1
+ ld de, wBattleMonType1
+ ld a, [H_WHOSETURN]
+ 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:
+ TX_FAR _ConvertedTypeText
+ db "@"
+
+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..e5f4681a
--- /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
+ ld a, [H_WHOSETURN]
+ 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
+ ld a, [H_WHOSETURN]
+ and a
+ coord hl, 10, 9
+ ld a, $1
+ jr z, .next2
+ coord hl, 2, 2
+ xor a
+.next2
+ ld [wHPBarType], a
+ predef UpdateHPBar2
+ predef DrawPlayerHUDAndHPBar
+ predef DrawEnemyHUDAndHPBar
+ callab ReadPlayerMonCurHPAndStatus
+ ld hl, SuckedHealthText
+ ld a, [H_WHOSETURN]
+ 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:
+ TX_FAR _SuckedHealthText
+ db "@"
+
+DreamWasEatenText:
+ TX_FAR _DreamWasEatenText
+ db "@"
diff --git a/engine/battle/move_effects/focus_energy.asm b/engine/battle/move_effects/focus_energy.asm
new file mode 100644
index 00000000..16dad7bb
--- /dev/null
+++ b/engine/battle/move_effects/focus_energy.asm
@@ -0,0 +1,22 @@
+FocusEnergyEffect_:
+ ld hl, wPlayerBattleStatus2
+ ld a, [H_WHOSETURN]
+ 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
+ callab PlayCurrentMoveAnimation
+ ld hl, GettingPumpedText
+ jp PrintText
+.alreadyUsing
+ ld c, 50
+ call DelayFrames
+ jpab PrintButItFailedText_
+
+GettingPumpedText:
+ TX_DELAY
+ TX_FAR _GettingPumpedText
+ db "@"
diff --git a/engine/battle/move_effects/haze.asm b/engine/battle/move_effects/haze.asm
new file mode 100644
index 00000000..47723ba2
--- /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
+ ld a, [H_WHOSETURN]
+ 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:
+ TX_FAR _StatusChangesEliminatedText
+ db "@"
diff --git a/engine/battle/move_effects/heal.asm b/engine/battle/move_effects/heal.asm
new file mode 100644
index 00000000..2e68acc0
--- /dev/null
+++ b/engine/battle/move_effects/heal.asm
@@ -0,0 +1,120 @@
+HealEffect_:
+ ld a, [H_WHOSETURN]
+ 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
+ ld a, [H_WHOSETURN]
+ 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 BankswitchEtoF
+ ld a, [H_WHOSETURN]
+ and a
+ coord hl, 10, 9
+ ld a, $1
+ jr z, .updateHPBar
+ coord hl, 2, 2
+ xor a
+.updateHPBar
+ ld [wHPBarType], a
+ predef UpdateHPBar2
+ ld hl, DrawHUDsAndHPBars
+ call BankswitchEtoF
+ ld hl, RegainedHealthText
+ jp PrintText
+.failed
+ ld c, 50
+ call DelayFrames
+ ld hl, PrintButItFailedText_
+ jp BankswitchEtoF
+
+StartedSleepingEffect:
+ TX_FAR _StartedSleepingEffect
+ db "@"
+
+FellAsleepBecameHealthyText:
+ TX_FAR _FellAsleepBecameHealthyText
+ db "@"
+
+RegainedHealthText:
+ TX_FAR _RegainedHealthText
+ db "@"
diff --git a/engine/battle/move_effects/leech_seed.asm b/engine/battle/move_effects/leech_seed.asm
new file mode 100644
index 00000000..f4d3ee9c
--- /dev/null
+++ b/engine/battle/move_effects/leech_seed.asm
@@ -0,0 +1,40 @@
+LeechSeedEffect_:
+ callab MoveHitTest
+ ld a, [wMoveMissed]
+ and a
+ jr nz, .moveMissed
+ ld hl, wEnemyBattleStatus2
+ ld de, wEnemyMonType1
+ ld a, [H_WHOSETURN]
+ 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]
+ callab PlayCurrentMoveAnimation
+ ld hl, WasSeededText
+ jp PrintText
+.moveMissed
+ ld c, 50
+ call DelayFrames
+ ld hl, EvadedAttackText
+ jp PrintText
+
+WasSeededText:
+ TX_FAR _WasSeededText
+ db "@"
+
+EvadedAttackText:
+ TX_FAR _EvadedAttackText
+ db "@"
diff --git a/engine/battle/move_effects/mist.asm b/engine/battle/move_effects/mist.asm
new file mode 100644
index 00000000..65070a3e
--- /dev/null
+++ b/engine/battle/move_effects/mist.asm
@@ -0,0 +1,19 @@
+MistEffect_:
+ ld hl, wPlayerBattleStatus2
+ ld a, [H_WHOSETURN]
+ 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
+ callab PlayCurrentMoveAnimation
+ ld hl, ShroudedInMistText
+ jp PrintText
+.mistAlreadyInUse
+ jpab PrintButItFailedText_
+
+ShroudedInMistText:
+ TX_FAR _ShroudedInMistText
+ db "@"
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..827e2197
--- /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
+ ld a, [H_WHOSETURN]
+ 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..95979ae6
--- /dev/null
+++ b/engine/battle/move_effects/paralyze.asm
@@ -0,0 +1,47 @@
+ParalyzeEffect_:
+ ld hl, wEnemyMonStatus
+ ld de, wPlayerMoveType
+ ld a, [H_WHOSETURN]
+ 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
+ callab MoveHitTest
+ pop hl
+ ld a, [wMoveMissed]
+ and a
+ jr nz, .didntAffect
+ set PAR, [hl]
+ callab QuarterSpeedDueToParalysis
+ ld c, 30
+ call DelayFrames
+ callab PlayCurrentMoveAnimation
+ jpab PrintMayNotAttackText
+.didntAffect
+ ld c, 50
+ call DelayFrames
+ jpab PrintDidntAffectText
+.doesntAffect
+ ld c, 50
+ call DelayFrames
+ jpab 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..e5daf014
--- /dev/null
+++ b/engine/battle/move_effects/pay_day.asm
@@ -0,0 +1,45 @@
+PayDayEffect_:
+ xor a
+ ld hl, wcd6d
+ ld [hli], a
+ ld a, [H_WHOSETURN]
+ and a
+ ld a, [wBattleMonLevel]
+ jr z, .payDayEffect
+ ld a, [wEnemyMonLevel]
+.payDayEffect
+; level * 2
+ add a
+ ld [H_DIVIDEND + 3], a
+ xor a
+ ld [H_DIVIDEND], a
+ ld [H_DIVIDEND + 1], a
+ ld [H_DIVIDEND + 2], a
+; convert to BCD
+ ld a, 100
+ ld [H_DIVISOR], a
+ ld b, $4
+ call Divide
+ ld a, [H_QUOTIENT + 3]
+ ld [hli], a
+ ld a, [H_REMAINDER]
+ ld [H_DIVIDEND + 3], a
+ ld a, 10
+ ld [H_DIVISOR], a
+ ld b, $4
+ call Divide
+ ld a, [H_QUOTIENT + 3]
+ swap a
+ ld b, a
+ ld a, [H_REMAINDER]
+ add b
+ ld [hl], a
+ ld de, wTotalPayDayMoney + 2
+ ld c, $3
+ predef AddBCDPredef
+ ld hl, CoinsScatteredText
+ jp PrintText
+
+CoinsScatteredText:
+ TX_FAR _CoinsScatteredText
+ db "@"
diff --git a/engine/battle/move_effects/recoil.asm b/engine/battle/move_effects/recoil.asm
new file mode 100644
index 00000000..0f2f087b
--- /dev/null
+++ b/engine/battle/move_effects/recoil.asm
@@ -0,0 +1,70 @@
+RecoilEffect_:
+ ld a, [H_WHOSETURN]
+ 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
+ coord hl, 10, 9
+ ld a, [H_WHOSETURN]
+ and a
+ ld a, $1
+ jr z, .updateHPBar
+ coord hl, 2, 2
+ xor a
+.updateHPBar
+ ld [wHPBarType], a
+ predef UpdateHPBar2
+ ld hl, HitWithRecoilText
+ jp PrintText
+HitWithRecoilText:
+ TX_FAR _HitWithRecoilText
+ db "@"
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..2805a969
--- /dev/null
+++ b/engine/battle/move_effects/reflect_light_screen.asm
@@ -0,0 +1,45 @@
+ReflectLightScreenEffect_:
+ ld hl, wPlayerBattleStatus3
+ ld de, wPlayerMoveEffect
+ ld a, [H_WHOSETURN]
+ 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 BankswitchEtoF
+ pop hl
+ jp PrintText
+.moveFailed
+ ld c, 50
+ call DelayFrames
+ ld hl, PrintButItFailedText_
+ jp BankswitchEtoF
+
+LightScreenProtectedText:
+ TX_FAR _LightScreenProtectedText
+ db "@"
+
+ReflectGainedArmorText:
+ TX_FAR _ReflectGainedArmorText
+ db "@"
+
+BankswitchEtoF:
+ 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..1bb6c887
--- /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
+ ld a, [H_WHOSETURN]
+ 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
+ jpab DrawHUDsAndHPBars
+.alreadyHasSubstitute
+ ld hl, HasSubstituteText
+ jr .printText
+.notEnoughHP
+ ld hl, TooWeakSubstituteText
+.printText
+ jp PrintText
+
+SubstituteText:
+ TX_FAR _SubstituteText
+ db "@"
+
+HasSubstituteText:
+ TX_FAR _HasSubstituteText
+ db "@"
+
+TooWeakSubstituteText:
+ TX_FAR _TooWeakSubstituteText
+ db "@"
diff --git a/engine/battle/move_effects/transform.asm b/engine/battle/move_effects/transform.asm
new file mode 100644
index 00000000..9a5de9cc
--- /dev/null
+++ b/engine/battle/move_effects/transform.asm
@@ -0,0 +1,148 @@
+TransformEffect_:
+ ld hl, wBattleMonSpecies
+ ld de, wEnemyMonSpecies
+ ld bc, wEnemyBattleStatus3
+ ld a, [wEnemyBattleStatus1]
+ ld a, [H_WHOSETURN]
+ 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
+ ld a, [H_WHOSETURN]
+ 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
+ ld a, [H_WHOSETURN]
+ 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
+ ld [de], a
+ inc de
+ dec b
+ jr nz, .copyPPLoop
+ jr .copyStats
+.lessThanFourMoves
+; 0 PP for blank moves
+ xor a
+ ld [de], a
+ inc de
+ dec b
+ jr nz, .lessThanFourMoves
+.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
+ ld a, [H_WHOSETURN]
+ 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 BankswitchEtoF
+
+TransformedText:
+ TX_FAR _TransformedText
+ db "@"