mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-21 13:09:54 +02:00
CLOSES #1478, CLOSES #1479, CLOSES #1488, CLOSES #1510, CLOSES #1511, CLOSES #1514, CLOSES #1521, CLOSES #1522, CLOSES #1545, CLOSES #1557, CLOSES #1563, CLOSES #1565, CLOSES #1566, CLOSES #1577, CLOSES #1594, CLOSES #1596, CLOSES #1606, CLOSES #1608
This commit is contained in:
@@ -23,8 +23,13 @@ return function(game)
|
||||
game.save.party = { mon }
|
||||
-- engine/movie/evolution.asm:103
|
||||
Evolution.evolve(game, mon, "RAICHU", nil, "ITEM")
|
||||
U.wait(12)
|
||||
local top = game.stack:top()
|
||||
-- the IsEvolvingText box now holds 50+ frames before the movie (#1596)
|
||||
local top
|
||||
for _ = 1, 300 do
|
||||
top = game.stack:top()
|
||||
if top and top.screenId == "EvolutionState" then break end
|
||||
U.wait(1)
|
||||
end
|
||||
check("the evolution screen opened",
|
||||
top and top.screenId == "EvolutionState")
|
||||
U.shot(game, DIR .. "/bug1412_evo_old_pikachu.png")
|
||||
|
||||
@@ -39,9 +39,13 @@ return function(game)
|
||||
local mon = Pokemon.new(game.data, "PIKACHU", 20)
|
||||
game.save.party = { mon }
|
||||
Evolution.evolve(game, mon, "RAICHU")
|
||||
U.wait(12)
|
||||
|
||||
local top = game.stack:top()
|
||||
-- the IsEvolvingText box now holds 50+ frames before the movie (#1596)
|
||||
local top
|
||||
for _ = 1, 300 do
|
||||
top = game.stack:top()
|
||||
if top and top.screenId == "EvolutionState" then break end
|
||||
U.wait(1)
|
||||
end
|
||||
check("the evolution screen opened", top and top.screenId == "EvolutionState")
|
||||
U.log("Issue #494: true-color sprites during evolutions and trades")
|
||||
U.log("Watch this PIKACHU evolve into RAICHU in ADVANCED colors.")
|
||||
|
||||
@@ -50,7 +50,8 @@ return function(game)
|
||||
U.wait(5)
|
||||
local ow = game.stack:top()
|
||||
U.shot(game, DIR .. "/" .. shots.prefix .. "_0_gym.png")
|
||||
ow:checkVictoryRewards(class, party)
|
||||
-- true: the badge line rode the battle screen on the real path (#1606)
|
||||
ow:checkVictoryRewards(class, party, true)
|
||||
U.wait(10)
|
||||
for _, s in ipairs(shots.pages) do
|
||||
advancePages(s.want, shots.prefix .. "_" .. s.name)
|
||||
@@ -63,26 +64,64 @@ return function(game)
|
||||
|
||||
game.save.player.name = game.save.player.name or "RED"
|
||||
|
||||
runLeader("PEWTER_GYM", 4, 3, "OPP_BROCK", 1, {
|
||||
prefix = "brock",
|
||||
pages = {
|
||||
{ want = "BOULDERBADGE", name = "1_badge" },
|
||||
{ want = "FLASH", name = "2_flash" },
|
||||
{ want = "Wait!", name = "3_wait" },
|
||||
{ want = "TM34", name = "4_tm34" },
|
||||
{ want = "BIDE", name = "5_bide" },
|
||||
},
|
||||
})
|
||||
-- The REAL gym path (#1606): the badge line and jingle must ride the
|
||||
-- battle it pushes (scripts/PewterGym.asm:117-119).
|
||||
while game.stack:top() do game.stack:pop() end
|
||||
local Pokemon = require("src.pokemon.Pokemon")
|
||||
game.save.party = { Pokemon.new(game.data, "CHARIZARD", 50) }
|
||||
game.save.flags = game.save.flags or {}
|
||||
game.save.inventory = game.save.inventory or {}
|
||||
game.save.defeatedTrainers = game.save.defeatedTrainers or {}
|
||||
game.stack:push(OW, "PEWTER_GYM", 4, 3, "up")
|
||||
U.wait(5)
|
||||
local realOw = game.stack:top()
|
||||
local brock
|
||||
for _, npc in ipairs(realOw.npcs or {}) do
|
||||
if npc.def and npc.def.trainerClass == "OPP_BROCK" then brock = npc end
|
||||
end
|
||||
assert(brock, "Brock stands in PEWTER_GYM")
|
||||
require("data.scripts.gyms").PEWTER_GYM.talk.TEXT_PEWTERGYM_BROCK(
|
||||
game, realOw, brock, function() end)
|
||||
local battle
|
||||
for _ = 1, 600 do
|
||||
local top = game.stack:top()
|
||||
if top and top.oppClass == "OPP_BROCK" and top.onFinish then
|
||||
battle = top
|
||||
break
|
||||
end
|
||||
U.tap(game, "a")
|
||||
U.wait(2)
|
||||
end
|
||||
assert(battle, "the real gym path reaches Brock's BattleState")
|
||||
assert(type(battle.endBattleText) == "string" and #battle.endBattleText > 0,
|
||||
"the battle carries the armed badge line (#1606)")
|
||||
assert(battle.endBattleSound == "Get_Item1",
|
||||
"and the badge jingle beside it (sound_level_up, PewterGym.asm)")
|
||||
U.shot(game, DIR .. "/brock_real_battle.png")
|
||||
U.log("force-finishing Brock's battle to run the reward chain")
|
||||
battle.onFinish("win")
|
||||
if game.stack:top() == battle then game.stack:pop() end
|
||||
U.wait(10)
|
||||
-- rewardDialogueShown: the badge line rode the battle screen, so the map
|
||||
-- chain opens on the TM prelude, not on a reprint of the badge line
|
||||
for _, s in ipairs({
|
||||
{ want = "Wait!", name = "1_wait" },
|
||||
{ want = "TM34", name = "2_tm34" },
|
||||
{ want = "BIDE", name = "3_bide" },
|
||||
}) do
|
||||
advancePages(s.want, "brock_" .. s.name)
|
||||
end
|
||||
assert(game.save.flags.EVENT_BEAT_BROCK, "EVENT_BEAT_BROCK")
|
||||
assert(game.save.inventory.BOULDERBADGE, "BOULDERBADGE")
|
||||
assert((game.save.inventory.TM_BIDE or 0) >= 1, "TM_BIDE")
|
||||
|
||||
-- checkVictoryRewards direct drive, as after a battle whose badge line
|
||||
-- rode the battle screen (shownOnBattleScreen = true)
|
||||
runLeader("CERULEAN_GYM", 5, 5, "OPP_MISTY", 1, {
|
||||
prefix = "misty",
|
||||
pages = {
|
||||
{ want = "CASCADEBADGE", name = "1_badge" },
|
||||
{ want = "CUT", name = "2_cut" },
|
||||
{ want = "TM11", name = "3_tm11" },
|
||||
{ want = "CUT", name = "1_cut" },
|
||||
{ want = "TM11", name = "2_tm11" },
|
||||
},
|
||||
})
|
||||
assert(game.save.flags.EVENT_BEAT_MISTY, "EVENT_BEAT_MISTY")
|
||||
|
||||
@@ -87,6 +87,22 @@ do
|
||||
"and no title row: the box carries no header text")
|
||||
end
|
||||
|
||||
-- the box keeps the palette beneath and caps the cursor at wMaxMenuItem
|
||||
do
|
||||
local list = newList(6)
|
||||
eq(list.sgbPalettes, false,
|
||||
"no SET_PAL_GENERIC: ItemMenuLoop keeps RunDefaultPaletteCommand's "
|
||||
.. "palette (start_sub_menus.asm:300)")
|
||||
eq(list.cursorRows, 3,
|
||||
"wMaxMenuItem 2: three cursor rows (home/list_menu.asm:46-48)")
|
||||
list.game = { input = { wasPressed = function(_, b) return b == "down" end,
|
||||
isDown = function() return false end } }
|
||||
for _ = 1, 3 do list:update(1 / 60) end
|
||||
eq(list.index, 4, "three downs reach the fourth item")
|
||||
eq(list.index - list.scroll, 3,
|
||||
"scrolling instead of dropping the cursor onto the look-ahead row")
|
||||
end
|
||||
|
||||
-- a short list stops at its last name, and the terminator's CANCEL row is
|
||||
-- what would follow -- never the '▼'
|
||||
do
|
||||
|
||||
@@ -40,6 +40,11 @@ do
|
||||
T.eq(battle:shrinkOutScale(battle.player), 3 / 7, "wDownscaledMonSize 1 -> 3x3")
|
||||
battle.shrinkOut.frame = 6
|
||||
T.eq(battle:shrinkOutScale(battle.player), 3 / 7, "through Delay3")
|
||||
battle.shrinkOut.frame = 7
|
||||
T.eq(battle:shrinkOutScale(battle.player), 0,
|
||||
"then the 7x7 area holds cleared: no full-size flash before the swap")
|
||||
T.check(battle:shrinkOutScale({}) == nil,
|
||||
"the swapped-in battler draws normally")
|
||||
T.check(battle:shrinkOutScale(battle.enemy) == nil,
|
||||
"AnimateRetreatingPlayerMon is player-side only")
|
||||
end
|
||||
@@ -62,6 +67,8 @@ do
|
||||
T.check(battle:shrinkOutScale(battle.player) == nil, "and no downscale stage")
|
||||
battle.queue[3].fn()
|
||||
T.check((battle.picOff or {}).playerMon == nil, "the slot clears afterwards")
|
||||
T.eq(battle.sendingOut, true,
|
||||
"and stays hidden until the swap's send-out (#1545)")
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
@@ -109,4 +116,62 @@ do
|
||||
T.check(battle.queue[2] and battle.queue[2].fn ~= nil, "which also reprompts")
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- the SHIFT prompt's picker reprompts on a dead or already-out pick too
|
||||
-- (HasMonFainted's NoWillText, core.asm:1473-1488) (#1608)
|
||||
-- ---------------------------------------------------------------------
|
||||
do
|
||||
local save = SaveData.newGame()
|
||||
save.player.name = "RED"
|
||||
save.party = { Pokemon.new(Data, "FIXMON_A", 30),
|
||||
Pokemon.new(Data, "FIXMON_B", 30) }
|
||||
save.options = { battleStyle = "shift" }
|
||||
local game = { data = Data, save = save,
|
||||
stack = { top = function() return nil end,
|
||||
push = function() end, pop = function() end } }
|
||||
local battle = BattleState.newTrainer(game, "OPP_FIX_YOUNGSTER", 1)
|
||||
battle.participants = {}
|
||||
battle.buildScreen = function(_, _, opts) return opts end
|
||||
-- capture the picker opts the SHIFT branch pushes
|
||||
local captured
|
||||
Data.screens = Data.screens or {}
|
||||
Data.screens.PartyMenu = function(_, opts) captured = opts; return {} end
|
||||
require("src.ui.Screens").invalidate()
|
||||
battle.enemyParty[1].hp = 0
|
||||
battle.enemy.mon = battle.enemyParty[1]
|
||||
battle:enemyMonFainted()
|
||||
local choiceRow
|
||||
for _, row in ipairs(battle.queue) do
|
||||
if row.choice then choiceRow = row end
|
||||
end
|
||||
T.check(choiceRow ~= nil, "SHIFT queues the change-POKeMON choice")
|
||||
choiceRow.choice(true)
|
||||
T.check(captured ~= nil and captured.forceSwitch == true,
|
||||
"YES opens the forced party picker")
|
||||
|
||||
battle.queue, battle.nextInsert = {}, 0
|
||||
save.party[2].hp = 0
|
||||
captured.onSwitch(save.party[2])
|
||||
T.check(battle.queue[1] and battle.queue[1].text
|
||||
and battle.queue[1].text:find("no will", 1, true) ~= nil,
|
||||
"a fainted SHIFT pick prints NoWillText first")
|
||||
T.check(battle.queue[2] and battle.queue[2].ui ~= nil,
|
||||
"then the picker goes straight back up, ahead of the send-out")
|
||||
T.eq(battle.queue[2].ui(), captured, "with the same forced opts")
|
||||
|
||||
battle.queue, battle.nextInsert = {}, 0
|
||||
captured.onSwitch(battle.player.mon)
|
||||
T.check(battle.queue[1] and battle.queue[1].text
|
||||
and battle.queue[1].text:find("already out", 1, true) ~= nil,
|
||||
"an already-out SHIFT pick prints AlreadyOutText")
|
||||
T.check(battle.queue[2] and battle.queue[2].ui ~= nil, "and reprompts too")
|
||||
|
||||
battle.queue, battle.nextInsert = {}, 0
|
||||
save.party[2].hp = 10
|
||||
captured.onSwitch(save.party[2])
|
||||
T.eq(#battle.queue, 0, "a healthy pick queues no reprompt rows")
|
||||
Data.screens.PartyMenu = nil
|
||||
require("src.ui.Screens").invalidate()
|
||||
end
|
||||
|
||||
T.finish("retreat animation and switch reprompt (#1563, #1545, #1608)")
|
||||
|
||||
@@ -30,7 +30,9 @@ package.loaded["src.core.Sound"] = {
|
||||
playCry = function() end,
|
||||
}
|
||||
package.loaded["src.render.TextBox"] = {
|
||||
new = function(_, text, done) return { textBox = true, text = text, done = done } end,
|
||||
new = function(_, text, done, opts)
|
||||
return { textBox = true, text = text, done = done, opts = opts }
|
||||
end,
|
||||
}
|
||||
-- BagMenu and PartyMenu bind TextBox at require time, so they load against the
|
||||
-- stub; Screens caches its factory per id and must be told to forget.
|
||||
@@ -126,17 +128,21 @@ local function useStone(game)
|
||||
game.input.pressed = "a"
|
||||
picker:update(1 / 60)
|
||||
game.input.pressed = nil
|
||||
-- IsEvolvingText hands off to the movie when it closes
|
||||
-- (engine/pokemon/evos_moves.asm:120-134)
|
||||
-- IsEvolvingText STAYS up; its onShown starts the DelayFrames 50 hold,
|
||||
-- which then pushes the movie over it (evos_moves.asm:120-134) (#1596)
|
||||
local intro = game.stack:top()
|
||||
if not (intro and intro.textBox and intro.done) then
|
||||
if not (intro and intro.textBox and intro.opts and intro.opts.stay) then
|
||||
return nil, "the \"is evolving!\" box never opened"
|
||||
end
|
||||
if not tostring(intro.text):find("evolving") then
|
||||
return nil, "the box before the movie is not _IsEvolvingText"
|
||||
end
|
||||
game.stack:pop()
|
||||
intro.done()
|
||||
intro.opts.stay.onShown()
|
||||
local hold = game.stack:top()
|
||||
for _ = 1, 60 do
|
||||
if pushed then break end
|
||||
if hold.update then hold.update() end
|
||||
end
|
||||
return list
|
||||
end
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
-- The evolution dialogue is the cart's, in the cart's order (#1596):
|
||||
-- engine/pokemon/evos_moves.asm:120-128, :136-150, :151-153
|
||||
-- engine/pokemon/evos_moves.asm:120-134 (the clear is rows 0-11 only),
|
||||
-- :136-150, :151-153
|
||||
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
@@ -65,10 +66,9 @@ do
|
||||
"_IsEvolvingText goes up in a real bordered text box first") then
|
||||
check(textOf(intro):find("is evolving"),
|
||||
"and it is the cart's line: " .. textOf(intro))
|
||||
check(intro.auto ~= nil and not intro.auto.wait,
|
||||
"which waits for no button (IsEvolvingText ends in `done`)")
|
||||
eq(intro.auto.delay, 50,
|
||||
"and holds DelayFrames 50 before it clears (evos_moves.asm:122)")
|
||||
check(intro.stay ~= nil and not intro.stay.prompt,
|
||||
"which waits for no button (IsEvolvingText ends in `done`) "
|
||||
.. "and stays up under whatever follows")
|
||||
end
|
||||
-- it hands off to the movie on its own, with no input at all
|
||||
local top
|
||||
@@ -78,7 +78,15 @@ do
|
||||
step(game)
|
||||
end
|
||||
check(getmetatable(top) == EvolutionState,
|
||||
"the flash movie opens once that box has cleared itself")
|
||||
"the flash movie opens once the DelayFrames 50 hold has passed")
|
||||
local underneath = false
|
||||
for _, s in ipairs(game.stack.states or {}) do
|
||||
if s == intro then underneath = true end
|
||||
end
|
||||
check(underneath, "the 'is evolving!' box is still on the stack under the "
|
||||
.. "flash (ClearScreenArea wipes rows 0-11 only, evos_moves.asm:126-128)")
|
||||
check(not EvolutionState.isOpaque,
|
||||
"and the flash screen is not opaque, so the box beneath draws")
|
||||
eq(mon.species, "FIXMON_A",
|
||||
"and nothing has evolved yet while the box was up")
|
||||
end
|
||||
@@ -122,12 +130,4 @@ do
|
||||
end
|
||||
end
|
||||
|
||||
-- The flash itself carries no text: EvolutionState draws sprites only.
|
||||
do
|
||||
local source = assert(io.open("src/ui/EvolutionState.lua")):read("*a")
|
||||
check(not source:find("Font%.draw"),
|
||||
"EvolutionState draws no bare text over the flash "
|
||||
.. "(ClearScreenArea ran before EvolveMon, evos_moves.asm:128)")
|
||||
end
|
||||
|
||||
T.finish()
|
||||
|
||||
@@ -35,4 +35,17 @@ local menu = PokedexMenu.new(game, { onClose = function() closed = true end })
|
||||
menu:close()
|
||||
check(closed, "close still runs the caller's onClose")
|
||||
|
||||
-- Save.newGame seeds the key and Save.validate clamps a hand-edited value
|
||||
local Save = require("src.core.gen2.Save")
|
||||
eq(Save.newGame().lastDexMode, "NEW",
|
||||
"a brand-new save carries the key from the start (wLastDexMode's zero)")
|
||||
local edited = Save.newGame()
|
||||
edited.lastDexMode = "SPICY"
|
||||
Save.validate(edited)
|
||||
eq(edited.lastDexMode, "NEW", "validate clamps an out-of-range mode to NEW")
|
||||
local kept = Save.newGame()
|
||||
kept.lastDexMode = "A-Z"
|
||||
Save.validate(kept)
|
||||
eq(kept.lastDexMode, "A-Z", "and keeps a legal one")
|
||||
|
||||
T.finish("gen2 pokedex mode persistence bug 1474")
|
||||
|
||||
@@ -34,4 +34,28 @@ T.check(not keys["TILESET_KANTO|ROOF_SILVER"],
|
||||
T.check(keys["TILESET_JOHTO|ROOF_SILVER"],
|
||||
"while TILESET_JOHTO still takes its group's roof")
|
||||
|
||||
print("gen2 Kanto roof gate (#1479): ok")
|
||||
-- World:atlasFor is the copy the bug was filed against; it has its own
|
||||
-- ROOF_TILESETS gate, so pin it separately from MapPreview's
|
||||
local realAssets = package.loaded["src.render.Assets"]
|
||||
package.loaded["src.render.Assets"] = setmetatable({
|
||||
image = function() return { setFilter = function() end } end,
|
||||
resolve = function(path) return path end,
|
||||
}, { __index = realAssets or { register = function() end } })
|
||||
package.loaded["src.world.gen2.World"] = nil
|
||||
local World = require("src.world.gen2.World")
|
||||
local world = setmetatable({
|
||||
tilesets = baker.tilesets,
|
||||
roofs = baker.roofs,
|
||||
atlasCache = {},
|
||||
}, { __index = World })
|
||||
world:atlasFor({ tileset = "TILESET_KANTO", group = 19 })
|
||||
world:atlasFor({ tileset = "TILESET_JOHTO", group = 19 })
|
||||
T.check(world.atlasCache["TILESET_KANTO"] ~= nil
|
||||
and world.atlasCache["TILESET_KANTO|ROOF_SILVER"] == nil,
|
||||
"World:atlasFor bakes Kanto with no map-group roof (home/map.asm:1738-1749)")
|
||||
T.check(world.atlasCache["TILESET_JOHTO|ROOF_SILVER"] ~= nil,
|
||||
"and still roofs TILESET_JOHTO by group")
|
||||
package.loaded["src.render.Assets"] = realAssets
|
||||
package.loaded["src.world.gen2.World"] = nil
|
||||
|
||||
T.finish("gen2 Kanto roof gate (#1479)")
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
-- #1558: menu_gfx.stats -> $31..$41 quads (engine/gfx/load_font.asm:90-95);
|
||||
-- PAGE_PALETTES is gfx/stats/pages.pal (cgb_layouts.asm:199-212)
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.modkit")
|
||||
local SummaryMenu = require("src.ui.gen2.SummaryMenu")
|
||||
|
||||
-- gfx/stats/pages.pal, RGB 5-bit rows in ROM order: pink, green, blue
|
||||
local ROM_PALS = {
|
||||
{ { 31, 31, 31 }, { 31, 19, 31 }, { 31, 15, 31 }, { 0, 0, 0 } },
|
||||
{ { 31, 31, 31 }, { 21, 31, 14 }, { 17, 31, 0 }, { 0, 0, 0 } },
|
||||
{ { 31, 31, 31 }, { 17, 31, 31 }, { 17, 31, 31 }, { 0, 0, 0 } },
|
||||
}
|
||||
local function up(v) return math.floor(v * 255 / 31 + 0.5) end
|
||||
for p = 1, 3 do
|
||||
for c = 1, 4 do
|
||||
for ch = 1, 3 do
|
||||
T.eq(SummaryMenu.PAGE_PALETTES[p][c][ch], up(ROM_PALS[p][c][ch]),
|
||||
("pages.pal palette %d color %d channel %d"):format(p, c, ch))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- the quads: 17 tiles from $31, one 8x8 cell each off the 136x8 sheet
|
||||
love.graphics = love.graphics or {}
|
||||
local realNewQuad = love.graphics.newQuad
|
||||
love.graphics.newQuad = function(x, y, w, h)
|
||||
return { x = x, y = y, w = w, h = h }
|
||||
end
|
||||
local menu = setmetatable({
|
||||
menuGfx = { stats = { sheet = "stats", tiles = 17, firstTile = 0x31 } },
|
||||
picImage = function()
|
||||
return { getDimensions = function() return 136, 8 end }
|
||||
end,
|
||||
}, { __index = SummaryMenu })
|
||||
local sheet = menu:statsTiles()
|
||||
if T.check(sheet ~= nil, "menu_gfx.stats builds the tile sheet") then
|
||||
for id = 0x31, 0x41 do
|
||||
local q = sheet.quads[id]
|
||||
T.check(q ~= nil and q.x == (id - 0x31) * 8 and q.w == 8 and q.h == 8,
|
||||
("tile $%02x maps to sheet cell %d"):format(id, id - 0x31))
|
||||
end
|
||||
T.check(sheet.quads[0x42] == nil, "and exactly 17 tiles, no more")
|
||||
end
|
||||
|
||||
-- a cache built before menu_gfx.stats existed keeps the hand-drawn fallback
|
||||
local old = setmetatable({ menuGfx = {} }, { __index = SummaryMenu })
|
||||
T.check(old:statsTiles() == nil, "no menu_gfx.stats falls back, not crashes")
|
||||
love.graphics.newQuad = realNewQuad
|
||||
|
||||
T.finish("gen2 stats tiles and page palettes (#1558)")
|
||||
@@ -93,7 +93,7 @@ do
|
||||
local money = indexOf(events, "money")
|
||||
T.check(ret and win and money, "all three win rows are queued: " ..
|
||||
kinds(events))
|
||||
T.check(defeated < ret, "BattleText_EnemyWasDefeated comes first")
|
||||
T.check(defeated < ret, "the last faint event precedes the pic's return")
|
||||
T.check(ret < win, "the frontpic slides back in before the line")
|
||||
T.check(win < money, "and PrintWinLossText runs before the payout")
|
||||
T.eq(winEvent.text, "Th-Thank you!", "the struct's win text is printed")
|
||||
@@ -153,12 +153,17 @@ do
|
||||
T.eq(win, "Th-Thank you!", "the struct's win text is decoded")
|
||||
T.eq(loss, "...Too weak...", "and its loss text with it")
|
||||
|
||||
world.vm.winLossArmed = true
|
||||
world.vm.winTextOverride = "3:4200"
|
||||
win = World.trainerWinLossText(world)
|
||||
win, loss = World.trainerWinLossText(world)
|
||||
T.eq(win, "Scripted win.", "`winlosstext` overwrites the pointer")
|
||||
-- winlosstext writes BOTH pointers; its 0 loss argument destroyed the
|
||||
-- struct's value (engine/overworld/scripting.asm:651)
|
||||
T.eq(loss, nil, "and a 0 loss argument zeroes the loss pointer with it")
|
||||
|
||||
world.vm.trainerObject = nil
|
||||
world.vm.winTextOverride = nil
|
||||
world.vm.winLossArmed = nil
|
||||
win, loss = World.trainerWinLossText(world)
|
||||
T.eq(win, nil, "a battle with no trainer object has no line")
|
||||
T.eq(loss, nil, "on either side")
|
||||
|
||||
@@ -35,13 +35,15 @@ end
|
||||
|
||||
local fakeGame = { data = { text = text }, save = { flags = {} } }
|
||||
|
||||
local armed
|
||||
local armed, armedSound
|
||||
local fakeOw = {
|
||||
engageTrainer = function(_, _, _, endBattleText) armed = endBattleText end,
|
||||
engageTrainer = function(_, _, _, endBattleText, _, endBattleSound)
|
||||
armed, armedSound = endBattleText, endBattleSound
|
||||
end,
|
||||
}
|
||||
|
||||
local function armedFor(mapId, textId, victoryKey)
|
||||
armed = nil
|
||||
armed, armedSound = nil, nil
|
||||
gyms[mapId].talk[textId](fakeGame, fakeOw, { id = "npc#1" }, function() end)
|
||||
local labels = victories[victoryKey].dialogue
|
||||
local want = {}
|
||||
@@ -63,6 +65,10 @@ local leaders = {
|
||||
for _, entry in ipairs(leaders) do
|
||||
local got, want = armedFor(entry[1], entry[2], entry[3])
|
||||
T.eq(got, want, entry[3] .. " arms its badge line for the battle screen")
|
||||
-- the dialogue's sound command rides the armed line onto the battle
|
||||
-- screen too (sound_get_item_1 / sound_get_key_item) (#1606)
|
||||
T.eq(armedSound, victories[entry[3]].badgeSound,
|
||||
entry[3] .. " arms its badge jingle beside the line")
|
||||
end
|
||||
|
||||
-- Brock's armed label is one text chain of two text_far pages
|
||||
@@ -137,3 +143,5 @@ T.check(afterBattleScreen:find(text["_CeruleanGymMistyCascadeBadgeInfoText"],
|
||||
1, true) ~= nil,
|
||||
"the TM hand-over still runs on the map")
|
||||
T.check(rewardGame.save.inventory.CASCADEBADGE == 1, "the badge is still given")
|
||||
|
||||
T.finish("gym end battle text (#1606)")
|
||||
|
||||
@@ -75,6 +75,25 @@ T.eq(title.menuOpen, true, "the title art stays down until the blink ends")
|
||||
run(back, 60)
|
||||
T.eq(title.menuOpen, false, "and comes back once the blink is over")
|
||||
T.eq(game.stack:top(), title, "leaving the menu lands back on the title")
|
||||
-- main_menu.asm:70 jumps back to DisplayTitleScreen: the whole cinematic reruns
|
||||
T.eq(title.phase, "drop", "cancel reruns the boot cinematic from the logo drop")
|
||||
|
||||
-- a stranded menuOpen (an onSelect that handed control straight back) may
|
||||
-- not leave a blank white title behind
|
||||
title.menuOpen = true
|
||||
title:update(1 / 60)
|
||||
T.eq(title.menuOpen, false, "a stranded menuOpen clears on the next update")
|
||||
|
||||
-- .finishedWaiting: PlayCry then WaitForSoundToFinish before the white-out
|
||||
-- (engine/movie/title.asm:241-243)
|
||||
while title.phase ~= "loop" do title:updateSequence() end
|
||||
game.input.wasPressed = function(_, b) return b == "start" end
|
||||
title:update(1 / 60)
|
||||
game.input.wasPressed = function() return false end
|
||||
T.eq(title.phase, "exitCry", "START waits out the cry before the white-out")
|
||||
T.eq(game.stack:top(), title, "no flash is pushed on the cry frame")
|
||||
run(title, 10)
|
||||
T.check(game.stack:top() ~= title, "the flash follows once the cry is done")
|
||||
|
||||
-- ------------------------------------------- #1511: the intro NAME box
|
||||
|
||||
@@ -144,11 +163,20 @@ local function pump(frames)
|
||||
end
|
||||
end
|
||||
pump(400)
|
||||
T.check(getmetatable(fgame.stack:top()) == Menu, "the preset list opens")
|
||||
-- _IntroducePlayerText ends in `prompt` (text_2.asm:1730): arrowed A wait
|
||||
T.check(getmetatable(fgame.stack:top()) ~= Menu,
|
||||
"the question box waits for A before the name list")
|
||||
T.check(fgame.stack:top() == flow.holdBox and flow.holdBox.done,
|
||||
"the typed-out question box is on top, waiting for the press")
|
||||
fgame.input.wasPressed = function(_, b) return b == "a" end
|
||||
fgame.stack:top():update(1 / 60)
|
||||
fgame.input.wasPressed = function() return false end
|
||||
pump(400)
|
||||
T.check(getmetatable(fgame.stack:top()) == Menu, "A opens the preset list")
|
||||
T.eq(fgame.stack.states[2], flow.holdBox,
|
||||
"IntroducePlayerText's box is still on the stack under the name list")
|
||||
T.check(flow.holdBox.done and flow.holdBox.stay,
|
||||
"it is a `stay` box: text_end returns from PrintText without a wait")
|
||||
T.check(flow.holdBox.stayShown == true,
|
||||
"prompt-then-hold: the box stays up after the press (home/text.asm:434)")
|
||||
|
||||
local preset = fgame.stack:top()
|
||||
preset.index = 2
|
||||
@@ -157,7 +185,9 @@ preset:update(1 / 60)
|
||||
fgame.input.wasPressed = function() return false end
|
||||
T.eq(fgame.save.player.name, "RED", "picking a preset names the player")
|
||||
T.eq(flow.holdBox, nil, "and takes the question box down with the list")
|
||||
run(fgame.stack:top(), 6 * 3)
|
||||
-- 13-frame ClearScreenArea / DelayFrames beat, then six tiles of slide
|
||||
-- (oak_speech2.asm:69-78)
|
||||
run(fgame.stack:top(), 13 + 6 * 3)
|
||||
T.eq(flow.picSlide, 0, "OakSpeechSlidePicLeft puts the pic back")
|
||||
|
||||
T.finish("intro_title_naming_bug1510_1511")
|
||||
|
||||
@@ -87,9 +87,12 @@ T.eq(box.text, fakeGame.data.text._AccessedHoFPCText,
|
||||
box.onDone()
|
||||
T.same(opened, { "LeaguePC" }, "the Hall of Fame roster screen opens")
|
||||
|
||||
-- without the Pokedex the league row still shows (bills_pc.asm checks
|
||||
-- wNumHoFTeams before EVENT_GOT_POKEDEX)
|
||||
-- without the Pokedex, .noOaksPC2 skips Oak's PC and the league row alike
|
||||
-- (bills_pc.asm:48-49, :68-72); only the box height ignores it (:5-7)
|
||||
fakeGame.save.flags.EVENT_GOT_POKEDEX = nil
|
||||
local noDex = labels()
|
||||
T.check(indexOf(noDex, "<PK><MN>LEAGUE"),
|
||||
"the league row does not depend on EVENT_GOT_POKEDEX")
|
||||
T.eq(indexOf(noDex, "<PK><MN>LEAGUE"), nil,
|
||||
"no dex, no PKMN LEAGUE row, HoF teams or not")
|
||||
T.eq(indexOf(noDex, "PROF.OAK's PC"), nil, "and no PROF.OAK's PC either")
|
||||
|
||||
T.finish("pc league row (#1566)")
|
||||
|
||||
@@ -159,6 +159,47 @@ do
|
||||
end
|
||||
end
|
||||
|
||||
-- .useRareCandy: TryEvolvingMon runs over the party list
|
||||
-- (item_effects.asm:1392-1418) (#1594)
|
||||
do
|
||||
local evolveCalls = {}
|
||||
package.loaded["src.pokemon.Evolution"] = {
|
||||
pendingFor = function() return "FIXMON_B", { method = "LEVEL" } end,
|
||||
evolve = function(_, _, to, onDone, via)
|
||||
evolveCalls[#evolveCalls + 1] = { to = to, onDone = onDone, via = via }
|
||||
end,
|
||||
}
|
||||
package.loaded["src.battle.BattleState"] = {
|
||||
StatBox = { new = function(_, _, cb) return { statBox = true, cb = cb } end },
|
||||
}
|
||||
local game = freshGame(3)
|
||||
local list = useFromBag(game, nil, "RARE_CANDY")
|
||||
if check(list ~= nil, "the bag reached the picker (evolution case)") then
|
||||
local box = game.stack:top()
|
||||
check(isBox(box), "the level line prints first")
|
||||
game.stack:pop() -- a real TextBox pops itself before onDone
|
||||
box.done()
|
||||
local stat = game.stack:top()
|
||||
if check(stat and stat.statBox, "then the stat window") then
|
||||
game.stack:pop() -- as does the stat window before its callback
|
||||
stat.cb()
|
||||
eq(#evolveCalls, 1, "the pending evolution starts")
|
||||
check(inStack(game.stack, isPicker),
|
||||
"with the party picker STILL up: the evolution prints over it, "
|
||||
.. "not over the bag list (#1594)")
|
||||
check(type(evolveCalls[1].onDone) == "function",
|
||||
"closePicker rides the evolution's completion callback")
|
||||
evolveCalls[1].onDone()
|
||||
check(not inStack(game.stack, isPicker),
|
||||
"and the picker comes down once the evolution flow completes")
|
||||
check(inStack(game.stack, function(s) return s == list end),
|
||||
"while the bag list survives (#796)")
|
||||
end
|
||||
end
|
||||
package.loaded["src.pokemon.Evolution"] = nil
|
||||
package.loaded["src.battle.BattleState"] = nil
|
||||
end
|
||||
|
||||
-- The last candy: the row goes away (RemoveUsedItem empties the slot) and the
|
||||
-- cursor clamps to a real row -- but the list itself still must not close.
|
||||
do
|
||||
|
||||
@@ -35,6 +35,10 @@ end
|
||||
T.check(save ~= nil, "the start menu lists SAVE")
|
||||
|
||||
save.onSelect()
|
||||
-- PrintSaveScreenText ends `ld c, 30 / jp DelayFrames`: the bare panel
|
||||
-- holds 30 frames before the prompt (main_menu.asm:404-405)
|
||||
T.eq(#game.stack.states, 1, "the panel shows alone first")
|
||||
for _ = 1, 30 do game.stack:top().update() end
|
||||
T.eq(#game.stack.states, 2, "the panel and the prompt are two separate states")
|
||||
local panel, prompt = game.stack.states[1], game.stack.states[2]
|
||||
T.check(type(panel.draw) == "function" and not panel.isTextBox,
|
||||
@@ -44,9 +48,11 @@ T.eq(#prompt.pages, 1, "the prompt is one page: no \\f-merged info panel")
|
||||
T.check(prompt.pages[1][1]:find("Would you like to"),
|
||||
"the prompt page is WouldYouLikeToSaveText")
|
||||
|
||||
-- save.asm:187 hlcoord 0, 7
|
||||
-- save.asm:188 hlcoord 0, 7
|
||||
T.eq(prompt.choiceBox.tx, 0, "the save Yes/No box sits at column 0 (left)")
|
||||
T.eq(prompt.choiceBox.ty, 7, "the save Yes/No box sits at row 7")
|
||||
T.eq(prompt.choiceBox, require("src.ui.Theme").saveBox,
|
||||
"the geometry routes through Theme so field.theme can restyle it")
|
||||
local choice = ChoiceBox.new(game, function() end, { box = prompt.choiceBox })
|
||||
T.eq(choice.tx, 0, "ChoiceBox honours the save-specific left placement")
|
||||
|
||||
|
||||
@@ -80,10 +80,11 @@ do
|
||||
battle:performMove(battle.player, battle.enemy, slot)
|
||||
T.check(indexOf(animRows(battle), "SHRINKING_SQUARE_ANIM") == nil,
|
||||
"a locked-in Thrash queues no setup animation")
|
||||
-- .ThrashingAboutCheck falls into PlayerCalcMoveDamage, the same
|
||||
-- animation pipeline every other move uses (core.asm:3540) (#1577)
|
||||
T.check(indexOf(animRows(battle), "FIX_THRASH") ~= nil,
|
||||
"but the move's own animation still plays on a continuation turn")
|
||||
-- .ThrashingAboutCheck (core.asm:3534-3535, enemy mirror :5909-5910) (#1577)
|
||||
T.check(indexOf(animRows(battle), "THRASH") ~= nil,
|
||||
"a continuation turn animates THRASH, not the locked move's own id")
|
||||
T.check(indexOf(animRows(battle), "FIX_THRASH") == nil,
|
||||
"so the locked move's own animation does not play")
|
||||
T.check(hasText(battle, "thrashing about"),
|
||||
"ThrashingAboutText prints in place of the used-move line")
|
||||
T.check(not hasText(battle, "used FIX THRASH"),
|
||||
@@ -128,4 +129,20 @@ do
|
||||
"and the user is locked into Thrash next turn")
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- JumpMoveEffect (core.asm:3129-3133) before MoveHitTest INVULNERABLE (:3150) (#1565)
|
||||
do
|
||||
local battle = newBattle()
|
||||
battle.queue, battle.nextInsert = {}, 0
|
||||
battle.enemy.invulnerable = true
|
||||
battle:performMove(battle.player, battle.enemy, { id = "FIX_THRASH", pp = 20 })
|
||||
T.check(indexOf(animRows(battle), "SHRINKING_SQUARE_ANIM") ~= nil,
|
||||
"the setup animation plays against a mid-Fly/Dig target")
|
||||
T.eq(battle.player.thrashTurns, 2,
|
||||
"the 2-3 roll commits against a mid-Fly/Dig target")
|
||||
T.check(battle:menuLockedAction(battle.player) ~= nil,
|
||||
"and the user is locked into Thrash next turn")
|
||||
T.check(hasText(battle, "attack missed"), "while the attack itself misses")
|
||||
end
|
||||
|
||||
T.finish("thrash setup animation (#1532)")
|
||||
|
||||
@@ -36,5 +36,10 @@ T.eq(boostedText(function(mon, save)
|
||||
mon.traded = true
|
||||
mon.otId = save.player.id
|
||||
end), false, "and a mon traded back to its original trainer loses it (#1488)")
|
||||
T.eq(boostedText(function(mon)
|
||||
-- repairTradedOtIds leaves traded mons with otId nil (#1265, #1461)
|
||||
mon.traded = true
|
||||
mon.otId = nil
|
||||
end), true, "a traded mon with no stored OTID keeps the boost (#1488)")
|
||||
|
||||
T.finish("traded exp boost is an OTID comparison (#1488)")
|
||||
|
||||
@@ -149,9 +149,11 @@ do
|
||||
local events = battle:takeTurn({ kind = "move", move = "ROAR" })
|
||||
check(saidSomethingLike(events, "fled in fear!"),
|
||||
"FledInFearText: the wild mon is blown away")
|
||||
check(not saidSomethingLike(events, "used TACKLE!"),
|
||||
"and the mon that left never takes its half of the turn")
|
||||
eq(player.hp, hpBefore, "so nothing came back the other way")
|
||||
-- EFFECT_FORCE_SWITCH is priority 0, below BASE_PRIORITY
|
||||
-- (data/moves/effects_priorities.asm:5): Roar goes last (#1475)
|
||||
check(saidSomethingLike(events, "used TACKLE!"),
|
||||
"so the wild mon takes its half of the turn first, Speed regardless")
|
||||
check(player.hp < hpBefore, "and its hit landed before the blow-away")
|
||||
eq(battle.over, true, "the battle is over")
|
||||
eq(battle.outcome, "fled", "as the cart's DRAW")
|
||||
end
|
||||
|
||||
@@ -1307,7 +1307,7 @@ do
|
||||
end
|
||||
|
||||
-- ---- GetMovePriority's Vital Throw carve-out (#1475) ----------------------
|
||||
-- engine/battle/core.asm:786
|
||||
-- engine/battle/core.asm:787-789
|
||||
do
|
||||
local screen = newScreen()
|
||||
check(runToMenu(screen), "reached the menu")
|
||||
@@ -1323,11 +1323,20 @@ do
|
||||
eq(battle:movePriority("SWIFT"), 0,
|
||||
"while SWIFT, which shares its effect, keeps BASE_PRIORITY")
|
||||
eq(battle:movePriority("QUICK_ATTACK"), 1, "and the table still reads")
|
||||
moves.ROAR_FIX = { id = "ROAR_FIX", name = "ROAR", power = 0,
|
||||
type = "NORMAL", accuracy = 100, pp = 20, effect = "EFFECT_FORCE_SWITCH" }
|
||||
-- MoveEffectPriorities: EFFECT_FORCE_SWITCH is 0, below BASE_PRIORITY
|
||||
-- (data/moves/effects_priorities.asm:5)
|
||||
eq(battle:movePriority("ROAR_FIX"), -1,
|
||||
"Whirlwind and Roar sit below BASE_PRIORITY")
|
||||
eq(battle:orderOf("VITAL_THROW", "ROAR_FIX"), "player",
|
||||
"VITAL_THROW ties a force-switch move (0 vs 0), so Speed decides")
|
||||
eq(battle:orderOf("TACKLE", "TACKLE"), "player",
|
||||
"the faster mon leads on equal priority")
|
||||
eq(battle:orderOf("VITAL_THROW", "TACKLE"), "enemy",
|
||||
"but VITAL_THROW loses to a normal move whatever the Speed")
|
||||
moves.VITAL_THROW, moves.SWIFT, moves.QUICK_ATTACK = nil, nil, nil
|
||||
moves.ROAR_FIX = nil
|
||||
end
|
||||
|
||||
-- ---- a send-out snapshots HP at send time (#1514) -------------------------
|
||||
@@ -1352,6 +1361,29 @@ do
|
||||
"and the HUD opens on the snapshot, not on the post-hit value")
|
||||
end
|
||||
|
||||
-- ---- the send-out snapshots level and exp the same way (#1514) ------------
|
||||
-- SendOutPlayerMon reloads wBattleMon* from the party slot (core.asm:3796-3838)
|
||||
do
|
||||
local lead = Mon.new(DATA, "CYNDAQUIL", 10, { dvs = perfect })
|
||||
local bench = Mon.new(DATA, "TOTODILE", 10, { dvs = perfect })
|
||||
local screen, battle = newScreen({ player = lead, party = { lead, bench } })
|
||||
check(runToMenu(screen), "reached the menu")
|
||||
battle:takeEvents()
|
||||
check(battle:switch(2), "the bench mon comes in")
|
||||
local send = battle:takeEvents()[1]
|
||||
eq(send.level, bench.level, "the send carries a level snapshot")
|
||||
eq(send.experience, bench.experience, "and an experience snapshot")
|
||||
-- awardExperience mutates the live table before the UI dequeues the send
|
||||
bench.level = bench.level + 3
|
||||
bench.experience = (bench.experience or 0) + 5000
|
||||
screen:push(send)
|
||||
screen:advanceQueue()
|
||||
eq(screen.shownLevel, send.level,
|
||||
"the HUD opens on the send-time level, not the post-award one")
|
||||
eq(screen.shownExp, screen:expPixels(bench, send.level, send.experience),
|
||||
"and the exp bar fills from the send-time experience")
|
||||
end
|
||||
|
||||
-- ---- LearnMove finishes before the queued send-out (#1516) ----------------
|
||||
-- LearnMove inside GiveExperiencePoints (engine/battle/core.asm:1959-2010)
|
||||
do
|
||||
@@ -1404,8 +1436,15 @@ do
|
||||
local drawn = table.concat(boxes, " ")
|
||||
check(drawn:find("0,8,11,5", 1, true) ~= nil, "the TYPE/PP box is drawn")
|
||||
check(drawn:find("4,12,16,6", 1, true) ~= nil, "over the narrow list box")
|
||||
check(drawn:find("0,12,20,6", 1, true) == nil,
|
||||
"and the full-width message box is not")
|
||||
-- SafeLoadTempTilemapToTilemap keeps the full battle textbox under the
|
||||
-- move list (core.asm:4689); Textbox then MoveInfoBox over it (:5084, :5157).
|
||||
check(drawn:find("0,12,20,6", 1, true) ~= nil,
|
||||
"over the restored full-width message box")
|
||||
local base = drawn:find("0,12,20,6", 1, true)
|
||||
local list = drawn:find("4,12,16,6", 1, true)
|
||||
local info = drawn:find("0,8,11,5", 1, true)
|
||||
check(base < list and list < info,
|
||||
"painted base box, then list box, then info box")
|
||||
local text = table.concat(prints, " ")
|
||||
check(text:find("TACKLE@6,13", 1, true) ~= nil, "names sit at column 6")
|
||||
check(text:find("cursor@5,13", 1, true) ~= nil, "with the cursor at 5")
|
||||
|
||||
@@ -137,6 +137,27 @@ do
|
||||
end
|
||||
eq(checktime(4), 1, "checktime NITE is TRUE inside the PALETTE_DAY room")
|
||||
eq(checktime(2), 0, "and checktime DAY is FALSE there")
|
||||
|
||||
-- the two out-of-World consumers read wTimeOfDay too (#1557)
|
||||
local Pokegear = require("src.ui.gen2.Pokegear")
|
||||
eq(Pokegear.timeOfDayIndex({ game = { world = world } }), 2,
|
||||
"the radio's program pick answers NITE, not the pin (pokegear.asm:1456)")
|
||||
local Specials = require("src.script.gen2.Specials")
|
||||
local buffer
|
||||
local pvm = { curPhoneCaller = 1,
|
||||
setStringBuffer = function(_, name) buffer = name end,
|
||||
specials = { world = {
|
||||
tod = world.tod, daytime = world.daytime,
|
||||
encounters = { grass = { PLAYERS_HOUSE_1F = { slots = {
|
||||
NITE = { { species = "NITEMON" }, { species = "NITEMON" },
|
||||
{ species = "NITEMON" }, { species = "NITEMON" } },
|
||||
DAY = { { species = "DAYMON" }, { species = "DAYMON" },
|
||||
{ species = "DAYMON" }, { species = "DAYMON" } },
|
||||
} } } },
|
||||
} } }
|
||||
Specials.ALL.RandomPhoneWildMon(pvm)
|
||||
eq(buffer, "NITEMON",
|
||||
"RandomPhoneWildMon reads the NITE column (wildmons.asm:861)")
|
||||
end
|
||||
|
||||
-- ---- the hour-window respawn is not eaten by a busy frame -------------------
|
||||
|
||||
+10
-3
@@ -116,9 +116,9 @@ do
|
||||
end
|
||||
|
||||
-- Every other givepoke in the game is the flag-FALSE form: no names, and the
|
||||
-- nickname prompt still runs.
|
||||
-- nickname prompt still runs (engine/pokemon/move_mon.asm:1753-1757).
|
||||
do
|
||||
local given = nil
|
||||
local given, asked = nil, false
|
||||
local plainVm = Vm.new({ generation = 2,
|
||||
["s:eevee"] = {
|
||||
{ op = "givepoke", species = 133, level = 20, item = 0, trainer = 0 },
|
||||
@@ -127,13 +127,20 @@ do
|
||||
}, {}, Events.new(), {
|
||||
givePoke = function(species, level, item, opts)
|
||||
given = { opts = opts }
|
||||
return nil
|
||||
return { species = "EEVEE" }
|
||||
end,
|
||||
showText = function(_, onDone) onDone() end,
|
||||
-- GiveANickname_YesNo (move_mon.asm:1753-1757): the prompt's yes/no
|
||||
yesorno = function(onChoose)
|
||||
asked = true
|
||||
onChoose(false)
|
||||
end,
|
||||
})
|
||||
plainVm:start("s:eevee")
|
||||
for _ = 1, 10 do plainVm:update() end
|
||||
check(given ~= nil and given.opts == nil,
|
||||
"the flag-FALSE form hands givePoke no names")
|
||||
check(asked, "and the nickname prompt still runs on it")
|
||||
end
|
||||
|
||||
-- Phone + verbosegiveitem (Elm directions / aide potion)
|
||||
|
||||
@@ -1709,14 +1709,21 @@ check(hw:mapSceneOf(3, 4) == nil,
|
||||
"a map with NO scene_var row answers nil, which the VM turns into $ff")
|
||||
check(hw:mapSceneOf(9, 9) == nil, "and an unresolvable pair is nil too")
|
||||
|
||||
-- hw.tod is the production read (the unpinned wTimeOfDay split, #1557);
|
||||
-- hw.daytime is the palette pin it must NOT follow
|
||||
hw.tod = "DAY"
|
||||
eq(hw:timeOfDayId(), 1, "DAY is wTimeOfDay 1")
|
||||
hw.daytime = "MORN"
|
||||
hw.tod = "MORN"
|
||||
eq(hw:timeOfDayId(), 0, "MORN is 0")
|
||||
hw.daytime = "NITE"
|
||||
hw.tod = "NITE"
|
||||
eq(hw:timeOfDayId(), 2, "NITE is 2")
|
||||
hw.tod = "NITE"
|
||||
hw.daytime = "DARK"
|
||||
eq(hw:timeOfDayId(), 3, "DARKNESS is 3")
|
||||
hw.daytime = "DAY"
|
||||
eq(hw:timeOfDayId(), 2, "a PALETTE_DARK pin does not leak into wTimeOfDay")
|
||||
hw.tod = nil
|
||||
hw.daytime = "DARK"
|
||||
eq(hw:timeOfDayId(), 3, "DARKNESS is 3 only on the tod-less fallback arm")
|
||||
hw.tod, hw.daytime = nil, "DAY"
|
||||
eq(hw:gsVersion(), 0, "checkver: a Gold save is 0")
|
||||
eq(hookWorld({ version = "silver" }):gsVersion(), 1, "and a Silver save is 1")
|
||||
|
||||
|
||||
@@ -978,14 +978,17 @@ do
|
||||
|
||||
game.stack:pop()
|
||||
battle.onFinish("win")
|
||||
for _ = 1, 12 do
|
||||
-- the "is evolving!" box types out and holds DelayFrames 50 before the
|
||||
-- movie (evos_moves.asm:120-134) (#1596), so drive frames to reach it
|
||||
game.input.wasPressed = function() return false end
|
||||
local evoTop
|
||||
for _ = 1, 900 do
|
||||
local t = game.stack:top()
|
||||
if not t or t.screenId == "EvolutionState" then break end
|
||||
game.stack:pop()
|
||||
if t.onDone then t.onDone() end
|
||||
if not t then break end
|
||||
if t.screenId == "EvolutionState" then evoTop = t break end
|
||||
if t.update then t:update(1 / 60) else break end
|
||||
end
|
||||
check(game.stack:top() and game.stack:top().screenId == "EvolutionState",
|
||||
"the win reaches the evolution screen")
|
||||
check(evoTop ~= nil, "the win reaches the evolution screen")
|
||||
end
|
||||
|
||||
do
|
||||
|
||||
Reference in New Issue
Block a user