mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-22 13:36:14 +02:00
Merge pull request #1673 from colsonrice/fix/gen2-battle-text-matches-cart
This commit is contained in:
@@ -1254,10 +1254,15 @@ function Battle:dealDamage(attacker, defender, damage, opts)
|
||||
if opts.critical then
|
||||
self:emit({ kind = "message", text = "A critical hit!" })
|
||||
end
|
||||
-- SuperEffectiveText / NotVeryEffectiveText (data/text/battle.asm:603,608).
|
||||
-- The cart breaks both across the box's two lines and hyphenates "super-"
|
||||
-- to do it, and the not-very line ends on the single ellipsis glyph Gold's
|
||||
-- charmap carries at $75, not three periods.
|
||||
if opts.effectiveness and opts.effectiveness > 10 then
|
||||
self:emit({ kind = "message", text = "It's super effective!" })
|
||||
self:emit({ kind = "message", text = Strings("It's super-\neffective!") })
|
||||
elseif opts.effectiveness and opts.effectiveness < 10 then
|
||||
self:emit({ kind = "message", text = "It's not very effective..." })
|
||||
self:emit({ kind = "message",
|
||||
text = Strings("It's not very\neffective…") })
|
||||
end
|
||||
if endured then
|
||||
self:emit({ kind = "message",
|
||||
@@ -1418,7 +1423,9 @@ function Battle:useMove(attacker, defender, moveId)
|
||||
|
||||
if not (charging or rampaging or rolling or biding or called) then
|
||||
if move and (move.pp or 0) <= 0 then
|
||||
self:emit({ kind = "message", text = "No PP left for this move!" })
|
||||
-- BattleText_TheresNoPPLeftForThisMove (data/text/battle.asm:315).
|
||||
self:emit({ kind = "message",
|
||||
text = Strings("There's no PP left\nfor this move!") })
|
||||
return
|
||||
end
|
||||
if move then move.pp = (move.pp or 1) - 1 end
|
||||
@@ -1810,8 +1817,12 @@ function Battle:useMove(attacker, defender, moveId)
|
||||
landed = landed + 1
|
||||
end
|
||||
if landed > 1 then
|
||||
self:emit({ kind = "message",
|
||||
text = ("Hit %d time(s)!"):format(landed) })
|
||||
-- PlayerHitTimesText / EnemyHitTimesText (data/text/battle.asm:749,755)
|
||||
-- are "Hit @ times!". Gen 2 has no singular form of this line, so the
|
||||
-- plural stands even at one hit rather than the "(s)" this printed.
|
||||
-- Gen 1 already says it this way (src/battle/EffectRegistry.lua,
|
||||
-- _HitXTimesText).
|
||||
self:emit({ kind = "message", text = Strings("Hit %d times!", landed) })
|
||||
end
|
||||
|
||||
-- move_effects/pay_day.asm:13
|
||||
@@ -2031,8 +2042,11 @@ Battle.MOVE_EFFECTS.EFFECT_PERISH_SONG = function(self)
|
||||
if mine.perish and theirs.perish then return fail(self) end
|
||||
if not mine.perish then mine.perish = Effects.PERISH_TURNS end
|
||||
if not theirs.perish then theirs.perish = Effects.PERISH_TURNS end
|
||||
-- StartPerishText (data/text/battle.asm:986). What shipped here was a
|
||||
-- sentence no cart prints; the Gen 2 line names both sides and counts in
|
||||
-- digits.
|
||||
self:emit({ kind = "message",
|
||||
text = "All POKéMON hearing the song will faint in three turns!" })
|
||||
text = Strings("Both POKéMON will\nfaint in 3 turns!") })
|
||||
end
|
||||
|
||||
-- BattleCommand_Encore: 3-6 turns locked into the move the target last used.
|
||||
@@ -2162,6 +2176,11 @@ Battle.MOVE_EFFECTS.EFFECT_SPIKES = function(self, attacker, defender)
|
||||
local side = self:sideOf(defender)
|
||||
if self.spikes[side] then return fail(self) end
|
||||
self.spikes[side] = true
|
||||
-- SpikesText (data/text/battle.asm:974) is three rows, the third scrolled
|
||||
-- (`cont`) and carrying <TARGET>. The battle message path has no `cont`:
|
||||
-- src/ui/gen2/BattleState.lua sets self.message straight from the event and
|
||||
-- printMessage cuts past two rows, so the cart's line cannot be told here
|
||||
-- yet without the name being dropped on screen. Left as it stands.
|
||||
self:emit({ kind = "message", text = "Spikes were scattered all around!" })
|
||||
end
|
||||
|
||||
@@ -2399,7 +2418,9 @@ Battle.MOVE_EFFECTS.EFFECT_BEAT_UP = function(self, attacker, defender, def)
|
||||
{ move = def, moveId = def and def.id })
|
||||
landed = landed + 1
|
||||
end
|
||||
self:emit({ kind = "message", text = ("Hit %d time(s)!"):format(landed) })
|
||||
-- BattleCommand_EndLoop prints the same line for Beat Up, and Beat Up can
|
||||
-- land exactly once, which is the case the cart still prints as "times".
|
||||
self:emit({ kind = "message", text = Strings("Hit %d times!", landed) })
|
||||
end
|
||||
|
||||
-- BattleCommand_Heal (effect_commands.asm:5986): Recover and Rest are both
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
-- Five Gen 2 battle messages printed something the cart does not say. Each
|
||||
-- case below drives a real turn and compares the emitted line against the
|
||||
-- label in pokegold's data/text/battle.asm:
|
||||
--
|
||||
-- SuperEffectiveText :603
|
||||
-- NotVeryEffectiveText :608
|
||||
-- BattleText_TheresNoPPLeftForThisMove :315
|
||||
-- PlayerHitTimesText / EnemyHitTimesText:749, :755
|
||||
-- StartPerishText :986
|
||||
--
|
||||
-- The marker is the one src/import/RomExtractorGen2.lua decodes the cart's own
|
||||
-- $4e (`line`) into, so a line written here reads exactly as an extracted one
|
||||
-- would. The tail case pins the other half of that: printMessage draws at
|
||||
-- most TEXT_ROWS rows and cuts the rest, so a line has to fit two of them.
|
||||
--
|
||||
-- luajit tests/engine/gen2_battle_text_matches_cart.lua
|
||||
--
|
||||
-- ROM-free: the fixtures below are the extractor's shapes.
|
||||
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
love = require("tests.love_stub")
|
||||
|
||||
local T = require("tests.harness")
|
||||
local Battle = require("src.battle.gen2.Battle")
|
||||
local Mon = require("src.battle.gen2.Mon")
|
||||
|
||||
local check = T.check
|
||||
|
||||
-- ---------------------------------------------------------------- fixtures
|
||||
|
||||
local TYPES = {
|
||||
NORMAL = { id = "NORMAL", index = 0, category = "physical" },
|
||||
GROUND = { id = "GROUND", index = 4, category = "physical" },
|
||||
ROCK = { id = "ROCK", index = 5, category = "physical" },
|
||||
FIRE = { id = "FIRE", index = 20, category = "special" },
|
||||
WATER = { id = "WATER", index = 21, category = "special" },
|
||||
}
|
||||
|
||||
local MATCHUPS = {
|
||||
{ attacker = "NORMAL", defender = "ROCK", multiplier = 5 },
|
||||
{ attacker = "WATER", defender = "FIRE", multiplier = 20 },
|
||||
}
|
||||
|
||||
local MOVES = {
|
||||
TACKLE = { id = "TACKLE", name = "TACKLE", power = 35, type = "NORMAL",
|
||||
accuracy = 100, pp = 35, effect = "EFFECT_NORMAL_HIT" },
|
||||
WATER_GUN = { id = "WATER_GUN", name = "WATER GUN", power = 40,
|
||||
type = "WATER", accuracy = 100, pp = 25, effect = "EFFECT_NORMAL_HIT" },
|
||||
DOUBLESLAP = { id = "DOUBLESLAP", name = "DOUBLESLAP", power = 15,
|
||||
type = "NORMAL", accuracy = 100, pp = 10, effect = "EFFECT_MULTI_HIT" },
|
||||
PERISH_SONG = { id = "PERISH_SONG", name = "PERISH SONG", power = 0,
|
||||
type = "NORMAL", accuracy = 100, pp = 5, effect = "EFFECT_PERISH_SONG" },
|
||||
}
|
||||
|
||||
local GROWTH = {
|
||||
GROWTH_MEDIUM_SLOW = { numerator = 6, denominator = 5, squared = -15,
|
||||
linear = 100, constant = 140 },
|
||||
}
|
||||
|
||||
local function species(id, index, types)
|
||||
return { id = id, index = index, name = id,
|
||||
baseStats = { hp = 50, attack = 60, defense = 50, speed = 50,
|
||||
specialAttack = 50, specialDefense = 50 },
|
||||
types = types, catchRate = 255, baseExp = 60,
|
||||
growthRate = "GROWTH_MEDIUM_SLOW", genderRatio = 31,
|
||||
levelMoves = { { level = 1, move = "TACKLE" } }, evolutions = {} }
|
||||
end
|
||||
|
||||
local POKEMON = {
|
||||
growthRates = GROWTH,
|
||||
CYNDAQUIL = species("CYNDAQUIL", 155, { "FIRE", "FIRE" }),
|
||||
GEODUDE = species("GEODUDE", 74, { "ROCK", "GROUND" }),
|
||||
}
|
||||
|
||||
local DATA = { pokemon = POKEMON, moves = MOVES,
|
||||
type_chart = { types = TYPES, matchups = MATCHUPS }, items = {} }
|
||||
|
||||
local perfect = { attack = 15, defense = 15, speed = 15, special = 15 }
|
||||
perfect.hp = Mon.hpDV(perfect)
|
||||
|
||||
-- The smallest roll that is neither a critical hit nor a miss.
|
||||
local function detRandom(n)
|
||||
if (n or 1) <= 1 then return 0 end
|
||||
return 1
|
||||
end
|
||||
|
||||
-- One turn of the player's move, returning every message line it printed.
|
||||
local function linesFrom(playerSpecies, playerMoves, wildSpecies, moveId)
|
||||
local player = Mon.new(DATA, playerSpecies, 20, { dvs = perfect })
|
||||
player.moves = playerMoves
|
||||
local wild = Mon.new(DATA, wildSpecies, 20, { dvs = perfect })
|
||||
wild.moves = { { id = "TACKLE", pp = 35, maxPp = 35 } }
|
||||
local battle = Battle.new({ data = DATA, party = { player }, wild = wild,
|
||||
random = detRandom })
|
||||
local said = {}
|
||||
for _, event in ipairs(battle:takeTurn({ kind = "move", move = moveId })) do
|
||||
if event.kind == "message" and event.text then
|
||||
said[#said + 1] = event.text
|
||||
end
|
||||
end
|
||||
return said
|
||||
end
|
||||
|
||||
local function saw(said, text)
|
||||
for _, line in ipairs(said) do
|
||||
if line == text then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function shown(said)
|
||||
return "printed: " .. table.concat(said, " | ")
|
||||
end
|
||||
|
||||
-- ---- the two effectiveness lines ------------------------------------------
|
||||
-- Both break across the box's two lines on the cart, and "super-" is
|
||||
-- hyphenated to make the break. The not-very line ends on the single
|
||||
-- ellipsis glyph the Gold charmap carries at $75, not on three periods.
|
||||
do
|
||||
local said = linesFrom("CYNDAQUIL",
|
||||
{ { id = "TACKLE", pp = 35, maxPp = 35 } }, "GEODUDE", "TACKLE")
|
||||
check(saw(said, "It's not very\neffective…"),
|
||||
"NotVeryEffectiveText prints as the cart writes it. " .. shown(said))
|
||||
|
||||
said = linesFrom("GEODUDE",
|
||||
{ { id = "WATER_GUN", pp = 25, maxPp = 25 } }, "CYNDAQUIL", "WATER_GUN")
|
||||
check(saw(said, "It's super-\neffective!"),
|
||||
"SuperEffectiveText keeps its hyphen and its break. " .. shown(said))
|
||||
end
|
||||
|
||||
-- ---- a move at zero PP ----------------------------------------------------
|
||||
do
|
||||
-- a second move with PP left, so the turn refuses the empty one rather
|
||||
-- than falling through to Struggle
|
||||
local said = linesFrom("CYNDAQUIL",
|
||||
{ { id = "TACKLE", pp = 0, maxPp = 35 },
|
||||
{ id = "WATER_GUN", pp = 25, maxPp = 25 } }, "GEODUDE", "TACKLE")
|
||||
check(saw(said, "There's no PP left\nfor this move!"),
|
||||
"the empty-PP refusal is the cart's sentence. " .. shown(said))
|
||||
end
|
||||
|
||||
-- ---- the multi-hit tally --------------------------------------------------
|
||||
-- "Hit @ times!" has no singular form on the cart, which is why the "(s)"
|
||||
-- this used to print is not a hedge the game ever makes. Gen 1 says it the
|
||||
-- same way (src/battle/EffectRegistry.lua, _HitXTimesText).
|
||||
do
|
||||
local said = linesFrom("CYNDAQUIL",
|
||||
{ { id = "DOUBLESLAP", pp = 10, maxPp = 10 } }, "GEODUDE", "DOUBLESLAP")
|
||||
local tally
|
||||
for _, line in ipairs(said) do
|
||||
if line:match("^Hit %d+ times!$") then tally = line end
|
||||
end
|
||||
check(tally ~= nil,
|
||||
"the multi-hit tally reads \"Hit N times!\". " .. shown(said))
|
||||
check(not table.concat(said, " "):find("time(s)", 1, true),
|
||||
"and no line hedges the plural with a parenthetical. " .. shown(said))
|
||||
end
|
||||
|
||||
-- ---- Perish Song ----------------------------------------------------------
|
||||
-- What shipped here was a sentence no cart prints. StartPerishText names
|
||||
-- both sides and counts in digits.
|
||||
do
|
||||
local said = linesFrom("CYNDAQUIL",
|
||||
{ { id = "PERISH_SONG", pp = 5, maxPp = 5 } }, "GEODUDE", "PERISH_SONG")
|
||||
check(saw(said, "Both POKéMON will\nfaint in 3 turns!"),
|
||||
"Perish Song prints StartPerishText. " .. shown(said))
|
||||
end
|
||||
|
||||
-- ---- and every one of them fits the box -----------------------------------
|
||||
-- printMessage draws Chrome.wrap(self.message, TEXT_WIDTH) and stops at
|
||||
-- TEXT_ROWS (home/text.asm:143, :397), so a third row is cut rather than
|
||||
-- spilled. A cart line that needs one, such as SpikesText's `cont` row
|
||||
-- carrying <TARGET>, cannot be told on this path at all: engine messages set
|
||||
-- self.message directly (src/ui/gen2/BattleState.lua) instead of going
|
||||
-- through showPages, so nothing paginates them. Hence Spikes is left alone
|
||||
-- above, and every line that IS changed is checked to fit here.
|
||||
do
|
||||
local Chrome = require("src.ui.gen2.Chrome")
|
||||
-- TEXT_WIDTH and TEXT_ROWS are file-locals in BattleState, so their values
|
||||
-- are repeated rather than required.
|
||||
local BOX_WIDTH, BOX_ROWS = 18, 2
|
||||
for _, line in ipairs({
|
||||
"It's super-\neffective!",
|
||||
"It's not very\neffective…",
|
||||
"There's no PP left\nfor this move!",
|
||||
"Hit 3 times!",
|
||||
"Both POKéMON will\nfaint in 3 turns!",
|
||||
}) do
|
||||
local rows = #Chrome.wrap(line, BOX_WIDTH)
|
||||
check(rows <= BOX_ROWS,
|
||||
("\"%s\" wraps to %d rows, and the box draws %d")
|
||||
:format((line:gsub("\n", "\\n")), rows, BOX_ROWS))
|
||||
end
|
||||
end
|
||||
|
||||
T.finish("gen2 battle text matches the cart")
|
||||
@@ -175,7 +175,7 @@ do
|
||||
dealt = event.amount
|
||||
end
|
||||
if event.kind == "message"
|
||||
and event.text == "It's not very effective..." then
|
||||
and event.text == "It's not very\neffective…" then
|
||||
sawNve = true
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user