mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-19 20:20:19 +02:00
Merge branch 'dev' of https://github.com/bryanthaboi/gen1recomp into dev
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
-- Test returning to launcher from Gen 1 and Gen 2 on Android without closing the process
|
||||
-- luajit tests/engine/android_exit_to_launcher_test.lua
|
||||
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.harness")
|
||||
local check, eq = T.check, T.eq
|
||||
love = love or require("tests.love_stub")
|
||||
|
||||
local TitleState = require("src.ui.TitleState")
|
||||
local Gen2MainMenu = require("src.ui.gen2.MainMenu")
|
||||
local Runtime = require("src.mods.Runtime")
|
||||
|
||||
-- 1. Test Gen 1 TitleState onExit callback support
|
||||
do
|
||||
local exitCalled = false
|
||||
local dummyGame = {
|
||||
data = { field = {} },
|
||||
stack = {
|
||||
states = {},
|
||||
push = function(self, state) table.insert(self.states, state) end,
|
||||
top = function(self) return self.states[#self.states] end,
|
||||
pop = function(self) return table.remove(self.states) end,
|
||||
},
|
||||
}
|
||||
local state = TitleState.new(dummyGame, {
|
||||
onExit = function()
|
||||
exitCalled = true
|
||||
end,
|
||||
})
|
||||
state:openMenu()
|
||||
local menu = dummyGame.stack:top()
|
||||
check(menu ~= nil, "TitleState:openMenu opens a menu")
|
||||
local exitItem = nil
|
||||
for _, item in ipairs(menu.items or {}) do
|
||||
if tostring(item.label):find("EXIT", 1, true) then
|
||||
exitItem = item
|
||||
break
|
||||
end
|
||||
end
|
||||
check(exitItem ~= nil, "Gen 1 TitleState menu contains an EXIT GAME item")
|
||||
if exitItem and exitItem.onSelect then
|
||||
exitItem.onSelect()
|
||||
end
|
||||
check(exitCalled, "Selecting EXIT GAME in Gen 1 TitleState invokes onExit callback")
|
||||
end
|
||||
|
||||
-- 2. Test Gen 2 MainMenu onExit callback support
|
||||
do
|
||||
local exitCalled = false
|
||||
local dummyGame2 = {
|
||||
data = {},
|
||||
stack = {
|
||||
states = {},
|
||||
push = function(self, state) table.insert(self.states, state) end,
|
||||
top = function(self) return self.states[#self.states] end,
|
||||
pop = function(self) return table.remove(self.states) end,
|
||||
},
|
||||
}
|
||||
local menu = Gen2MainMenu.new(dummyGame2, {
|
||||
hasSave = false,
|
||||
onExit = function()
|
||||
exitCalled = true
|
||||
end,
|
||||
})
|
||||
menu:choose("exit")
|
||||
check(exitCalled, "Selecting EXIT GAME in Gen 2 MainMenu invokes onExit callback")
|
||||
end
|
||||
|
||||
-- 3. Test Runtime.reset restores NullEvents and NullHooks
|
||||
do
|
||||
Runtime.install({ emit = function() end }, { call = function() end }, { "err" })
|
||||
check(Runtime.errors ~= nil, "Runtime has errors list after install")
|
||||
Runtime.reset()
|
||||
check(Runtime.errors == nil, "Runtime.reset clears errors")
|
||||
check(Runtime.currentMod == nil, "Runtime.reset clears currentMod")
|
||||
check(Runtime.modRequire == nil, "Runtime.reset clears modRequire")
|
||||
end
|
||||
|
||||
T.finish("android_exit_to_launcher_test")
|
||||
@@ -0,0 +1,81 @@
|
||||
-- tests/engine/android_shortcuts_payload_test.lua
|
||||
-- Tests Android dynamic shortcuts synchronization, launch options intent resolution,
|
||||
-- and love.handlers.intent_game in-process game hot-swapping.
|
||||
-- luajit tests/engine/android_shortcuts_payload_test.lua
|
||||
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.harness")
|
||||
local check, eq = T.check, T.eq
|
||||
love = love or require("tests.love_stub")
|
||||
|
||||
require("main")
|
||||
|
||||
-- 1. Verify LaunchOptions handles getLaunchGame on Android
|
||||
local LaunchOptions = require("src.core.LaunchOptions")
|
||||
|
||||
local savedOS = love.system and love.system.getOS
|
||||
local savedGetLaunchGame = love.system and love.system.getLaunchGame
|
||||
|
||||
love.system = love.system or {}
|
||||
love.system.getOS = function() return "Android" end
|
||||
love.system.getLaunchGame = function() return "gold" end
|
||||
|
||||
local game, slot = LaunchOptions.resolve({})
|
||||
check(game == "gold", "LaunchOptions resolves intent game from love.system.getLaunchGame on Android")
|
||||
|
||||
local gameCli, slotCli = LaunchOptions.resolve({ "--game=red" })
|
||||
check(gameCli == "red", "CLI --game flag overrides intent game")
|
||||
|
||||
-- 2. Verify RomImporter.syncAndroidShortcuts ranking and 4-item cap
|
||||
local RomImporter = require("src.import.RomImporter")
|
||||
|
||||
local originalIsReady = RomImporter.isReady
|
||||
local capturedShortcuts = nil
|
||||
|
||||
love.system.updateShortcuts = function(versions)
|
||||
capturedShortcuts = versions
|
||||
return true
|
||||
end
|
||||
|
||||
-- Mock isReady
|
||||
RomImporter.isReady = function(v)
|
||||
return v == "red" or v == "gold" or v == "blue" or v == "yellow"
|
||||
end
|
||||
|
||||
local ok = RomImporter.syncAndroidShortcuts("gold")
|
||||
check(ok == true, "syncAndroidShortcuts returns true on Android")
|
||||
check(#capturedShortcuts == 4, "syncAndroidShortcuts caps at 4 items")
|
||||
check(capturedShortcuts[1] == "gold", "activeVersion 'gold' is placed first")
|
||||
|
||||
-- Test with subset of ready games (e.g. only Red and Gold)
|
||||
RomImporter.isReady = function(v)
|
||||
return v == "red" or v == "gold"
|
||||
end
|
||||
|
||||
capturedShortcuts = nil
|
||||
RomImporter.syncAndroidShortcuts("red")
|
||||
check(#capturedShortcuts == 2, "syncAndroidShortcuts only includes ready ROMs")
|
||||
check(capturedShortcuts[1] == "red" and capturedShortcuts[2] == "gold", "ready ROMs correctly passed")
|
||||
|
||||
-- Test on non-Android platform (safe no-op)
|
||||
love.system.getOS = function() return "Linux" end
|
||||
capturedShortcuts = nil
|
||||
local nonAndroidOk = RomImporter.syncAndroidShortcuts("red")
|
||||
check(nonAndroidOk == false, "syncAndroidShortcuts safely no-ops on non-Android")
|
||||
check(capturedShortcuts == nil, "no shortcuts updated on non-Android")
|
||||
|
||||
-- 3. Verify love.handlers.intent_game definition
|
||||
check(type(love.handlers.intent_game) == "function", "main.lua defines love.handlers.intent_game")
|
||||
|
||||
-- Restore
|
||||
RomImporter.isReady = originalIsReady
|
||||
if savedOS then
|
||||
love.system.getOS = savedOS
|
||||
else
|
||||
love.system.getOS = nil
|
||||
end
|
||||
love.system.getLaunchGame = savedGetLaunchGame
|
||||
love.system.updateShortcuts = nil
|
||||
|
||||
print("8/8 checks passed (android_shortcuts_payload_test)")
|
||||
@@ -397,7 +397,8 @@ local GEN2_HOOKS = {
|
||||
"world.tod", "map.palette", "fieldmove.eligibility",
|
||||
-- menus and the battle intro
|
||||
"ui.start_menu.items", "ui.title_menu.items", "ui.options.rows",
|
||||
"ui.party.submenu", "ui.naming.grid", "ui.pc.items", "ui.list_menu",
|
||||
"ui.party.submenu", "ui.party.grid_navigation", "ui.naming.grid",
|
||||
"ui.pc.items", "ui.list_menu",
|
||||
"transition.style",
|
||||
-- battle
|
||||
"battle.damage", "battle.crit", "battle.accuracy", "battle.turn_order",
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
-- BoxMenu's Yellow-only "Pikachu looks unhappy" release path
|
||||
-- (release()'s isYellow()/species=="PIKACHU"/otId/ot branch) pushes its
|
||||
-- TextBox with `TextBox.new(game, (...):gsub(...))` -- the gsub call is
|
||||
-- the last argument, unparenthesized, so Lua expands its second return
|
||||
-- value (the substitution count) into TextBox.new's third parameter,
|
||||
-- onDone. TextBox.lua later calls onDone() unconditionally once the box
|
||||
-- is dismissed, and a number is not callable: every release of your own
|
||||
-- caught Pikachu in Yellow crashed, regardless of its nickname (unlike
|
||||
-- the separate %-escape gsub bug, this needs no special save content --
|
||||
-- ordinary play reaches it every time). ROM-free: registers a fake
|
||||
-- Data.pokemon.PIKACHU cloned from the fixture species so the species ==
|
||||
-- "PIKACHU" check can be exercised without a real ROM import.
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.modkit")
|
||||
local Data = T.fixtures.fresh()
|
||||
local ids = T.fixtures.ids
|
||||
require("src.render.Font").load(Data)
|
||||
|
||||
-- clone a real fixture species under the literal id release() checks for
|
||||
Data.pokemon.PIKACHU = Data.pokemon[ids.species[1]]
|
||||
|
||||
local Pokemon = require("src.pokemon.Pokemon")
|
||||
local Boxes = require("src.pokemon.Boxes")
|
||||
local TextBox = require("src.render.TextBox")
|
||||
local BoxMenu = require("src.ui.BoxMenu")
|
||||
local ListMenu = require("src.ui.ListMenu")
|
||||
local ChoiceBox = require("src.ui.ChoiceBox")
|
||||
local SaveData = require("src.core.SaveData")
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
local Sound = require("src.core.Sound")
|
||||
|
||||
local realCry, realPlay = Sound.playCry, Sound.play
|
||||
Sound.playCry = function() end
|
||||
Sound.play = function() end
|
||||
|
||||
local stack = { states = {} }
|
||||
function stack:push(s) self.states[#self.states + 1] = s end
|
||||
function stack:pop()
|
||||
local t = self.states[#self.states]
|
||||
self.states[#self.states] = nil
|
||||
return t
|
||||
end
|
||||
function stack:top() return self.states[#self.states] end
|
||||
function stack:update(dt)
|
||||
local t = self:top()
|
||||
if t and t.update then t:update(dt) end
|
||||
end
|
||||
|
||||
local pressed = {}
|
||||
local function press(btn)
|
||||
pressed = { [btn] = true }
|
||||
stack:update(1 / 60)
|
||||
pressed = {}
|
||||
end
|
||||
|
||||
local function topMt() return getmetatable(stack:top()) end
|
||||
local function mash(btn, cond, n)
|
||||
for _ = 1, (n or 400) do
|
||||
if cond() then return true end
|
||||
press(btn)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
GameVersion.set("yellow")
|
||||
|
||||
local save = SaveData.newGame()
|
||||
local game = {
|
||||
data = Data,
|
||||
save = save,
|
||||
stack = stack,
|
||||
input = {
|
||||
wasPressed = function(_, key) return pressed[key] or false end,
|
||||
isDown = function() return false end,
|
||||
},
|
||||
}
|
||||
game.save.options = game.save.options or {}
|
||||
game.save.options.textSpeed = 1
|
||||
|
||||
local box = Boxes.active(save)
|
||||
local mon = Pokemon.new(Data, "PIKACHU", 5)
|
||||
mon.otId = save.player.id
|
||||
mon.ot = save.player.name
|
||||
box[1] = mon
|
||||
|
||||
stack:push(BoxMenu.new(game))
|
||||
press("down"); press("down"); press("a") -- open RELEASE list
|
||||
T.check(topMt() == ListMenu, "RELEASE opens the box list")
|
||||
|
||||
-- release() calls Sound.playCry (stubbed) then pushes the "unhappy"
|
||||
-- TextBox before any confirmation prompt -- pre-fix this line itself
|
||||
-- raises "attempt to call field 'onDone' (a number value)" the moment
|
||||
-- TextBox.new stores the leaked count and something dismisses the box.
|
||||
local ok, err = pcall(function()
|
||||
press("a") -- choose the Pikachu; release() runs synchronously here
|
||||
T.check(topMt() == TextBox, "the unhappy-Pikachu TextBox opens directly, no confirm prompt")
|
||||
-- dismiss it: this is what calls onDone, which is where the pre-fix
|
||||
-- leaked count used to crash
|
||||
mash("a", function() return topMt() ~= TextBox end)
|
||||
end)
|
||||
T.check(ok, "releasing your own caught Pikachu in Yellow does not crash: " .. tostring(err))
|
||||
|
||||
GameVersion.set("red")
|
||||
Sound.playCry, Sound.play = realCry, realPlay
|
||||
T.finish("pikachu_unhappy_release_crash")
|
||||
@@ -26,5 +26,7 @@ check(helperStart ~= nil, "requiredFilesFor helper exists")
|
||||
local helper = src:sub(helperStart, start)
|
||||
check(helper:find("VERSION_REQUIRED_FILES_OVERRIDE", 1, true) ~= nil,
|
||||
"requiredFilesFor consults VERSION_REQUIRED_FILES_OVERRIDE")
|
||||
check(src:find('"assets/generated/battle/hud/balls.png"', 1, true) ~= nil,
|
||||
"Gold caches require the trainer HUD ball sheet")
|
||||
|
||||
T.finish()
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local S = require("tests.harness").suite("safe mode and issue report")
|
||||
local check = S.check
|
||||
|
||||
local SaveData = require("src.core.SaveData")
|
||||
local LauncherMods = require("src.mods.LauncherMods")
|
||||
local IssueReport = require("src.core.IssueReport")
|
||||
local Version = require("src.core.Version")
|
||||
|
||||
local options = SaveData.defaultOptions()
|
||||
check(not SaveData.isSafeMode(options), "safe mode defaults off")
|
||||
SaveData.setSafeMode(options, true)
|
||||
check(SaveData.isSafeMode(options), "safe mode can be enabled")
|
||||
|
||||
local manifests = {
|
||||
{ id = "alpha", name = "Alpha", version = "1.0.0", experimental = false,
|
||||
raw = {}, dependencySpecs = {}, conflictSpecs = {} },
|
||||
{ id = "beta", name = "Beta", version = "1.0.0", experimental = false,
|
||||
raw = {}, dependencySpecs = {}, conflictSpecs = {} },
|
||||
}
|
||||
options.mods.alpha = false
|
||||
options.mods.beta = true
|
||||
local rows = LauncherMods.deriveList(manifests, options, "red")
|
||||
check(#rows == 2, "safe mode keeps installed mods visible")
|
||||
check(not rows[1].enabled and not rows[2].enabled,
|
||||
"safe mode disables every launcher mod row")
|
||||
check(rows[1].status == "safe_mode" and rows[2].status == "safe_mode",
|
||||
"safe mode explains every disabled launcher row")
|
||||
|
||||
SaveData.setSafeMode(options, false)
|
||||
rows = LauncherMods.deriveList(manifests, options, "red")
|
||||
local byId = {}
|
||||
for _, row in ipairs(rows) do byId[row.id] = row end
|
||||
check(not byId.alpha.enabled and byId.beta.enabled,
|
||||
"turning safe mode off restores saved mod choices")
|
||||
|
||||
local previousLove = _G.love
|
||||
local openedURL
|
||||
_G.love = {
|
||||
getVersion = function() return 12, 0, 0, "Mysterious Mysteries" end,
|
||||
system = {
|
||||
getOS = function() return "iOS" end,
|
||||
getModel = function() return "iPad Test" end,
|
||||
openURL = function(url) openedURL = url end,
|
||||
},
|
||||
graphics = {
|
||||
getRendererInfo = function()
|
||||
return "Metal", "3.0", "Apple", "Simulator GPU"
|
||||
end,
|
||||
getDimensions = function() return 1024, 768 end,
|
||||
getPixelDimensions = function() return 2048, 1536 end,
|
||||
},
|
||||
window = {
|
||||
getMode = function() return 1024, 768, { fullscreen = false } end,
|
||||
},
|
||||
}
|
||||
|
||||
local url, fields, info = IssueReport.build({
|
||||
safeMode = true,
|
||||
lastVersion = "gold",
|
||||
}, {
|
||||
version = "gold",
|
||||
mods = { { id = "alpha", name = "Alpha", enabled = true } },
|
||||
})
|
||||
check(url:find("template=bug_report.yml", 1, true) ~= nil,
|
||||
"report URL selects the bug form")
|
||||
check(url:find("title=bug%3A%20replace%20this%20with%20a%20meaningful%20title", 1, true) ~= nil,
|
||||
"report URL uses the requested bug title")
|
||||
check(info.os == "iOS",
|
||||
"report metadata maps the platform")
|
||||
check(fields.mods_which == "",
|
||||
"safe mode leaves the optional mod list blank")
|
||||
check(not url:find("game=", 1, true)
|
||||
and not url:find("os=", 1, true)
|
||||
and not url:find("mods_enabled=", 1, true),
|
||||
"report URL omits unsupported dropdown and checkbox prefills")
|
||||
check(fields.summary == "" and fields.location == "" and fields.screenshot == ""
|
||||
and fields.steps == "" and fields.expected == "",
|
||||
"report leaves user-entered fields blank")
|
||||
check(info.metadata:find("Device: iPad Test", 1, true) ~= nil
|
||||
and info.metadata:find("LÖVE: 12.0.0", 1, true) ~= nil
|
||||
and info.metadata:find("Safe mode: on", 1, true) ~= nil,
|
||||
"report metadata includes device and app details")
|
||||
check(not info.metadata:find("unknown", 1, true),
|
||||
"report metadata omits unknown values")
|
||||
check(not info.metadata:find("Game id", 1, true)
|
||||
and not info.metadata:find("Game:", 1, true)
|
||||
and not info.metadata:find("Mods:", 1, true)
|
||||
and not info.metadata:find("Processors", 1, true)
|
||||
and not info.metadata:find("Power", 1, true),
|
||||
"report metadata omits redundant system fields")
|
||||
|
||||
local previousEngine = Version.engine
|
||||
Version.engine = "0.1.50"
|
||||
local _, versionFields, versionInfo = IssueReport.build({}, { mods = {} })
|
||||
check(versionFields.version == "0.1.50"
|
||||
and versionInfo.metadata:find("App: gen1recomp v0.1.50", 1, true) ~= nil,
|
||||
"report uses the stamped app version")
|
||||
Version.engine = previousEngine
|
||||
local _, _, developmentInfo = IssueReport.build({}, { mods = {} })
|
||||
check(not developmentInfo.metadata:find("0.0.0-dev", 1, true),
|
||||
"report omits an unstamped development version")
|
||||
|
||||
local previousOS = love.system.getOS
|
||||
local previousModel = love.system.getModel
|
||||
local previousIO = _G.io
|
||||
love.system.getOS = function() return "OS X" end
|
||||
love.system.getModel = nil
|
||||
_G.io = {
|
||||
popen = function()
|
||||
return {
|
||||
read = function() return "MacBookPro18,3" end,
|
||||
close = function() end,
|
||||
}
|
||||
end,
|
||||
}
|
||||
local desktopInfo = IssueReport.metadata({}, { mods = {} })
|
||||
check(desktopInfo.device == "MacBookPro18,3",
|
||||
"report finds desktop device model when LOVE has no model")
|
||||
love.system.getOS = function() return "UWP" end
|
||||
local xboxInfo = IssueReport.metadata({}, { mods = {} })
|
||||
check(xboxInfo.os == "Xbox", "report maps the Xbox runtime platform")
|
||||
love.system.getOS = previousOS
|
||||
love.system.getModel = previousModel
|
||||
_G.io = previousIO
|
||||
|
||||
local opened = IssueReport.open({ safeMode = false }, {
|
||||
version = "red",
|
||||
mods = {},
|
||||
})
|
||||
check(opened and openedURL and openedURL:find("title=bug%3A%20replace%20this%20with%20a%20meaningful%20title", 1, true) ~= nil,
|
||||
"report action opens the generated URL")
|
||||
|
||||
_G.love = previousLove
|
||||
|
||||
S.finish()
|
||||
@@ -0,0 +1,166 @@
|
||||
-- SummaryMenu.lua:148 and PartyMenu.lua:824 used to draw mon.status as a
|
||||
-- bare literal ("PSN", "PAR", "BRN", "FRZ", "SLP"), invisible to any
|
||||
-- translation a mod supplies. Unlike the strings catalog, a mod translates
|
||||
-- status abbreviations through the statuses content registry
|
||||
-- (mod.content.statuses:patch(id, { label = value }), label only), the
|
||||
-- same registry src/battle/BattleState.lua:statusLabel already reads in
|
||||
-- battle. This test drives both screens' status draw with a mod-patched
|
||||
-- registry and checks the patched label reaches Font.draw, not the raw
|
||||
-- status id.
|
||||
--
|
||||
-- It also guards a second bug found alongside the first: Status.RECORDS'
|
||||
-- five vanilla entries used to set hudLabel to the same literal as label
|
||||
-- ("FRZ", hudLabel = "FRZ", ...). Since Status.hudLabelFor (and
|
||||
-- BattleState:statusLabel before it) reads "hudLabel or label", and
|
||||
-- Registry:patch only overrides the fields a mod actually passes, a
|
||||
-- real label-only patch left the untouched vanilla hudLabel shadowing it
|
||||
-- forever -- the translation was stored but never displayed anywhere,
|
||||
-- in or out of battle.
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.harness")
|
||||
|
||||
love = love or {}
|
||||
love.graphics = {
|
||||
setColor = function() end,
|
||||
rectangle = function() end,
|
||||
draw = function() end,
|
||||
push = function() end, pop = function() end,
|
||||
translate = function() end, scale = function() end,
|
||||
}
|
||||
|
||||
package.loaded["src.render.Font"] = {
|
||||
draw = function() end,
|
||||
drawCode = function() end,
|
||||
drawBox = function() end,
|
||||
}
|
||||
package.loaded["src.render.HudTiles"] = {
|
||||
statusTile = function() end,
|
||||
tile = function() end,
|
||||
drawHPBar = function() end,
|
||||
}
|
||||
package.loaded["src.render.PaletteFX"] = {
|
||||
shader = function() return nil end,
|
||||
pal = function() return nil end,
|
||||
markTrueColor = function() end,
|
||||
}
|
||||
package.loaded["src.ui.Theme"] = { cursor = 0, cursorHollow = 0 }
|
||||
package.loaded["src.render.Assets"] = {}
|
||||
package.loaded["src.world.FieldDefaults"] = {}
|
||||
package.loaded["src.world.Map"] = {}
|
||||
package.loaded["src.mods.Runtime"] = { wantsHook = function() return false end }
|
||||
package.loaded["src.ui.Screens"] = {}
|
||||
package.loaded["src.core.Logger"] = { warn = function() end }
|
||||
|
||||
local Status = require("src.battle.Status")
|
||||
|
||||
-- a mod's registered status translation, same shape mod.content.statuses:
|
||||
-- patch(id, { label = ..., hudLabel = ... }) merges into Data.statuses
|
||||
local function moddedStatuses()
|
||||
local statuses = {}
|
||||
for id, record in pairs(Status.RECORDS) do statuses[id] = record end
|
||||
statuses.PSN = { id = "PSN", label = "PSN", hudLabel = "TOX" }
|
||||
return statuses
|
||||
end
|
||||
|
||||
local Font = package.loaded["src.render.Font"]
|
||||
local drawn
|
||||
local origDraw = Font.draw
|
||||
Font.draw = function(text, x, y)
|
||||
drawn[#drawn + 1] = { text = text, x = x, y = y }
|
||||
return origDraw(text, x, y)
|
||||
end
|
||||
|
||||
local function mkDef()
|
||||
return { name = "BULBASAUR", dex = 1, types = { "GRASS" } }
|
||||
end
|
||||
|
||||
local function mkMon(status)
|
||||
return {
|
||||
nickname = "SAUR", species = "BULBASAUR", level = 5,
|
||||
hp = 10, stats = { hp = 10, attack = 5, defense = 5, speed = 5, special = 5 },
|
||||
status = status,
|
||||
}
|
||||
end
|
||||
|
||||
-- ---- SummaryMenu: page 1's STATUS/ line (~line 148-151) ----
|
||||
do
|
||||
local SummaryMenu = assert(loadfile("src/ui/SummaryMenu.lua"))()
|
||||
local game = {
|
||||
data = { pokemon = { BULBASAUR = mkDef() }, statuses = moddedStatuses() },
|
||||
save = { player = { id = 1, name = "RED" } },
|
||||
}
|
||||
local menu = setmetatable(
|
||||
{ game = game, mon = mkMon("PSN"), page = 1 }, SummaryMenu)
|
||||
drawn = {}
|
||||
menu:draw()
|
||||
local statusDraw
|
||||
for _, d in ipairs(drawn) do
|
||||
if d.x == 128 and d.y == 48 then statusDraw = d end
|
||||
end
|
||||
T.check(statusDraw ~= nil, "SummaryMenu draws a status label at (128,48)")
|
||||
T.eq(statusDraw.text, "TOX",
|
||||
"SummaryMenu draws the mod-patched hudLabel, not the raw status id")
|
||||
end
|
||||
|
||||
-- vanilla (no mod): falls back to the plain id, same as before the fix
|
||||
do
|
||||
local SummaryMenu = assert(loadfile("src/ui/SummaryMenu.lua"))()
|
||||
local game = {
|
||||
data = { pokemon = { BULBASAUR = mkDef() }, statuses = nil },
|
||||
save = { player = { id = 1, name = "RED" } },
|
||||
}
|
||||
local menu = setmetatable(
|
||||
{ game = game, mon = mkMon("PSN"), page = 1 }, SummaryMenu)
|
||||
drawn = {}
|
||||
menu:draw()
|
||||
local statusDraw
|
||||
for _, d in ipairs(drawn) do
|
||||
if d.x == 128 and d.y == 48 then statusDraw = d end
|
||||
end
|
||||
T.eq(statusDraw.text, "PSN",
|
||||
"SummaryMenu still shows the vanilla PSN label with no mod loaded")
|
||||
end
|
||||
|
||||
-- ---- PartyMenu: the roster row's status column (~line 824-827) ----
|
||||
do
|
||||
local PartyMenu = assert(loadfile("src/ui/PartyMenu.lua"))()
|
||||
PartyMenu.drawIcon = function() end
|
||||
local game = {
|
||||
data = { pokemon = { BULBASAUR = mkDef() }, statuses = moddedStatuses(),
|
||||
text = {} },
|
||||
save = { party = { mkMon("PSN") } },
|
||||
}
|
||||
local list = setmetatable({ game = game, index = 1 }, PartyMenu)
|
||||
drawn = {}
|
||||
list:draw()
|
||||
local statusDraw
|
||||
for _, d in ipairs(drawn) do
|
||||
if d.x == 136 then statusDraw = d end
|
||||
end
|
||||
T.check(statusDraw ~= nil, "PartyMenu draws a status label at x=136")
|
||||
T.eq(statusDraw.text, "TOX",
|
||||
"PartyMenu draws the mod-patched hudLabel, not the raw status id")
|
||||
end
|
||||
|
||||
-- ---- real Registry:patch, not a hand-built table: a label-only patch (the
|
||||
-- shape a translation mod would send for every one of the 5 vanilla
|
||||
-- statuses) must reach the HUD despite the vanilla record already
|
||||
-- defining hudLabel ----
|
||||
do
|
||||
local Registry = require("src.mods.Registry")
|
||||
local reg = Registry.new("statuses", { semantics = "record", target = "statuses" })
|
||||
reg.base = function() return Status.RECORDS end
|
||||
local LABEL_ONLY_PATCH = { SLP = "SOM", FRZ = "GEL", PSN = "PSN", BRN = "BRU", PAR = "PAR" }
|
||||
for id, translated in pairs(LABEL_ONLY_PATCH) do
|
||||
reg:patch(id, { label = translated }, "mod")
|
||||
end
|
||||
local merged = {}
|
||||
for id in pairs(Status.RECORDS) do merged[id] = reg:get(id) end
|
||||
for id, translated in pairs(LABEL_ONLY_PATCH) do
|
||||
T.eq(Status.hudLabelFor(merged, id), translated,
|
||||
"a label-only mod patch on " .. id .. " reaches the HUD label")
|
||||
end
|
||||
end
|
||||
|
||||
T.finish("status_abbreviation_translation_test")
|
||||
@@ -14,6 +14,7 @@ local NamingScreen = require("src.ui.NamingScreen")
|
||||
local TextBox = require("src.render.TextBox")
|
||||
local ChoiceBox = require("src.ui.ChoiceBox")
|
||||
local PartyMenu = require("src.ui.PartyMenu")
|
||||
local Gen2PartyMenu = require("src.ui.gen2.PartyMenu")
|
||||
local Player = require("src.world.Player")
|
||||
local Music = require("src.core.Music")
|
||||
|
||||
@@ -295,6 +296,16 @@ do
|
||||
menu:update(0)
|
||||
check(menu.index == 4,
|
||||
"removing the hook restores native list navigation immediately")
|
||||
|
||||
local gold = Gen2PartyMenu.new(game, { battle = true })
|
||||
unsub = wrap("ui.party.grid_navigation", function() return true end)
|
||||
gold:update(0)
|
||||
check(gold.index == 3,
|
||||
"a Gold battle party can follow the same companion grid")
|
||||
unsub()
|
||||
gold:update(0)
|
||||
check(gold.index == 4,
|
||||
"Gold restores native party list navigation without the hook")
|
||||
end
|
||||
|
||||
-- ------- music.volume (distance / indoor muffling)
|
||||
|
||||
Reference in New Issue
Block a user