Files
gen1recomp/tests/engine/gen2_link_cross_version_bug2382.lua

183 lines
6.7 KiB
Lua

-- ../pokecrystal/engine/battle/effect_commands.asm:2644-2654
-- ../pokecrystal/data/items/attributes.asm:149-150
-- ../pokegold/data/items/attributes.asm:149-150
package.path = "./?.lua;./?/init.lua;" .. package.path
love = require("tests.love_stub")
local T = require("tests.harness")
local Fingerprint = require("src.link.Fingerprint")
local Handshake = require("src.link.Handshake")
local Wire = require("src.link.Wire")
local GameVersion = require("src.core.GameVersion")
local Battle = require("src.battle.gen2.Battle")
local Damage = require("src.battle.gen2.Damage")
local Mon = require("src.battle.gen2.Mon")
local function deepCopy(v)
if type(v) ~= "table" then return v end
local out = {}
for k, x in pairs(v) do out[k] = deepCopy(x) end
return out
end
local function tmhmList(n)
local out = {}
for i = 1, n do out[i] = "MOVE_" .. i end
return out
end
local TYPES = {
NORMAL = { id = "NORMAL", index = 0, category = "physical" },
FIGHTING = { id = "FIGHTING", index = 1, category = "physical" },
}
local function goldData()
return {
pokemon = {
growthRates = {
GROWTH_MEDIUM_FAST = { numerator = 1, denominator = 1, squared = 0,
linear = 0, constant = 0 },
},
tmhmMoves = tmhmList(57),
MACHOP = {
id = "MACHOP", index = 66, name = "MACHOP",
baseStats = { hp = 70, attack = 80, defense = 50, speed = 35,
specialAttack = 35, specialDefense = 35 },
types = { "NORMAL", "NORMAL" }, catchRate = 180, baseExp = 75,
growthRate = "GROWTH_MEDIUM_FAST", genderRatio = 63,
levelMoves = { { level = 1, move = "TACKLE" } }, evolutions = {},
},
},
moves = {
TACKLE = { id = "TACKLE", name = "TACKLE", power = 35, type = "NORMAL",
accuracy = 100, pp = 35, effect = "EFFECT_NORMAL_HIT" },
},
type_chart = { generation = 2, types = deepCopy(TYPES), matchups = {} },
gen2HeldItems = {
ITEM_46 = { heldEffect = "HELD_NONE", heldParameter = 0 },
ITEM_73 = { heldEffect = "HELD_NONE", heldParameter = 0 },
LEFTOVERS = { heldEffect = "HELD_LEFTOVERS", heldParameter = 10 },
},
}
end
local function crystalData()
local data = goldData()
data.pokemon.tmhmMoves = tmhmList(60)
data.pokemon.tutorMoves = { "FLAMETHROWER", "THUNDERBOLT", "ICE_BEAM" }
data.gen2HeldItems.ITEM_46 = nil
data.gen2HeldItems.ITEM_73 = nil
data.gen2HeldItems.CLEAR_BELL = { heldEffect = "HELD_NONE", heldParameter = 0 }
data.gen2HeldItems.GS_BALL = { heldEffect = "HELD_NONE", heldParameter = 0 }
return data
end
do
local gold, crystal = goldData(), crystalData()
local g = Fingerprint.compute(gold, {}, 2)
local c = Fingerprint.compute(crystal, {}, 2)
T.eq(c, g, "Crystal's tutor list and renamed HELD_NONE slots do not split the digest")
local helloG = Handshake.hello({ data = gold })
local helloC = Handshake.hello({ data = crystal })
T.eq(helloG.generation, 2, "the gold hello is gen 2")
T.eq((Handshake.checkCompat(helloG, helloC)), "full",
"a Gold hello meeting a Crystal hello pairs as full")
T.eq((Handshake.checkCompat(helloC, helloG)), "full", "and in reverse")
end
do
local crystal = crystalData()
local base = Fingerprint.compute(crystal, {}, 2)
local armed = crystalData()
armed.gen2HeldItems.CLEAR_BELL = { heldEffect = "HELD_LEFTOVERS",
heldParameter = 10 }
T.check(Fingerprint.compute(armed, {}, 2) ~= base,
"a HELD_NONE slot given a real effect moves the digest")
local tuned = crystalData()
tuned.gen2HeldItems.LEFTOVERS.heldParameter = 20
T.check(Fingerprint.compute(tuned, {}, 2) ~= base,
"a real held item's parameter still moves the digest")
local species = crystalData()
species.pokemon.MACHOP.baseStats.attack = 81
T.check(Fingerprint.compute(species, {}, 2) ~= base,
"a species edit still moves the digest")
end
do
local gold, crystal = goldData(), crystalData()
local cmons = Fingerprint.records(crystal, "pokemon", 2)
T.eq(cmons.tutorMoves, nil, "tutorMoves is not a species record")
T.eq(cmons.tmhmMoves, nil, "tmhmMoves is not a species record")
T.eq(cmons.growthRates, nil, "growthRates is not a species record")
T.check(cmons.MACHOP ~= nil, "a real species keeps its record")
local gheld = Fingerprint.records(gold, "held_items", 2)
T.check(gheld.ITEM_46 ~= nil,
"the per-record held map keeps HELD_NONE ids for party eligibility")
local cheld = Fingerprint.records(crystal, "held_items", 2)
T.eq(cheld.ITEM_46, nil, "Crystal has no ITEM_46 record")
end
do
local detail = "dataset fingerprint differs: the room has 6f577c0127a1e94d, "
.. "you have c444a8971640c763"
local out = Wire.sanitize({ type = "join_error", reason = "profile_mismatch",
field = "fingerprint", detail = detail })
T.eq(out.detail, detail, "the relay's mismatch detail survives the wire whole")
local long = string.rep("x", 120)
out = Wire.sanitize({ type = "join_error", reason = "x", detail = long })
T.eq(out.detail, long, "a 120-byte detail survives")
end
do
local data = goldData()
data.items = {}
local perfect = { attack = 15, defense = 15, speed = 15, special = 15 }
perfect.hp = Mon.hpDV(perfect)
local function highRoll(n) return 99 % math.max(1, n or 1) end
local seen
local realCalc = Damage.calc
Damage.calc = function(opts)
seen = opts
return realCalc(opts)
end
local function reflectHit(version, link)
GameVersion.set(version)
local player = Mon.new(data, "MACHOP", 15, { dvs = perfect })
player.moves = { { id = "TACKLE", pp = 35, maxPp = 35 } }
local wild = Mon.new(data, "MACHOP", 15, { dvs = perfect })
wild.moves = { { id = "TACKLE", pp = 35, maxPp = 35 } }
local battle = Battle.new({ data = data, party = { player }, wild = wild,
random = highRoll })
player.stats.attack = 200
wild.stats.defense = 512
wild.maxHp, wild.hp = 999, 999
battle.linkBattle = link or nil
battle.screens.enemy.reflect = 5
seen = nil
battle:useMove(player, wild, "TACKLE")
battle:takeEvents()
return 999 - wild.hp, seen and seen.reflectOverflowFixed
end
local restore = GameVersion.get()
local gold = reflectHit("gold", false)
local crystalSolo, soloFlag = reflectHit("crystal", false)
local crystalLink, linkFlag = reflectHit("crystal", true)
local goldLink = reflectHit("gold", true)
GameVersion.set(restore)
Damage.calc = realCalc
T.check(gold > 0, "the Reflect hit lands")
T.eq(soloFlag, nil, "a single-player battle leaves the version's fix in charge")
T.check(crystalSolo < gold, "single-player Crystal keeps its Reflect fix")
T.eq(linkFlag, false, "a link battle forces the single truncation pass")
T.eq(crystalLink, gold, "a Crystal link battle computes Gold's Reflect damage")
T.eq(goldLink, gold, "and Gold's link battle is unchanged")
end
T.finish("gen2 link cross version bug 2382")