mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 00:10:56 +02:00
G2 support
This commit is contained in:
@@ -0,0 +1,273 @@
|
||||
-- The no-escape web around a Gen 2 battle: Mean Look's CANT_RUN pin, the
|
||||
-- Bind class wrap count, the FORCESHINY / TRAP battle types, and Roar and
|
||||
-- Whirlwind's force switch -- including a wild mon's own Roar ending the
|
||||
-- battle as a draw so a roamer banks its HP.
|
||||
--
|
||||
-- luajit tests/gen2_trap_escape_test.lua
|
||||
--
|
||||
-- ROM-free. pokegold: core.asm TryToRunAwayFromBattle / TryEnemyFlee /
|
||||
-- TryPlayerSwitch, effect_commands.asm BattleCommand_ArenaTrap /
|
||||
-- BattleCommand_ForceSwitch, and BattleEnd_HandleRoamMons' non-WIN arm
|
||||
-- (src/core/gen2/Roamers.endBattle).
|
||||
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local S = require("tests.harness").suite("gen2 trap escape")
|
||||
local check, eq = S.check, S.eq
|
||||
|
||||
local Battle = require("src.battle.gen2.Battle")
|
||||
local Mon = require("src.battle.gen2.Mon")
|
||||
local Roamers = require("src.core.gen2.Roamers")
|
||||
|
||||
-- ---------------------------------------------------------------- fixtures
|
||||
|
||||
local TYPES = {
|
||||
NORMAL = { id = "NORMAL", index = 0, category = "physical" },
|
||||
ELECTRIC = { id = "ELECTRIC", index = 23, category = "special" },
|
||||
}
|
||||
|
||||
local MOVES = {
|
||||
TACKLE = { id = "TACKLE", name = "TACKLE", power = 35, type = "NORMAL",
|
||||
accuracy = 95, pp = 35, effect = "EFFECT_NORMAL_HIT" },
|
||||
MEAN_LOOK = { id = "MEAN_LOOK", name = "MEAN LOOK", power = 0,
|
||||
type = "NORMAL", accuracy = 100, pp = 5, effect = "EFFECT_MEAN_LOOK" },
|
||||
WRAP = { id = "WRAP", name = "WRAP", power = 15, type = "NORMAL",
|
||||
accuracy = 85, pp = 20, effect = "EFFECT_TRAP_TARGET" },
|
||||
ROAR = { id = "ROAR", name = "ROAR", power = 0, type = "NORMAL",
|
||||
accuracy = 100, pp = 20, effect = "EFFECT_FORCE_SWITCH" },
|
||||
WHIRLWIND = { id = "WHIRLWIND", name = "WHIRLWIND", power = 0,
|
||||
type = "NORMAL", accuracy = 100, pp = 20,
|
||||
effect = "EFFECT_FORCE_SWITCH" },
|
||||
}
|
||||
|
||||
local GROWTH = {
|
||||
GROWTH_MEDIUM_FAST = { numerator = 1, denominator = 1, squared = 0,
|
||||
linear = 0, constant = 0 },
|
||||
}
|
||||
|
||||
local function species(id, index, speed)
|
||||
return {
|
||||
id = id, index = index, name = id,
|
||||
baseStats = { hp = 70, attack = 80, defense = 50, speed = speed or 35,
|
||||
specialAttack = 65, specialDefense = 65 },
|
||||
types = { "NORMAL", "NORMAL" }, catchRate = 45, baseExp = 100,
|
||||
growthRate = "GROWTH_MEDIUM_FAST", genderRatio = 127,
|
||||
levelMoves = { { level = 1, move = "TACKLE" } }, evolutions = {},
|
||||
}
|
||||
end
|
||||
|
||||
local POKEMON = {
|
||||
growthRates = GROWTH,
|
||||
MACHOP = species("MACHOP", 66, 35),
|
||||
PIDGEY = species("PIDGEY", 16, 56),
|
||||
-- The one species name that matters: Roamers.ALWAYS_FLEE keys off it.
|
||||
RAIKOU = species("RAIKOU", 243, 115),
|
||||
}
|
||||
|
||||
local DATA = {
|
||||
pokemon = POKEMON,
|
||||
moves = MOVES,
|
||||
type_chart = { types = TYPES, matchups = {} },
|
||||
items = {},
|
||||
}
|
||||
|
||||
local perfect = { attack = 15, defense = 15, speed = 15, special = 15 }
|
||||
perfect.hp = Mon.hpDV(perfect)
|
||||
|
||||
local function rolls(queue, fill)
|
||||
local at = 0
|
||||
return function(n)
|
||||
at = at + 1
|
||||
local value = queue[at]
|
||||
if value == nil then value = fill or 0 end
|
||||
return value % math.max(1, n or 1)
|
||||
end
|
||||
end
|
||||
|
||||
local function mon(id, level, moves)
|
||||
local built = Mon.new(DATA, id, level, { dvs = perfect })
|
||||
local list = {}
|
||||
for i, moveId in ipairs(moves or { "TACKLE" }) do
|
||||
list[i] = { id = moveId, pp = 20, maxPp = 20 }
|
||||
end
|
||||
built.moves = list
|
||||
return built
|
||||
end
|
||||
|
||||
local function findText(events, text)
|
||||
for _, event in ipairs(events or {}) do
|
||||
if (event.kind == "message" or event.kind == "run"
|
||||
or event.kind == "send") and event.text == text then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- ---- Mean Look pins a roamer ----------------------------------------------
|
||||
do
|
||||
local player = mon("MACHOP", 45, { "MEAN_LOOK", "TACKLE" })
|
||||
local raikou = mon("RAIKOU", 40)
|
||||
local battle = Battle.new({ data = DATA, party = { player }, wild = raikou,
|
||||
roaming = 1, random = rolls({}, 1) })
|
||||
|
||||
-- AlwaysFleeMons: with no pin the beast is gone before it can be fought.
|
||||
eq(battle:tryEnemyFlee(), true, "an unpinned RAIKOU always flees")
|
||||
battle.over, battle.outcome = false, nil
|
||||
|
||||
battle:useMove(player, raikou, "MEAN_LOOK")
|
||||
eq(player.volatile.trapsTarget, true,
|
||||
"ArenaTrap: CANT_RUN lives on the USER's side")
|
||||
check(findText(battle:takeEvents(), "RAIKOU can't escape now!"),
|
||||
"CantEscapeNowText")
|
||||
eq(battle:tryEnemyFlee(), false,
|
||||
"TryEnemyFlee reads the player's substatus and stays")
|
||||
|
||||
-- The pin dies with its user: a switch drops the volatile.
|
||||
battle:clearVolatile(player)
|
||||
eq(battle:tryEnemyFlee(), true, "with the user gone the beast flees again")
|
||||
end
|
||||
|
||||
-- ---- a wrap pins the flee and the run -------------------------------------
|
||||
do
|
||||
local player = mon("MACHOP", 45, { "WRAP" })
|
||||
local raikou = mon("RAIKOU", 40)
|
||||
local battle = Battle.new({ data = DATA, party = { player }, wild = raikou,
|
||||
roaming = 1, random = rolls({ 1, 0, 0, 1 }, 1) })
|
||||
battle:useMove(player, raikou, "WRAP")
|
||||
eq(raikou.volatile.wrapCount, 4, "the wrap count is live")
|
||||
eq(battle:tryEnemyFlee(), false, "wEnemyWrapCount pins the flee")
|
||||
|
||||
-- The mirrored gate: a WRAPPED PLAYER cannot run or switch voluntarily.
|
||||
local player2 = mon("MACHOP", 45)
|
||||
local bench = mon("PIDGEY", 40)
|
||||
local wild = mon("PIDGEY", 40, { "WRAP" })
|
||||
local battle2 = Battle.new({ data = DATA, party = { player2, bench },
|
||||
wild = wild, random = rolls({ 1, 0, 0, 1 }, 1) })
|
||||
battle2:useMove(wild, player2, "WRAP")
|
||||
eq(player2.volatile.wrapCount, 4, "the player's mon is wrapped")
|
||||
eq(battle2:tryRun(), false, "TryToRunAwayFromBattle refuses")
|
||||
check(findText(battle2:takeEvents(), "Can't escape!"),
|
||||
"with the cart's line")
|
||||
eq(battle2:switchLocked(), true,
|
||||
"TryPlayerSwitch's .check_trapped refuses the voluntary switch")
|
||||
end
|
||||
|
||||
-- ---- Mean Look on the player pins the run and the switch -------------------
|
||||
do
|
||||
local player = mon("MACHOP", 45)
|
||||
local bench = mon("PIDGEY", 40)
|
||||
local wild = mon("PIDGEY", 40, { "MEAN_LOOK" })
|
||||
local battle = Battle.new({ data = DATA, party = { player, bench },
|
||||
wild = wild, random = rolls({}, 1) })
|
||||
battle:useMove(wild, player, "MEAN_LOOK")
|
||||
eq(wild.volatile.trapsTarget, true, "the wild side pinned the player")
|
||||
eq(battle:tryRun(), false, "no running from a Mean Look")
|
||||
eq(battle:switchLocked(), true, "and no voluntary switch either")
|
||||
|
||||
-- Battle:switch (the faint path's entry too) breaks every trap on send.
|
||||
battle:switch(2)
|
||||
eq(battle:switchLocked(), false, "a send-out clears the pin")
|
||||
end
|
||||
|
||||
-- ---- FORCESHINY and TRAP forbid running -----------------------------------
|
||||
do
|
||||
for _, battleType in ipairs({ Battle.BATTLETYPE_FORCESHINY,
|
||||
Battle.BATTLETYPE_TRAP }) do
|
||||
local player = mon("MACHOP", 45)
|
||||
local wild = mon("PIDGEY", 30)
|
||||
local battle = Battle.new({ data = DATA, party = { player },
|
||||
wild = wild, battleType = battleType, random = rolls({}, 1) })
|
||||
eq(battle:tryRun(), false,
|
||||
"battle type " .. battleType .. " jumps straight to .cant_escape")
|
||||
check(findText(battle:takeEvents(), "Can't escape!"),
|
||||
"with the cart's line for type " .. battleType)
|
||||
eq(battle.over, false, "the battle goes on")
|
||||
|
||||
-- BattleCommand_ForceSwitch fails for the same two types.
|
||||
player.moves = { { id = "ROAR", pp = 20, maxPp = 20 } }
|
||||
battle:useMove(player, wild, "ROAR")
|
||||
eq(battle.over, false, "Roar cannot end a type-" .. battleType .. " battle")
|
||||
check(findText(battle:takeEvents(), "But it failed!"),
|
||||
"ForceSwitch's .fail for type " .. battleType)
|
||||
end
|
||||
end
|
||||
|
||||
-- ---- a wild mon's own Roar ends the battle --------------------------------
|
||||
do
|
||||
-- The wild PIDGEY is level 40 against a level 40 player: `cp` with the
|
||||
-- user's level at or above the target's succeeds outright.
|
||||
local save = {}
|
||||
Roamers.init(save)
|
||||
local player = mon("MACHOP", 40)
|
||||
local wild = mon("PIDGEY", 40, { "ROAR" })
|
||||
local battle = Battle.new({ data = DATA, party = { player }, wild = wild,
|
||||
random = rolls({}, 1) })
|
||||
local events = battle:takeTurn({ kind = "move", move = "TACKLE" })
|
||||
eq(battle.over, true, "the wild Roar ends the battle")
|
||||
eq(battle.outcome, "fled", "as the DRAW that banks a roamer's HP")
|
||||
check(findText(events, "MACHOP fled in fear!"),
|
||||
"FledInFearText names the mon sent away")
|
||||
|
||||
-- BattleEnd_HandleRoamMons' non-WIN arm: the outcome banks the HP.
|
||||
Roamers.endBattle(save, 1, battle.outcome, 120, nil, rolls({}, 1))
|
||||
eq(Roamers.slot(save, 1).hp, 120, "the roamer slot banked the HP")
|
||||
check(Roamers.slot(save, 1).species ~= nil, "and the beast is NOT retired")
|
||||
end
|
||||
|
||||
-- ---- the player's Roar on a wild mon --------------------------------------
|
||||
do
|
||||
local player = mon("MACHOP", 45, { "ROAR" })
|
||||
local wild = mon("PIDGEY", 30)
|
||||
local battle = Battle.new({ data = DATA, party = { player }, wild = wild,
|
||||
random = rolls({}, 1) })
|
||||
battle:useMove(player, wild, "ROAR")
|
||||
eq(battle.over, true, "a higher-level Roar sends the wild mon off")
|
||||
eq(battle.outcome, "fled", "as a draw, not a win")
|
||||
check(findText(battle:takeEvents(), "PIDGEY fled in fear!"),
|
||||
"FledInFearText")
|
||||
end
|
||||
|
||||
-- ---- trainer Roar drags a bench mon out -----------------------------------
|
||||
do
|
||||
local player = mon("MACHOP", 45)
|
||||
local bench = mon("PIDGEY", 40)
|
||||
local roarer = mon("PIDGEY", 40, { "ROAR" })
|
||||
local battle = Battle.new({ data = DATA, party = { player, bench },
|
||||
trainer = { class = "FALKNER_X", name = "TESTER", party = { roarer } },
|
||||
random = rolls({}, 1) })
|
||||
|
||||
-- The user must be moving SECOND (wEnemyGoesFirst): with the player first,
|
||||
-- the enemy's Roar drags the player's bench mon out.
|
||||
battle.firstMover = "player"
|
||||
battle:useMove(roarer, player, "ROAR")
|
||||
eq(battle.player, bench, "the bench PIDGEY was dragged out")
|
||||
eq(battle.participants[2], true, "and counts as a participant")
|
||||
check(findText(battle:takeEvents(), "PIDGEY was dragged out!"),
|
||||
"DraggedOutText")
|
||||
eq(battle.over, false, "a trainer battle goes on")
|
||||
|
||||
-- Moving first, the same Roar fails.
|
||||
local battle2 = Battle.new({ data = DATA,
|
||||
party = { mon("MACHOP", 45), mon("PIDGEY", 40) },
|
||||
trainer = { class = "FALKNER_X", name = "TESTER",
|
||||
party = { mon("PIDGEY", 40, { "ROAR" }) } },
|
||||
random = rolls({}, 1) })
|
||||
battle2.firstMover = "enemy"
|
||||
battle2:useMove(battle2.enemy, battle2.player, "ROAR")
|
||||
eq(battle2.player, battle2.party[1], "moving first, the Roar moves nobody")
|
||||
check(findText(battle2:takeEvents(), "But it failed!"),
|
||||
"ForceSwitch's `.switch_fail` needs the user to move second")
|
||||
|
||||
-- With nothing on the bench there is nobody to drag.
|
||||
local battle3 = Battle.new({ data = DATA, party = { mon("MACHOP", 45) },
|
||||
trainer = { class = "FALKNER_X", name = "TESTER",
|
||||
party = { mon("PIDGEY", 40, { "ROAR" }) } },
|
||||
random = rolls({}, 1) })
|
||||
battle3.firstMover = "player"
|
||||
battle3:useMove(battle3.enemy, battle3.player, "ROAR")
|
||||
check(findText(battle3:takeEvents(), "But it failed!"),
|
||||
"CheckPlayerHasMonToSwitchTo carries: no bench, no drag")
|
||||
end
|
||||
|
||||
S.finish()
|
||||
Reference in New Issue
Block a user