mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 16:31:05 +02:00
110 lines
4.4 KiB
Lua
110 lines
4.4 KiB
Lua
-- Parity test: losing your last POKéMON always blacks you out, even when
|
|
-- the battle was already decided in your favour.
|
|
--
|
|
-- A double faint -- the lead dying to residual damage on the same turn it
|
|
-- lands the KO -- used to leave BattleState:playerMonFainted at its
|
|
-- "the battle is decided" guard, so it returned with result = "win" still
|
|
-- set. OverworldState:afterBattle only revives the party and warps to the
|
|
-- heal point on "lose" (src/world/OverworldController.lua), so the player
|
|
-- was handed back to the overworld standing on the map with every POKéMON
|
|
-- at 0 HP. Nothing recovers from that: BattleState refuses to start an
|
|
-- encounter with no healthy party ("wild battle with no healthy party;
|
|
-- skipping"), so the save is bricked in place.
|
|
--
|
|
-- pokered cannot reach that state -- HandlePlayerMonFainted runs the
|
|
-- player-side check on its own account, so being out of useable POKéMON
|
|
-- blacks you out whatever happened to the enemy.
|
|
--
|
|
-- Found by the automated route driver: an attempt spent its whole length
|
|
-- "wiping" on VIRIDIAN_FOREST fourteen times without ever moving, because
|
|
-- the party was dead but the game had never blacked out.
|
|
-- Self-contained; run via `luajit tests/parity_double_faint.lua`.
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
if not _G.love then _G.love = require("tests.love_stub") end
|
|
local BattleState = require("src.battle.BattleState")
|
|
local S = require("tests.harness").suite("parity double faint")
|
|
local check, eq = S.check, S.eq
|
|
|
|
-- A stand-in carrying only what playerMonFainted touches. Deliberately not
|
|
-- a real battle: the point is the decision, and a full encounter would need
|
|
-- a loaded Data, an RNG and a queue to say nothing more than this does.
|
|
local function battleWith(partyHP, result)
|
|
local party = {}
|
|
for i, hp in ipairs(partyHP) do
|
|
party[i] = { species = "SQUIRTLE", hp = hp, stats = { hp = 20 } }
|
|
end
|
|
return {
|
|
kind = "wild",
|
|
result = result,
|
|
afterQueue = nil,
|
|
said = {},
|
|
-- the reserves-left path reads the "Use next POKéMON?" prompt off Data
|
|
data = { text = { _UseNextMonText = "Use next POKéMON?" } },
|
|
game = { save = { party = party, player = { name = "RED" } } },
|
|
sayNext = function(self, m) self.said[#self.said + 1] = m end,
|
|
say = function(self, m) self.said[#self.said + 1] = m end,
|
|
ui = function() end,
|
|
}
|
|
end
|
|
|
|
local function saidBlackout(b)
|
|
for _, m in ipairs(b.said) do
|
|
if tostring(m):find("blacked") then return true end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- The regression itself: a won battle whose last mon died with it.
|
|
do
|
|
local b = battleWith({ 0 }, "win")
|
|
BattleState.playerMonFainted(b)
|
|
eq(b.result, "lose", "a win with the last mon dead becomes a blackout")
|
|
eq(b.afterQueue, "finish", "the blackout ends the battle")
|
|
check(saidBlackout(b), "the blackout text still prints on a double faint")
|
|
end
|
|
|
|
-- Same for the other non-lose results, so no path can strand the party.
|
|
for _, result in ipairs({ "run", "caught" }) do
|
|
local b = battleWith({ 0 }, result)
|
|
BattleState.playerMonFainted(b)
|
|
eq(b.result, "lose", ("a %q result with no healthy party becomes a blackout")
|
|
:format(result))
|
|
end
|
|
|
|
-- An undecided battle keeps behaving exactly as before.
|
|
do
|
|
local b = battleWith({ 0 }, nil)
|
|
BattleState.playerMonFainted(b)
|
|
eq(b.result, "lose", "the ordinary last-mon faint still blacks out")
|
|
check(saidBlackout(b), "and still prints the blackout text")
|
|
end
|
|
|
|
-- The guard must not fire while something is still standing: a won battle
|
|
-- with a healthy reserve stays won, and the fainted-mon flow is untouched.
|
|
do
|
|
local b = battleWith({ 0, 15 }, "win")
|
|
BattleState.playerMonFainted(b)
|
|
eq(b.result, "win", "a win with a healthy reserve is still a win")
|
|
check(not saidBlackout(b), "and prints no blackout text")
|
|
end
|
|
|
|
-- Already lost: no second helping of the blackout text.
|
|
do
|
|
local b = battleWith({ 0 }, "lose")
|
|
BattleState.playerMonFainted(b)
|
|
eq(b.result, "lose", "an already-lost battle stays lost")
|
|
eq(#b.said, 0, "and does not re-announce the blackout")
|
|
end
|
|
|
|
-- A mid-battle faint with reserves left is the wild "Use next POKéMON?"
|
|
-- prompt (DoUseNextMonDialogue), not a blackout -- the path the guard
|
|
-- sits in front of, so prove it still runs.
|
|
do
|
|
local b = battleWith({ 0, 15 }, nil)
|
|
BattleState.playerMonFainted(b)
|
|
eq(b.result, nil, "a faint with reserves left does not decide the battle")
|
|
check(not saidBlackout(b), "and does not black out")
|
|
end
|
|
|
|
S.finish()
|