mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-17 11:11:10 +02:00
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:
@@ -9,6 +9,7 @@
|
||||
local Json = require("src.link.Json")
|
||||
local Logger = require("src.core.Logger")
|
||||
local Runtime = require("src.mods.Runtime")
|
||||
local Strings = require("src.core.Strings")
|
||||
|
||||
local DiscordPresence = {
|
||||
APP_ID = "1529183141267374262",
|
||||
@@ -436,7 +437,7 @@ end
|
||||
local function buildActivity()
|
||||
local details, activityState
|
||||
if state.activity == "battle" then
|
||||
details = state.battleLabel or "In battle"
|
||||
details = state.battleLabel or Strings("In battle")
|
||||
activityState = state.location and (state.location) or "Kanto"
|
||||
elseif state.activity == "exploring" and state.location then
|
||||
details = state.location
|
||||
@@ -550,20 +551,20 @@ local function subscribe(game)
|
||||
|
||||
track("battle.started", function(ev)
|
||||
local battle = ev and ev.battle
|
||||
local label = "In battle"
|
||||
local label = Strings("In battle")
|
||||
if ev and ev.kind == "wild" then
|
||||
local name = speciesName(game, ev.species)
|
||||
if name then
|
||||
label = "Battling wild " .. name
|
||||
if ev.level then label = label .. " Lv" .. tostring(ev.level) end
|
||||
else
|
||||
label = "Wild battle"
|
||||
label = Strings("Wild battle")
|
||||
end
|
||||
elseif ev and ev.kind == "trainer" then
|
||||
local tname = battle and battle.trainer and battle.trainer.name
|
||||
label = tname and ("Battling " .. tname) or "Trainer battle"
|
||||
label = tname and ("Battling " .. tname) or Strings("Trainer battle")
|
||||
elseif ev and ev.kind == "link" then
|
||||
label = "Link battle"
|
||||
label = Strings("Link battle")
|
||||
end
|
||||
local mapId = state.mapId
|
||||
or (game.save and game.save.player and game.save.player.map)
|
||||
@@ -594,7 +595,7 @@ local function subscribe(game)
|
||||
state.mapId = nil
|
||||
setPresence({
|
||||
activity = "menu",
|
||||
location = id == "IntroMovie" and "Watching the intro" or "Title screen",
|
||||
location = id == "IntroMovie" and "Watching the intro" or Strings("Title screen"),
|
||||
clearBattle = true,
|
||||
})
|
||||
end
|
||||
|
||||
@@ -53,6 +53,9 @@ function Game:load()
|
||||
require("src.render.Font").load(Data)
|
||||
-- menu cursor/border/geometry constants; field.theme restyles them
|
||||
require("src.ui.Theme").load(Data)
|
||||
-- the engine's own text, after the merge so a translation mod's catalog
|
||||
-- is already in Data.strings; empty on a mod-free boot and skipped
|
||||
require("src.core.Strings").load(Data)
|
||||
|
||||
self.stack = StateStack
|
||||
StateStack:init()
|
||||
|
||||
+13
-6
@@ -1155,9 +1155,16 @@ function SaveData.defaultHeal(boot)
|
||||
end
|
||||
|
||||
-- Post-credits home (issue #103). pokered left the player in HALL_OF_FAME
|
||||
-- after jp Init; this port places CONTINUE at the NewGameWarp bedroom and
|
||||
-- retargets LAST_MAP exits (Red's house mats) at the heal-point town so
|
||||
-- leaving the house does not dump the player back at Indigo Plateau.
|
||||
-- after jp Init; this port relocates CONTINUE instead, and retargets
|
||||
-- LAST_MAP exits (Red's house mats) at the heal-point town so leaving the
|
||||
-- house does not dump the player back at Indigo Plateau.
|
||||
--
|
||||
-- The landing spot is the blackout point, not the NewGameWarp bedroom:
|
||||
-- HallOfFameResetEventsAndSaveScript sets wLastBlackoutMap := PALLET_TOWN,
|
||||
-- so resuming puts the player outside the front door. Spawning them back
|
||||
-- in the upstairs bedroom was a port-only detour (#253). Non-vanilla
|
||||
-- spawns are unaffected -- defaultHeal returns boot's own start map for
|
||||
-- those, which is what the bedroom branch resolved to anyway.
|
||||
-- Marks postGameHomeOk so a later intentional HoF save is not relocated.
|
||||
function SaveData.applyPostGameHome(save, boot)
|
||||
boot = type(boot) == "table" and boot or {}
|
||||
@@ -1165,9 +1172,9 @@ function SaveData.applyPostGameHome(save, boot)
|
||||
save.lastHeal = { map = heal.map, x = heal.x, y = heal.y }
|
||||
save.lastOutdoor = { id = heal.map, x = heal.x, y = heal.y }
|
||||
save.player = save.player or {}
|
||||
save.player.map = boot.startMap or "REDS_HOUSE_2F"
|
||||
save.player.x = boot.startX or 3
|
||||
save.player.y = boot.startY or 6
|
||||
save.player.map = heal.map
|
||||
save.player.x = heal.x
|
||||
save.player.y = heal.y
|
||||
save.player.facing = boot.startFacing or "down"
|
||||
save.postGameHomeOk = true
|
||||
return heal
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
-- The engine's own player-facing text, made overridable.
|
||||
--
|
||||
-- Extracted dialogue already had a home: `Data.text`, keyed by pokered's
|
||||
-- labels, which a mod reaches through `mod.content.text`. The strings the
|
||||
-- engine *authors* had none. Battle messages, item results, menu labels
|
||||
-- and the link-play screens were literals in Lua, so a translator could
|
||||
-- reach two thirds of the game and no more (#186, #245).
|
||||
--
|
||||
-- Those literals stay where they are, wrapped in `S(...)`, and the English
|
||||
-- source doubles as the catalog key:
|
||||
--
|
||||
-- self:say(S("But, it failed!"))
|
||||
-- self:say(S("Wild %s\nappeared!", self.enemy.name))
|
||||
--
|
||||
-- Keying on the source rather than on an invented id is deliberate. It
|
||||
-- keeps the English readable at the point it is used (the alternative
|
||||
-- scatters a thousand `battle.it_failed` ids that have to be looked up to
|
||||
-- review a diff), it needs no id registry to stay in sync, and an entry a
|
||||
-- translation has not covered yet falls through to English instead of
|
||||
-- rendering a raw id at the player. The cost is that editing an English
|
||||
-- string orphans its translations; `tools/modkit.py translation --refresh`
|
||||
-- reports those as changed keys rather than silently dropping them.
|
||||
--
|
||||
-- Same-source-different-meaning is the one case source keys cannot hold on
|
||||
-- their own ("OFF" as a filter setting vs "OFF" as a toggle, which some
|
||||
-- languages render differently). Those sites pass a context:
|
||||
--
|
||||
-- S("OFF", "options.musicFilter") -- key: "options.musicFilter|OFF"
|
||||
--
|
||||
-- A mod supplies the catalog through the `strings` registry:
|
||||
--
|
||||
-- mod.content.strings:override("But, it failed!", "Echec !")
|
||||
--
|
||||
-- With no mod loaded the catalog is empty and `S` is an identity function
|
||||
-- guarded by one boolean, so a vanilla boot draws byte-identical text.
|
||||
|
||||
local Strings = {}
|
||||
|
||||
local catalog = nil -- Data.strings once a mod has put something in it
|
||||
local missing = {} -- format-arity complaints, reported once each
|
||||
|
||||
-- Called from Game after the mod merge, and again on dev-mode hot reload.
|
||||
-- Holding the table (not a copy) means a mod that registers late still
|
||||
-- takes effect without a second load.
|
||||
function Strings.load(data)
|
||||
local t = data and data.strings
|
||||
catalog = nil
|
||||
if type(t) ~= "table" then return end
|
||||
for _ in pairs(t) do
|
||||
catalog = t
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
function Strings.active()
|
||||
return catalog ~= nil
|
||||
end
|
||||
|
||||
-- The lookup itself. `context` is optional and only disambiguates sources
|
||||
-- that collide; the plain key is tried after it, so a translation that does
|
||||
-- not care about the distinction can supply one entry for both.
|
||||
function Strings.lookup(source, context)
|
||||
if not catalog then return source end
|
||||
if context then
|
||||
local hit = catalog[context .. "|" .. source]
|
||||
if type(hit) == "string" then return hit end
|
||||
end
|
||||
local hit = catalog[source]
|
||||
if type(hit) == "string" then return hit end
|
||||
return source
|
||||
end
|
||||
|
||||
-- Count `%`-directives so a translation that drops or adds one is caught
|
||||
-- here rather than as a mid-battle `string.format` error.
|
||||
local function specifiers(s)
|
||||
local n = 0
|
||||
for spec in s:gmatch("%%(.)") do
|
||||
if spec ~= "%" then n = n + 1 end
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
-- S(source) -> translated source
|
||||
-- S(source, ...) -> translated source, string.format'ed
|
||||
-- S(source, context) -> context-disambiguated lookup, no formatting
|
||||
--
|
||||
-- The two-argument forms are told apart by whether the source carries any
|
||||
-- format directives: a source with no `%s` cannot be formatting, so a lone
|
||||
-- string second argument is a context.
|
||||
function Strings.get(source, ...)
|
||||
local argc = select("#", ...)
|
||||
if argc == 0 then return Strings.lookup(source) end
|
||||
|
||||
local wants = specifiers(source)
|
||||
if wants == 0 and argc == 1 and type((...)) == "string" then
|
||||
return Strings.lookup(source, (...))
|
||||
end
|
||||
|
||||
local text = Strings.lookup(source)
|
||||
-- A translation with the wrong arity would raise inside string.format,
|
||||
-- which in a battle means a crash the player cannot escape. Fall back to
|
||||
-- the English source, which is known to match, and say so once.
|
||||
if specifiers(text) ~= wants then
|
||||
if not missing[source] then
|
||||
missing[source] = true
|
||||
require("src.core.Logger").warn(
|
||||
"strings: translation of %q has %d format directives, source has %d"
|
||||
.. " -- using the source", source, specifiers(text), wants)
|
||||
end
|
||||
text = source
|
||||
end
|
||||
local ok, out = pcall(string.format, text, ...)
|
||||
if not ok then return source end
|
||||
return out
|
||||
end
|
||||
|
||||
-- A marker, not a lookup: returns its argument untouched.
|
||||
--
|
||||
-- Some templates are declared in a module-level table and formatted much
|
||||
-- later (BattleState's charge-move lines, for one). Translating at the
|
||||
-- declaration would freeze the English, because those tables are built at
|
||||
-- require time and Strings.load has no catalog yet; the use site therefore
|
||||
-- calls Strings(template, ...) and looks the source up then. That works at
|
||||
-- runtime but leaves the literal invisible to the catalog generator, which
|
||||
-- only sees what is spelled out at a call site. Wrapping the declaration in
|
||||
-- Strings.source puts it back in the harvest while changing nothing at all
|
||||
-- about when the lookup happens.
|
||||
function Strings.source(text)
|
||||
return text
|
||||
end
|
||||
|
||||
setmetatable(Strings, { __call = function(_, ...) return Strings.get(...) end })
|
||||
|
||||
return Strings
|
||||
Reference in New Issue
Block a user