-- The strings an ENGINE routine prints, looked up by their pokegold label. -- -- data/generated/text.lua is keyed by "bank:addr", because every string in it -- was found by following a script pointer. The Day-Care, the POKeMART and the -- Hall of Fame are printed by asm instead (`ld hl, .SomeText / call -- PrintText`), so RomExtractorGen2's NAMED_TEXT seeds the walker at those -- symbols by name and writes text.labels[label] -> that key. This is the -- lookup on the other side of that table: a screen asks for -- "_MartWelcomeText" and never for an address, so a repointed string still -- resolves and a cache built before the seed simply answers nil. -- -- `pages` puts the decoded stream back into the shape the Gen 2 speech box -- draws it in -- up to two lines per screenful -- following home/text.asm: -- -- \n `line` / `next`: the box's second row. -- \f `para`: PlaceString clears the box, so the next screenful starts empty. -- \v `cont`: the box SCROLLS one row, so the line that was on the bottom -- row is now on the top one and the new text lands under it. That is -- why a `cont` shows up here as a page whose first line repeats the -- previous page's second. -- -- `fill` substitutes the markers the decoder leaves behind for the values the -- cart splices at runtime: {STRBUF} for a TX_RAM name and {NUM} for a -- TX_DECIMAL PrintNum field, both in the order they appear, plus the named -- {PLAYER} / {RIVAL}. local CommonText = {} -- The decoded string for a pokegold label, or nil when this cache predates -- the seed (or the string is genuinely empty, like _DaycareDummyText). function CommonText.get(text, label) if type(text) ~= "table" or not label then return nil end local labels = text.labels local key = type(labels) == "table" and labels[label] local body = key and text[key] if type(body) ~= "string" or body == "" then return nil end return body end -- One page is an array of one or two lines. function CommonText.pages(body) if type(body) ~= "string" or body == "" then return nil end local out = {} local top, bottom = "", nil local function flush() if bottom then out[#out + 1] = { top, bottom } else out[#out + 1] = { top } end end local i = 1 while i <= #body do local marker = body:find("[\n\f\v]", i) local chunk = body:sub(i, (marker or (#body + 1)) - 1) if bottom then bottom = bottom .. chunk else top = top .. chunk end if not marker then break end local m = body:sub(marker, marker) if m == "\n" then bottom = bottom or "" elseif m == "\f" then flush() top, bottom = "", nil else -- "\v" flush() top, bottom = bottom or "", "" end i = marker + 1 end flush() return out end -- values: an array consumed in order by {STRBUF} and {NUM}, and optionally -- values.player / values.rival for the two named markers. function CommonText.fill(pages, values) if not pages then return nil end values = values or {} local next_ = 1 -- One pass over both markers, because the order they are CONSUMED in is the -- order they appear in: _MartFinalPriceText opens on its {NUM} and -- _BargainShopFinalPriceText on its {STRBUF}, and filling one kind before -- the other would swap the price and the item name on one of them. local function marker(name) if name == "PLAYER" or name == "RIVAL" then return values[name:lower()] or ("{" .. name .. "}") end if name ~= "STRBUF" and name ~= "NUM" then return "{" .. name .. "}" end local v = values[next_] next_ = next_ + 1 return v ~= nil and tostring(v) or "" end local out = {} for p, page in ipairs(pages) do local lines = {} for l, line in ipairs(page) do lines[l] = (line:gsub("{(%u+)}", marker)) end out[p] = lines end return out end -- The whole lookup in one call: nil when the cache has no such label, which -- is every call site's cue to fall back to its own transcription. function CommonText.of(text, label, values) local pages = CommonText.pages(CommonText.get(text, label)) if not pages then return nil end if values then return CommonText.fill(pages, values) end return pages end return CommonText