mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
233 lines
9.3 KiB
Lua
233 lines
9.3 KiB
Lua
-- engine/battle/effect_commands.asm:121 (CheckTurn zeroes wBattleAnimParam)
|
|
-- engine/battle_anims/anim_commands.asm:550 (BattleAnimCmd_IfParamEqual)
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local T = require("tests.harness")
|
|
local check, eq = T.check, T.eq
|
|
local Battle = require("src.battle.gen2.Battle")
|
|
local Mon = require("src.battle.gen2.Mon")
|
|
|
|
local TYPES = {}
|
|
for i, id in ipairs({ "NORMAL", "GHOST", "GROUND", "FLYING", "FIGHTING",
|
|
"GRASS", "DARK", "BUG", "WATER" }) do
|
|
TYPES[id] = { id = id, index = i - 1,
|
|
category = (id == "GRASS" or id == "WATER" or id == "DARK")
|
|
and "special" or "physical" }
|
|
end
|
|
|
|
local function move(id, effect, power, type, accuracy)
|
|
return { id = id, name = id, power = power, type = type,
|
|
accuracy = accuracy or 100, pp = 20, effect = effect }
|
|
end
|
|
|
|
local MOVES = {
|
|
TACKLE = move("TACKLE", "EFFECT_NORMAL_HIT", 35, "NORMAL"),
|
|
CURSE = move("CURSE", "EFFECT_CURSE", 0, "GHOST"),
|
|
DIG = move("DIG", "EFFECT_FLY", 60, "GROUND"),
|
|
FLY = move("FLY", "EFFECT_FLY", 70, "FLYING"),
|
|
RAZOR_WIND = move("RAZOR_WIND", "EFFECT_RAZOR_WIND", 80, "NORMAL"),
|
|
SKULL_BASH = move("SKULL_BASH", "EFFECT_SKULL_BASH", 100, "NORMAL"),
|
|
SKY_ATTACK = move("SKY_ATTACK", "EFFECT_SKY_ATTACK", 140, "FLYING"),
|
|
SOLARBEAM = move("SOLARBEAM", "EFFECT_SOLARBEAM", 120, "GRASS"),
|
|
SELFDESTRUCT = move("SELFDESTRUCT", "EFFECT_SELFDESTRUCT", 200, "NORMAL"),
|
|
EXPLOSION = move("EXPLOSION", "EFFECT_SELFDESTRUCT", 250, "NORMAL"),
|
|
BIDE = move("BIDE", "EFFECT_BIDE", 0, "NORMAL"),
|
|
ROAR = move("ROAR", "EFFECT_FORCE_SWITCH", 0, "NORMAL"),
|
|
WHIRLWIND = move("WHIRLWIND", "EFFECT_FORCE_SWITCH", 0, "NORMAL"),
|
|
PRESENT = move("PRESENT", "EFFECT_PRESENT", 1, "NORMAL", 90),
|
|
HI_JUMP_KICK = move("HI_JUMP_KICK", "EFFECT_JUMP_KICK", 85, "FIGHTING", 90),
|
|
JUMP_KICK = move("JUMP_KICK", "EFFECT_JUMP_KICK", 70, "FIGHTING", 95),
|
|
COMET_PUNCH = move("COMET_PUNCH", "EFFECT_MULTI_HIT", 18, "NORMAL", 85),
|
|
DOUBLESLAP = move("DOUBLESLAP", "EFFECT_MULTI_HIT", 15, "NORMAL", 85),
|
|
FURY_SWIPES = move("FURY_SWIPES", "EFFECT_MULTI_HIT", 18, "NORMAL", 80),
|
|
DOUBLE_KICK = move("DOUBLE_KICK", "EFFECT_DOUBLE_HIT", 30, "FIGHTING"),
|
|
TRIPLE_KICK = move("TRIPLE_KICK", "EFFECT_TRIPLE_KICK", 10, "FIGHTING", 90),
|
|
DESTINY_BOND = move("DESTINY_BOND", "EFFECT_DESTINY_BOND", 0, "GHOST"),
|
|
BEAT_UP = move("BEAT_UP", "EFFECT_BEAT_UP", 10, "DARK"),
|
|
FURY_CUTTER = move("FURY_CUTTER", "EFFECT_FURY_CUTTER", 10, "BUG", 95),
|
|
MOONLIGHT = move("MOONLIGHT", "EFFECT_MOONLIGHT", 0, "NORMAL"),
|
|
MORNING_SUN = move("MORNING_SUN", "EFFECT_MORNING_SUN", 0, "NORMAL"),
|
|
SYNTHESIS = move("SYNTHESIS", "EFFECT_SYNTHESIS", 0, "GRASS"),
|
|
OCTAZOOKA = move("OCTAZOOKA", "EFFECT_ACCURACY_DOWN_HIT", 65, "WATER", 85),
|
|
PURSUIT = move("PURSUIT", "EFFECT_PURSUIT", 40, "DARK"),
|
|
SUBSTITUTE = move("SUBSTITUTE", "EFFECT_SUBSTITUTE", 0, "NORMAL"),
|
|
}
|
|
|
|
local POKEMON = {
|
|
growthRates = {
|
|
GROWTH_MEDIUM_FAST = { numerator = 1, denominator = 1, squared = 0,
|
|
linear = 0, constant = 0 },
|
|
},
|
|
SNORLAX = { id = "SNORLAX", index = 143, name = "SNORLAX",
|
|
baseStats = { hp = 160, attack = 110, defense = 65, speed = 30,
|
|
specialAttack = 65, specialDefense = 110 },
|
|
types = { "NORMAL", "NORMAL" }, catchRate = 25, baseExp = 154,
|
|
growthRate = "GROWTH_MEDIUM_FAST", genderRatio = 31,
|
|
levelMoves = {}, evolutions = {} },
|
|
}
|
|
|
|
local DATA = { pokemon = POKEMON, moves = MOVES,
|
|
type_chart = { types = TYPES, matchups = {} }, items = {} }
|
|
|
|
local perfect = { attack = 15, defense = 15, speed = 15, special = 15 }
|
|
perfect.hp = Mon.hpDV(perfect)
|
|
|
|
local function lowRoll() return 0 end
|
|
|
|
local function newBattle(moveId, random)
|
|
local player = Mon.new(DATA, "SNORLAX", 50, { dvs = perfect })
|
|
player.moves = { { id = moveId, pp = 20, maxPp = 20 } }
|
|
local wild = Mon.new(DATA, "SNORLAX", 50, { dvs = perfect })
|
|
wild.moves = { { id = "TACKLE", pp = 35, maxPp = 35 } }
|
|
return Battle.new({ data = DATA, party = { player }, wild = wild,
|
|
random = random or lowRoll }), player, wild
|
|
end
|
|
|
|
local function moveEvent(events)
|
|
for _, e in ipairs(events or {}) do
|
|
if e.kind == "move" then return e end
|
|
end
|
|
end
|
|
|
|
local function turn(battle, player, wild, moveId)
|
|
battle.events = {}
|
|
battle:useMove(player, wild, moveId)
|
|
return moveEvent(battle.events)
|
|
end
|
|
|
|
local function once(moveId, random)
|
|
return function()
|
|
local battle, player, wild = newBattle(moveId, random)
|
|
return turn(battle, player, wild, moveId)
|
|
end
|
|
end
|
|
|
|
local ENFORCED = {
|
|
CURSE = { param = 1, cite = "move_effects/curse.asm:39", run = once("CURSE") },
|
|
DIG = { param = 1, cite = "effect_commands.asm:5500", charge = true },
|
|
FLY = { param = 1, cite = "effect_commands.asm:5500", charge = true },
|
|
RAZOR_WIND = { param = 1, cite = "effect_commands.asm:5500", charge = true },
|
|
SKULL_BASH = { param = 1, cite = "effect_commands.asm:5500", charge = true },
|
|
SKY_ATTACK = { param = 1, cite = "effect_commands.asm:5500", charge = true },
|
|
SOLARBEAM = { param = 1, cite = "effect_commands.asm:5500", charge = true },
|
|
}
|
|
|
|
local KNOWN_MISSING = {
|
|
SELFDESTRUCT = { param = 1, cite = "move_effects/selfdestruct.asm:14",
|
|
run = once("SELFDESTRUCT") },
|
|
EXPLOSION = { param = 1, cite = "move_effects/selfdestruct.asm:14",
|
|
run = once("EXPLOSION") },
|
|
BIDE = { param = 1, cite = "move_effects/bide.asm:93", run = function()
|
|
local battle, player, wild = newBattle("BIDE")
|
|
local ev
|
|
for _ = 1, 4 do
|
|
ev = turn(battle, player, wild, "BIDE")
|
|
if not battle:volatile(player).bideTurns then break end
|
|
end
|
|
return ev
|
|
end },
|
|
ROAR = { param = 1, cite = "effect_commands.asm:5160", run = once("ROAR") },
|
|
WHIRLWIND = { param = 1, cite = "effect_commands.asm:5160",
|
|
run = once("WHIRLWIND") },
|
|
PRESENT = { param = 3, cite = "move_effects/present.asm:42",
|
|
run = once("PRESENT") },
|
|
HI_JUMP_KICK = { param = 1, cite = "effect_commands.asm:2246",
|
|
run = once("HI_JUMP_KICK", function(n) return (n or 256) - 1 end) },
|
|
JUMP_KICK = { param = 1, cite = "effect_commands.asm:2246",
|
|
run = once("JUMP_KICK", function(n) return (n or 256) - 1 end) },
|
|
COMET_PUNCH = { param = 1, cite = "effect_commands.asm:1994",
|
|
run = once("COMET_PUNCH") },
|
|
DOUBLESLAP = { param = 1, cite = "effect_commands.asm:1994",
|
|
run = once("DOUBLESLAP") },
|
|
FURY_SWIPES = { param = 1, cite = "effect_commands.asm:1994",
|
|
run = once("FURY_SWIPES") },
|
|
DOUBLE_KICK = { param = 1, cite = "effect_commands.asm:1994",
|
|
run = once("DOUBLE_KICK") },
|
|
TRIPLE_KICK = { param = 1, cite = "move_effects/triple_kick.asm:27",
|
|
run = once("TRIPLE_KICK") },
|
|
DESTINY_BOND = { param = 1, cite = "effect_commands.asm:2394",
|
|
run = once("DESTINY_BOND") },
|
|
}
|
|
|
|
local DEAD_ARMS = {
|
|
BEAT_UP = "effect_commands.asm:121",
|
|
FURY_CUTTER = "effect_commands.asm:121",
|
|
MOONLIGHT = "effect_commands.asm:121",
|
|
MORNING_SUN = "effect_commands.asm:121",
|
|
OCTAZOOKA = "effect_commands.asm:121",
|
|
PURSUIT = "effect_commands.asm:121",
|
|
SYNTHESIS = "effect_commands.asm:121",
|
|
SUBSTITUTE = "move_effects/substitute.asm:63",
|
|
}
|
|
|
|
local function sortedKeys(t)
|
|
local keys = {}
|
|
for k in pairs(t) do keys[#keys + 1] = k end
|
|
table.sort(keys)
|
|
return keys
|
|
end
|
|
|
|
for _, id in ipairs(sortedKeys(ENFORCED)) do
|
|
local spec = ENFORCED[id]
|
|
if spec.charge then
|
|
local battle, player, wild = newBattle(id)
|
|
local ev = turn(battle, player, wild, id)
|
|
check(ev ~= nil, id .. ": the charge turn queues a move event")
|
|
eq(ev and ev.animParam, spec.param,
|
|
("%s: charge turn stamps animParam %d (%s)"):format(id, spec.param, spec.cite))
|
|
local ev2 = turn(battle, player, wild, id)
|
|
check(ev2 ~= nil, id .. ": the strike turn queues a move event")
|
|
eq(ev2 and ev2.animParam, nil, id .. ": the strike turn leaves animParam nil")
|
|
else
|
|
local ev = spec.run()
|
|
check(ev ~= nil, id .. ": queues a move event")
|
|
eq(ev and ev.animParam, spec.param,
|
|
("%s: stamps animParam %d (%s)"):format(id, spec.param, spec.cite))
|
|
end
|
|
end
|
|
|
|
for _, id in ipairs(sortedKeys(KNOWN_MISSING)) do
|
|
local spec = KNOWN_MISSING[id]
|
|
check(ENFORCED[id] == nil, id .. ": listed once, not in both sets")
|
|
local ev = spec.run()
|
|
check(ev ~= nil, id .. ": queues a move event")
|
|
check(ev and ev.animParam ~= spec.param,
|
|
("%s: animParam %d (%s) is not stamped yet; move it to ENFORCED"):format(
|
|
id, spec.param, spec.cite))
|
|
end
|
|
|
|
for id in pairs(DEAD_ARMS) do
|
|
check(ENFORCED[id] == nil and KNOWN_MISSING[id] == nil,
|
|
id .. ": a dead arm is listed nowhere else")
|
|
end
|
|
|
|
local cache = os.getenv("GOLD_CACHE")
|
|
or ((os.getenv("HOME") or "") .. "/Library/Application Support/LOVE/gold-dev/gold")
|
|
local anims = loadfile(cache .. "/data/generated/battle_anims.lua")
|
|
local data = anims and anims()
|
|
if data and data.scripts and data.moves then
|
|
local branching = {}
|
|
for id, key in pairs(data.moves) do
|
|
for _, row in ipairs(data.scripts[key] or {}) do
|
|
if row[1] == "if_param_equal" or row[1] == "if_param_and" then
|
|
branching[id] = true
|
|
break
|
|
end
|
|
end
|
|
end
|
|
for _, id in ipairs(sortedKeys(branching)) do
|
|
check(ENFORCED[id] or KNOWN_MISSING[id] or DEAD_ARMS[id],
|
|
id .. ": the cart script branches on the param, so it is classified")
|
|
end
|
|
for _, set in ipairs({ ENFORCED, KNOWN_MISSING, DEAD_ARMS }) do
|
|
for _, id in ipairs(sortedKeys(set)) do
|
|
check(branching[id], id .. ": the cart script really branches on the param")
|
|
end
|
|
end
|
|
else
|
|
check(true, "no Gold cache at " .. cache .. " (cart script cross-check SKIP)")
|
|
end
|
|
|
|
T.finish("gen2 anim param feed sweep")
|