mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
190 lines
7.9 KiB
Lua
190 lines
7.9 KiB
Lua
-- Kurt's balls and the specialty multipliers
|
|
-- (engine/items/item_effects.asm BallMultiplierFunctionTable).
|
|
--
|
|
-- luajit tests/gen2_specialty_balls_test.lua
|
|
--
|
|
-- ROM-free. Heavy Ball's dex-weight brackets (Lugia's 4760 tenths-lb turns
|
|
-- catch rate 3 into 23, the walkthrough's whole reason to brew one), Level
|
|
-- Ball's three rungs, Lure Ball's fishing gate, and the three deliberate
|
|
-- cart bugs: Fast Ball knows exactly three species, Love Ball boosts
|
|
-- SAME-sex pairs, Moon Ball never boosts. Friend Ball is happiness only,
|
|
-- proven through the real catch in BattleState.
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local S = require("tests.harness").suite("gen2 specialty balls")
|
|
local check, eq = S.check, S.eq
|
|
|
|
love = require("tests.love_stub")
|
|
|
|
local Battle = require("src.battle.gen2.Battle")
|
|
local BattleState = require("src.ui.gen2.BattleState")
|
|
local Catching = require("src.battle.gen2.Catching")
|
|
local Input = require("src.core.Input")
|
|
local Mon = require("src.battle.gen2.Mon")
|
|
|
|
-- ---- Heavy Ball -----------------------------------------------------------
|
|
do
|
|
-- Lugia: dex weight 4760 (tenths of a pound) converts to 2158 tenths-kg
|
|
-- through the cart's w/2 - w/32 - w/64 walk; high byte 8 is the +20 rung.
|
|
eq(Catching.heavyBallBoost(4760), 20, "Lugia sits in the +20 bracket")
|
|
eq(Catching.specialtyRate(3, "HEAVY_BALL", { weight = 4760 }), 23,
|
|
"catch rate 3 becomes 23 -- against the Ultra Ball's 6")
|
|
-- A light mon LOSES 20, floored at 1 (`ld b, $1` on underflow).
|
|
eq(Catching.heavyBallBoost(40), -20, "a 4 lb mon is in the -20 bracket")
|
|
eq(Catching.specialtyRate(3, "HEAVY_BALL", { weight = 40 }), 1,
|
|
"and the subtraction floors at 1")
|
|
-- Snorlax: 10140 tenths-lb -> 4599 tenths-kg, high byte 17: +40.
|
|
eq(Catching.heavyBallBoost(10140), 40, "Snorlax earns the +40 rung")
|
|
eq(Catching.specialtyRate(25, "HEAVY_BALL", {}), 25,
|
|
"no weight supplied, no change")
|
|
end
|
|
|
|
-- ---- Level Ball -----------------------------------------------------------
|
|
do
|
|
local function level(rate, player, enemy)
|
|
return Catching.specialtyRate(rate, "LEVEL_BALL",
|
|
{ playerLevel = player, level = enemy })
|
|
end
|
|
eq(level(30, 10, 10), 30, "an equal level is no boost")
|
|
eq(level(30, 20, 15), 60, "below the player's level: x2")
|
|
eq(level(30, 40, 15), 120, "below half: x4")
|
|
eq(level(30, 44, 10), 240, "below a quarter: x8")
|
|
eq(level(30, 40, 10), 120,
|
|
"the rungs are strict: 10 is NOT below floor(40/4)")
|
|
eq(level(200, 44, 10), 255, "capped at 255 like every sla")
|
|
end
|
|
|
|
-- ---- Lure Ball ------------------------------------------------------------
|
|
do
|
|
eq(Catching.specialtyRate(45, "LURE_BALL", { fishing = true }), 135,
|
|
"x3 on a BATTLETYPE_FISH encounter")
|
|
eq(Catching.specialtyRate(45, "LURE_BALL", {}), 45,
|
|
"and nothing anywhere else")
|
|
end
|
|
|
|
-- ---- Fast Ball's bug ------------------------------------------------------
|
|
do
|
|
for species in pairs({ MAGNEMITE = 1, GRIMER = 1, TANGELA = 1 }) do
|
|
eq(Catching.specialtyRate(45, "FAST_BALL", { species = species }), 180,
|
|
species .. " is one of the three the bug leaves covered")
|
|
end
|
|
eq(Catching.specialtyRate(45, "FAST_BALL", { species = "DRATINI" }), 45,
|
|
"DRATINI flees on the cart too, but the broken loop never sees it")
|
|
end
|
|
|
|
-- ---- Moon Ball's bug ------------------------------------------------------
|
|
do
|
|
eq(Catching.specialtyRate(45, "MOON_BALL",
|
|
{ evolveItem = "MOON_STONE" }), 45,
|
|
"a Moon Stone evolver gets nothing: the check reads Gen 1's constant")
|
|
eq(Catching.specialtyRate(45, "MOON_BALL",
|
|
{ evolveItem = "MOON_STONE", fixBugs = true }), 180,
|
|
"fixBugs restores the intended x4")
|
|
end
|
|
|
|
-- ---- Love Ball's bug ------------------------------------------------------
|
|
do
|
|
local function love_(gender, playerGender, fix)
|
|
return Catching.specialtyRate(30, "LOVE_BALL", {
|
|
species = "NIDORAN_F", playerSpecies = "NIDORAN_F",
|
|
gender = gender, playerGender = playerGender, fixBugs = fix,
|
|
})
|
|
end
|
|
eq(love_("female", "female"), 240,
|
|
"same species, SAME sex: the x8 the `ret nz` bug ships")
|
|
eq(love_("female", "male"), 30, "opposite sexes get nothing")
|
|
eq(love_("female", "male", true), 240, "fixBugs flips it back")
|
|
eq(Catching.specialtyRate(30, "LOVE_BALL", {
|
|
species = "NIDORAN_F", playerSpecies = "PIDGEY",
|
|
gender = "female", playerGender = "female" }), 30,
|
|
"different species never boost")
|
|
end
|
|
|
|
-- ---- through Catching.rate ------------------------------------------------
|
|
do
|
|
-- Full HP Lugia at rate 3: (3*maxHp - 2*hp) with the >=256 shift path,
|
|
-- both balls through the same formula; the Heavy Ball's 23 must beat the
|
|
-- Ultra Ball's 6 on the final rate too.
|
|
local heavy = Catching.rate({ maxHp = 200, hp = 200, catchRate = 3,
|
|
ball = "HEAVY_BALL", weight = 4760 })
|
|
local ultra = Catching.rate({ maxHp = 200, hp = 200, catchRate = 3,
|
|
ball = "ULTRA_BALL" })
|
|
check(heavy > ultra,
|
|
("the brewed ball out-catches the bought one (%d > %d)"):format(
|
|
heavy, ultra))
|
|
end
|
|
|
|
-- ---- Friend Ball happiness through the real catch -------------------------
|
|
do
|
|
local TYPES = { NORMAL = { id = "NORMAL", index = 0,
|
|
category = "physical" } }
|
|
local MOVES = { TACKLE = { id = "TACKLE", name = "TACKLE", power = 35,
|
|
type = "NORMAL", accuracy = 95, pp = 35,
|
|
effect = "EFFECT_NORMAL_HIT" } }
|
|
local POKEMON = {
|
|
growthRates = { GROWTH_MEDIUM_FAST = { numerator = 1, denominator = 1,
|
|
squared = 0, linear = 0, constant = 0 } },
|
|
CYNDAQUIL = {
|
|
id = "CYNDAQUIL", index = 155, name = "CYNDAQUIL",
|
|
baseStats = { hp = 39, attack = 52, defense = 43, speed = 65,
|
|
specialAttack = 60, specialDefense = 50 },
|
|
types = { "NORMAL", "NORMAL" }, catchRate = 45, baseExp = 65,
|
|
growthRate = "GROWTH_MEDIUM_FAST", genderRatio = 31,
|
|
levelMoves = { { level = 1, move = "TACKLE" } }, evolutions = {},
|
|
},
|
|
PIDGEY = {
|
|
id = "PIDGEY", index = 16, name = "PIDGEY",
|
|
baseStats = { hp = 40, attack = 45, defense = 40, speed = 56,
|
|
specialAttack = 35, specialDefense = 35 },
|
|
types = { "NORMAL", "NORMAL" }, catchRate = 255, baseExp = 55,
|
|
growthRate = "GROWTH_MEDIUM_FAST", genderRatio = 127,
|
|
levelMoves = { { level = 1, move = "TACKLE" } }, evolutions = {},
|
|
},
|
|
}
|
|
local DATA = {
|
|
pokemon = POKEMON,
|
|
moves = MOVES,
|
|
type_chart = { types = TYPES, matchups = {} },
|
|
items = {
|
|
FRIEND_BALL = { id = "FRIEND_BALL", name = "FRIEND BALL",
|
|
pocket = "BALL" },
|
|
POKE_BALL = { id = "POKE_BALL", name = "POKe BALL", pocket = "BALL" },
|
|
},
|
|
}
|
|
local perfect = { attack = 15, defense = 15, speed = 15, special = 15 }
|
|
perfect.hp = Mon.hpDV(perfect)
|
|
|
|
local function catchWith(ball)
|
|
Input:init()
|
|
local player = Mon.new(DATA, "CYNDAQUIL", 10, { dvs = perfect })
|
|
player.moves = { { id = "TACKLE", pp = 35, maxPp = 35 } }
|
|
local wild = Mon.new(DATA, "PIDGEY", 5, { dvs = perfect })
|
|
wild.moves = { { id = "TACKLE", pp = 35, maxPp = 35 } }
|
|
local save = { party = { player }, inventory = { [ball] = 1 } }
|
|
local game = {
|
|
data = DATA, save = save, input = Input, options = {},
|
|
stack = { push = function() end, pop = function() end,
|
|
top = function() return nil end },
|
|
}
|
|
-- random 0: the catch roll always lands (rate 255 PIDGEY).
|
|
local battle = Battle.new({ data = DATA, party = { player },
|
|
wild = wild, save = save, random = function() return 0 end })
|
|
local screen = BattleState.new(game, { battle = battle, save = save })
|
|
screen:useItem(ball)
|
|
return battle, save, wild
|
|
end
|
|
|
|
local battle, save, wild = catchWith("FRIEND_BALL")
|
|
eq(battle.outcome, "caught", "the Friend Ball catch lands")
|
|
eq(wild.happiness, Catching.FRIEND_BALL_HAPPINESS,
|
|
"and the caught mon's happiness is set to 200")
|
|
eq(save.party[2], wild, "in the party slot it landed in")
|
|
|
|
battle, save, wild = catchWith("POKE_BALL")
|
|
eq(battle.outcome, "caught", "the control catch lands too")
|
|
eq(wild.happiness, 70, "an ordinary ball leaves the base 70")
|
|
end
|
|
|
|
S.finish()
|