shithub: pokecrystal

Download patch

ref: 597701f168e69a316f18951e3002c322e23cf3ea
parent: 0ffdb6521065aeeb0eb792b36db33916e86847b0
author: Idain <luiscarlosholguinperez@outlook.com>
date: Mon Jan 10 17:32:41 EST 2022

Document fix for game freezing while switching mon (#869)

Fixes #868

Co-authored-by: Rangi <35663410+Rangi42@users.noreply.github.com>

--- a/docs/bugs_and_glitches.md
+++ b/docs/bugs_and_glitches.md
@@ -35,9 +35,10 @@
   - [Beat Up may trigger King's Rock even if it failed](#beat-up-may-trigger-kings-rock-even-if-it-failed)
   - [Present damage is incorrect in link battles](#present-damage-is-incorrect-in-link-battles)
   - [Dragon Scale, not Dragon Fang, boosts Dragon-type moves](#dragon-scale-not-dragon-fang-boosts-dragon-type-moves)
+  - [Switching out or switching against a Pokémon with max HP below 4 freezes the game](#switching-out-or-switching-against-a-pokémon-with-max-HP-below-4-freezes-the-game)
+  - [Moves that do damage and increase your stats do not increase stats after a KO](#moves-that-do-damage-and-increase-your-stats-do-not-increase-stats-after-a-ko)
   - [HP bar animation is slow for high HP](#hp-bar-animation-is-slow-for-high-hp)
   - [HP bar animation off-by-one error for low HP](#hp-bar-animation-off-by-one-error-for-low-hp)
-  - [Moves that do damage and increase your stats do not increase stats after a KO](#moves-that-do-damage-and-increase-your-stats-do-not-increase-stats-after-a-ko)
 - [Single-player battle engine](#single-player-battle-engine)
   - [A Transformed Pokémon can use Sketch and learn otherwise unobtainable moves](#a-transformed-pokémon-can-use-sketch-and-learn-otherwise-unobtainable-moves)
   - [Catching a Transformed Pokémon always catches a Ditto](#catching-a-transformed-pokémon-always-catches-a-ditto)
@@ -714,6 +715,143 @@
 ```
 
 
+### Switching out or switching against a Pokémon with max HP below 4 freezes the game
+
+This happens because switching involves calculating a percentage of maximum enemy HP. Directly calculating *HP* × 100 / *max HP* would require a two-byte denominator, so instead the game calculates *HP* × 25 / (*max HP* / 4), since even a maximum HP of 999 divided by 4 is 249, which fits in one byte. However, if the maximum HP is below 4 this will divide by 0, which enters an infinite loop in `_Divide`.
+
+**Fix:** First, edit `SendOutMonText` in [engine/battle/core.asm](https://github.com/pret/pokecrystal/blob/master/engine/battle/core.asm):
+
+```diff
+ 	; compute enemy health remaining as a percentage
+ 	xor a
+ 	ldh [hMultiplicand + 0], a
+ 	ld hl, wEnemyMonHP
+ 	ld a, [hli]
+ 	ld [wEnemyHPAtTimeOfPlayerSwitch], a
+ 	ldh [hMultiplicand + 1], a
+ 	ld a, [hl]
+ 	ld [wEnemyHPAtTimeOfPlayerSwitch + 1], a
+ 	ldh [hMultiplicand + 2], a
+-	ld a, 25
+-	ldh [hMultiplier], a
+-	call Multiply
+ 	ld hl, wEnemyMonMaxHP
+ 	ld a, [hli]
+ 	ld b, [hl]
+-	srl a
+-	rr b
+-	srl a
+-	rr b
++	ld c, 100
++	and a
++	jr z, .shift_done
++.shift
++	rra
++	rr b
++	srl c
++	and a
++	jr nz, .shift
++.shift_done
++	ld a, c
++	ldh [hMultiplier], a
++	call Multiply
+ 	ld a, b
+ 	ld b, 4
+ 	ldh [hDivisor], a
+ 	call Divide
+```
+
+Then edit `WithdrawMonText` in the same file:
+
+```diff
+ 	; compute enemy health lost as a percentage
+ 	ld hl, wEnemyMonHP + 1
+ 	ld de, wEnemyHPAtTimeOfPlayerSwitch + 1
+ 	ld b, [hl]
+ 	dec hl
+ 	ld a, [de]
+ 	sub b
+ 	ldh [hMultiplicand + 2], a
+ 	dec de
+ 	ld b, [hl]
+ 	ld a, [de]
+ 	sbc b
+ 	ldh [hMultiplicand + 1], a
+-	ld a, 25
+-	ldh [hMultiplier], a
+-	call Multiply
+ 	ld hl, wEnemyMonMaxHP
+ 	ld a, [hli]
+ 	ld b, [hl]
+-	srl a
+-	rr b
+-	srl a
+-	rr b
++	ld c, 100
++	and a
++	jr z, .shift_done
++.shift
++	rra
++	rr b
++	srl c
++	and a
++	jr nz, .shift
++.shift_done
++	ld a, c
++	ldh [hMultiplier], a
++	call Multiply
+ 	ld a, b
+ 	ld b, 4
+ 	ldh [hDivisor], a
+ 	call Divide
+```
+
+This changes both calculations to *HP* × (100 / *N*) / (*max HP* / *N*) for the smallest necessary *N*, which will be at least 1, so it avoids dividing by zero and is also more accurate.
+
+
+### Moves that do damage and increase your stats do not increase stats after a KO
+
+`BattleCommand_CheckFaint` "ends the move effect if the opponent faints", and these moves attempt to raise the user's stats *after* `checkfaint`. Note that fixing this can lead to stats being increased at the end of battle, but will not have any negative effects.
+
+**Fix:** Edit [data/moves/effects.asm](https://github.com/pret/pokecrystal/blob/master/data/moves/effects.asm):
+
+```diff
+ DefenseUpHit:
+ 	...
+ 	criticaltext
+ 	supereffectivetext
++	defenseup
++	statupmessage
+ 	checkfaint
+ 	buildopponentrage
+-	defenseup
+-	statupmessage
+ 	endmove
+
+ AttackUpHit:
+ 	...
+ 	criticaltext
+ 	supereffectivetext
++	attackup
++	statupmessage
+ 	checkfaint
+ 	buildopponentrage
+-	attackup
+-	statupmessage
+ 	endmove
+
+ AllUpHit:
+ 	...
+ 	criticaltext
+ 	supereffectivetext
++	allstatsup
+ 	checkfaint
+ 	buildopponentrage
+-	allstatsup
+ 	endmove
+```
+
+
 ### HP bar animation is slow for high HP
 
 *Fixing this cosmetic bug will* not *break link battle compatibility.*
@@ -768,48 +906,6 @@
  	jr c, .done
  	inc b
  	jr .loop
-```
-
-### Moves that do damage and increase your stats do not increase stats after a KO
-
-`BattleCommand_CheckFaint` "ends the move effect if the opponent faints", and these moves attempt to raise the user's stats *after* `checkfaint`. Note that fixing this can lead to stats being increased at the end of battle, but will not have any negative effects.
-
-**Fix:** Edit [data/moves/effects.asm](https://github.com/pret/pokecrystal/blob/master/data/moves/effects.asm):
-
-```diff
- DefenseUpHit:
- 	...
- 	criticaltext
- 	supereffectivetext
-+	defenseup
-+	statupmessage
- 	checkfaint
- 	buildopponentrage
--	defenseup
--	statupmessage
- 	endmove
-
- AttackUpHit:
- 	...
- 	criticaltext
- 	supereffectivetext
-+	attackup
-+	statupmessage
- 	checkfaint
- 	buildopponentrage
--	attackup
--	statupmessage
- 	endmove
-
- AllUpHit:
- 	...
- 	criticaltext
- 	supereffectivetext
-+	allstatsup
- 	checkfaint
- 	buildopponentrage
--	allstatsup
- 	endmove
 ```