mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
198 lines
7.3 KiB
Lua
198 lines
7.3 KiB
Lua
-- BATTLETYPE_FORCESHINY: the Lake of Rage Gyarados must come out SHINY and
|
|
-- must not be run from.
|
|
--
|
|
-- GOLD_CACHE=".../gold" luajit tests/gen2_forceshiny_test.lua
|
|
--
|
|
-- maps/LakeOfRage.asm arms the fight with
|
|
--
|
|
-- loadwildmon GYARADOS, 30
|
|
-- loadvar VAR_BATTLETYPE, BATTLETYPE_FORCESHINY
|
|
-- startbattle
|
|
--
|
|
-- and InitEnemyMon's `.NotRoaming` arm (engine/battle/core.asm:5876)
|
|
-- answers the type by replacing the rolled DVs with ATKDEFDV_SHINY $EA /
|
|
-- SPDSPCDV_SHINY $AA -- Attack 14, Defense/Speed/Special 10, the classic
|
|
-- shiny pattern. TryToRunAwayFromBattle:3469 jumps straight to
|
|
-- .cant_escape for the same type, so the one-shot encounter cannot be
|
|
-- forfeited by RUN returning a WIN to the RedGyarados script. What is
|
|
-- asserted here is the REAL World:startScriptedBattle -> World:startBattle
|
|
-- chain with the type armed the way Script_loadvar arms it.
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local S = require("tests.harness").suite("gen2 forceshiny")
|
|
local check, eq = S.check, S.eq
|
|
|
|
local World = require("src.world.gen2.World")
|
|
local Map = require("src.world.gen2.Map")
|
|
local Mon = require("src.battle.gen2.Mon")
|
|
|
|
-- ---- the latest load command decides what startbattle fights --------------
|
|
-- Script_loadwildmon rewrites wBattleScriptFlags to the wild shape, erasing
|
|
-- the trainer bit Script_loadtemptrainer set. The port's VM lives as long
|
|
-- as the World, so a trainer record left by an earlier script (any sight
|
|
-- trainer fought on the way to the lake) must not shadow `loadwildmon` --
|
|
-- with the stale record, pressing A on the Red Gyarados refought the last
|
|
-- trainer instead of starting the FORCESHINY encounter at all. ROM-free,
|
|
-- so it runs even without the cache below.
|
|
do
|
|
local Vm = require("src.script.gen2.Vm")
|
|
local Events = require("src.world.gen2.Events")
|
|
local fought = {}
|
|
local vm = Vm.new({
|
|
["s:trainer"] = {
|
|
{ op = "loadtemptrainer" },
|
|
{ op = "startbattle" },
|
|
{ op = "end" },
|
|
},
|
|
["s:gyarados"] = {
|
|
{ op = "loadwildmon", species = 130, level = 30 },
|
|
{ op = "loadvar", args = { 3, 7 } },
|
|
{ op = "startbattle" },
|
|
{ op = "end" },
|
|
},
|
|
}, {}, Events.new(), {
|
|
lookupTrainer = function() return { class = "POKEMANIAC", id = 1 } end,
|
|
startBattle = function(trainer, wild, onDone)
|
|
fought[#fought + 1] = { trainer = trainer, wild = wild }
|
|
onDone("win")
|
|
end,
|
|
})
|
|
vm.trainerObject = { class = "POKEMANIAC", member = 1 }
|
|
check(vm:start("s:trainer"), "the trainer script starts")
|
|
for _ = 1, 20 do vm:update() end
|
|
check(fought[1] and fought[1].trainer ~= nil, "and fights its trainer")
|
|
|
|
check(vm:start("s:gyarados"), "the wild script starts on the same VM")
|
|
for _ = 1, 20 do vm:update() end
|
|
check(fought[2] ~= nil, "and reaches its own startbattle")
|
|
eq(fought[2] and fought[2].trainer, nil,
|
|
"loadwildmon erased the stale trainer record")
|
|
eq(fought[2] and fought[2].wild and fought[2].wild.species, 130,
|
|
"so the wild GYARADOS is what gets fought")
|
|
end
|
|
|
|
local cache = os.getenv("GOLD_CACHE")
|
|
if not cache then
|
|
local home = os.getenv("HOME") or ""
|
|
cache = home .. "/Library/Application Support/LOVE/gold-dev/gold"
|
|
end
|
|
local probe = io.open(cache .. "/data/generated/maps.lua", "r")
|
|
if not probe then
|
|
check(true, "gold cache absent (SKIP)")
|
|
S.finish()
|
|
return
|
|
end
|
|
probe:close()
|
|
|
|
local function loadLua(rel) return assert(loadfile(cache .. "/" .. rel))() end
|
|
local maps = loadLua("data/generated/maps.lua")
|
|
local tilesets = loadLua("data/generated/tilesets.lua")
|
|
local pokemon = loadLua("data/generated/pokemon.lua")
|
|
local moves = loadLua("data/generated/moves.lua")
|
|
|
|
-- constants/battle_constants.asm / engine/overworld/variables.asm.
|
|
local VAR_BATTLETYPE = 0x03
|
|
local BATTLETYPE_FORCESHINY = 7
|
|
local GYARADOS_INDEX = 130
|
|
|
|
-- The two battle screens as registry fakes: the transition hands off
|
|
-- immediately, the battle parks its Battle instance for the assertions.
|
|
local battleDone, builtBattle
|
|
local registry = {
|
|
Gen2BattleTransition = { new = function(_g, opts)
|
|
opts.onDone()
|
|
return { screenId = "Gen2BattleTransition" }
|
|
end },
|
|
Gen2BattleState = { new = function(_g, opts)
|
|
battleDone = opts.onDone
|
|
builtBattle = opts.battle
|
|
return { screenId = "Gen2BattleState" }
|
|
end },
|
|
}
|
|
|
|
local function makeStack()
|
|
local stack = { items = {} }
|
|
function stack:push(inst) self.items[#self.items + 1] = inst end
|
|
function stack:pop()
|
|
local top = self.items[#self.items]
|
|
self.items[#self.items] = nil
|
|
return top
|
|
end
|
|
return stack
|
|
end
|
|
|
|
local function makeWorld()
|
|
battleDone, builtBattle = nil, nil
|
|
local game = {
|
|
data = { audio = {}, screens = registry, pokemon = pokemon,
|
|
moves = moves },
|
|
save = {
|
|
player = { name = "GOLD", money = 3000 },
|
|
party = {},
|
|
blackoutMap = "MAHOGANY_POKECENTER_1F",
|
|
},
|
|
stack = makeStack(),
|
|
}
|
|
local mine = Mon.new(game.data, "CYNDAQUIL", 30)
|
|
check(mine ~= nil, "the cache can build the player's mon")
|
|
game.save.party[1] = mine
|
|
local w = World.new(game)
|
|
w.maps, w.tilesets = maps, tilesets
|
|
w.map = Map.new(maps.LAKE_OF_RAGE, tilesets[maps.LAKE_OF_RAGE.tileset])
|
|
w.player = { cellX = 20, cellY = 20, facing = "up", moving = false }
|
|
return w, game
|
|
end
|
|
|
|
-- ---- the armed type forces the shiny DV pattern ---------------------------
|
|
do
|
|
local w = makeWorld()
|
|
-- Script_loadvar's write, exactly as the extracted RedGyarados script
|
|
-- (49:4f6f) runs it ahead of `startbattle`.
|
|
w:writeVar(VAR_BATTLETYPE, BATTLETYPE_FORCESHINY)
|
|
eq(w:battleType(), BATTLETYPE_FORCESHINY, "the type is armed")
|
|
|
|
check(w:startScriptedBattle(nil, { species = GYARADOS_INDEX, level = 30 },
|
|
function() end), "the scripted battle starts")
|
|
check(builtBattle ~= nil, "the battle screen got the Battle instance")
|
|
local wild = builtBattle.enemy
|
|
eq(wild.species, "GYARADOS", "the wild mon is the GYARADOS")
|
|
eq(wild.level, 30, "at level 30")
|
|
eq(wild.dvs.attack, 14, "ATKDEFDV_SHINY $EA: Attack DV 14")
|
|
eq(wild.dvs.defense, 10, "Defense DV 10")
|
|
eq(wild.dvs.speed, 10, "SPDSPCDV_SHINY $AA: Speed DV 10")
|
|
eq(wild.dvs.special, 10, "Special DV 10")
|
|
eq(wild.shiny, true, "and the mon IS shiny")
|
|
eq(Mon.isShiny(wild.dvs), true,
|
|
"shininess survives a re-read of the DVs, so a catch keeps it")
|
|
|
|
-- The battle got the type, and RUN is refused before any speed math.
|
|
eq(builtBattle.battleType, BATTLETYPE_FORCESHINY,
|
|
"opts.battleType reached Battle.new")
|
|
eq(builtBattle:tryRun(), false,
|
|
"TryToRunAwayFromBattle's .cant_escape arm for the type")
|
|
eq(builtBattle.over, false, "the encounter is still live after RUN")
|
|
eq(w:battleType(), 0, "the var is a one-shot, consumed on the way in")
|
|
battleDone("win")
|
|
end
|
|
|
|
-- ---- without the type: an ordinary blue Gyarados that can be fled ---------
|
|
do
|
|
local w = makeWorld()
|
|
eq(w:battleType(), 0, "no type armed")
|
|
check(w:startScriptedBattle(nil, { species = GYARADOS_INDEX, level = 30 },
|
|
function() end), "the plain battle starts")
|
|
eq(builtBattle.battleType, 0, "no battle type handed over")
|
|
check(builtBattle.enemy.dvs ~= nil, "the DVs exist")
|
|
-- One in 8192 runs would roll shiny DVs honestly; assert the FORCED
|
|
-- pattern is not simply stamped on everything.
|
|
local dvs = builtBattle.enemy.dvs
|
|
local forced = dvs.attack == 14 and dvs.defense == 10
|
|
and dvs.speed == 10 and dvs.special == 10
|
|
check(not forced or Mon.isShiny(dvs),
|
|
"unforced DVs are random, not the shiny constant")
|
|
battleDone("win")
|
|
end
|
|
|
|
S.finish()
|