- 239
- Posts
- 9
- Years
- Seen Apr 17, 2025
Thanks to Daniils and JPAN and everyone else who has worked on expanding pokeballs, we can expand pokeballs and create custom catch rate functions. However, I have yet to see any work on porting the critical capture to Gen 3 games (at least any public work)
I would like to share some research I have done into the subject. I noticed from koople's dodge rate routine that there is some index that appears to determine the outcome of the throw (see below).
First, some info on how the catch rate and subsequent success is determined:
1. The catch rate is calculated starting at 0802d620 and finally loaded into r6:
2. There is then a check for safari ball and master ball, and we jump to 0802d6a8 for everything else. Eventually, we end up at 0802d752, which is a loop that gets a random value, and compares to the catch rate in r6.
The loop incrementally increases r4 to a maximum value of 0x4, which holds the "catch index" as follows:
An non-ideal critical capture routine could be made with a tentative algorithm such as this:
This routine would theoretically imitate the logic for critical capture; however, the pokeball would still shake three times before capture instead of just once. I haven't had much luck with figuring out how the number of shakes is tied to the catch index yet. I have to imagine it is related to the ball object template table, as it contains data that appears to be related to "shaking" the pokeball. I would guess that the game loops over some animation related to shaking the pokeball based on the value of the "catch index" above.
(hopefully) more to follow. I hope to having a working code of the proposed algorithm soon.
EDIT: Here is an initial working version of the critical capture algorithm. The shaking animation and throwing sound need to be fixed, still
I would like to share some research I have done into the subject. I noticed from koople's dodge rate routine that there is some index that appears to determine the outcome of the throw (see below).
First, some info on how the catch rate and subsequent success is determined:
1. The catch rate is calculated starting at 0802d620 and finally loaded into r6:
Spoiler:
Code:
0802d620 491c ldr r1, [$0802d694] (=$08250892)
0802d622 8810 ldrh r0, [r2, #0x0]
0802d624 3802 sub r0, #0x2
0802d626 1840 add r0, r0, r1
0802d628 7804 ldrb r4, [r0, #0x0]
0802d62a 1c28 add r0, r5, #0x0
0802d62c 4360 mul r0, r4
0802d62e 210a mov r1, #0xa
0802d630 f1b6 bl $081e4018
0802d634 4d18 ldr r5, [$0802d698] (=$02023be4)
0802d636 4919 ldr r1, [$0802d69c] (=$02023d6c)
0802d638 780a ldrb r2, [r1, #0x0]
0802d63a 2158 mov r1, #0x58
0802d63c 1c14 add r4, r2, #0x0
0802d63e 434c mul r4, r1
0802d640 1963 add r3, r4, r5
0802d642 8d9a ldrh r2, [r3, #0x2c]
0802d644 0051 lsl r1, r2, #0x01
0802d646 1889 add r1, r1, r2
0802d648 8d1a ldrh r2, [r3, #0x28]
0802d64a 0052 lsl r2, r2, #0x01
0802d64c 1a8a sub r2, r1, r2
0802d64e 4350 mul r0, r2
0802d650 f1b6 bl $081e4018
0802d654 1c06 add r6, r0, #0x0
0802d656 354c add r5, #0x4c
0802d658 1964 add r4, r4, r5
0802d65a 6824 ldr r4, [r4, #0x0]
0802d65c 2027 mov r0, #0x27
0802d65e 4020 and r0, r4
0802d660 2800 cmp r0, #0x0
0802d662 d000 beq $0802d666
0802d664 0076 lsl r6, r6, #0x01
0802d666 20d8 mov r0, #0xd8
0802d668 4004 and r4, r0
0802d66a 2c00 cmp r4, #0x0
0802d66c d005 beq $0802d67a
0802d66e 0130 lsl r0, r6, #0x04
0802d670 1b80 sub r0, r0, r6
0802d672 210a mov r1, #0xa
0802d674 f1b6 bl $081e460c
0802d678 1c06 add r6, r0, #0x0
2. There is then a check for safari ball and master ball, and we jump to 0802d6a8 for everything else. Eventually, we end up at 0802d752, which is a loop that gets a random value, and compares to the catch rate in r6.
Spoiler:
Code:
0802d74c 1c60 add r0, r4, #0x1
0802d74e 0600 lsl r0, r0, #0x18
0802d750 0e04 lsr r4, r0, #0x18
0802d752 2c03 cmp r4, #0x3
0802d754 d805 bhi $0802d762
0802d756 f017 bl $08044ec8
0802d75a 0400 lsl r0, r0, #0x10
0802d75c 0c00 lsr r0, r0, #0x10
0802d75e 42b0 cmp r0, r6
0802d760 d3f4 bcc $0802d74c
0802d762 4d15 ldr r5, [$0802d7b8] (=$02023d68) @thrown ball index
0802d764 8828 ldrh r0, [r5, #0x0]
0802d766 2801 cmp r0, #0x1
0802d768 d100 bne $0802d76c
0802d76a 2404 mov r4, #0x4 @master ball automatically sets catch index to 0x4
0802d76c 2000 mov r0, #0x0
0802d76e 1c21 add r1, r4, #0x0 @r1 holds "catch index"
0802d770 f7e0 bl $0800e194
0802d770 f7e0 bl $0800e194
0802d774 4811 ldr r0, [$0802d7bc] (=$02023bc4)
0802d776 7800 ldrb r0, [r0, #0x0]
0802d778 f7e9 bl $08017248
0802d77c 2c04 cmp r4, #0x4
0802d77e d131 bne $0802d7e4
0802d780 490f ldr r1, [$0802d7c0] (=$02023d74)
0802d782 4810 ldr r0, [$0802d7c4] (=$081d9a42) @caught poke battle script
0802d784 6008 str r0, [r1, #0x0]
0802d7e4 4803 ldr r0, [$0802d7f4] (=$02023e82)
0802d7e6 7144 strb r4, [r0, #0x5]
0802d7e8 4903 ldr r1, [$0802d7f8] (=$02023d74)
0802d7ea 4804 ldr r0, [$0802d7fc] (=$081d9a93) @breakout battle script
0802d7ec 6008 str r0, [r1, #0x0]
0802d7ee bc70 pop {r4-r6}
0802d7f0 bc01 pop {r0}
0802d7f2 4700 bx r0
The loop incrementally increases r4 to a maximum value of 0x4, which holds the "catch index" as follows:
Code:
r4 = 0x0 --> breakout immediately
r4 = 0x1 --> shake once and breakout
r4 = 0x2 --> shake twice and breaout
r4 = 0x3 --> shake three times and breakout
r4 = 0x4 --> shake three times and catch
r4 = 0x5 --> trainer deflection (handled separately)
r4 = 0x6 --> ghost battle dodge (handled separately)
r4 > 0x6 -> ball rolls [r4] times and breaks out. sort of funny to watch it roll 10 times
An non-ideal critical capture routine could be made with a tentative algorithm such as this:
Code:
1. get ratio of caught pokemon to pokedex size
2. check brackets via [URL="https://bulbapedia.bulbagarden.net/wiki/Catch_rate#Critical_capture"]this guide[/URL]
3. get crit_catch_rate * catch_rate (eg. (0 - 2.5)*r6)
4. get random value via 08044ec8
5. compare critical capture rate to random value
6. if lower, critical capture has occurred. otherwise, perform regular capture checks (ie jump to 0802d752)
7. for critical capture: only perform one "shake check".
7a. set r4 to 0x2 and jump to 0802d74c, or do logic yourself
This routine would theoretically imitate the logic for critical capture; however, the pokeball would still shake three times before capture instead of just once. I haven't had much luck with figuring out how the number of shakes is tied to the catch index yet. I have to imagine it is related to the ball object template table, as it contains data that appears to be related to "shaking" the pokeball. I would guess that the game loops over some animation related to shaking the pokeball based on the value of the "catch index" above.
(hopefully) more to follow. I hope to having a working code of the proposed algorithm soon.
EDIT: Here is an initial working version of the critical capture algorithm. The shaking animation and throwing sound need to be fixed, still
Spoiler:
Code:
.text
.align 2
.thumb
.thumb_func
.global CriticalCapture
/*
insert 00 48 00 47 (xx+1) xx xx 08 at 2d67c
see 2nd post in thread for notes about routine
*/
Main:
ldrh r0, [r1] @thrown ball
cmp r0, #0x5
beq SafariBall
cmp r0, #0x1
bne CheckNationalDex
MasterBall:
ldr r0, =(0x0802d686 +1)
bx r0
SafariBall:
ldr r0, =(0x0802d6bc +1)
bx r0
CheckNationalDex:
mov r7, r6
push {r2-r6}
ldr r0, .TrainerData
ldr r0, [r0]
ldrb r0, [r0, #0x1b]
cmp r0, #0xb9 @[0300500c] + 0x1b set to 0xb9 with special 0x16F to enable national dex
bne LocalDex
ldr r0, =(0x00000840) @flag 0x840 set with sp. 0x16f (redundant check but whatevs)
bl CheckFlag
cmp r0, #0x0
beq LocalDex
NationalDex:
mov r0, #0x1
bl GetNationalDexCount
b CheckBrackets
LocalDex:
mov r0, #0x1
bl GetLocalDexCount
CheckBrackets:
mov r5, r0 @r5 = num caught pokemon
ldr r6, =(0x00000xxx) @r7 = size of your pokedex (0x182 = 386 if no expanded dex)
CheckLargest:
mov r4, #0x19 @2.5x critical capture rate
lsr r0, r6, #0x2
sub r0, r6, r0 @3/4 dex size
cmp r5, r0
bgt GetCritCatchMultiplier
Check2nd:
mov r4, #0x14
mov r0, #0x9
mul r0, r6
mov r1, #0x10 @9/16ths dex size
bl Divide
cmp r5, r0
bgt GetCritCatchMultiplier
Check3rd:
mov r4, #0xF
mov r0, #0x3
mul r0, r6
mov r1, #0x8 @3/8ths dex_size
bl Divide
cmp r5, r0
bgt GetCritCatchMultiplier
Check4th:
mov r4, #0xA
mov r0, #0x3
mul r0, r6
mov r1, #0x10 @3/16ths dex size
bl Divide
cmp r5, r0
bgt GetCritCatchMultiplier
CheckLast:
mov r4, #0x5
mov r0, #0x3
mul r0, r6
mov r1, #0x50 @3/80ths dex size
bl Divide
cmp r5, r0
bls NoCriticalCapture
GetCritCatchMultiplier:
mov r0, r4 @critical capture chance multiplier
mul r0, r7
pop {r2-r6}
mov r6, r7
mov r1, #0x3C
bl Divide @r0 = c = (([0:5:25] x catch rate) / 10) / 6)
push {r0}
bl Random
mov r1, r0
lsl r1, r1, #0x18
lsr r1, r1, #0x18
pop {r0}
cmp r1, r0
bls CritCapture @if rand(0,255) <= c, critical capture occurs
NoCriticalCapture:
pop {r2-r6}
ldr r0, =(0x0802d6a8 +1) @return to check all 3 shakes
bx r0
CritCapture:
bl Random
lsl r0, r0, #0x18
lsr r0, r0, #0x18
cmp r0, r6
bcc SuccessCritCatch @rand < catch rate
FailedCritCapture:
mov r4, #0x1 @shake once and breakout
ldr r0, =(0x0802d76c +1)
bx r0
SuccessCritCatch:
mov r4, #0x4
ldr r0, =(0x08xxxxxx) @battle script to load (see below for example)
ldr r1, .ScriptPointer
str r0, [r1]
mov r0, #0x0
mov r1, #0x4 @3 shakes then catch - need to find a way to make 1 shake and catch
ldr r2, =(0x0800E194 +1) @store catch index
bl jump_r2
ldr r0, =(0x02023bc4)
ldrb r0, [r0]
ldr r2, =(0x08017248 +1)
bl jump_r2
ldr r1, =(0x0802d6d6 +1)
bx r1
Random:
ldr r2, =(0x08044ec8 +1)
b jump_r2
Divide:
ldr r2, =(0x081e4018 +1)
jump_r2:
bx r2
CheckFlag:
ldr r1, =(0x0806e6d0 +1)
bx r1
GetLocalDexCount:
ldr r4, =(0x08088edc +1)
bx r4
GetNationalDexCount:
ldr r4, =(0x08088e8c +1)
bx r4
.align 2
.ScriptPointer: .word 0x02023d74
.TrainerData: .word 0x0300500c
/* battle script example:
#org @start
setword 0x0203c020 0x08xxxxxx //custom critical capture string
jumpifhalfword 0x0 0x2023D68 0x5 @safari
cmd60 0xB
printstring 0x184
goto 0x8165781
#org @safari
printstring 0x184
goto 0x81D9A53 //catch pokemon
*/
Last edited: