mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-28 07:36:30 -04:00
319 lines
12 KiB
Lua
319 lines
12 KiB
Lua
-- ../pokecrystal/home/text.asm:322-345 PlaceBattlersName: <USER> / <TARGET>
|
|
-- print "Enemy " ahead of wEnemyMonNickname on the foe's side, in wild,
|
|
-- trainer and link battles alike. Lines that read the nickname RAM directly
|
|
-- (data/text/battle.asm:10-55, :162-167, :208-212, :240-246) print it bare.
|
|
--
|
|
-- luajit tests/engine/gen2_enemy_name_prefix.lua
|
|
--
|
|
-- ROM-free.
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local T = require("tests.harness")
|
|
love = love or require("tests.love_stub")
|
|
require("src.core.Logger").warn = function() end
|
|
|
|
local Battle = require("src.battle.gen2.Battle")
|
|
local BattleState = require("src.ui.gen2.BattleState")
|
|
local Mon = require("src.battle.gen2.Mon")
|
|
|
|
local TYPES = {
|
|
NORMAL = { id = "NORMAL", index = 0, category = "physical" },
|
|
GHOST = { id = "GHOST", index = 8, category = "physical" },
|
|
FIRE = { id = "FIRE", index = 20, category = "special" },
|
|
DARK = { id = "DARK", index = 27, category = "special" },
|
|
}
|
|
|
|
local MOVES = {
|
|
TACKLE = { id = "TACKLE", name = "TACKLE", power = 35, type = "NORMAL",
|
|
accuracy = 95, pp = 35, effect = "EFFECT_NORMAL_HIT" },
|
|
GROWL = { id = "GROWL", name = "GROWL", power = 0, type = "NORMAL",
|
|
accuracy = 100, pp = 40, effect = "EFFECT_ATTACK_DOWN" },
|
|
CONFUSE_RAY = { id = "CONFUSE_RAY", name = "CONFUSE RAY", power = 0,
|
|
type = "GHOST", accuracy = 100, pp = 10, effect = "EFFECT_CONFUSE" },
|
|
EMBER = { id = "EMBER", name = "EMBER", power = 40, type = "FIRE",
|
|
accuracy = 100, pp = 25, effect = "EFFECT_BURN_HIT", effectChance = 100 },
|
|
WRAP = { id = "WRAP", name = "WRAP", power = 15, type = "NORMAL",
|
|
accuracy = 85, pp = 20, effect = "EFFECT_TRAP_TARGET" },
|
|
THIEF = { id = "THIEF", name = "THIEF", power = 40, type = "DARK",
|
|
accuracy = 100, pp = 10, effect = "EFFECT_THIEF", effectChance = 100 },
|
|
STRUGGLE = { id = "STRUGGLE", name = "STRUGGLE", power = 50, type = "NORMAL",
|
|
accuracy = 100, pp = 1, effect = "EFFECT_RECOIL_HIT" },
|
|
}
|
|
|
|
local GROWTH = {
|
|
GROWTH_MEDIUM_FAST = { numerator = 1, denominator = 1, squared = 0,
|
|
linear = 0, constant = 0 },
|
|
}
|
|
|
|
local function species(id, index)
|
|
return {
|
|
id = id, index = index, name = id,
|
|
baseStats = { hp = 70, attack = 80, defense = 50, speed = 35,
|
|
specialAttack = 35, specialDefense = 35 },
|
|
types = { "NORMAL", "NORMAL" }, catchRate = 180, baseExp = 75,
|
|
growthRate = "GROWTH_MEDIUM_FAST", genderRatio = 63,
|
|
levelMoves = { { level = 1, move = "TACKLE" } }, evolutions = {},
|
|
}
|
|
end
|
|
|
|
local DATA = {
|
|
pokemon = {
|
|
growthRates = GROWTH,
|
|
MACHOP = species("MACHOP", 66),
|
|
PIDGEY = species("PIDGEY", 16),
|
|
GEODUDE = species("GEODUDE", 74),
|
|
},
|
|
moves = MOVES,
|
|
type_chart = { types = TYPES, matchups = {} },
|
|
items = {
|
|
METAL_COAT = { id = "METAL_COAT", name = "METAL COAT", pocket = "ITEM" },
|
|
BERRY = { id = "BERRY", name = "BERRY", pocket = "ITEM",
|
|
heldEffect = "HELD_BERRY", heldParameter = 10 },
|
|
},
|
|
}
|
|
|
|
local perfect = { attack = 15, defense = 15, speed = 15, special = 15 }
|
|
perfect.hp = Mon.hpDV(perfect)
|
|
|
|
local function zero() return 0 end
|
|
|
|
local function mon(id, level, moves)
|
|
local m = Mon.new(DATA, id, level, { dvs = perfect })
|
|
m.moves = {}
|
|
for i, move in ipairs(moves or { "TACKLE" }) do
|
|
m.moves[i] = { id = move, pp = 30, maxPp = 30 }
|
|
end
|
|
return m
|
|
end
|
|
|
|
local function texts(events)
|
|
local out = {}
|
|
for _, e in ipairs(events or {}) do
|
|
if type(e.text) == "string" then out[#out + 1] = e.text end
|
|
end
|
|
return out
|
|
end
|
|
|
|
local function has(events, text)
|
|
for _, t in ipairs(texts(events)) do if t == text then return true end end
|
|
return false
|
|
end
|
|
|
|
local function lacks(events, pattern)
|
|
for _, t in ipairs(texts(events)) do
|
|
if t:find(pattern, 1, true) then return false, t end
|
|
end
|
|
return true
|
|
end
|
|
|
|
local function wildBattle(playerMoves, wildMoves, wildId)
|
|
local player = mon("MACHOP", 15, playerMoves)
|
|
local wild = mon(wildId or "PIDGEY", 15, wildMoves)
|
|
local battle = Battle.new({ data = DATA, party = { player }, wild = wild,
|
|
random = zero })
|
|
return battle, player, wild
|
|
end
|
|
|
|
local function trainerBattle(playerMoves, foeMoves)
|
|
local player = mon("MACHOP", 15, playerMoves)
|
|
local foe = mon("GEODUDE", 15, foeMoves)
|
|
local battle = Battle.new({ data = DATA, party = { player },
|
|
trainer = { class = "YOUNGSTER", name = "JOEY", party = { foe } },
|
|
random = zero })
|
|
return battle, player, foe
|
|
end
|
|
|
|
-- ---- the helper itself ----------------------------------------------------
|
|
do
|
|
local battle, player, wild = wildBattle()
|
|
T.eq(battle:battlerName(wild), "Enemy PIDGEY", "foe side: Enemy + nickname")
|
|
T.eq(battle:battlerName(player), "MACHOP", "player side: bare nickname")
|
|
T.eq(battle:monName(wild), "PIDGEY", "monName stays raw")
|
|
local benched = mon("MACHOP", 5)
|
|
T.eq(battle:battlerName(benched), "MACHOP",
|
|
"a mon that is not the foe's active never gets the prefix")
|
|
end
|
|
|
|
-- ---- _ActorNameText + _UsedMove1Text (data/text/common_2.asm:738-746) ----
|
|
do
|
|
local battle, player, wild = wildBattle()
|
|
battle:useMove(player, wild, "TACKLE")
|
|
T.check(has(battle:takeEvents(), "MACHOP\nused TACKLE!"), "wild: player's move line is bare")
|
|
battle:useMove(wild, player, "TACKLE")
|
|
T.check(has(battle:takeEvents(), "Enemy PIDGEY\nused TACKLE!"), "wild: foe's move line is prefixed")
|
|
|
|
local tb, tp, foe = trainerBattle()
|
|
tb:useMove(foe, tp, "TACKLE")
|
|
T.check(has(tb:takeEvents(), "Enemy GEODUDE\nused TACKLE!"), "trainer: foe's move line is prefixed")
|
|
|
|
local lb, lp, link = trainerBattle()
|
|
lb.linkBattle = true
|
|
lb:useMove(link, lp, "TACKLE")
|
|
T.check(has(lb:takeEvents(), "Enemy GEODUDE\nused TACKLE!"),
|
|
"link: PlaceBattlersName has no wLinkMode check")
|
|
end
|
|
|
|
-- ---- stat changes: Text_BattleFoeEffectActivate (common_2.asm:688) --------
|
|
do
|
|
local battle, player, wild = wildBattle({ "GROWL" }, { "GROWL" })
|
|
battle:useMove(player, wild, "GROWL")
|
|
T.check(has(battle:takeEvents(), "Enemy PIDGEY's ATTACK fell!"), "foe's stat line")
|
|
battle.random = function(n) return 200 % math.max(1, n or 256) end
|
|
battle:useMove(wild, player, "GROWL")
|
|
T.check(has(battle:takeEvents(), "MACHOP's ATTACK fell!"), "player's stat line")
|
|
battle.random = zero
|
|
for _ = 1, 6 do battle:useMove(player, wild, "GROWL") end
|
|
T.check(has(battle:takeEvents(), "Enemy PIDGEY's ATTACK won't drop anymore!"),
|
|
"WontDropAnymoreText (battle.asm:740)")
|
|
end
|
|
|
|
-- ---- status: BecameConfusedText / WasBurnedText / HurtByBurnText ---------
|
|
do
|
|
local battle, player, wild = wildBattle({ "CONFUSE_RAY", "EMBER" }, { "EMBER" })
|
|
battle:useMove(player, wild, "CONFUSE_RAY")
|
|
T.check(has(battle:takeEvents(), "Enemy PIDGEY became confused!"), "BecameConfusedText (battle.asm:413)")
|
|
battle:useMove(player, wild, "EMBER")
|
|
T.check(has(battle:takeEvents(), "Enemy PIDGEY was burned!"), "WasBurnedText (battle.asm:717)")
|
|
battle:tickStatus(wild)
|
|
T.check(has(battle:takeEvents(), "Enemy PIDGEY is hurt by its burn!"), "HurtByBurnText (battle.asm:62)")
|
|
battle:useMove(wild, player, "EMBER")
|
|
battle:takeEvents()
|
|
battle:tickStatus(player)
|
|
T.check(has(battle:takeEvents(), "MACHOP is hurt by its burn!"), "player's residual line is bare")
|
|
T.eq(battle:canAct(wild, "TACKLE") ~= nil, true, "canAct runs")
|
|
T.check(has(battle:takeEvents(), "Enemy PIDGEY is confused!"), "IsConfusedText (battle.asm:398)")
|
|
end
|
|
|
|
-- ---- partial trap: WrappedByText / UsersHurtByStringBuffer1 --------------
|
|
do
|
|
local battle, player, wild = wildBattle({ "WRAP" })
|
|
battle:useMove(player, wild, "WRAP")
|
|
T.check(has(battle:takeEvents(), "Enemy PIDGEY was WRAPPED by MACHOP!"),
|
|
"WrappedByText: <TARGET> prefixed, <USER> bare (battle.asm:463)")
|
|
battle:tickWrap(wild)
|
|
T.check(has(battle:takeEvents(), "Enemy PIDGEY's hurt by WRAP!"), "BattleText_UsersHurtByStringBuffer1")
|
|
end
|
|
|
|
-- ---- held berry: RecoveredUsingText (battle.asm:289) ---------------------
|
|
do
|
|
local battle, _, wild = wildBattle()
|
|
wild.item = "BERRY"
|
|
wild.hp = 1
|
|
battle:tickHeldItem(wild)
|
|
local evs = battle:takeEvents()
|
|
local ok = false
|
|
for _, t in ipairs(texts(evs)) do
|
|
if t:find("^Enemy PIDGEY ") then ok = true end
|
|
end
|
|
T.check(ok, "the foe's berry line carries the prefix")
|
|
end
|
|
|
|
-- ---- Thief: StoleText (battle.asm:946) ------------------------------------
|
|
do
|
|
local battle, player, wild = wildBattle({ "THIEF" }, { "THIEF" })
|
|
wild.item = "METAL_COAT"
|
|
battle:useMove(player, wild, "THIEF")
|
|
T.check(has(battle:takeEvents(), "MACHOP\nstole METAL COAT\vfrom its foe!"), "player steals: bare")
|
|
player.item = "METAL_COAT"
|
|
wild.item = nil
|
|
battle:useMove(wild, player, "THIEF")
|
|
T.check(has(battle:takeEvents(), "Enemy PIDGEY\nstole METAL COAT\vfrom its foe!"), "foe steals: prefixed")
|
|
end
|
|
|
|
-- ---- faint: BattleText_EnemyMonFainted spells "Enemy @" itself -----------
|
|
do
|
|
local battle, player, wild = wildBattle()
|
|
wild.hp = 0
|
|
battle:announceEnemyFaint()
|
|
T.check(has(battle:takeEvents(), "Enemy PIDGEY fainted!"), "wild foe: Enemy X fainted! (battle.asm:162)")
|
|
local tb, _, foe = trainerBattle()
|
|
foe.hp = 0
|
|
tb:announceEnemyFaint()
|
|
T.check(has(tb:takeEvents(), "Enemy GEODUDE fainted!"), "trainer foe: the same line")
|
|
player.hp = 0
|
|
battle:announcePlayerFaint()
|
|
T.check(has(battle:takeEvents(), "MACHOP fainted!"), "BattleText_MonFainted is bare (battle.asm:208)")
|
|
end
|
|
|
|
-- ---- raw-nickname lines stay bare ----------------------------------------
|
|
do
|
|
T.eq(BattleState.battleStartText("PIDGEY", nil), "Wild PIDGEY appeared!",
|
|
"WildPokemonAppearedText (battle.asm:10)")
|
|
T.eq(Battle.sentOutText("JOEY", "GEODUDE"), "JOEY\nsent out\vGEODUDE!",
|
|
"BattleText_EnemySentOut (battle.asm:240)")
|
|
local battle = wildBattle()
|
|
battle:enemyFled()
|
|
local evs = battle:takeEvents()
|
|
T.check(has(evs, "Wild PIDGEY fled!"), "BattleText_WildFled (battle.asm:43)")
|
|
T.check(lacks(evs, "Enemy "), "and no Enemy anywhere in the fled turn")
|
|
|
|
local tb = trainerBattle()
|
|
tb.enemyParty[2] = mon("PIDGEY", 15)
|
|
tb:switchEnemy(2)
|
|
local sw = tb:takeEvents()
|
|
T.check(has(sw, "JOEY withdrew GEODUDE!"), "the withdraw line names the mon bare")
|
|
T.check(lacks(sw, "Enemy "), "and the send-out does too")
|
|
end
|
|
|
|
-- ---- mod payloads keep the raw name -------------------------------------
|
|
do
|
|
local battle, _, wild = wildBattle()
|
|
battle:useMove(wild, battle.player, "TACKLE")
|
|
for _, e in ipairs(battle:takeEvents()) do
|
|
if e.kind == "move" then
|
|
T.eq(e.side, "enemy", "the move event carries the side")
|
|
T.eq(e.mon, nil, "and no name field to drift")
|
|
end
|
|
end
|
|
T.eq(wild.name, "PIDGEY", "the record itself is untouched")
|
|
end
|
|
|
|
-- ---- the cross-gen facade renders the peer as Enemy ----------------------
|
|
do
|
|
local okF, F = pcall(require, "tests.engine._g3u_fixture")
|
|
local okL, L = pcall(require, "tests.support.g3u_loopback")
|
|
local okG, Gen2Facade = pcall(require, "src.ui.g3u.Gen2Facade")
|
|
local okTbl, Table = pcall(require, "src.battle.g3u.Table")
|
|
local okBS, BattleSession = pcall(require, "src.online.union.BattleSession")
|
|
if okF and okL and okG and okTbl and okBS then
|
|
local pokemon, moves = {}, {}
|
|
for n = 1, 251 do
|
|
pokemon[("MON%03d"):format(n)] = { dex = n, name = ("MON%03d"):format(n), types = { "NORMAL" } }
|
|
end
|
|
for id = 1, 251 do
|
|
moves[("MOVE%03d"):format(id)] = { index = id, name = ("MOVE%03d"):format(id) }
|
|
end
|
|
pokemon.growthRates = {}
|
|
local game = { data = { pokemon = pokemon, moves = moves },
|
|
save = { player = { name = "GOLD" } } }
|
|
local t2 = assert(Table.build(F.gen2(), 2))
|
|
local netA, netB = L.pair()
|
|
local go = { seed = 11, size = 6 }
|
|
local gens = { [0] = 2, [1] = 3 }
|
|
local names = { [0] = "GOLD", [1] = "MAY" }
|
|
local mine = { F.record(t2, 25, { 33 }, 50, { nickname = "SPARKY" }) }
|
|
local foe = { F.record(t2, 4, { 33 }) }
|
|
local me = BattleSession.new({ net = netA, seat = 0, go = go, gens = gens,
|
|
data = F.gen2(), records = mine, names = names })
|
|
local bot = BattleSession.new({ net = netB, seat = 1, go = go, gens = gens,
|
|
records = foe, names = names })
|
|
local facade = Gen2Facade.new(game, me, { names = { me = "GOLD", foe = "MAY" } })
|
|
for _ = 1, 4 do
|
|
me:update()
|
|
bot:update()
|
|
facade:translate(me:events())
|
|
end
|
|
T.check(facade.ready, "facade ready")
|
|
T.eq(facade:N({ side = 0, index = 1 }), "SPARKY", "facade: my mon is bare")
|
|
T.eq(facade:N({ side = 1, index = 1 }), "Enemy MON004", "facade: the peer's mon is Enemy")
|
|
T.eq(facade:nameOf(facade.enemy), "MON004", "facade: nameOf stays raw")
|
|
local msg = Gen2Facade.MSG.STRINGID_PKMNFLINCHED(facade, { atk = { side = 1, index = 1 } })
|
|
T.eq(msg, "Enemy MON004 flinched!", "facade: <USER> text on the peer")
|
|
else
|
|
print("[skip] g3u fixtures unavailable")
|
|
end
|
|
end
|
|
|
|
T.finish("gen2 enemy name prefix")
|