mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-16 16:21:30 +02:00
109 lines
4.3 KiB
Lua
109 lines
4.3 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 }
|
|
|
|
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 rate = rateOverride or targetDef.catchRate
|
|
|
|
-- the status subtraction and the wobble bonus come off the merged
|
|
-- status record (SLP/FRZ 25 and +10, the rest 12 and +5)
|
|
local s = targetMon.status
|
|
local record = Status.recordFor(statuses, s)
|
|
local statusBonus = record and record.catchBonus or 0
|
|
|
|
-- HP factor (X)
|
|
local maxhp = targetMon.stats.hp
|
|
local hpQuarter = math.max(1, math.floor(targetMon.hp / 4))
|
|
local factor = def.hpFactor or DEFAULT_BALL.hpFactor
|
|
-- the 255 cap applies only after BOTH divisions (ItemUseBall keeps
|
|
-- the intermediate in 16 bits); capping early collapses the value
|
|
local f = math.min(255, math.floor(math.floor(maxhp * 255 / factor) / hpQuarter))
|
|
|
|
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
|
|
|
|
-- 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
|