mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-23 14:07:34 +02:00
124 lines
5.0 KiB
Lua
124 lines
5.0 KiB
Lua
-- Gen 1 catch algorithm (engine/items/item_effects.asm, ItemUseBall).
|
|
|
|
local Status = require("src.battle.Status")
|
|
|
|
local Catching = {}
|
|
|
|
-- randMax is the ceiling of the catch roll, hpFactor the X of the HP term,
|
|
-- wobbleFactor the ballFactor2 divisor of the wobble math. MASTER_BALL
|
|
-- never rolls (autoCatch), so its factors are unused. tossAnim picks the
|
|
-- TossBallAnimation arc and flicker the Master/Ultra OBJ-palette strobe
|
|
-- (DoBallTossSpecialEffects).
|
|
local BALLS = {
|
|
MASTER_BALL = { randMax = 0, autoCatch = true,
|
|
tossAnim = "ULTRATOSS_ANIM", flicker = true },
|
|
POKE_BALL = { randMax = 255, hpFactor = 12, wobbleFactor = 255,
|
|
tossAnim = "TOSS_ANIM" },
|
|
GREAT_BALL = { randMax = 200, hpFactor = 8, wobbleFactor = 200,
|
|
tossAnim = "GREATTOSS_ANIM" },
|
|
ULTRA_BALL = { randMax = 150, hpFactor = 12, wobbleFactor = 150,
|
|
tossAnim = "ULTRATOSS_ANIM", flicker = true },
|
|
SAFARI_BALL = { randMax = 150, hpFactor = 12, wobbleFactor = 150,
|
|
tossAnim = "ULTRATOSS_ANIM" },
|
|
}
|
|
Catching.BALLS = BALLS
|
|
|
|
-- an unknown ball falls back to POKE_BALL's roll and the 150 wobble
|
|
-- divisor, which is what the old per-field `or` defaults resolved to
|
|
local DEFAULT_BALL = { randMax = 255, hpFactor = 12, wobbleFactor = 150 }
|
|
|
|
local function stockFactors(def, targetMon, targetDef, rateOverride, statuses)
|
|
local rate = rateOverride or targetDef.catchRate
|
|
local record = Status.recordFor(statuses, targetMon.status)
|
|
local statusBonus = record and record.catchBonus or 0
|
|
local hpQuarter = math.max(1, math.floor(targetMon.hp / 4))
|
|
local factor = def.hpFactor or DEFAULT_BALL.hpFactor
|
|
local f = math.min(255, math.floor(math.floor(
|
|
targetMon.stats.hp * 255 / factor) / hpQuarter))
|
|
return rate, statusBonus, f, record
|
|
end
|
|
|
|
function Catching.registerInto(registry, _, owner)
|
|
for id, record in pairs(BALLS) do
|
|
registry:register(id, record, owner)
|
|
end
|
|
end
|
|
|
|
-- The stock ItemUseBall math. On failure the ball wobbles per the
|
|
-- original's shake calculation: Y = rate*100/ballFactor2 (255/200/150),
|
|
-- Z = X*Y/255 + status2 (5/10) where X is the HP factor; Z<10: 0 shakes,
|
|
-- <30: 1, <70: 2, else 3. (We use the HP factor for X on both failure
|
|
-- paths; the original reads a stale quotient when the first roll fails.)
|
|
local function stockAttempt(def, targetMon, targetDef, rng, rateOverride, statuses)
|
|
if def.autoCatch then return true, 3 end
|
|
local randMax = def.randMax
|
|
local s = targetMon.status
|
|
local rate, statusBonus, f, record = stockFactors(
|
|
def, targetMon, targetDef, rateOverride, statuses)
|
|
|
|
local function shakes()
|
|
local ballFactor2 = def.wobbleFactor or DEFAULT_BALL.wobbleFactor
|
|
local y = math.floor(rate * 100 / ballFactor2)
|
|
local z
|
|
if y > 255 then
|
|
z = 255
|
|
else
|
|
z = math.floor(f * y / 255)
|
|
end
|
|
if s then
|
|
z = z + ((record and record.shakeBonus) or 5)
|
|
end
|
|
if z < 10 then return 0 elseif z < 30 then return 1
|
|
elseif z < 70 then return 2 else return 3 end
|
|
end
|
|
|
|
local r = rng(0, randMax) - statusBonus
|
|
if r < 0 then return true, 3 end
|
|
if r > rate then return false, shakes() end
|
|
if rng(0, 255) <= f then return true, 3 end
|
|
return false, shakes()
|
|
end
|
|
|
|
-- Exact stock catch probability for read-only previews. A custom attempt
|
|
-- function may do anything, so nil is safer than presenting a plausible lie.
|
|
function Catching.chance(ball, targetMon, targetDef, rateOverride, opts)
|
|
opts = opts or {}
|
|
local def = opts.ballDef or BALLS[ball] or DEFAULT_BALL
|
|
if def.attempt then return nil end
|
|
if def.autoCatch then return 100 end
|
|
local rate, statusBonus, f = stockFactors(
|
|
def, targetMon, targetDef, rateOverride, opts.statuses)
|
|
local outcomes = def.randMax + 1
|
|
local automatic = math.min(outcomes, math.max(0, statusBonus))
|
|
local passed = math.min(outcomes, math.max(0, rate + statusBonus + 1))
|
|
return (automatic + (passed - automatic) * (f + 1) / 256)
|
|
* 100 / outcomes
|
|
end
|
|
|
|
-- Returns caught, shakes (0-3). rateOverride replaces the species catch
|
|
-- rate (the Safari game's BAIT/ROCK-modified wEnemyMonActualCatchRate).
|
|
-- opts (all optional): ballDef = the merged ball record, statuses = the
|
|
-- merged statuses table, battle = the running battle. A ball record's
|
|
-- attempt fn supersedes the whole formula; its ctx.vanillaAttempt() runs
|
|
-- the stock math with the ctx's (possibly rewritten) rateOverride.
|
|
function Catching.attempt(ball, targetMon, targetDef, rng, rateOverride, opts)
|
|
rng = rng or love.math.random
|
|
opts = opts or {}
|
|
local def = opts.ballDef or BALLS[ball] or DEFAULT_BALL
|
|
local statuses = opts.statuses
|
|
if def.attempt then
|
|
local ctx = {
|
|
ballDef = def, targetMon = targetMon, targetDef = targetDef,
|
|
rng = rng, rateOverride = rateOverride, battle = opts.battle,
|
|
}
|
|
ctx.vanillaAttempt = function()
|
|
return stockAttempt(def, targetMon, targetDef, rng, ctx.rateOverride,
|
|
statuses)
|
|
end
|
|
return def.attempt(ctx)
|
|
end
|
|
return stockAttempt(def, targetMon, targetDef, rng, rateOverride, statuses)
|
|
end
|
|
|
|
return Catching
|