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
|
if opts.critical then
|
||||||
self:emit({ kind = "message", text = "A critical hit!" })
|
self:emit({ kind = "message", text = "A critical hit!" })
|
||||||
end
|
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
|
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
|
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
|
end
|
||||||
if endured then
|
if endured then
|
||||||
self:emit({ kind = "message",
|
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 not (charging or rampaging or rolling or biding or called) then
|
||||||
if move and (move.pp or 0) <= 0 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
|
return
|
||||||
end
|
end
|
||||||
if move then move.pp = (move.pp or 1) - 1 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
|
landed = landed + 1
|
||||||
end
|
end
|
||||||
if landed > 1 then
|
if landed > 1 then
|
||||||
self:emit({ kind = "message",
|
-- PlayerHitTimesText / EnemyHitTimesText (data/text/battle.asm:749,755)
|
||||||
text = ("Hit %d time(s)!"):format(landed) })
|
-- 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
|
end
|
||||||
|
|
||||||
-- move_effects/pay_day.asm:13
|
-- 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 mine.perish and theirs.perish then return fail(self) end
|
||||||
if not mine.perish then mine.perish = Effects.PERISH_TURNS end
|
if not mine.perish then mine.perish = Effects.PERISH_TURNS end
|
||||||
if not theirs.perish then theirs.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",
|
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
|
end
|
||||||
|
|
||||||
-- BattleCommand_Encore: 3-6 turns locked into the move the target last used.
|
-- 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)
|
local side = self:sideOf(defender)
|
||||||
if self.spikes[side] then return fail(self) end
|
if self.spikes[side] then return fail(self) end
|
||||||
self.spikes[side] = true
|
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!" })
|
self:emit({ kind = "message", text = "Spikes were scattered all around!" })
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -2399,7 +2418,9 @@ Battle.MOVE_EFFECTS.EFFECT_BEAT_UP = function(self, attacker, defender, def)
|
|||||||
{ move = def, moveId = def and def.id })
|
{ move = def, moveId = def and def.id })
|
||||||
landed = landed + 1
|
landed = landed + 1
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
-- BattleCommand_Heal (effect_commands.asm:5986): Recover and Rest are both
|
-- 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
|
dealt = event.amount
|
||||||
end
|
end
|
||||||
if event.kind == "message"
|
if event.kind == "message"
|
||||||
and event.text == "It's not very effective..." then
|
and event.text == "It's not very\neffective…" then
|
||||||
sawNve = true
|
sawNve = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user