Bug squashing and translation mods (#311)

* audio timing stuff

* bug fixes and translation additions

* translation stuff

* Update modkit.py

* better asset resolution
This commit is contained in:
bryanthaboi
2026-07-27 13:37:05 -04:00
committed by GitHub
parent 31365d8dfd
commit f0a88ea473
78 changed files with 3691 additions and 787 deletions
+29 -20
View File
@@ -9,6 +9,7 @@ local TextBox = require("src.render.TextBox")
local BagMenu = {}
local Bag = require("src.inventory.Bag")
local Strings = require("src.core.Strings")
-- acquisition order like wBagItems (Bag.order), not alphabetical
local function buildItems(game)
@@ -108,13 +109,13 @@ local function useOn(game, battle, id, target, list, moveIndex)
if game.save.onBike then
game.save.onBike = false
Music.playMap(game.data, ow and ow.map.id, false)
showMessages(game, { save_name(game) .. " got off\nthe BICYCLE." })
showMessages(game, { Strings("%s got off\nthe BICYCLE.", save_name(game)) })
elseif bikeAllowed() then
game.save.onBike = true
Music.playMap(game.data, ow.map.id, true)
showMessages(game, { save_name(game) .. " got on\nthe BICYCLE!" })
showMessages(game, { Strings("%s got on\nthe BICYCLE!", save_name(game)) })
else
showMessages(game, { "No cycling\nallowed here." })
showMessages(game, { Strings("No cycling\nallowed here.") })
end
return
end
@@ -130,13 +131,14 @@ local function useOn(game, battle, id, target, list, moveIndex)
return
end
end
showMessages(game, { "No good! It's not\neven near water." })
showMessages(game, { Strings("No good! It's not\neven near water.") })
return
end
if result == "ball" then
if not battle then
showMessages(game, { "OAK: " .. game.save.player.name .. "!\nThis isn't the\ntime to use that!" })
showMessages(game, { Strings("OAK: %s!\nThis isn't the\ntime to use that!",
game.save.player.name) })
return
end
consume(game, id)
@@ -151,7 +153,7 @@ local function useOn(game, battle, id, target, list, moveIndex)
local function teach()
if #target.moves < 4 then
table.insert(target.moves, { id = moveId, pp = mdef.pp })
showMessages(game, { ("%s learned\n%s!"):format(target.nickname or
showMessages(game, { Strings("%s learned\n%s!", target.nickname or
game.data.pokemon[target.species].name, mdef.name) })
if result == "learn" then consume(game, id) end
else
@@ -172,7 +174,7 @@ local function useOn(game, battle, id, target, list, moveIndex)
require("src.ui.Screens").push(game, "TownMap")
end)
if not ok then
showMessages(game, { "The TOWN MAP is\nunreadable here." })
showMessages(game, { Strings("The TOWN MAP is\nunreadable here.") })
end
return
end
@@ -184,10 +186,10 @@ local function useOn(game, battle, id, target, list, moveIndex)
local t = game.data.text
if ow and ow:hasHiddenItemLeft() then
showMessages(game, { t._ItemfinderFoundItemText
or "Yes! ITEMFINDER\nindicates there's\nan item nearby." })
or Strings("Yes! ITEMFINDER\nindicates there's\nan item nearby.") })
else
showMessages(game, { t._ItemfinderFoundNothingText
or "Nope! ITEMFINDER\nisn't responding." })
or Strings("Nope! ITEMFINDER\nisn't responding.") })
end
return
end
@@ -219,8 +221,9 @@ local function useOn(game, battle, id, target, list, moveIndex)
-- departure helper -- the same path Dig/Teleport take from the party menu
ow:beginTeleportOut()
else
showMessages(game, { "OAK: " .. game.save.player.name
.. "!\nThis isn't the\ntime to use that!" })
showMessages(game, { Strings(
"OAK: %s!\nThis isn't the\ntime to use that!",
game.save.player.name) })
end
return
end
@@ -264,7 +267,7 @@ local function useOn(game, battle, id, target, list, moveIndex)
if #target.moves < 4 then
table.insert(target.moves, { id = moveId, pp = mdef.pp })
local name = target.nickname or def.name
showMessages(game, { ("%s learned\n%s!"):format(name, mdef.name) },
showMessages(game, { Strings("%s learned\n%s!", name, mdef.name) },
nextStep)
else
require("src.ui.Screens").push(game, "MoveLearnMenu",
@@ -354,8 +357,8 @@ local function useItem(game, battle, id, list)
local moveDef = game.data.moves[def.machine.move]
local moveName = moveDef and moveDef.name or def.machine.move
local booted = def.machine.kind == "HM"
and "Booted up an HM!" or "Booted up a TM!"
showMessages(game, { booted, ("It contained\n%s!"):format(moveName) },
and "Booted up an HM!" or Strings("Booted up a TM!")
showMessages(game, { booted, Strings("It contained\n%s!", moveName) },
function() pickTargetAndUse(game, battle, id, list) end)
return
end
@@ -402,17 +405,23 @@ function BagMenu.new(game, opts)
useItem(game, battle, id, list)
return
end
-- USE / TOSS submenu (the original's item options)
-- USE / TOSS submenu (the original's item options).
-- data/text_boxes.asm USE_TOSS_MENU_TEMPLATE: box (13,10)-(19,14),
-- text at (15,11); start_sub_menus.asm then sets wTopMenuItemY/X to
-- 11/14 for the cursor. Menu's own geometry reproduces all of that
-- from the box alone, so this needs opts rather than a change to the
-- shared Menu. The old 12/10/8/6 box was a column too wide and a row
-- too tall, which left the labels stranded near its top edge (#284).
local Menu = require("src.ui.Menu")
game.stack:push(Menu.new(game, {
{ label = "USE", onSelect = function()
{ label = Strings("USE"), onSelect = function()
useItem(game, battle, id, list)
end },
{ label = "TOSS", onSelect = function()
{ label = Strings("TOSS"), onSelect = function()
-- KeyItemFlags + HMs decide tossability (not price:
-- MOON STONE is price 0 but tossable)
if not def or def.keyItem or id:find("^HM_") then
showMessages(game, { "That's too impor-\ntant to toss!" })
showMessages(game, { Strings("That's too impor-\ntant to toss!") })
return
end
local QuantityBox = require("src.ui.QuantityBox")
@@ -426,12 +435,12 @@ function BagMenu.new(game, opts)
Bag.remove(game.save, id, qty)
list.items = buildItems(game)
list.index = math.min(list.index, math.max(1, #list.items))
showMessages(game, { ("Threw away\n%s."):format(def and def.name or id) })
showMessages(game, { Strings("Threw away\n%s.", def and def.name or id) })
end))
end,
}))
end },
}, { tx = 12, ty = 10, tw = 8, th = 6 }))
}, { tx = 13, ty = 10, tw = 7, th = 5 }))
end,
})
return list
+5 -2
View File
@@ -7,6 +7,7 @@
local Font = require("src.render.Font")
local ListMenu = require("src.ui.ListMenu")
local Input = require("src.core.Input")
local Strings = require("src.core.Strings")
local BindingsMenu = setmetatable({}, { __index = ListMenu })
BindingsMenu.__index = BindingsMenu
@@ -55,7 +56,9 @@ function BindingsMenu.new(game)
and game.save.options.bindings
local items = {}
for i, def in ipairs(BUTTONS) do
items[i] = { label = def.label,
-- translated here, not in ROWS: that table is built at require
-- time, before Strings.load has a catalog to look in
items[i] = { label = Strings(def.label),
right = boundRight(overlay, def), button = def }
end
local self = setmetatable(ListMenu.new(game, "CONTROLS", items, {}),
@@ -111,7 +114,7 @@ function BindingsMenu:draw()
if self.capture then
Font.drawBox(1, 6, 18, 4)
love.graphics.setColor(0, 0, 0, 1)
Font.draw("PRESS A BUTTON", 24, 60)
Font.draw(Strings("PRESS A BUTTON"), 24, 60)
love.graphics.setColor(1, 1, 1, 1)
end
end
+26 -25
View File
@@ -8,12 +8,13 @@ local ListMenu = require("src.ui.ListMenu")
local Menu = require("src.ui.Menu")
local Party = require("src.pokemon.Party")
local TextBox = require("src.render.TextBox")
local Strings = require("src.core.Strings")
local BoxMenu = {}
local function monLabel(game, mon)
local def = game.data.pokemon[mon.species]
return ("%s :L%d"):format(mon.nickname or def.name, mon.level)
return Strings("%s :L%d", mon.nickname or def.name, mon.level)
end
local function monName(game, mon)
@@ -28,13 +29,13 @@ local function monSubmenu(game, action, mon, onAction)
game.stack:push(Menu.new(game, {
{ label = action, onSelect = onAction },
{
label = "STATS",
label = Strings("STATS"),
keepOpen = true,
onSelect = function()
require("src.ui.Screens").push(game, "SummaryMenu", mon)
end,
},
{ label = "CANCEL" },
{ label = Strings("CANCEL") },
}, { tx = 9, ty = 10, tw = 11, th = 8, noSound = true }))
end
@@ -50,12 +51,12 @@ local function withdraw(game)
local t = game.data.text
if #box == 0 then
game.stack:push(TextBox.new(game, t._NoMonText
or "What? There are\nno POKéMON here!"))
or Strings("What? There are\nno POKéMON here!")))
return
end
if #game.save.party >= Party.MAX then
game.stack:push(TextBox.new(game, t._CantTakeMonText
or "You can't take\nany more POKéMON.\fDeposit POKéMON\nfirst."))
or Strings("You can't take\nany more POKéMON.\fDeposit POKéMON\nfirst.")))
return
end
local items = {}
@@ -63,7 +64,7 @@ local function withdraw(game)
table.insert(items, { label = monLabel(game, mon), value = i })
end
game.stack:push(ListMenu.new(game,
("BOX %d (WITHDRAW)"):format(game.save.currentBox), items, {
Strings("BOX %d (WITHDRAW)", game.save.currentBox), items, {
onChoose = function(item, list)
local mon = box[item.value]
if not mon then return end
@@ -78,7 +79,7 @@ local function withdraw(game)
game.stringBuffer = name
require("src.core.Sound").playCry(game.data, mon.species)
afterTransfer(game, list, t._MonIsTakenOutText
or (name .. " is\ntaken out.\vGot " .. name .. "."))
or Strings("%s is\ntaken out.\vGot %s.", name, name))
end)
end,
}))
@@ -88,13 +89,13 @@ local function deposit(game)
local t = game.data.text
if #game.save.party <= 1 then
game.stack:push(TextBox.new(game, t._CantDepositLastMonText
or "You can't deposit\nthe last POKéMON!"))
or Strings("You can't deposit\nthe last POKéMON!")))
return
end
local box = Boxes.active(game.save)
if #box >= Boxes.CAPACITY then
game.stack:push(TextBox.new(game, t._BoxFullText
or "Oops! This Box is\nfull of POKéMON."))
or Strings("Oops! This Box is\nfull of POKéMON.")))
return
end
local items = {}
@@ -107,12 +108,12 @@ local function deposit(game)
if not mon then return end
monSubmenu(game, "DEPOSIT", mon, function()
if #game.save.party <= 1 then
list.footer = "You need at least\none POKéMON!"
list.footer = Strings("You need at least\none POKéMON!")
return
end
local active = Boxes.active(game.save)
if #active >= Boxes.CAPACITY then
list.footer = ("BOX %d is full!"):format(game.save.currentBox)
list.footer = Strings("BOX %d is full!", game.save.currentBox)
return
end
table.remove(game.save.party, item.value)
@@ -122,7 +123,7 @@ local function deposit(game)
game.boxNumString = tostring(game.save.currentBox)
require("src.core.Sound").playCry(game.data, mon.species)
afterTransfer(game, list, t._MonWasStoredText
or (name .. " was\nstored in Box " .. game.boxNumString .. "."))
or Strings("%s was\nstored in Box %s.", name, game.boxNumString))
end)
end,
}))
@@ -135,7 +136,7 @@ local function release(game)
local t = game.data.text
if #box == 0 then
game.stack:push(TextBox.new(game, t._NoMonText
or "What? There are\nno POKéMON here!"))
or Strings("What? There are\nno POKéMON here!")))
return
end
local items = {}
@@ -143,20 +144,20 @@ local function release(game)
table.insert(items, { label = monLabel(game, mon), value = i })
end
game.stack:push(ListMenu.new(game,
("BOX %d (RELEASE)"):format(game.save.currentBox), items, {
Strings("BOX %d (RELEASE)", game.save.currentBox), items, {
onChoose = function(_, list)
local mon = box[list.index]
if not mon then return end
local name = monName(game, mon)
local ChoiceBox = require("src.ui.ChoiceBox")
game.stack:push(TextBox.new(game,
"Once released,\n" .. name .. " is\ngone forever. OK?", function()
Strings("Once released,\n%s is\ngone forever. OK?", name), function()
game.stack:push(ChoiceBox.new(game, function(yes)
if not yes then return end
table.remove(box, list.index)
require("src.core.Sound").playCry(game.data, mon.species)
game.stack:push(TextBox.new(game,
("%s was\nreleased outside.\fBye %s!"):format(name, name)))
Strings("%s was\nreleased outside.\fBye %s!", name, name)))
list:removeCurrent()
end, { defaultNo = true, noSound = true }))
end))
@@ -170,7 +171,7 @@ local function changeBox(game)
for i = 1, Boxes.COUNT do
local mark = i == game.save.currentBox and "*" or " "
table.insert(items, {
label = ("%sBOX %2d"):format(mark, i),
label = Strings("%sBOX %2d", mark, i),
right = ("%d/%d"):format(#boxes[i], Boxes.CAPACITY),
value = i,
})
@@ -181,7 +182,7 @@ local function changeBox(game)
-- BOX, data will be saved. OK?"); declining aborts the change
local ChoiceBox = require("src.ui.ChoiceBox")
game.stack:push(TextBox.new(game,
"When you change a\nPOKéMON BOX, data\nwill be saved. OK?", function()
Strings("When you change a\nPOKéMON BOX, data\nwill be saved. OK?"), function()
game.stack:push(ChoiceBox.new(game, function(yes)
if not yes then return end
game.save.currentBox = item.value
@@ -197,10 +198,10 @@ end
local function drawChrome(game)
Font.drawBox(0, 12, 20, 6)
love.graphics.setColor(0, 0, 0, 1)
Font.draw("What?", 8, 112)
Font.draw(Strings("What?"), 8, 112)
Font.drawBox(9, 14, 11, 4)
love.graphics.setColor(0, 0, 0, 1)
Font.draw("BOX No.", 80, 128)
Font.draw(Strings("BOX No."), 80, 128)
local n = game.save.currentBox or 1
if n >= 10 then
Font.draw(tostring(n), 136, 128)
@@ -217,15 +218,15 @@ function BoxMenu.new(game)
-- full interior (cursor col + label). keepOpen so WITHDRAW/DEPOSIT/
-- RELEASE/CHANGE BOX leave this menu underneath (jp BillsPCMenu).
local menu = Menu.new(game, {
{ label = "WITHDRAW <PK><MN>", keepOpen = true,
{ label = Strings("WITHDRAW <PK><MN>"), keepOpen = true,
onSelect = function() withdraw(game) end },
{ label = "DEPOSIT <PK><MN>", keepOpen = true,
{ label = Strings("DEPOSIT <PK><MN>"), keepOpen = true,
onSelect = function() deposit(game) end },
{ label = "RELEASE <PK><MN>", keepOpen = true,
{ label = Strings("RELEASE <PK><MN>"), keepOpen = true,
onSelect = function() release(game) end },
{ label = "CHANGE BOX", keepOpen = true,
{ label = Strings("CHANGE BOX"), keepOpen = true,
onSelect = function() changeBox(game) end },
{ label = "SEE YA!" },
{ label = Strings("SEE YA!") },
-- Bill's PC runs silent end to end (BIT_NO_MENU_BUTTON_SOUND,
-- engine/menus/pokemon_pc.asm)
}, { tx = 0, ty = 0, tw = 14, th = 12, noSound = true })
+3 -2
View File
@@ -2,6 +2,7 @@
local Font = require("src.render.Font")
local Theme = require("src.ui.Theme")
local Strings = require("src.core.Strings")
local ChoiceBox = {}
ChoiceBox.__index = ChoiceBox
@@ -46,8 +47,8 @@ function ChoiceBox:draw()
local tx, ty, tw, th = self.tx, self.ty, self.tw, self.th
Font.drawBox(tx, ty, tw, th)
love.graphics.setColor(0, 0, 0, 1)
Font.draw("YES", (tx + 2) * 8, (ty + 1) * 8)
Font.draw("NO", (tx + 2) * 8, (ty + 3) * 8)
Font.draw(Strings("YES"), (tx + 2) * 8, (ty + 1) * 8)
Font.draw(Strings("NO"), (tx + 2) * 8, (ty + 3) * 8)
Font.drawCode(Theme.cursor, (tx + 1) * 8,
(ty + (self.index == 1 and 1 or 3)) * 8)
love.graphics.setColor(1, 1, 1, 1)
+6 -5
View File
@@ -31,6 +31,7 @@
local Font = require("src.render.Font")
local Music = require("src.core.Music")
local Strings = require("src.core.Strings")
local Credits = {}
Credits.__index = Credits
@@ -261,14 +262,14 @@ function Credits:drawCopyright(xoff)
love.graphics.draw(self.gfImg, xoff + 80, 88)
else
love.graphics.setColor(0, 0, 0, 1)
Font.draw("GAME FREAK", xoff + 80, 88)
Font.draw(Strings("GAME FREAK"), xoff + 80, 88)
love.graphics.setColor(1, 1, 1, 1)
end
else
love.graphics.setColor(0, 0, 0, 1)
Font.draw("Nintendo", xoff + 80, 56)
Font.draw("Creatures inc.", xoff + 80, 72)
Font.draw("GAME FREAK inc.", xoff + 16, 88)
Font.draw(Strings("Nintendo"), xoff + 80, 56)
Font.draw(Strings("Creatures inc."), xoff + 80, 72)
Font.draw(Strings("GAME FREAK inc."), xoff + 16, 88)
love.graphics.setColor(1, 1, 1, 1)
end
end
@@ -298,7 +299,7 @@ function Credits:drawTheEnd()
end
else
love.graphics.setColor(0, 0, 0, 1)
Font.draw((te and te.display) or "T H E E N D", 32, 64)
Font.draw((te and te.display) or Strings("T H E E N D"), 32, 64)
love.graphics.setColor(1, 1, 1, 1)
end
end
+4 -3
View File
@@ -8,6 +8,7 @@
-- without permanently marking the mon owned.
local Font = require("src.render.Font")
local Strings = require("src.core.Strings")
local DexEntryMenu = {}
DexEntryMenu.__index = DexEntryMenu
@@ -76,8 +77,8 @@ function DexEntryMenu:draw()
-- feet/inches use the dex screen's /″ glyphs ("HT ???″" in
-- pokedex.asm; the tiles come from gfx/pokedex/pokedex.png via
-- engine/gfx/load_pokedex_tiles.asm)
Font.draw(("HT %d%02d″"):format(e.heightFt, e.heightIn or 0), 72, 44)
Font.draw(("WT %.1flb"):format((e.weight or 0) / 10), 72, 54)
Font.draw(Strings("HT %d%02d″", e.heightFt, e.heightIn or 0), 72, 44)
Font.draw(Strings("WT %.1flb", (e.weight or 0) / 10), 72, 54)
end
local text = owned and e.text and self.game.data.text[e.text] or nil
local y = 72
@@ -88,7 +89,7 @@ function DexEntryMenu:draw()
y = y + 10
end
else
Font.draw("Data unknown.", 8, y)
Font.draw(Strings("Data unknown."), 8, y)
end
love.graphics.setColor(1, 1, 1, 1)
end
+19 -13
View File
@@ -1,14 +1,17 @@
-- The evolution movie (engine/movie/evolution.asm): the mon's pic
-- flashes back and forth with the evolved form, speeding up, then the
-- new form appears with its cry and the congratulations text.
-- pokered engine/pokemon/evos_moves.asm (EvolveMon) polls hJoyHeld during
-- the flash: holding B aborts the evolution -- the mon keeps its species
-- and _StoppedEvolvingText ("Huh? MON stopped evolving!") prints. The
-- lone exception is trade evolutions (wLinkState == LINK_STATE_TRADING),
-- which skip that poll and cannot be cancelled (#213).
-- pokered engine/movie/evolution.asm (Evolution_CheckForCancel) polls the
-- joypad during the flash: holding B aborts the evolution -- the mon keeps
-- its species and _StoppedEvolvingText ("Huh? MON stopped evolving!")
-- prints. Two kinds are exempt: trade evolutions, which evos_moves.asm
-- routes past the poll entirely (wLinkState == LINK_STATE_TRADING, #213),
-- and stone evolutions, where the B press is read but thrown away because
-- ItemUseEvoStone left wForceEvolution set (#290).
local Font = require("src.render.Font")
local Music = require("src.core.Music")
local Strings = require("src.core.Strings")
local EvolutionState = {}
EvolutionState.__index = EvolutionState
@@ -43,9 +46,12 @@ function EvolutionState.new(game, mon, newSpecies, onDone, via)
self.newSpecies = newSpecies
self.onDone = onDone
self.via = via
-- evos_moves.asm: only trade evolutions (LINK_STATE_TRADING) skip the
-- B-cancel poll; level-up, stone and rare-candy evos are all cancelable.
self.cancelable = (via ~= "TRADE")
-- evolution.asm Evolution_CheckForCancel: a B press is discarded when
-- wForceEvolution is set, and ItemUseEvoStone sets it before calling
-- TryEvolvingMon, so a stone evolution (via == "ITEM") cannot be
-- cancelled either. Only level-up and rare-candy evolutions run with
-- wForceEvolution clear and so honour B (#290, #213).
self.cancelable = (via ~= "TRADE" and via ~= "ITEM")
self.oldName = mon.nickname or game.data.pokemon[mon.species].name
self.oldSprite = frontSprite(game, mon.species, mon)
self.newSprite = frontSprite(game, newSpecies, mon)
@@ -69,7 +75,7 @@ function EvolutionState:update(dt)
local TextBox = require("src.render.TextBox")
-- mirrors data/generated/text.lua _StoppedEvolvingText
game.stack:push(TextBox.new(game,
("Huh? %s\nstopped evolving!"):format(self.oldName),
Strings("Huh? %s\nstopped evolving!", self.oldName),
function()
Music.restoreMap(game.data)
game.stack:pop() -- the evolution screen itself
@@ -85,8 +91,8 @@ function EvolutionState:update(dt)
local TextBox = require("src.render.TextBox")
local newName = game.data.pokemon[self.newSpecies].name
game.stack:push(TextBox.new(game,
("Congratulations!\nYour %s\nevolved into\n%s!")
:format(self.oldName, newName),
Strings("Congratulations!\nYour %s\nevolved into\n%s!",
self.oldName, newName),
function()
Music.restoreMap(game.data)
game.stack:pop() -- the evolution screen itself
@@ -121,9 +127,9 @@ function EvolutionState:draw()
love.graphics.setColor(0, 0, 0, 1)
if not self.done then
Font.draw("What?", 8, 104)
Font.draw(Strings("What?"), 8, 104)
Font.draw(self.oldName .. " is", 8, 114)
Font.draw("evolving!", 8, 124)
Font.draw(Strings("evolving!"), 8, 124)
end
love.graphics.setColor(1, 1, 1, 1)
end
+16 -11
View File
@@ -7,10 +7,12 @@
-- name/time/money boxes plus the dex rating. Plays Music_HallOfFame
-- when the audio data has it. Calls onDone() after popping itself.
local Assets = require("src.render.Assets")
local Font = require("src.render.Font")
local Music = require("src.core.Music")
local Sound = require("src.core.Sound")
local TypeChart = require("src.battle.TypeChart")
local Strings = require("src.core.Strings")
local HallOfFame = {}
HallOfFame.__index = HallOfFame
@@ -47,9 +49,11 @@ local FADE_FRAMES = 20
-- HoFPrintTextAndDelay after each dex line
local DEX_HOLD = 120
-- through Assets.resolve so an enabled mod's overrides/ shadows these the
-- same way it shadows every other generated asset
local function tryImage(path)
if not path then return nil end
local ok, img = pcall(love.graphics.newImage, path)
local ok, img = pcall(love.graphics.newImage, Assets.resolve(path))
return ok and img or nil
end
@@ -79,7 +83,8 @@ function HallOfFame.new(game, onDone)
self.timer = 0
self.phase = "mons"
self.sprites = {} -- species -> image or false
self.playerPic = tryImage("assets/generated/trainer_card/red.png")
self.playerPic = tryImage(require("src.pokemon.Sprites").playerPath(
game.data, "front", { kind = "hof" }))
self.scrollX = PIC_X
self.showHofBanner = false
self.fade = 0
@@ -206,13 +211,13 @@ function HallOfFame:drawMonInfo(mon)
love.graphics.setColor(0, 0, 0, 1)
local name = mon.nickname or (def and def.name) or mon.species
Font.draw(name, 1 * 8, 4 * 8)
Font.draw("LEVEL/", 2 * 8, 6 * 8)
Font.draw("TYPE1/", 2 * 8, 7 * 8)
Font.draw(Strings("LEVEL/"), 2 * 8, 6 * 8)
Font.draw(Strings("TYPE1/"), 2 * 8, 7 * 8)
local t1 = def and def.types and def.types[1]
local t2 = def and def.types and def.types[2]
local dual = t2 and t2 ~= t1
if dual then
Font.draw("TYPE2/", 2 * 8, 8 * 8)
Font.draw(Strings("TYPE2/"), 2 * 8, 8 * 8)
end
-- PrintLevelCommon at (8,7): bare level digits (no <LV> tile here)
Font.draw(tostring(mon.level), 8 * 8, 7 * 8)
@@ -229,7 +234,7 @@ end
function HallOfFame:drawHofBanner()
Font.drawBox(2, 13, 16, 5)
love.graphics.setColor(0, 0, 0, 1)
Font.draw("HALL OF FAME", 4 * 8, 15 * 8)
Font.draw(Strings("HALL OF FAME"), 4 * 8, 15 * 8)
end
function HallOfFame:drawPic(img)
@@ -249,11 +254,11 @@ function HallOfFame:drawPlayerStats()
-- play time / money box: TextBoxBorder (0,4) b=6,c=10 → drawBox(0,4,12,8)
Font.drawBox(0, 4, 12, 8)
love.graphics.setColor(0, 0, 0, 1)
Font.draw("PLAY TIME", 1 * 8, 6 * 8)
Font.draw(Strings("PLAY TIME"), 1 * 8, 6 * 8)
local t = math.floor(save.playTime or 0)
Font.draw(("%3d:%02d"):format(math.floor(t / 3600), math.floor(t / 60) % 60),
5 * 8, 7 * 8)
Font.draw("MONEY", 1 * 8, 9 * 8)
Font.draw(Strings("MONEY"), 1 * 8, 9 * 8)
-- PrintBCDNumber with MONEY_SIGN; port uses ¥ like TrainerCard
Font.draw(("¥%d"):format(save.money or 0), 4 * 8, 10 * 8)
end
@@ -266,16 +271,16 @@ function HallOfFame:drawDexBox(kind)
if kind == "seen" then
local seen, owned = self:dexSeenOwned()
local seenOwned = text._DexSeenOwnedText
or "POKéDEX Seen:{NUM:wDexRatingNumMonsSeen, 1, 3}\n Owned:{NUM:wDexRatingNumMonsOwned, 1, 3}"
or Strings("POKéDEX Seen:{NUM:wDexRatingNumMonsSeen, 1, 3}\n Owned:{NUM:wDexRatingNumMonsOwned, 1, 3}")
seenOwned = seenOwned
:gsub("{NUM:wDexRatingNumMonsSeen[^}]*}", tostring(seen))
:gsub("{NUM:wDexRatingNumMonsOwned[^}]*}", tostring(owned))
drawTextBlock(seenOwned, 1 * 8, 14 * 8, 17 * 8)
else
local _, owned = self:dexSeenOwned()
local ratingHeader = (text._DexRatingText or "POKéDEX Rating{COLON}"):gsub("{COLON}", ":")
local ratingHeader = (text._DexRatingText or Strings("POKéDEX Rating{COLON}")):gsub("{COLON}", ":")
Font.draw(ratingHeader, 1 * 8, 14 * 8)
local rating = text[dexRatingKey(owned)] or "Keep it up!"
local rating = text[dexRatingKey(owned)] or Strings("Keep it up!")
drawTextBlock(rating, 1 * 8, 15 * 8, 17 * 8)
end
end
+4 -3
View File
@@ -27,6 +27,7 @@
local Font = require("src.render.Font")
local Music = require("src.core.Music")
local Sound = require("src.core.Sound")
local Strings = require("src.core.Strings")
local IntroMovie = {}
IntroMovie.__index = IntroMovie
@@ -287,7 +288,7 @@ function IntroMovie:drawSplash()
end
-- custom studio name (replaces the GAME FREAK splash text)
love.graphics.setColor(0, 0, 0, dim and 0.35 or 1)
local card = self.studio.card or "bois club games"
local card = self.studio.card or Strings("bois club games")
Font.draw(card, (160 - #card * 8) / 2, TEXT_Y)
love.graphics.setColor(1, 1, 1, 1)
end
@@ -348,7 +349,7 @@ function IntroMovie:drawFight()
if not gengar and not nido then
love.graphics.setColor(0, 0, 0, 1)
Font.draw("GENGAR VS NIDORINO", (160 - 18 * 8) / 2, 64)
Font.draw(Strings("GENGAR VS NIDORINO"), (160 - 18 * 8) / 2, 64)
love.graphics.setColor(1, 1, 1, 1)
end
drawBars()
@@ -366,7 +367,7 @@ function IntroMovie:draw()
-- custom boot card (replaces the Nintendo / GAME FREAK copyright
-- card; no (c) glyph in the charmap, keep it ASCII-safe)
love.graphics.setColor(0, 0, 0, 1)
local credit = self.studio.credit or "bois club"
local credit = self.studio.credit or Strings("bois club")
Font.draw("2026", (160 - 4 * 8) / 2, 48)
Font.draw(credit, (160 - #credit * 8) / 2, 64)
Font.draw("bryanthaboi", (160 - 11 * 8) / 2, 80)
+6 -2
View File
@@ -5,6 +5,7 @@
local Font = require("src.render.Font")
local Runtime = require("src.mods.Runtime")
local Theme = require("src.ui.Theme")
local Strings = require("src.core.Strings")
local ListMenu = {}
ListMenu.__index = ListMenu
@@ -192,7 +193,7 @@ function ListMenu:draw()
love.graphics.setColor(0, 0, 0, 1)
Font.draw(self.title, 8, 4)
if #self.items == 0 then
Font.draw("Nothing here.", 16, 64)
Font.draw(Strings("Nothing here."), 16, 64)
end
for row = 1, self.rows do
local i = self.scroll + row
@@ -201,7 +202,10 @@ function ListMenu:draw()
local y = 8 + row * 16
Font.draw(item.label, 16, y)
if item.ball then -- the Pokédex owned-ball marker tile
local bx = 16 + (#item.label + 1) * 8 + 3
-- one blank glyph after the name, measured in glyph advances rather
-- than bytes: NIDORAN♂/♀ carry a multi-byte charmap entry, so
-- `#item.label` overcounted by 2 and pushed their ball 16px right (#285)
local bx = 16 + Font.width(item.label) + 8 + 3
local by = y + 3
love.graphics.circle("fill", bx, by, 3.5)
love.graphics.setColor(1, 1, 1, 1)
+12 -11
View File
@@ -4,6 +4,7 @@
-- can't be forgotten; B / CANCEL gives up on the new move.
local Font = require("src.render.Font")
local Strings = require("src.core.Strings")
local MoveLearnMenu = {}
MoveLearnMenu.__index = MoveLearnMenu
@@ -43,9 +44,9 @@ function MoveLearnMenu:enter()
local name = self:monName()
self.selecting = false
game.stack:push(TextBox.new(game,
("%s is\ntrying to learn\v%s!\fBut, %s\ncan't learn more\vthan 4 moves!\f")
:format(name, mdef.name, name) ..
("Delete an older\nmove to make room\vfor %s?"):format(mdef.name),
Strings("%s is\ntrying to learn\v%s!\fBut, %s\ncan't learn more\vthan 4 moves!\f",
name, mdef.name, name) ..
Strings("Delete an older\nmove to make room\vfor %s?", mdef.name),
nil, {
choice = function(yes)
if yes then
@@ -76,7 +77,7 @@ function MoveLearnMenu:update(dt)
-- HMCantDeleteText, then back to the forget list
local TextBox = require("src.render.TextBox")
self.game.stack:push(TextBox.new(self.game,
"HM techniques\ncan't be deleted!"))
Strings("HM techniques\ncan't be deleted!")))
return
end
local mdef = self.game.data.moves[self.newMoveId]
@@ -96,7 +97,7 @@ function MoveLearnMenu:confirmAbandon()
local mdef = game.data.moves[self.newMoveId]
self.selecting = false
game.stack:push(TextBox.new(game,
("Abandon learning\n%s?"):format(mdef.name), nil, {
Strings("Abandon learning\n%s?", mdef.name), nil, {
choice = function(yes)
if yes then self:finish(false) else self:enter() end
end,
@@ -113,11 +114,11 @@ function MoveLearnMenu:finish(learned)
local msg
if learned then
-- OneTwoAndText/PoofText/ForgotAndText
msg = ("1, 2 and... Poof!\f%s forgot\n%s!\fAnd...\f%s learned\n%s!")
:format(name, self.forgot, name, mdef.name)
msg = Strings("1, 2 and... Poof!\f%s forgot\n%s!\fAnd...\f%s learned\n%s!",
name, self.forgot, name, mdef.name)
else
-- DidNotLearnText
msg = ("%s\ndid not learn\v%s!"):format(name, mdef.name)
msg = Strings("%s\ndid not learn\v%s!", name, mdef.name)
end
game.stack:push(TextBox.new(game, msg, function()
if self.onDone then self.onDone(learned) end
@@ -133,12 +134,12 @@ function MoveLearnMenu:draw()
for i, mv in ipairs(self.mon.moves) do
Font.draw(self.game.data.moves[mv.id].name, 48, (5 + i) * 8)
end
Font.draw("CANCEL", 48, (6 + #self.mon.moves) * 8)
Font.draw(Strings("CANCEL"), 48, (6 + #self.mon.moves) * 8)
Font.drawCode(CURSOR, 40, (5 + self.index) * 8)
-- WhichMoveToForgetText in the bottom dialogue box
Font.drawBox(0, 12, 20, 6)
Font.draw("Which move should", 8, 14 * 8)
Font.draw("be forgotten?", 8, 16 * 8)
Font.draw(Strings("Which move should"), 8, 14 * 8)
Font.draw(Strings("be forgotten?"), 8, 16 * 8)
love.graphics.setColor(1, 1, 1, 1)
end
+3 -2
View File
@@ -13,6 +13,7 @@ local Font = require("src.render.Font")
local Runtime = require("src.mods.Runtime")
local Sound = require("src.core.Sound")
local Theme = require("src.ui.Theme")
local Strings = require("src.core.Strings")
local NamingScreen = {}
NamingScreen.__index = NamingScreen
@@ -64,7 +65,7 @@ function NamingScreen.new(game, opts)
opts = opts or {}
local self = setmetatable({}, NamingScreen)
self.game = game
self.title = opts.title or "YOUR NAME?"
self.title = opts.title or Strings("YOUR NAME?")
self.presets = opts.presets
self.maxLen = opts.maxLen or 7
self.default = opts.default
@@ -78,7 +79,7 @@ end
function NamingScreen:enter()
if self.presets and #self.presets > 0 then
local Menu = require("src.ui.Menu")
local items = { { label = "NEW NAME" } }
local items = { { label = Strings("NEW NAME") } }
for _, preset in ipairs(self.presets) do
table.insert(items, {
label = preset,
+14 -7
View File
@@ -7,12 +7,14 @@
-- mod can insertBefore("name_player", ...) without counting indices.
-- Calls onDone() after popping itself.
local Assets = require("src.render.Assets")
local Sound = require("src.core.Sound")
local Music = require("src.core.Music")
local Logger = require("src.core.Logger")
local Runtime = require("src.mods.Runtime")
local TextBox = require("src.render.TextBox")
local Font = require("src.render.Font")
local Strings = require("src.core.Strings")
local OakSpeech = {}
OakSpeech.__index = OakSpeech
@@ -51,9 +53,11 @@ local function textOr(game, key)
return (t and t[key]) or FALLBACKS[key]
end
-- through Assets.resolve so an enabled mod's overrides/ shadows these the
-- same way it shadows every other generated asset
local function tryImage(path)
if not path then return nil end
local ok, img = pcall(love.graphics.newImage, path)
local ok, img = pcall(love.graphics.newImage, Assets.resolve(path))
return ok and img or nil
end
@@ -101,7 +105,9 @@ function OakSpeech.resolvePic(game, desc, speech)
if speech and speech.playerPic and not desc.path then
return speech.playerPic, false
end
return tryImage(desc.path or "assets/generated/trainer_card/red.png"), false
if desc.path then return tryImage(desc.path), false end
return tryImage(require("src.pokemon.Sprites").playerPath(
game.data, "front", { kind = "intro" })), false
elseif t == "image" then
return tryImage(desc.path), desc.flip and true or false
elseif t == "sprite" then
@@ -140,7 +146,7 @@ function OakSpeech.defaultSteps(speech)
id = "name_player",
kind = "name",
who = "player",
title = "YOUR NAME?",
title = Strings("YOUR NAME?"),
presetsWho = "player",
presetsFallback = { "RED", "ASH", "JACK" },
},
@@ -154,7 +160,7 @@ function OakSpeech.defaultSteps(speech)
id = "name_rival",
kind = "name",
who = "rival",
title = "HIS NAME?",
title = Strings("HIS NAME?"),
presetsWho = "rival",
presetsFallback = { "BLUE", "GARY", "JOHN" },
},
@@ -220,7 +226,8 @@ function OakSpeech.new(game, onDone)
self.nameLen = constants.playerNameLength or 7
-- RedPicFront (gfx/player/red.png, shared with the trainer card) and
-- the ShrinkPic1/ShrinkPic2 frames (gfx/player/shrink{1,2}.png)
self.playerPic = tryImage("assets/generated/trainer_card/red.png")
self.playerPic = tryImage(require("src.pokemon.Sprites").playerPath(
game.data, "front", { kind = "intro" }))
self.shrinkPic1 = tryImage(oakGfx.shrink1
or "assets/generated/intro/shrink1.png")
self.shrinkPic2 = tryImage(oakGfx.shrink2
@@ -337,7 +344,7 @@ function OakSpeech:runStep(step)
self.picFlip = true
self:revealPic("wipe", function()
Sound.playCry(self.game.data, self.demoSpecies)
self:say("_OakSpeechText2A", function() self:advance() end)
self:say(Strings("_OakSpeechText2A"), function() self:advance() end)
end)
elseif kind == "name" then
local who = step.who or "player"
@@ -345,7 +352,7 @@ function OakSpeech:runStep(step)
or namePresets(self.game, step.presetsWho or who,
step.presetsFallback or { "RED" })
require("src.ui.Screens").push(self.game, "NamingScreen", {
title = step.title or (who == "rival" and "HIS NAME?" or "YOUR NAME?"),
title = step.title or (who == "rival" and "HIS NAME?" or Strings("YOUR NAME?")),
presets = presets,
maxLen = step.maxLen or self.nameLen,
onDone = function(name)
+19 -18
View File
@@ -22,6 +22,7 @@ local Logger = require("src.core.Logger")
local Runtime = require("src.mods.Runtime")
local OptionRows = require("src.ui.OptionRows")
local Renderer = require("src.render.Renderer")
local Strings = require("src.core.Strings")
local OptionsMenu = {}
OptionsMenu.__index = OptionsMenu
@@ -116,14 +117,14 @@ local function sameRows(_, rows) return rows end
-- ladder's, so the save.options mutations are unchanged
local function buildRows(game)
local rows = {
{ id = "textSpeed", label = "TEXT SPEED",
{ id = "textSpeed", label = Strings("TEXT SPEED"),
value = function(g) return SPEEDS[speedIndex(g)][2] end,
step = function(g)
local i = speedIndex(g) % #SPEEDS + 1
g.save.options.textSpeed = SPEEDS[i][1]
return true
end },
{ id = "animations", label = "BATTLE ANIMATION",
{ id = "animations", label = Strings("BATTLE ANIMATION"),
value = function(g)
return g.save.options.animations == false and "OFF" or "ON"
end,
@@ -132,7 +133,7 @@ local function buildRows(game)
o.animations = o.animations == false and true or false
return true
end },
{ id = "battleStyle", label = "BATTLE STYLE",
{ id = "battleStyle", label = Strings("BATTLE STYLE"),
value = function(g)
return g.save.options.battleStyle == "set" and "SET" or "SHIFT"
end,
@@ -141,7 +142,7 @@ local function buildRows(game)
o.battleStyle = o.battleStyle == "set" and "shift" or "set"
return true
end },
{ id = "ruleset", label = "RULESET",
{ id = "ruleset", label = Strings("RULESET"),
value = function(g) return rulesetName(g) end,
step = function(g, dir)
local ids = rulesetIds(g)
@@ -150,7 +151,7 @@ local function buildRows(game)
g.save.options.ruleset = ids[wrapIndex(i - 1 + dir, #ids) + 1]
return true
end },
{ id = "musicVol", label = "MUSIC VOL",
{ id = "musicVol", label = Strings("MUSIC VOL"),
value = function(g) return volLabel(g.save.options.musicVol) end,
step = function(g, dir)
local o = g.save.options
@@ -158,7 +159,7 @@ local function buildRows(game)
require("src.core.Music").setVolumeLevel(o.musicVol)
return true
end },
{ id = "sfxVol", label = "SFX VOL",
{ id = "sfxVol", label = Strings("SFX VOL"),
value = function(g) return volLabel(g.save.options.sfxVol) end,
step = function(g, dir)
local o = g.save.options
@@ -166,7 +167,7 @@ local function buildRows(game)
require("src.core.Sound").setVolumeLevel(o.sfxVol)
return true
end },
{ id = "musicFilter", label = "MUSIC FILTER",
{ id = "musicFilter", label = Strings("MUSIC FILTER"),
value = function(g)
return FILTERS[(g.save.options.musicFilter or 0) + 1]
end,
@@ -176,7 +177,7 @@ local function buildRows(game)
require("src.core.Music").setFilterLevel(o.musicFilter)
return true
end },
{ id = "colors", label = "COLORS",
{ id = "colors", label = Strings("COLORS"),
value = function(g)
return PaletteFX.modeLabel(g.save.options.colors or "gbc")
end,
@@ -188,7 +189,7 @@ local function buildRows(game)
PaletteFX.setMode(o.colors)
return true
end },
{ id = "tilt", label = "TILT",
{ id = "tilt", label = Strings("TILT"),
value = function(g) return Tilt.levelLabel(g.save.options.tilt or 0) end,
step = function(g, dir)
local o = g.save.options
@@ -205,7 +206,7 @@ local function buildRows(game)
end
return true
end },
{ id = "gbcfx", label = "GBC FX",
{ id = "gbcfx", label = Strings("GBC FX"),
value = function(g)
return GBCFX.levelLabel(g.save.options.gbcfx or 0)
end,
@@ -215,7 +216,7 @@ local function buildRows(game)
GBCFX.setLevel(o.gbcfx)
return true
end },
{ id = "zoom", label = "ZOOM",
{ id = "zoom", label = Strings("ZOOM"),
value = function(g)
return Zoom.offsetLabel(g.save.options.zoom or 0)
end,
@@ -230,7 +231,7 @@ local function buildRows(game)
Zoom.offset = off
return true
end },
{ id = "voidFill", label = "VOID FILL",
{ id = "voidFill", label = Strings("VOID FILL"),
value = function(g)
return TileRenderer.voidFillLabel(g.save.options.voidFill)
end,
@@ -246,7 +247,7 @@ local function buildRows(game)
TileRenderer.setVoidFill(o.voidFill)
return true
end },
{ id = "videoMode", label = "VIDEO MODE",
{ id = "videoMode", label = Strings("VIDEO MODE"),
value = function(g)
return VideoMode.modeLabel(g.save.options.videoMode)
end,
@@ -259,7 +260,7 @@ local function buildRows(game)
-- hard render cap (issue #88): bounds the present rate so a
-- driver-forced vsync-off run cannot spin at thousands of FPS. Logic
-- is fixed-step off dt, so this touches presentation only.
{ id = "fpsCap", label = "MAX FPS",
{ id = "fpsCap", label = Strings("MAX FPS"),
value = function(g)
return FrameCap.label(g.save.options.fpsCap)
end,
@@ -271,7 +272,7 @@ local function buildRows(game)
end },
-- fast-forward the logic clock only; music and sfx keep their tempo
-- (src/core/GameSpeed.lua), so this is safe to leave on
{ id = "speed", label = "GAME SPEED",
{ id = "speed", label = Strings("GAME SPEED"),
value = function(g)
return GameSpeed.levelLabel(g.save.options.speed)
end,
@@ -282,17 +283,17 @@ local function buildRows(game)
end },
-- the manager's discoverable home (18-mod-manager-ux); inert until
-- opened, so the row costs a vanilla install nothing
{ id = "mods", label = "MODS",
{ id = "mods", label = Strings("MODS"),
value = function(g)
local status = g.modStatus or {}
return ("%d INSTALLED"):format(#(status.available or {}))
return Strings("%d INSTALLED", #(status.available or {}))
end,
activate = function(g)
require("src.ui.Screens").push(g, "ManagerState")
end },
-- rebinding UI (gap C2, 12-ui-extensibility 4.4); captured inputs
-- live in options.bindings, so the row costs a vanilla install nothing
{ id = "controls", label = "CONTROLS",
{ id = "controls", label = Strings("CONTROLS"),
activate = function(g)
require("src.ui.Screens").push(g, "BindingsMenu")
end },
+39 -33
View File
@@ -17,6 +17,7 @@ local Screens = require("src.ui.Screens")
local Theme = require("src.ui.Theme")
local FieldDefaults = require("src.world.FieldDefaults")
local Map = require("src.world.Map")
local Strings = require("src.core.Strings")
local PartyMenu = {}
PartyMenu.__index = PartyMenu
@@ -204,7 +205,7 @@ function PartyMenu:update(dt)
self.game.save.flashLit = true
self.game.stack:push(TextBox.new(self.game,
self.game.data.text._FlashLightsAreaText
or "A blinding FLASH\nlights the area!", function()
or Strings("A blinding FLASH\nlights the area!"), function()
self.game.stack:push(Transition.whiteFlash(self.game))
end))
return
@@ -247,7 +248,7 @@ function PartyMenu:update(dt)
current = "_CurrentTooFastText",
no_place = "_SurfingNoPlaceToGetOffText" })[reason]
or "_NoSurfingHereText"
local txt = (self.game.data.text[key] or "No SURFing here!")
local txt = (self.game.data.text[key] or Strings("No SURFing here!"))
:gsub("{RAM:wNameBuffer}", mon.nickname or def.name)
if reason == "no_place" then
-- .cannotStopSurfing prints _SurfingNoPlaceToGetOffText but
@@ -278,7 +279,7 @@ function PartyMenu:update(dt)
local def = self.game.data.pokemon[mon.species]
local key = (reason == "no_badge") and "_NewBadgeRequiredText"
or "_NothingToCutText"
local txt = (self.game.data.text[key] or "Nothing to CUT!")
local txt = (self.game.data.text[key] or Strings("Nothing to CUT!"))
:gsub("{RAM:wNameBuffer}", mon.nickname or def.name)
self.game.stack:push(TextBox.new(self.game, txt))
return -- .loop: submenu stays open behind the message
@@ -300,9 +301,9 @@ function PartyMenu:update(dt)
self.game.stack:pop() -- close the party menu (jp .goBackToMap)
ow.strengthActive = true
local t1 = (self.game.data.text._UsedStrengthText
or "{RAM:wNameBuffer} used\nSTRENGTH."):gsub("{RAM:wNameBuffer}", name)
or Strings("{RAM:wNameBuffer} used\nSTRENGTH.")):gsub("{RAM:wNameBuffer}", name)
local t2 = (self.game.data.text._CanMoveBouldersText
or "{RAM:wNameBuffer} can\nmove boulders."):gsub("{RAM:wNameBuffer}", name)
or Strings("{RAM:wNameBuffer} can\nmove boulders.")):gsub("{RAM:wNameBuffer}", name)
self.game.stack:push(TextBox.new(self.game, t1, function()
self.game.stack:push(TextBox.new(self.game, t2, function()
self.game.stack:push(Transition.whiteFlash(self.game))
@@ -348,7 +349,7 @@ function PartyMenu:update(dt)
or user.hp <= heal then
self.softboiledFrom = nil
local TextBox = require("src.render.TextBox")
self.game.stack:push(TextBox.new(self.game, "It won't have\nany effect."))
self.game.stack:push(TextBox.new(self.game, Strings("It won't have\nany effect.")))
else
user.hp = user.hp - heal
mon.hp = math.min(mon.stats.hp, mon.hp + heal)
@@ -357,7 +358,7 @@ function PartyMenu:update(dt)
local def = self.game.data.pokemon[mon.species]
local TextBox = require("src.render.TextBox")
self.game.stack:push(TextBox.new(self.game,
("%s's HP\nwas restored!"):format(mon.nickname or def.name)))
Strings("%s's HP\nwas restored!", mon.nickname or def.name)))
end
elseif self.swapFrom then
if self.swapFrom ~= self.index then
@@ -375,14 +376,14 @@ function PartyMenu:update(dt)
local ow = self.game.overworld
if self.battle and self.onSwitch then
-- SwitchStatsCancelText (core.asm PartyMenuOrRockOrRun)
items = { { label = "SWITCH", action = "battle_switch" },
{ label = "STATS", action = "stats" },
{ label = "CANCEL", action = "cancel" } }
items = { { label = Strings("SWITCH"), action = "battle_switch" },
{ label = Strings("STATS"), action = "stats" },
{ label = Strings("CANCEL"), action = "cancel" } }
else
-- STATS/SWITCH plus this mon's field moves (start_sub_menus.asm
-- builds the same dynamic list)
items = { { label = "STATS", action = "stats" },
{ label = "SWITCH", action = "switch" } }
items = { { label = Strings("STATS"), action = "stats" },
{ label = Strings("SWITCH"), action = "switch" } }
-- Field moves (HMs/TMs) are usable out of battle even when the mon
-- is fainted -- Gen 1 does not require HP for Cut/Fly/Surf/etc.
-- Battle still excludes this list via `not self.battle`. Softboiled
@@ -395,35 +396,35 @@ function PartyMenu:update(dt)
for _, mv in ipairs(mon.moves) do
if mv.id == "FLY" and outside
and self.game.save.inventory.THUNDERBADGE then
table.insert(items, { label = "FLY", action = "fly" })
table.insert(items, { label = Strings("FLY"), action = "fly" })
elseif mv.id == "FLASH" and ow.dark
and self.game.save.inventory.BOULDERBADGE then
table.insert(items, { label = "FLASH", action = "flash" })
table.insert(items, { label = Strings("FLASH"), action = "flash" })
elseif mv.id == "CUT" and self.game.save.inventory.CASCADEBADGE then
-- CUT/SURF/STRENGTH are party-menu field moves too
-- (start_sub_menus.asm .outOfBattleMovePointers); listed here
-- with the same list-time badge filter this file already uses
-- for FLY/FLASH. The facing-tile/activation check happens on
-- selection (useCutFieldMove/useSurfFieldMove).
table.insert(items, { label = "CUT", action = "cut" })
table.insert(items, { label = Strings("CUT"), action = "cut" })
elseif mv.id == "SURF" and self.game.save.inventory.SOULBADGE then
table.insert(items, { label = "SURF", action = "surf" })
table.insert(items, { label = Strings("SURF"), action = "surf" })
elseif mv.id == "STRENGTH" and self.game.save.inventory.RAINBOWBADGE then
table.insert(items, { label = "STRENGTH", action = "strength" })
table.insert(items, { label = Strings("STRENGTH"), action = "strength" })
elseif mv.id == "SOFTBOILED" then
table.insert(items, { label = "SOFTBOILED", action = "softboiled" })
table.insert(items, { label = Strings("SOFTBOILED"), action = "softboiled" })
elseif mv.id == "TELEPORT" and outside then
-- TELEPORT works only OUTDOORS (start_sub_menus.asm
-- .teleport -> CheckIfInOutsideMap); dark maps don't
-- block it
table.insert(items, { label = "TELEPORT", action = "escape" })
table.insert(items, { label = Strings("TELEPORT"), action = "escape" })
elseif mv.id == "DIG" and DIG_TILESETS[ow.map.def.tileset]
and ow.map.id ~= "AGATHAS_ROOM" then
-- DIG runs ItemUseEscapeRope (.dig sets wCurItem =
-- ESCAPE_ROPE): usable in the dungeon tilesets of
-- escape_rope_tilesets.asm minus Agatha's room, even in
-- the dark (Rock Tunnel)
table.insert(items, { label = "DIG", action = "escape" })
table.insert(items, { label = Strings("DIG"), action = "escape" })
end
end
end
@@ -456,13 +457,13 @@ function PartyMenu:bottomMessage()
return "Use on which one?"
elseif self.tmhm then
return self.game.data.text._PartyMenuUseTMText
or "Use TM on which\nPOKéMON?"
or Strings("Use TM on which\nPOKéMON?")
elseif self.battle then
return self.game.data.text._PartyMenuBattleText
or "Bring out which\nPOKéMON?"
or Strings("Bring out which\nPOKéMON?")
else
return self.game.data.text._PartyMenuNormalText
or "Choose a POKéMON."
or Strings("Choose a POKéMON.")
end
end
@@ -480,7 +481,7 @@ function PartyMenu:draw()
love.graphics.setColor(0, 0, 0, 1)
local party = self.party or self.game.save.party
if #party == 0 then
Font.draw("No POKéMON!", 16, 64)
Font.draw(Strings("No POKéMON!"), 16, 64)
end
local HudTiles = require("src.render.HudTiles")
for i, mon in ipairs(party) do
@@ -512,13 +513,13 @@ function PartyMenu:draw()
end
-- right-aligned so the shorter "ABLE" shares "NOT ABLE"'s right edge
if can then
Font.draw("ABLE", 120, y + 8)
Font.draw(Strings("ABLE"), 120, y + 8)
else
Font.draw("NOT ABLE", 88, y + 8)
Font.draw(Strings("NOT ABLE"), 88, y + 8)
end
else
if mon.hp <= 0 then
Font.draw("FNT", 136, y)
Font.draw(Strings("FNT"), 136, y)
elseif mon.status then
Font.draw(mon.status, 136, y)
end
@@ -528,17 +529,22 @@ function PartyMenu:draw()
love.graphics.setColor(0, 0, 0, 1)
Font.draw(("%3d/%3d"):format(mon.hp, mon.stats.hp), 104, y + 8)
end
-- home/pokemon.asm PartyMenuInit seeds wTopMenuItemY/X with 1/0, so the
-- cursor sits on the entry's *second* tile row (the level/HP line),
-- level with the middle of the two-row icon -- not on the name row that
-- entryY returns. Drawing it at y put it a tile too high (#278).
local cursorY = y + 8
if i == self.index then
Font.drawCode(Theme.cursor, 0, y)
Font.drawCode(Theme.cursor, 0, cursorY)
end
if i == self.swapFrom or i == self.softboiledFrom then
Font.drawCode(Theme.cursorHollow, 0, y) -- the unfilled swap arrow
Font.drawCode(Theme.cursorHollow, 0, cursorY) -- the unfilled swap arrow
end
end
if self.swapFrom then
Font.draw("Move to where?", 8, 136)
Font.draw(Strings("Move to where?"), 8, 136)
elseif self.softboiledFrom then
Font.draw("Use on which one?", 8, 136)
Font.draw(Strings("Use on which one?"), 8, 136)
elseif self.tmhm then
-- "Use TM on which\nPOKeMON?" in the standard bottom text box
-- (party_menu.asm keeps the message box for the TM/HM menu); box + line
@@ -546,14 +552,14 @@ function PartyMenu:draw()
Font.drawBox(0, 12, 20, 6)
love.graphics.setColor(0, 0, 0, 1)
local prompt = self.game.data.text._PartyMenuUseTMText
or "Use TM on which\nPOKéMON?"
or Strings("Use TM on which\nPOKéMON?")
local ly = 112
for line in (prompt .. "\n"):gmatch("([^\n]*)\n") do
Font.draw(line, 8, ly)
ly = ly + 16
end
elseif self.pickOnly then
Font.draw("Use on which one?", 8, 136)
Font.draw(Strings("Use on which one?"), 8, 136)
else
-- default field party menu (StartMenu) and the battle voluntary-switch
-- (BattleState:openParty): Gen1 prints PartyMenuNormalText / PartyMenuBattleText
+12 -11
View File
@@ -10,6 +10,7 @@ local ChoiceBox = require("src.ui.ChoiceBox")
local ListMenu = require("src.ui.ListMenu")
local Menu = require("src.ui.Menu")
local Sound = require("src.core.Sound")
local Strings = require("src.core.Strings")
local PlayerPC = {}
@@ -76,14 +77,14 @@ local function withdraw(game)
askQuantity(game, list, pc[item.value] or 1, item.value, function(qty)
local Bag = require("src.inventory.Bag")
if not Bag.add(game.save, item.value, qty) then
list.footer = "You can't carry\nany more items."
list.footer = Strings("You can't carry\nany more items.")
return
end
pc[item.value] = pc[item.value] - qty
if pc[item.value] <= 0 then pc[item.value] = nil end
refreshRow(list, pc, item.value)
Sound.play(game.data, "Withdraw_Deposit")
list.footer = ("Withdrew\n%s."):format(itemName(game, item.value))
list.footer = Strings("Withdrew\n%s.", itemName(game, item.value))
end)
end,
}))
@@ -112,14 +113,14 @@ local function deposit(game)
onChoose = function(item, list)
askQuantity(game, list, inv[item.value] or 1, item.value, function(qty)
if pcFull(game, pc, item.value) then
list.footer = "No room left to\nstore items."
list.footer = Strings("No room left to\nstore items.")
return
end
require("src.inventory.Bag").remove(game.save, item.value, qty)
pc[item.value] = (pc[item.value] or 0) + qty
refreshRow(list, inv, item.value)
Sound.play(game.data, "Withdraw_Deposit")
list.footer = ("%s was\nstored via PC."):format(itemName(game, item.value))
list.footer = Strings("%s was\nstored via PC.", itemName(game, item.value))
end)
end,
}))
@@ -132,7 +133,7 @@ local function toss(game)
onChoose = function(item, list)
local def = game.data.items[item.value]
if (def and def.keyItem) or item.value:find("^HM_") then
list.footer = "That's too impor-\ntant to toss!"
list.footer = Strings("That's too impor-\ntant to toss!")
return
end
local QuantityBox = require("src.ui.QuantityBox")
@@ -140,13 +141,13 @@ local function toss(game)
max = pc[item.value] or 1,
onDone = function(qty)
if not qty then return end
list.footer = ("Toss %s?"):format(itemName(game, item.value))
list.footer = Strings("Toss %s?", itemName(game, item.value))
game.stack:push(ChoiceBox.new(game, function(yes)
if yes then
pc[item.value] = pc[item.value] - qty
if pc[item.value] <= 0 then pc[item.value] = nil end
refreshRow(list, pc, item.value)
list.footer = ("Threw away %s."):format(itemName(game, item.value))
list.footer = Strings("Threw away %s.", itemName(game, item.value))
else
list.footer = nil
end
@@ -163,10 +164,10 @@ function PlayerPC.new(game)
-- keepOpen so B in the item lists returns here instead of dropping the
-- whole PC session (players_pc.asm re-shows the PC menu); same pattern
-- as BoxMenu's rows
{ label = "WITHDRAW ITEM", keepOpen = true, onSelect = function() withdraw(game) end },
{ label = "DEPOSIT ITEM", keepOpen = true, onSelect = function() deposit(game) end },
{ label = "TOSS ITEM", keepOpen = true, onSelect = function() toss(game) end },
{ label = "LOG OFF" },
{ label = Strings("WITHDRAW ITEM"), keepOpen = true, onSelect = function() withdraw(game) end },
{ label = Strings("DEPOSIT ITEM"), keepOpen = true, onSelect = function() deposit(game) end },
{ label = Strings("TOSS ITEM"), keepOpen = true, onSelect = function() toss(game) end },
{ label = Strings("LOG OFF") },
-- silent PC session (BIT_NO_MENU_BUTTON_SOUND); players_pc.asm
-- PlayersPCMenu TextBoxBorder (0,0) b=8 c=14 → 16x10
}, { tx = 0, ty = 0, tw = 16, th = 10, noSound = true })
+6 -5
View File
@@ -1,6 +1,7 @@
-- Minimal Pokédex: dex-ordered list with seen/owned markers.
local ListMenu = require("src.ui.ListMenu")
local Strings = require("src.core.Strings")
local PokedexMenu = {}
@@ -46,7 +47,7 @@ function PokedexMenu.new(game, opts)
end
end
local list = ListMenu.new(game, "POKéDEX", items, {
footer = ("SEEN %d OWNED %d"):format(seen, owned),
footer = Strings("SEEN %d OWNED %d", seen, owned),
pageJump = true, -- Left/Right page jumps like the original
onCancel = opts.onCancel, -- B returns to the start menu when opened from it
onChoose = function(item)
@@ -57,16 +58,16 @@ function PokedexMenu.new(game, opts)
local Menu = require("src.ui.Menu")
local Screens = require("src.ui.Screens")
game.stack:push(Menu.new(game, {
{ label = "DATA", onSelect = function()
{ label = Strings("DATA"), onSelect = function()
Screens.push(game, "DexEntryMenu", item.value)
end },
{ label = "CRY", keepOpen = true, onSelect = function()
{ label = Strings("CRY"), keepOpen = true, onSelect = function()
require("src.core.Sound").playCry(game.data, item.value)
end },
{ label = "AREA", onSelect = function()
{ label = Strings("AREA"), onSelect = function()
Screens.push(game, "TownMap", { nestSpecies = item.value })
end },
{ label = "QUIT" },
{ label = Strings("QUIT") },
}, { tx = 12, ty = 8, tw = 8, th = 10 }))
end,
})
+8 -7
View File
@@ -6,6 +6,7 @@
local Font = require("src.render.Font")
local SaveData = require("src.core.SaveData")
local Strings = require("src.core.Strings")
local QuarantineReport = {}
QuarantineReport.__index = QuarantineReport
@@ -38,12 +39,12 @@ local function buildLines(report, meta)
end
local rows = {}
for _, mon in ipairs(report.lostMons or {}) do
rows[#rows + 1] = ("%s (%s)"):format(mon.species or "?", mon.from or "?")
rows[#rows + 1] = Strings("%s (%s)", mon.species or "?", mon.from or "?")
end
section(lines, "Moved to LOST box:", rows)
rows = {}
for _, item in ipairs(report.lostItems or {}) do
rows[#rows + 1] = ("%s x%d"):format(item.id or "?", item.count or 1)
rows[#rows + 1] = Strings("%s x%d", item.id or "?", item.count or 1)
end
section(lines, "Items removed:", rows)
rows = {}
@@ -51,16 +52,16 @@ local function buildLines(report, meta)
if map.to then
rows[#rows + 1] = ("%s>%s"):format(map.id or "?", map.to)
else
rows[#rows + 1] = ("%s (%s)"):format(map.id or "?", map.field or "?")
rows[#rows + 1] = Strings("%s (%s)", map.id or "?", map.field or "?")
end
end
section(lines, "Location reset:", rows)
rows = {}
for _, mon in ipairs(report.restoredMons or {}) do
rows[#rows + 1] = ("%s to box %d"):format(mon.species or "?", mon.box or 0)
rows[#rows + 1] = Strings("%s to box %d", mon.species or "?", mon.box or 0)
end
for _, item in ipairs(report.restoredItems or {}) do
rows[#rows + 1] = ("%s x%d"):format(item.id or "?", item.count or 1)
rows[#rows + 1] = Strings("%s x%d", item.id or "?", item.count or 1)
end
section(lines, "Restored:", rows)
local notice = SaveData.modsDiffNotice(report.modsDiff, meta)
@@ -113,7 +114,7 @@ function QuarantineReport:draw()
love.graphics.rectangle("fill", 0, 0, 160, 144)
Font.drawBox(0, 0, 20, 18)
love.graphics.setColor(0, 0, 0, 1)
Font.draw("LOAD REPORT", 8, 8)
Font.draw(Strings("LOAD REPORT"), 8, 8)
for row = 1, VISIBLE do
local line = self.lines[self.offset + row]
if line then Font.draw(line, 8, 12 + row * 8) end
@@ -121,7 +122,7 @@ function QuarantineReport:draw()
if self.offset < self:maxOffset() then
Font.drawCode(require("src.ui.Theme").moreArrow, 144, 124)
end
Font.draw("A:CONTINUE", 8, 130)
Font.draw(Strings("A:CONTINUE"), 8, 130)
love.graphics.setColor(1, 1, 1, 1)
end
+10 -9
View File
@@ -12,6 +12,7 @@ local ChoiceBox = require("src.ui.ChoiceBox")
local ListMenu = require("src.ui.ListMenu")
local Menu = require("src.ui.Menu")
local QuantityBox = require("src.ui.QuantityBox")
local Strings = require("src.core.Strings")
local ShopMenu = {}
@@ -33,7 +34,7 @@ local function buy(game, stock)
end
local greet = txt(game, "_PokemartBuyingGreetingText", "Take your time.")
local notEnough = txt(game, "_PokemartNotEnoughMoneyText",
"You don't have\nenough money.")
Strings("You don't have\nenough money."))
local list
list = ListMenu.new(game, "BUY", items, {
dialogue = true,
@@ -56,7 +57,7 @@ local function buy(game, stock)
end
local cost = qty * def.price
-- _PokemartTellBuyPriceText + yes/no confirm
list.footer = ("%s?\nThat will be\n¥%d. OK?"):format(def.name, cost)
list.footer = Strings("%s?\nThat will be\n¥%d. OK?", def.name, cost)
game.stack:push(ChoiceBox.new(game, function(yes)
if not yes then
list.footer = greet
@@ -68,13 +69,13 @@ local function buy(game, stock)
end
if not Bag.add(game.save, item.value, qty) then
list.footer = txt(game, "_PokemartItemBagFullText",
"You can't carry\nany more items.")
Strings("You can't carry\nany more items."))
return
end
require("src.core.Sound").play(game.data, "Purchase")
game.save.money = game.save.money - cost
list.footer = txt(game, "_PokemartBoughtItemText",
"Here you are!\nThank you!")
Strings("Here you are!\nThank you!"))
end))
end,
}))
@@ -112,7 +113,7 @@ local function sell(game)
-- ITEM_NONE "0" from Blue's House before that pickup was fixed (#11).
if not def or def.keyItem or item.value:find("^HM_") then
list.footer = txt(game, "_PokemartUnsellableItemText",
"I can't put a\nprice on that.")
Strings("I can't put a\nprice on that."))
return
end
local unit = math.floor(def.price / 2)
@@ -125,7 +126,7 @@ local function sell(game)
return
end
-- _PokemartTellSellPriceText + yes/no confirm
list.footer = ("I can pay you\n¥%d for that."):format(unit * qty)
list.footer = Strings("I can pay you\n¥%d for that.", unit * qty)
game.stack:push(ChoiceBox.new(game, function(yes)
if not yes then
list.footer = greet
@@ -152,9 +153,9 @@ function ShopMenu.new(game, stock, onQuit)
-- keepOpen: the mart menu stays underneath its list so closing the
-- list lands back here; only QUIT (or B) leaves and fires onQuit
local menu = Menu.new(game, {
{ label = "BUY", keepOpen = true, onSelect = function() buy(game, stock) end },
{ label = "SELL", keepOpen = true, onSelect = function() sell(game) end },
{ label = "QUIT", onSelect = onQuit },
{ label = Strings("BUY"), keepOpen = true, onSelect = function() buy(game, stock) end },
{ label = Strings("SELL"), keepOpen = true, onSelect = function() sell(game) end },
{ label = Strings("QUIT"), onSelect = onQuit },
}, { tx = 0, ty = 0, tw = 8, th = 8 })
menu.onCancel = onQuit
return menu
+8 -7
View File
@@ -45,6 +45,7 @@
local Font = require("src.render.Font")
local Sound = require("src.core.Sound")
local Strings = require("src.core.Strings")
local SlotMachine = {}
SlotMachine.__index = SlotMachine
@@ -356,7 +357,7 @@ function SlotMachine:resolveWin(win)
-- SlotReward300Func prints "Yeah!" (text_pause) before the flash; the port
-- shows it in the box while the screen flashes. LinedUpText follows.
self.yeah = (sym == "7")
self.message = ("%s lined up!\nScored %d coins!"):format(sym, pay)
self.message = Strings("%s lined up!\nScored %d coins!", sym, pay)
-- .flashScreenLoop: flip rBGP, wait 5 frames, b times. The coins are not
-- credited until the player dismisses the "lined up" text (see startPayout).
self.stage = "flash"
@@ -394,7 +395,7 @@ end
-- delay then CloseTextDisplay), otherwise ask "One more go?".
function SlotMachine:afterSpin()
if coins(self) == 0 then
self.message = "Darn!\nRan out of coins!"
self.message = Strings("Darn!\nRan out of coins!")
self.stage = "message"
self.afterMessage = nil
self.exitTimer = 60
@@ -515,7 +516,7 @@ function SlotMachine:update(dt)
self.bet = 3 - self.betIndex
if input:wasPressed("a") then
if coins(self) < self.bet then
self.message = "Not enough\ncoins!"
self.message = Strings("Not enough\ncoins!")
self.afterMessage = "bet"
self.stage = "message"
return
@@ -671,8 +672,8 @@ function SlotMachine:drawBottom()
local by = self.stage == "intro" and 6 or 11
Font.drawBox(13, by, 6, 5)
love.graphics.setColor(0, 0, 0, 1)
Font.draw("YES", 15 * 8, (by + 1) * 8)
Font.draw("NO", 15 * 8, (by + 2) * 8)
Font.draw(Strings("YES"), 15 * 8, (by + 1) * 8)
Font.draw(Strings("NO"), 15 * 8, (by + 2) * 8)
Font.drawCode(0xED, 14 * 8, (by + 1 + (self.yesno == 1 and 0 or 1)) * 8)
end
love.graphics.setColor(1, 1, 1, 1)
@@ -723,8 +724,8 @@ function SlotMachine:drawPlain(art)
love.graphics.setColor(1, 1, 1, 1)
love.graphics.rectangle("fill", 0, 0, 160, 144)
love.graphics.setColor(0, 0, 0, 1)
Font.draw("SLOT MACHINE", 32, 4)
Font.draw(("COINS %4d"):format(coins(self)), 8, 16)
Font.draw(Strings("SLOT MACHINE"), 32, 4)
Font.draw(Strings("COINS %4d", coins(self)), 8, 16)
if art then self:drawReels(art) end
love.graphics.setColor(0, 0, 0, 1)
Font.draw(">", 12, 56)
+17 -16
View File
@@ -10,6 +10,7 @@ local Menu = require("src.ui.Menu")
local Renderer = require("src.render.Renderer")
local Runtime = require("src.mods.Runtime")
local Screens = require("src.ui.Screens")
local Strings = require("src.core.Strings")
local StartMenu = {}
@@ -26,19 +27,19 @@ function StartMenu.new(game)
-- POKéDEX: only after Oak hands it over
if flags.EVENT_GOT_POKEDEX then
table.insert(items, { label = "POKéDEX", onSelect = function()
table.insert(items, { label = Strings("POKéDEX"), onSelect = function()
Screens.push(game, "PokedexMenu", { onCancel = reopen })
end })
end
-- POKéMON is always listed (draw_start_menu.asm prints it even with
-- an empty party; selecting it then just no-ops)
table.insert(items, { label = "POKéMON", onSelect = function()
table.insert(items, { label = Strings("POKéMON"), onSelect = function()
if #game.save.party == 0 then return end
Screens.push(game, "PartyMenu", { onCancel = reopen })
end })
table.insert(items, { label = "ITEM", onSelect = function()
table.insert(items, { label = Strings("ITEM"), onSelect = function()
Screens.push(game, "BagMenu", { onCancel = reopen })
end })
@@ -50,7 +51,7 @@ function StartMenu.new(game)
-- SAVE shows the player/badges/dex/time panel then asks to confirm
-- (PrintSaveScreenText)
table.insert(items, { label = "SAVE", onSelect = function()
table.insert(items, { label = Strings("SAVE"), onSelect = function()
local TextBox = require("src.render.TextBox")
local ChoiceBox = require("src.ui.ChoiceBox")
local badges = require("src.inventory.Badges").count(game.data, game.save)
@@ -59,32 +60,32 @@ function StartMenu.new(game)
owned = owned + 1
end
local t = math.floor(game.save.playTime or 0)
local panel = ("PLAYER %s\nBADGES %d\nPOKéDEX %3d\nTIME %6d:%02d")
:format(game.save.player.name or "RED", badges, owned,
math.floor(t / 3600), math.floor(t / 60) % 60)
local panel = Strings("PLAYER %s\nBADGES %d\nPOKéDEX %3d\nTIME %6d:%02d",
game.save.player.name or "RED", badges, owned,
math.floor(t / 3600), math.floor(t / 60) % 60)
game.stack:push(TextBox.new(game,
panel .. "\fWould you like to\nSAVE the game?", function()
panel .. Strings("\fWould you like to\nSAVE the game?"), function()
game.stack:push(ChoiceBox.new(game, function(yes)
if not yes then return end
-- "Now saving..." beat before the write (save.asm
-- NowSavingString), then GameSavedText + SFX_SAVE
game.stack:push(TextBox.new(game, "Now saving...", function()
game.stack:push(TextBox.new(game, Strings("Now saving..."), function()
game:writeSave()
require("src.core.Sound").play(game.data, "Save")
game.stack:push(TextBox.new(game,
(game.save.player.name or "RED") .. " saved\nthe game!"))
Strings("%s saved\nthe game!", game.save.player.name or "RED")))
end))
end))
end))
end })
table.insert(items, { label = "OPTION", onSelect = function()
table.insert(items, { label = Strings("OPTION"), onSelect = function()
Screens.push(game, "OptionsMenu", { onCancel = reopen })
end })
-- LINK needs a party
if #game.save.party > 0 then
table.insert(items, { label = "LINK", onSelect = function()
table.insert(items, { label = Strings("LINK"), onSelect = function()
local LinkState = require("src.link.LinkState")
game.stack:push(LinkState.new(game))
end })
@@ -94,7 +95,7 @@ function StartMenu.new(game)
-- one discovered mod so a vanilla install's menu is unchanged
local status = game.modStatus
if status and #(status.available or {}) > 0 then
table.insert(items, { label = "MODS", onSelect = function()
table.insert(items, { label = Strings("MODS"), onSelect = function()
Screens.push(game, "ManagerState")
end })
end
@@ -102,10 +103,10 @@ function StartMenu.new(game)
-- the original's EXIT just closed the menu (CloseStartMenu); with a
-- window close button covering that, QUIT instead power-cycles back
-- to the title after a confirm (defaultNo guards accidental quits)
table.insert(items, { label = "QUIT", onSelect = function()
table.insert(items, { label = Strings("QUIT"), onSelect = function()
local TextBox = require("src.render.TextBox")
local ChoiceBox = require("src.ui.ChoiceBox")
game.stack:push(TextBox.new(game, "RETURN TO MAIN\nMENU?", function()
game.stack:push(TextBox.new(game, Strings("RETURN TO MAIN\nMENU?"), function()
game.stack:push(ChoiceBox.new(game, function(yes)
if yes then game:returnToTitle() end
end, { defaultNo = true }))
@@ -155,7 +156,7 @@ function StartMenu.new(game)
love.graphics.setColor(0, 0, 0, 1)
Font.draw(("%3d"):format(math.floor(safari.steps or 0)), 8, 8)
Font.draw("/500", 32, 8)
Font.draw("BALL", 8, 24)
Font.draw(Strings("BALL"), 8, 24)
Font.draw(("%2d"):format(math.floor(safari.balls or 0)), 48, 24)
love.graphics.setColor(1, 1, 1, 1)
end
+9 -8
View File
@@ -12,6 +12,7 @@ local Font = require("src.render.Font")
-- TypeChart.displayName maps it back to "PSYCHIC", like HallOfFame and the
-- battle move-type box already do (#214).
local TypeChart = require("src.battle.TypeChart")
local Strings = require("src.core.Strings")
local SummaryMenu = {}
SummaryMenu.__index = SummaryMenu
@@ -90,7 +91,7 @@ function SummaryMenu:draw()
drawLineBox(19, 1, 6, 10)
HudTiles.drawHPBar(data, 11, 3, mon, 1) -- wHPBarType 1
Font.draw(("%3d/%3d"):format(mon.hp, mon.stats.hp), 96, 32)
Font.draw("STATUS/", 72, 48)
Font.draw(Strings("STATUS/"), 72, 48)
Font.draw(mon.status or "OK", 128, 48)
-- stats box (0,8) 10x10: names rows 9/11/13/15, values indented
@@ -107,27 +108,27 @@ function SummaryMenu:draw()
-- TYPE1/TYPE2/IDNo/OT column (10,9) with values indented (11,10)
drawLineBox(19, 9, 8, 6)
Font.draw("TYPE1/", 80, 72)
Font.draw(Strings("TYPE1/"), 80, 72)
Font.draw(def.types[1] and TypeChart.displayName(def.types[1]) or "", 88, 80)
if def.types[2] then
Font.draw("TYPE2/", 80, 88)
Font.draw(Strings("TYPE2/"), 80, 88)
Font.draw(TypeChart.displayName(def.types[2]), 88, 96)
end
Font.draw("IDNo/", 80, 104)
Font.draw(Strings("IDNo/"), 80, 104)
-- the trainer ID is rolled at new game (SaveData.newGame) and
-- backfilled on load for old saves
Font.draw(("%05d"):format(mon.otId or game.save.player.id or 0), 96, 112)
Font.draw("OT/", 80, 120)
Font.draw(Strings("OT/"), 80, 120)
Font.draw(mon.ot or game.save.player.name or "RED", 96, 128)
else
-- page 2: EXP + the moves with PP (StatusScreen2)
drawLineBox(19, 1, 6, 10)
Font.draw("EXP POINTS", 72, 24)
Font.draw(Strings("EXP POINTS"), 72, 24)
Font.draw(("%d"):format(mon.exp), 96, 32)
-- StatusScreen2: "LEVEL UP" at (9,5); next-exp PrintNumber 7 cols
-- at (7,6); space at (14,6); PrintLevel at (16,6). The old
-- "%d to L%d" string at x=88 overflowed the DrawLineBox edge.
Font.draw("LEVEL UP", 72, 40)
Font.draw(Strings("LEVEL UP"), 72, 40)
local Growth = require("src.pokemon.Growth")
local nextExp = mon.level < 100
and (Growth.expForLevel(def.growthRate, mon.level + 1) - mon.exp) or 0
@@ -141,7 +142,7 @@ function SummaryMenu:draw()
if mv then
local mdef = data.moves[mv.id]
Font.draw(mdef.name, 16, y)
Font.draw("PP", 88, y + 8)
Font.draw(Strings("PP"), 88, y + 8)
Font.draw(("%2d/%2d"):format(mv.pp, mdef.pp), 112, y + 8)
else
Font.draw("-", 16, y)
+11 -10
View File
@@ -6,6 +6,7 @@
local Font = require("src.render.Font")
local Music = require("src.core.Music")
local GameVersion = require("src.core.GameVersion")
local Strings = require("src.core.Strings")
local TitleState = {}
TitleState.__index = TitleState
@@ -165,19 +166,19 @@ function ContinueInfo:draw()
-- box at (4,7), 8x14 content; labels double-spaced from (5,9)
Font.drawBox(4, 7, 16, 10)
love.graphics.setColor(0, 0, 0, 1)
Font.draw("PLAYER", 40, 72)
Font.draw(Strings("PLAYER"), 40, 72)
Font.draw((save.player and save.player.name) or "RED", 96, 72)
local badges = require("src.inventory.Badges").count(self.game.data, save)
Font.draw("BADGES", 40, 88)
Font.draw(Strings("BADGES"), 40, 88)
Font.draw(("%2d"):format(badges), 128, 88)
local owned = 0
for _ in pairs(save.pokedex and save.pokedex.owned or {}) do
owned = owned + 1
end
Font.draw("POKéDEX", 40, 104)
Font.draw(Strings("POKéDEX"), 40, 104)
Font.draw(("%3d"):format(owned), 120, 104)
local t = math.floor(save.playTime or 0)
Font.draw("TIME", 40, 120)
Font.draw(Strings("TIME"), 40, 120)
Font.draw(("%3d:%02d"):format(math.floor(t / 3600),
math.floor(t / 60) % 60), 104, 120)
love.graphics.setColor(1, 1, 1, 1)
@@ -188,7 +189,7 @@ function TitleState:openMenu()
local game = self.game
local items = {}
if hasSave() then
table.insert(items, { label = "CONTINUE", onSelect = function()
table.insert(items, { label = Strings("CONTINUE"), onSelect = function()
-- peek at the save for the info window; fall through if the
-- file can't be read
local ok, loaded = pcall(require("src.core.SaveData").load)
@@ -199,13 +200,13 @@ function TitleState:openMenu()
end
end })
end
table.insert(items, { label = "NEW GAME", onSelect = function()
table.insert(items, { label = Strings("NEW GAME"), onSelect = function()
if self.onNewGame then self.onNewGame() end
end })
table.insert(items, { label = "OPTION", onSelect = function()
table.insert(items, { label = Strings("OPTION"), onSelect = function()
require("src.ui.Screens").push(game, "OptionsMenu")
end })
table.insert(items, { label = "EXIT GAME", onSelect = function()
table.insert(items, { label = Strings("EXIT GAME"), onSelect = function()
if love.event and love.event.quit then
love.event.quit()
end
@@ -254,7 +255,7 @@ function TitleState:draw()
love.graphics.draw(self.logo, 16, 8)
else
love.graphics.setColor(0, 0, 0, 1)
Font.draw(self.blue and "POKéMON BLUE" or "POKéMON RED",
Font.draw(self.blue and "POKéMON BLUE" or Strings("POKéMON RED"),
(160 - 12 * 8) / 2, 24)
love.graphics.setColor(1, 1, 1, 1)
end
@@ -290,7 +291,7 @@ function TitleState:draw()
love.graphics.setColor(0, 0, 0, 1)
-- the copyright row (tile 2,17); copyrightText because field.title's
-- copyright key already names the extracted image strip
Font.draw(self.title.copyrightText or "2026 bois club games", 1, 136)
Font.draw(self.title.copyrightText or Strings("2026 bois club games"), 1, 136)
love.graphics.setColor(1, 1, 1, 1)
end
+2 -1
View File
@@ -4,6 +4,7 @@
local Font = require("src.render.Font")
local Sound = require("src.core.Sound")
local TextBox = require("src.render.TextBox")
local Strings = require("src.core.Strings")
local TradeAnim = {}
TradeAnim.__index = TradeAnim
@@ -343,7 +344,7 @@ function TradeAnim:drawMonInfo(mon, ot, otId, boxTy)
love.graphics.setColor(0, 0, 0, 1)
Font.draw(no, 56, y0)
Font.draw(speciesName(self.game, mon), 40, y0 + 16)
Font.draw("OT/" .. (ot or "????"), 40, y0 + 32)
Font.draw(Strings("OT/%s", ot or "????"), 40, y0 + 32)
Font.draw(("IDNo.%05d"):format(otId or 0), 40, y0 + 48)
love.graphics.setColor(1, 1, 1, 1)
end
+10 -4
View File
@@ -4,8 +4,10 @@
-- are built from the real trainer_info.png frame tiles (the patterned
-- band + line style).
local Assets = require("src.render.Assets")
local Badges = require("src.inventory.Badges")
local Font = require("src.render.Font")
local Strings = require("src.core.Strings")
local TrainerCard = {}
TrainerCard.__index = TrainerCard
@@ -16,8 +18,11 @@ function TrainerCard:sgbPalettes(game)
return require("src.render.PaletteFX").wholeNamed(game.data, "MEWMON")
end
-- through Assets.resolve so an enabled mod's overrides/ shadows these the
-- same way it shadows every other generated asset
local function tryImage(path)
local ok, img = pcall(love.graphics.newImage, path)
if not path then return nil end
local ok, img = pcall(love.graphics.newImage, Assets.resolve(path))
return ok and img or nil
end
@@ -62,7 +67,8 @@ function TrainerCard.new(game, opts)
end
end
self.circle = tryImage("assets/generated/trainer_card/circle_tile.png")
self.pic = tryImage("assets/generated/trainer_card/red.png")
self.pic = tryImage(require("src.pokemon.Sprites").playerPath(
game.data, "front", { kind = "trainer_card" }))
return self
end
@@ -114,7 +120,7 @@ function TrainerCard:draw()
love.graphics.draw(self.pic, 104, 4)
end
love.graphics.setColor(0, 0, 0, 1)
Font.draw("NAME/" .. (save.player.name or "RED"), 16, 16)
Font.draw(Strings("NAME/%s", save.player.name or "RED"), 16, 16)
Font.draw(("MONEY/¥%d"):format(save.money or 0), 16, 32)
local t = math.floor(save.playTime or 0)
Font.draw(("TIME/%3d:%02d"):format(math.floor(t / 3600),
@@ -123,7 +129,7 @@ function TrainerCard:draw()
-- the circle-dotted BADGES banner (TrainerInfo_BadgesText)
self:frameBox(0, 8, 20, 3)
love.graphics.setColor(0, 0, 0, 1)
Font.draw("BADGES", 56, 72)
Font.draw(Strings("BADGES"), 56, 72)
if self.circle then
love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(self.circle, 48, 72)