X-Git-Url: http://git.veekun.com/zzz-pokedex.git/blobdiff_plain/18a6660e197674363e3ba5baa7bc2d809ebc51e4..c87f91105dbce6befb0d4b68c1d6b9bb2a69e805:/pokedex/formulae.py diff --git a/pokedex/formulae.py b/pokedex/formulae.py index 3c70ffa..62271f1 100644 --- a/pokedex/formulae.py +++ b/pokedex/formulae.py @@ -18,24 +18,29 @@ def nCr(n, r): 1) -def calculated_stat(base_stat, level, iv, effort): +def calculated_stat(base_stat, level, iv, effort, nature=None): """Returns the calculated stat -- i.e. the value actually shown in the game on a Pokémon's status tab. """ # Remember: this is from C; use floor division! - return (base_stat * 2 + iv + effort // 4) * level // 100 + 5 + stat = (base_stat * 2 + iv + effort // 4) * level // 100 + 5 -def calculated_hp(base_hp, level, iv, effort): + if nature: + stat = int(stat * nature) + + return stat + +def calculated_hp(base_stat, level, iv, effort, nature=None): """Similar to `calculated_stat`, except with a slightly different formula used specifically for HP. """ # Shedinja's base stat of 1 is special; its HP is always 1 - if base_hp == 1: + if base_stat == 1: return 1 - return (base_hp * 2 + iv + effort // 4) * level // 100 + 10 + level + return (base_stat * 2 + iv + effort // 4) * level // 100 + 10 + level def earned_exp(base_exp, level): """Returns the amount of EXP earned when defeating a Pokémon at the given @@ -44,40 +49,55 @@ def earned_exp(base_exp, level): return base_exp * level // 7 -def capture_chance(current_hp, max_hp, capture_rate, - ball_bonus=1, status_bonus=1, heavy_modifier=0): - """Calculates the chance that a Pokémon will be caught. +def capture_chance(percent_hp, capture_rate, + ball_bonus=10, status_bonus=1, + capture_bonus=10, capture_modifier=0): + """Calculates the chance that a Pokémon will be caught, given its capture + rate and the percentage of HP it has remaining. + + Bonuses are such that 10 means "unchanged". Returns five values: the chance of a capture, then the chance of the ball shaking three, two, one, or zero times. Each of these is a float such that 0.0 <= n <= 1.0. Feel free to ignore all but the first. """ - if heavy_modifier: - # Only used by Heavy Ball. Changes the target's capture rate outright - capture_rate += heavy_modifier - if capture_rate <= 1: - capture_rate = 1 - - # This should really be integer math, right? But the formula uses FOURTH - # ROOTS in a moment, so it can't possibly be. It probably doesn't matter - # either way, so whatever; use regular ol' division. ball_bonus and - # status_bonus can be 1.5, anyway. - base_chance = ((3 * max_hp - 2 * current_hp) * capture_rate * ball_bonus) \ - / (3 * max_hp) \ - * status_bonus - - shake_index = (base_chance / 255) ** 0.25 * (2**16 - 1) + # HG/SS Pokéballs modify capture rate rather than the ball bonus + capture_rate = capture_rate * capture_bonus // 10 + capture_modifier + if capture_rate < 1: + capture_rate = 1 + elif capture_rate > 255: + capture_rate = 255 + + # A slight math note: + # The actual formula uses (3 * max_hp - 2 * curr_hp) / (3 * max_hp) + # This uses (1 - 2/3 * curr_hp/max_hp) + # Integer division is taken into account by flooring immediately + # afterwards, so there should be no appreciable rounding error. + base_chance = int( + capture_rate * ball_bonus // 10 * (1 - 2/3 * percent_hp) + ) + base_chance = base_chance * status_bonus // 10 + + # Shake index involves integer sqrt. Lovely. + isqrt = lambda x: int(x ** 0.5) + if not base_chance: + # This is very silly. Due to what must be an oversight, it's possible + # for the above formula to end with a zero chance to catch, which is + # then thrown blindly into the below denominator. Luckily, the games' + # division function is a no-op with a denominator of zero.. which + # means a base_chance of 0 is effectively a base chance of 1. + base_chance = 1 + shake_index = 1048560 // isqrt(isqrt(16711680 // base_chance)) # Iff base_chance < 255, then shake_index < 65535. - # The game now picks four random uwords. However many of them are <= - # shake_index is the number of times the ball will shake. If all four are - # <= shake_index, the Pokémon is caught. - - # The RNG tends to work with integers, so integer math likely kicks in now. - shake_index = int(shake_index) + # The Pokémon now has four chances to escape. The game starts picking + # random uint16s. If such a random number is < shake_index, the Pokémon + # stays in the ball, and it wobbles. If the number is >= shake_index, the + # ball breaks open then and there, and the capture fails. + # If all four are < shake_index, the Pokémon is caught. - # If shake_index >= 65535, all four randoms must be <= it, and the Pokémon + # If shake_index >= 65535, all four randoms must be < it, and the Pokémon # will be caught. Skip hard math if shake_index >= 65535: return (1.0, 0.0, 0.0, 0.0, 0.0) @@ -86,21 +106,20 @@ def capture_chance(current_hp, max_hp, capture_rate, # Something is guaranteed to happen. # Alrighty. Here's some probability. - # The chance that a single random number will be <= shake_index is: - p = (shake_index + 1) / 65536 - # Now, the chance that two random numbers will be <= shake_index is p**2. - # And the chance that neither will be is (1 - p)**2. - # With me so far? - # The chance that one will be and one will NOT be is p * (1 - p) * 2. - # The 2 is because they can go in any order: the first could be less, or - # the second could be less. That 2 is actually nCr(2, 1); the number of - # ways of picking one item in any order from a group of two. - # Try it yourself add up those three values and you'll get 1. - - # Right. Hopefully, the following now makes sense. - # There are five cases: four randoms are <= shake_index (which means - # capture), or three are, etc. + # The chance that a single random uint16 will be < shake_index, thus + # keeping the Pokémon in the ball, is: + p = shake_index / 65536 + + # Now, the chance for n wobbles is the chance that the Pokémon will stay in + # the ball for (n-1) attempts, then break out on the nth. + # The chance of capture is just the chance that the Pokémon stays in the + # ball for all four tries. + + # There are five cases: captured, wobbled three times, etc. return [ - p**i * (1 - p)**(4 - i) * nCr(4, i) - for i in reversed(range(5)) + p**4, # capture + p**3 * (1 - p), + p**2 * (1 - p), + p**1 * (1 - p), + (1 - p), ]