mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
102 lines
3.8 KiB
Lua
102 lines
3.8 KiB
Lua
-- A faint is announced once, and charged to happiness once.
|
|
--
|
|
-- luajit tests/gen2_faint_once_test.lua
|
|
--
|
|
-- Found by the Gold route bot (tests/drivers/gold_bot.lua), whose logs are full
|
|
-- of this:
|
|
--
|
|
-- TYPHLOSION fainted!
|
|
-- TYPHLOSION fainted!
|
|
-- TYPHLOSION fainted!
|
|
--
|
|
-- Battle:resolveFaints' player arm is the only one that returns WITHOUT
|
|
-- changing whose turn it is: it emits `choose-switch` and waits for the caller
|
|
-- to pick a replacement, so the caller calls back in with the same mon still at
|
|
-- 0 HP and the whole arm ran again. The repeated line is cosmetic; the bug
|
|
-- underneath it is not, because `faintHappiness` sat on the same path and was
|
|
-- charged once per re-entry. A single faint cost two or three times the
|
|
-- happiness the cart takes -- engine/battle/core.asm runs its happiness arm
|
|
-- once per faint.
|
|
--
|
|
-- The enemy arm cannot do this: it either ends the battle or switches in the
|
|
-- next mon, so it never re-enters holding a fainted enemy.
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local S = require("tests.harness").suite("gen2 faint once")
|
|
local check, eq = S.check, S.eq
|
|
|
|
local Battle = require("src.battle.gen2.Battle")
|
|
|
|
-- resolveFaints' player arm reads self.player/self.party/self.enemy and calls
|
|
-- out to emit, faintHappiness and firstHealthy, so it runs against a stub.
|
|
local function stub(party)
|
|
local st = {
|
|
said = {},
|
|
happiness = 0,
|
|
}
|
|
st.player = party[1]
|
|
st.party = party
|
|
st.enemy = { hp = 50, level = 50 }
|
|
st.emit = function(_, ev) st.said[#st.said + 1] = ev.kind end
|
|
st.faintHappiness = function() st.happiness = st.happiness + 1 end
|
|
st.monName = function(_, m) return m.name or "MON" end
|
|
return setmetatable(st, { __index = Battle })
|
|
end
|
|
|
|
local function countKind(said, kind)
|
|
local n = 0
|
|
for _, k in ipairs(said) do if k == kind then n = n + 1 end end
|
|
return n
|
|
end
|
|
|
|
local function countFaints(said)
|
|
return countKind(said, "faint")
|
|
end
|
|
|
|
-- The reported case: a fainted lead with a healthy mon behind it. The caller
|
|
-- drives the switch, so it calls resolveFaints repeatedly in the meantime.
|
|
do
|
|
local b = stub({
|
|
{ hp = 0, level = 88, name = "TYPHLOSION" },
|
|
{ hp = 40, level = 30, name = "SLOWPOKE" },
|
|
})
|
|
eq(b:resolveFaints(), false, "the battle is not over, there is a mon left")
|
|
eq(b:resolveFaints(), false, "and calling again while the switch is pending")
|
|
eq(b:resolveFaints(), false, "and again")
|
|
eq(countFaints(b.said), 1, "the faint is announced exactly once")
|
|
eq(b.happiness, 1, "and happiness is charged exactly once")
|
|
-- HandlePlayerMonFaint runs ForcePlayerMonChoice once (core.asm:2543) and
|
|
-- the turn loop never comes back for a second answer. Three prompts in the
|
|
-- queue reopened the party list on top of the pick that had already been
|
|
-- made, which is why the switch looked like it took several attempts.
|
|
eq(countKind(b.said, "choose-switch"), 1, "and the party list is asked for "
|
|
.. "exactly once")
|
|
end
|
|
|
|
-- The replacement announces its own faint normally: the guard is keyed on the
|
|
-- mon, not on the battle.
|
|
do
|
|
local slowpoke = { hp = 40, level = 30, name = "SLOWPOKE" }
|
|
local b = stub({ { hp = 0, level = 88, name = "TYPHLOSION" }, slowpoke })
|
|
b:resolveFaints()
|
|
b:resolveFaints()
|
|
-- Battle:switch is what clears the guards; do what it does.
|
|
b.faintAnnounced = nil
|
|
b.pendingSwitch = nil
|
|
b.player = slowpoke
|
|
slowpoke.hp = 0
|
|
eq(b:resolveFaints(), true, "no healthy mon left ends the battle")
|
|
eq(countFaints(b.said), 2, "the second mon's faint is announced too")
|
|
eq(b.happiness, 2, "and charged")
|
|
end
|
|
|
|
-- A wipe still reports the wipe, once.
|
|
do
|
|
local b = stub({ { hp = 0, level = 88, name = "TYPHLOSION" } })
|
|
eq(b:resolveFaints(), true, "a lone fainted mon ends the battle")
|
|
eq(b.outcome, "lose", "as a loss")
|
|
eq(countFaints(b.said), 1, "announced once")
|
|
end
|
|
|
|
S.finish()
|