Skip to content

Improved Catching System

ZetaPx edited this page Aug 28, 2024 · 3 revisions

This code was written by ZetaPhoenix except the "CompareDEHL" function that was written by Vortiene. The objective of the code is to make the catching system closer to Gen 3's catching system. Please note that this will not change the "Ball Shake" system which work mostly the same as the regular game.

The formula that this code will apply is the following:

Catch Rate * HP Modifier * Ball Modifier * Status Modifier = Catch Threshold

HP Modifier:

The HP values correspond to the wild pokémon the player's trying to catch.

  • 75% - 100% HP = 1/3
  • 50% - 75% HP = 1/2
  • 25% - 50% HP = 2/3
  • 1 - 25% HP = 5/6
  • 1 HP = 1

Ball Modifier:

Determined on a table that will also be included.

  • Poké Ball = 1
  • Great Ball = 3/2
  • Safari Ball = 3/2
  • Ultra Ball = 2

The Master Ball will always succeed and doesn't need to have a Ball Modifier.

Status Modifier:

  • SLP = 2
  • FRZ = 2
  • PAR = 3/2
  • PSN = 3/2
  • BRN = 3/2
  • No status = 1

Catch Threshold:

If the result of the formula is 255 (dec) or greater the catch will succeed. If it's a number from 0 to 254 a random number will be generated and if this random number is lower than the catch threshold the catch will succeed, otherwise it will fail.

engine/items/item_effects.asm

ItemUseBall.loop

...
.loop
-	call Random
-	ld b, a
-
-; Get the item ID.
-	ld hl, wCurItem
-	ld a, [hl]
-
-; The Master Ball always succeeds.
-	cp MASTER_BALL
-	jp z, .captured
-
-; Anything will do for the basic Poké Ball.
-	cp POKE_BALL
-	jr z, .checkForAilments
-
-; If it's a Great/Ultra/Safari Ball and Rand1 is greater than 200, try again.
-	ld a, 200
-	cp b
-	jr c, .loop
-
-; Less than or equal to 200 is good enough for a Great Ball.
-	ld a, [hl]
-	cp GREAT_BALL
-	jr z, .checkForAilments
-
-; If it's an Ultra/Safari Ball and Rand1 is greater than 150, try again.
-	ld a, 150
-	cp b
-	jr c, .loop
-
-.checkForAilments
-; Pokémon can be caught more easily with a status ailment.
-; Depending on the status ailment, a certain value will be subtracted from
-; Rand1. Let this value be called Status.
-; The larger Status is, the more easily the Pokémon can be caught.
-; no status ailment:     Status = 0
-; Burn/Paralysis/Poison: Status = 12
-; Freeze/Sleep:          Status = 25
-; If Status is greater than Rand1, the Pokémon will be caught for sure.
-	ld a, [wEnemyMonStatus]
-	and a
-	jr z, .skipAilmentValueSubtraction ; no ailments
-	and (1 << FRZ) | SLP_MASK
-	ld c, 12
-	jr z, .notFrozenOrAsleep
-	ld c, 25
-.notFrozenOrAsleep
-	ld a, b
-	sub c
-	jp c, .captured
-	ld b, a
-
-.skipAilmentValueSubtraction
-	push bc ; save (Rand1 - Status)
-
-; Calculate MaxHP * 255.
-	xor a
-	ldh [hMultiplicand], a
-	ld hl, wEnemyMonMaxHP
-	ld a, [hli]
-	ldh [hMultiplicand + 1], a
-	ld a, [hl]
-	ldh [hMultiplicand + 2], a
-	ld a, 255
-	ldh [hMultiplier], a
-	call Multiply
-
-; Determine BallFactor. It's 8 for Great Balls and 12 for the others.
-	ld a, [wCurItem]
-	cp GREAT_BALL
-	ld a, 12
-	jr nz, .skip1
-	ld a, 8
-
-.skip1
-; Note that the results of all division operations are floored.
-
-; Calculate (MaxHP * 255) / BallFactor.
-	ldh [hDivisor], a
-	ld b, 4 ; number of bytes in dividend
-	call Divide
-
-; Divide the enemy's current HP by 4. HP is not supposed to exceed 999 so
-; the result should fit in a. If the division results in a quotient of 0,
-; change it to 1.
-	ld hl, wEnemyMonHP
-	ld a, [hli]
-	ld b, a
-	ld a, [hl]
-	srl b
-	rr a
-	srl b
-	rr a
-	and a
-	jr nz, .skip2
-	inc a
-
-.skip2
-; Let W = ((MaxHP * 255) / BallFactor) / max(HP / 4, 1). Calculate W.
-	ldh [hDivisor], a
-	ld b, 4
-	call Divide
-
-; If W > 255, store 255 in [hQuotient + 3].
-; Let X = min(W, 255) = [hQuotient + 3].
-	ldh a, [hQuotient + 2]
-	and a
-	jr z, .skip3
-	ld a, 255
-	ldh [hQuotient + 3], a
-
-.skip3
-	pop bc ; b = Rand1 - Status
-
-; If Rand1 - Status > CatchRate, the ball fails to capture the Pokémon.
-	ld a, [wEnemyMonActualCatchRate]
-	cp b
-	jr c, .failedToCapture
-
-; If W > 255, the ball captures the Pokémon.
-	ldh a, [hQuotient + 2]
-	and a
-	jr nz, .captured
-
-	call Random ; Let this random number be called Rand2.
-
-; If Rand2 > X, the ball fails to capture the Pokémon.
-	ld b, a
-	ldh a, [hQuotient + 3]
-	cp b
-	jr c, .failedToCapture
+
+; Gen 3 Style Catching by ZetaPhoenix
+
+; HP calc
+	ld a, [wEnemyMonHP]
+	ld d, a
+	ld a, [wEnemyMonHP + 1]
+	ld e, a
+	push de
+	xor a
+	ldh [hMultiplicand], a
+	ld hl, wEnemyMonMaxHP
+	ld a, [hli]
+	ldh [hMultiplicand + 1], a
+	ld a, [hl]
+	ldh [hMultiplicand + 2], a
+	ld a, 3
+	ldh [hMultiplier], a
+	call Multiply
+	ld a, 4
+	ldh [hDivisor], a
+	ld b, 4 ; number of bytes in dividend
+	call Divide
+	ldh a, [hQuotient + 2]
+	ld h, a
+	ldh a, [hQuotient + 3]
+	ld l, a
+	pop de
+	; de = Cur HP
+	; hl = Max HP * 3/4
+	call CompareDEHL ; c = de greater, nc = hl greater, z = equal
+	jr c, .enemyHas75PercentTo100PercentHP
+	ld a, [wEnemyMonMaxHP]
+	ld h, a
+	ld a, [wEnemyMonMaxHP + 1]
+	ld l, a
+	srl h
+	rr l
+	call CompareDEHL ; c = de greater, nc = hl greater, z = equal
+	jr c, .enemyHas50PercentTo75PercentHP
+	srl h
+	rr l
+	call CompareDEHL ; c = de greater, nc = hl greater, z = equal
+	jr c, .enemyHas25PercentTo50PercentHP
+	ld hl, 1
+	call CompareDEHL ; c = de greater, nc = hl greater, z = equal
+	jr c, .enemyHas1HPto25PercentHP
+	; enemy has 1HP
+	ld b, 1 ; multiplier
+	ld c, 1 ; divisor
+	jr .calcCatchThreshold
+.enemyHas1HPto25PercentHP
+	ld b, 5 ; multiplier
+	ld c, 6 ; divisor
+	jr .calcCatchThreshold
+.enemyHas25PercentTo50PercentHP
+	ld b, 2 ; multiplier
+	ld c, 3 ; divisor
+	jr .calcCatchThreshold
+.enemyHas50PercentTo75PercentHP
+	ld b, 1 ; multiplier
+	ld c, 2 ; divisor
+	jr .calcCatchThreshold	
+.enemyHas75PercentTo100PercentHP
+	ld b, 1 ; multiplier
+	ld c, 3 ; divisor
+.calcCatchThreshold	
+	xor a
+	ldh [hMultiplicand], a
+	ldh [hMultiplicand + 1], a
+	ld a, [wEnemyMonActualCatchRate]
+	ldh [hMultiplicand + 2], a
+	ld a, b
+	ldh [hMultiplier], a
+	call Multiply
+	ld a, c
+	ldh [hDivisor], a
+	ld b, 4 ; number of bytes in dividend
+	call Divide
+	
+; read ball table
+	ld a, [wCurItem]
+	cp MASTER_BALL
+	jr z, .captured
+	ld b, a
+	ld hl, BallMultipliers
+.checkLoop
+	ld a, [hl]
+	cp b
+	jr z, .ballFound
+	cp -1
+	jr z, .failedToCapture
+	inc hl
+	inc hl
+	inc hl
+	jr .checkLoop
+.ballFound
+	inc hl
+	ld b, [hl]
+	inc hl
+	ld c, [hl]
+	ld a, b
+	ldh [hMultiplier], a
+	call Multiply
+	ld a, c
+	ldh [hDivisor], a
+	ld b, 4 ; number of bytes in dividend
+	call Divide
+	
+; read status
+	ld b, 1
+	ld c, 1
+	ld a, [wEnemyMonStatus]
+	and a
+	jr z, .ailmentMultiplierFound
+	ld b, 3
+	ld c, 2
+	and (1 << FRZ) | SLP_MASK
+	jr z, .ailmentMultiplierFound
+	ld b, 2
+	ld c, 1
+.ailmentMultiplierFound
+	ld a, b
+	ldh [hMultiplier], a
+	call Multiply
+	ld a, c
+	ldh [hDivisor], a
+	ld b, 4 ; number of bytes in dividend
+	call Divide
+	
+; threshold check
+	ldh a, [hQuotient]
+	and a
+	jr nz, .captured
+	ldh a, [hQuotient + 1]
+	and a
+	jr nz, .captured
+	ldh a, [hQuotient + 2]
+	and a
+	jr nz, .captured
+	ldh a, [hQuotient + 3]
+	cp 255
+	jr z, .captured
+	ld b, a
+	call Random
+	cp b
+	jr nc, .failedToCapture

.captured
	jr .skipShakeCalculations

.failedToCapture
	ldh a, [hQuotient + 3]
	ld [wPokeBallCaptureCalcTemp], a ; Save X.

; Calculate CatchRate * 100.
	xor a
	ldh [hMultiplicand], a
	ldh [hMultiplicand + 1], a
	ld a, [wEnemyMonActualCatchRate]
	ldh [hMultiplicand + 2], a
	ld a, 100
	ldh [hMultiplier], a
	call Multiply
...

End of item_effects.asm

+; registers de and hl comparison by Vortiene
+
+; sets carry flag if DE is greater than HL. Sets zero flag if they're equal.
+CompareDEHL:
+    ld a, h
+    sub d
+    ret nz ; if carry, DE is greater, if no carry, HL is greater
+; 2nd byte comparison
+    ld a, l
+    sub e
+    ret ; if carry, DE is greater, if no carry, HL is greater, if z, they're equal
+BallMultipliers:
+; 	db ITEM_ID, Numerator, Denominator
+	db POKE_BALL   , 1, 1	; x1
+	db GREAT_BALL  , 3, 2	; x1.5
+	db SAFARI_BALL , 3, 2   ; x1.5
+	db ULTRA_BALL  , 2, 1	; x2
+	db -1 ; end
Clone this wiki locally