mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-03 08:07:07 -04:00
+1
-12
@@ -545,22 +545,11 @@ function Game.wideBattleInStack(stack)
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Which of "battle"/"overworld"/"menu" per-category GAME SPEED (RFC 0007)
|
||||
-- applies right now. Whole-stack, the same idiom as fillScaleInStack/
|
||||
-- wideBattleInStack above: an overlay with neither marker (PartyMenu,
|
||||
-- ChoiceBox, a NamingScreen, a text box) is transparent to the walk and
|
||||
-- inherits whatever is under it, making the category a property of the
|
||||
-- STACK POSITION the overlay sits over, not of the overlay itself. A
|
||||
-- scripted sequence (script.started/ended) never pushes a state of its
|
||||
-- own either -- it runs through the owning overworld/battle state's own
|
||||
-- script runner or message queue -- so it inherits the same way. Nothing
|
||||
-- identifying as either (the title screen, credits, an intro cutscene
|
||||
-- with nothing under it) falls to "menu", the bucket every non-gameplay
|
||||
-- screen gets; see the RFC's Decisions section for the full reasoning.
|
||||
function Game.speedCategoryInStack(stack)
|
||||
local states = stack and stack.states
|
||||
for i = #(states or {}), 1, -1 do
|
||||
local state = states[i]
|
||||
if state and state.isMenu then return "menu" end
|
||||
if state and state.isBattle then return "battle" end
|
||||
if state and state.isOverworld then return "overworld" end
|
||||
end
|
||||
|
||||
@@ -2462,6 +2462,7 @@ function Game2:applyOptions()
|
||||
hotbar = options.hotbar,
|
||||
})
|
||||
require("src.core.VideoMode").applyOptions(options)
|
||||
require("src.core.Orientation").applyOptions(options)
|
||||
require("src.core.FaithfulRes").applyOptions(options)
|
||||
require("src.core.ScreenPosition").applyOptions(options)
|
||||
require("src.core.VSync").applyOptions(options)
|
||||
|
||||
@@ -604,6 +604,10 @@ function Game3:fixedUpdate(dt)
|
||||
end
|
||||
|
||||
function Game3:speedCategory()
|
||||
local Stack = lazyReq("src.ui.game3.stack")
|
||||
for i = #(Stack._layers or {}), 1, -1 do
|
||||
if Stack._layers[i].isMenu then return "menu" end
|
||||
end
|
||||
local okB, Battle = pcall(lazyReq, "src.core.game3.battle")
|
||||
if okB and Battle and Battle.isActive and Battle.isActive() then
|
||||
return "battle"
|
||||
|
||||
@@ -976,6 +976,13 @@ function Collision.isWater(cx, cy)
|
||||
return Collision.isWaterOn(Collision._mapDef, cx, cy, Collision.cell(cx, cy))
|
||||
end
|
||||
|
||||
-- pokeemerald/src/field_player_avatar.c:693
|
||||
-- pokefirered/src/field_player_avatar.c:568
|
||||
function Collision.isSurfDismount(cx, cy, elevation)
|
||||
return Collision.elevationAt(cx, cy) == 3
|
||||
and Collision.elevationMismatchOn(Collision._mapDef, elevation, cx, cy)
|
||||
end
|
||||
|
||||
-- pokeemerald/src/rotating_gate.c:961
|
||||
function Collision.rotatingGateCollision(game, dir, x, y)
|
||||
local RG = package.loaded["src.core.game3.rotating_gate"]
|
||||
|
||||
@@ -604,7 +604,7 @@ function Player.tryMove(dir, game, run)
|
||||
return "blocked", "acro"
|
||||
end
|
||||
local isDismount = Player.surfing and not Player.underwater
|
||||
and (not (Collision.isWater and Collision.isWater(tx, ty)))
|
||||
and Collision.isSurfDismount(tx, ty, Player.currentElevation)
|
||||
if isDismount then
|
||||
Player.dismounting = true
|
||||
lazyReq("src.core.game3.audio").stopSurfMusic()
|
||||
@@ -747,8 +747,8 @@ function Player.forcedStep(dir, frames, opts)
|
||||
beginStep(opts.ledgeX, opts.ledgeY, false, true)
|
||||
else
|
||||
local tx, ty = Player.cellX + d[1], Player.cellY + d[2]
|
||||
Player.dismounting = Player.surfing
|
||||
and (not (Collision.isWater and Collision.isWater(tx, ty))) or false
|
||||
Player.dismounting = Player.surfing and not Player.underwater
|
||||
and Collision.isSurfDismount(tx, ty, Player.currentElevation) or false
|
||||
-- pokefirered/src/field_player_avatar.c:1609
|
||||
if Player.dismounting then lazyReq("src.core.game3.audio").stopSurfMusic() end
|
||||
beginStep(tx, ty, false, false)
|
||||
@@ -931,11 +931,6 @@ local function finishStep(game)
|
||||
elseif Player.dismounting then
|
||||
Player.dismounting = false
|
||||
Player.surfing = false
|
||||
elseif Player.surfing then
|
||||
local onWater = Collision.isWater and Collision.isWater(Player.cellX, Player.cellY)
|
||||
if not onWater then
|
||||
Player.surfing = false
|
||||
end
|
||||
end
|
||||
|
||||
local session = package.loaded["src.core.game3.runtime"]
|
||||
|
||||
@@ -353,6 +353,7 @@ Save.DEFAULT_OPTIONS = {
|
||||
haptics = "light",
|
||||
touchControls = { enabled = true },
|
||||
screenPos = "center",
|
||||
orientation = "auto",
|
||||
}
|
||||
|
||||
function Save.defaultOptions()
|
||||
@@ -374,7 +375,7 @@ Save.OPTIONS_KEY = "gold"
|
||||
|
||||
local SHARED_KEYS = {
|
||||
touchControls = true, haptics = true, screenPos = true,
|
||||
videoMode = true, faithfulRes = true,
|
||||
videoMode = true, faithfulRes = true, orientation = true,
|
||||
mods = true, modsByVersion = true, modsGen2 = true,
|
||||
modOptions = true, modProfiles = true, modProfilesSeeded = true,
|
||||
activeProfile = true,
|
||||
@@ -400,6 +401,7 @@ function Save.loadOptions(fs)
|
||||
end
|
||||
end
|
||||
end
|
||||
options.orientation = require("src.core.Orientation").normalize(options.orientation)
|
||||
return options
|
||||
end
|
||||
|
||||
@@ -413,7 +415,8 @@ function Save.saveOptions(options, fs)
|
||||
local block = {}
|
||||
for key, value in pairs(options) do
|
||||
if SHARED_KEYS[key] then
|
||||
file[key] = value
|
||||
file[key] = key == "orientation"
|
||||
and require("src.core.Orientation").normalize(value) or value
|
||||
else
|
||||
block[key] = value
|
||||
end
|
||||
|
||||
@@ -146,6 +146,19 @@ local function addTouchRows(rows, add, opts, hooks)
|
||||
end
|
||||
end
|
||||
|
||||
local function addOrientationRow(add, opts)
|
||||
local okOr, Orientation = pcall(require, "src.core.Orientation")
|
||||
if okOr and (Orientation.isAndroid() or Orientation.isIOS()) then
|
||||
add(Strings("ORIENTATION"),
|
||||
function() return Strings(Orientation.modeLabel(opts.orientation)) end,
|
||||
function(dir)
|
||||
opts.orientation = Orientation.cycle(opts.orientation, dir)
|
||||
Orientation.apply(opts.orientation)
|
||||
return true
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
local function coreRows(opts, hooks)
|
||||
local rows = {}
|
||||
local function add(label, value, step)
|
||||
@@ -295,21 +308,7 @@ local function coreRows(opts, hooks)
|
||||
end)
|
||||
end
|
||||
|
||||
-- ORIENTATION (#592, #1638): mobile only. Unlike the other launcher rows
|
||||
-- this one live-applies: the window exists here too, and rotating under
|
||||
-- the player's finger is the only feedback that reads.
|
||||
do
|
||||
local okOr, Orientation = pcall(require, "src.core.Orientation")
|
||||
if okOr and (Orientation.isAndroid() or Orientation.isIOS()) then
|
||||
add(Strings("ORIENTATION"),
|
||||
function() return Strings(Orientation.modeLabel(opts.orientation)) end,
|
||||
function(dir)
|
||||
opts.orientation = Orientation.cycle(opts.orientation, dir)
|
||||
Orientation.apply(opts.orientation)
|
||||
return true
|
||||
end)
|
||||
end
|
||||
end
|
||||
addOrientationRow(add, opts)
|
||||
|
||||
local okFr, FaithfulRes = pcall(require, "src.core.FaithfulRes")
|
||||
if okFr then
|
||||
@@ -738,6 +737,7 @@ local function gen2Rows(opts, hooks, shared)
|
||||
end)
|
||||
|
||||
addTouchRows(rows, add, shared, hooks)
|
||||
addOrientationRow(add, shared)
|
||||
|
||||
return rows
|
||||
end
|
||||
|
||||
+2
-1
@@ -8,7 +8,7 @@ local Runtime = require("src.mods.Runtime")
|
||||
local TextBox = require("src.render.TextBox")
|
||||
local romText = require("src.core.RomText")
|
||||
|
||||
local BagMenu = {}
|
||||
local BagMenu = { isMenu = true }
|
||||
|
||||
local Bag = require("src.inventory.Bag")
|
||||
local Strings = require("src.core.Strings")
|
||||
@@ -735,6 +735,7 @@ function BagMenu.new(game, opts)
|
||||
-- the item box overlaps the kept-open START menu box, so neither docks to
|
||||
-- a screen edge on its own (start_sub_menus.asm:302-329) #1745
|
||||
list.holdsUIAnchors = true
|
||||
list.isMenu = true
|
||||
return list
|
||||
end
|
||||
|
||||
|
||||
+2
-1
@@ -11,7 +11,7 @@ local Stats = require("src.pokemon.Stats")
|
||||
local TextBox = require("src.render.TextBox")
|
||||
local Strings = require("src.core.Strings")
|
||||
|
||||
local BoxMenu = {}
|
||||
local BoxMenu = { isMenu = true }
|
||||
|
||||
-- engine/pokemon/bills_pc.asm:118
|
||||
-- vChars2 $78; engine/menus/save.asm:497
|
||||
@@ -396,6 +396,7 @@ function BoxMenu.new(game)
|
||||
baseDraw(self)
|
||||
drawChrome(game)
|
||||
end
|
||||
menu.isMenu = true
|
||||
return menu
|
||||
end
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ local Font = require("src.render.Font")
|
||||
local Strings = require("src.core.Strings")
|
||||
local Theme = require("src.ui.Theme")
|
||||
|
||||
local DexEntryMenu = {}
|
||||
local DexEntryMenu = { isMenu = true }
|
||||
DexEntryMenu.__index = DexEntryMenu
|
||||
DexEntryMenu.isOpaque = true
|
||||
|
||||
|
||||
+4
-2
@@ -5,7 +5,7 @@ local ListMenu = require("src.ui.ListMenu")
|
||||
local Map = require("src.world.Map")
|
||||
local Strings = require("src.core.Strings")
|
||||
|
||||
local FlyMenu = {}
|
||||
local FlyMenu = { isMenu = true }
|
||||
|
||||
function FlyMenu.new(game)
|
||||
local items = {}
|
||||
@@ -25,12 +25,14 @@ function FlyMenu.new(game)
|
||||
})
|
||||
end
|
||||
end
|
||||
return ListMenu.new(game, Strings.source("FLY TO?"), items, {
|
||||
local menu = ListMenu.new(game, Strings.source("FLY TO?"), items, {
|
||||
onChoose = function(item, list)
|
||||
list:close()
|
||||
game.overworld:flyTo(item.value)
|
||||
end,
|
||||
})
|
||||
menu.isMenu = true
|
||||
return menu
|
||||
end
|
||||
|
||||
return FlyMenu
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ local Font = require("src.render.Font")
|
||||
local Strings = require("src.core.Strings")
|
||||
local HallOfFame = require("src.ui.HallOfFame")
|
||||
|
||||
local LeaguePC = {}
|
||||
local LeaguePC = { isMenu = true }
|
||||
LeaguePC.__index = LeaguePC
|
||||
LeaguePC.isOpaque = true
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ local Sound = require("src.core.Sound")
|
||||
local Theme = require("src.ui.Theme")
|
||||
local Strings = require("src.core.Strings")
|
||||
|
||||
local NamingScreen = {}
|
||||
local NamingScreen = { isMenu = true }
|
||||
NamingScreen.__index = NamingScreen
|
||||
NamingScreen.isOpaque = true
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ local OptionRows = require("src.ui.OptionRows")
|
||||
local Renderer = require("src.render.Renderer")
|
||||
local Strings = require("src.core.Strings")
|
||||
|
||||
local OptionsMenu = {}
|
||||
local OptionsMenu = { isMenu = true }
|
||||
OptionsMenu.__index = OptionsMenu
|
||||
OptionsMenu.isOpaque = true
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ local Map = require("src.world.Map")
|
||||
local Strings = require("src.core.Strings")
|
||||
local Status = require("src.battle.Status")
|
||||
|
||||
local PartyMenu = {}
|
||||
local PartyMenu = { isMenu = true }
|
||||
PartyMenu.__index = PartyMenu
|
||||
PartyMenu.isOpaque = true
|
||||
|
||||
|
||||
+2
-1
@@ -16,7 +16,7 @@ local romText = require("src.core.RomText")
|
||||
local Font = require("src.render.Font")
|
||||
local Theme = require("src.ui.Theme")
|
||||
|
||||
local PlayerPC = {}
|
||||
local PlayerPC = { isMenu = true }
|
||||
|
||||
local function itemName(game, id)
|
||||
local def = game.data.items[id]
|
||||
@@ -308,6 +308,7 @@ function PlayerPC.new(game, opts)
|
||||
end
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
end
|
||||
menu.isMenu = true
|
||||
return menu
|
||||
end
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ local Strings = require("src.core.Strings")
|
||||
local Theme = require("src.ui.Theme")
|
||||
local MenuRepeat = require("src.ui.MenuRepeat")
|
||||
|
||||
local PokedexMenu = {}
|
||||
local PokedexMenu = { isMenu = true }
|
||||
PokedexMenu.__index = PokedexMenu
|
||||
PokedexMenu.isOpaque = true
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ local Font = require("src.render.Font")
|
||||
local Strings = require("src.core.Strings")
|
||||
local Theme = require("src.ui.Theme")
|
||||
|
||||
local PrizeCounter = {}
|
||||
local PrizeCounter = { isMenu = true }
|
||||
PrizeCounter.__index = PrizeCounter
|
||||
|
||||
local NAME_X, TOP_Y, ROW_STEP = 16, 32, 16
|
||||
|
||||
@@ -172,6 +172,7 @@ local function build(game, id, ...)
|
||||
inst = factory.new(game, ...)
|
||||
end
|
||||
inst.screenId = inst.screenId or id
|
||||
if factory.isMenu and inst.isMenu == nil then inst.isMenu = true end
|
||||
-- Standardized opt-in marker for mod-created options/settings screens.
|
||||
-- A mod may declare `isModOptions = true` on its screen factory table or
|
||||
-- on the returned instance. Either way the flag is propagated so that other
|
||||
|
||||
+2
-1
@@ -16,7 +16,7 @@ local Strings = require("src.core.Strings")
|
||||
local TextBox = require("src.render.TextBox")
|
||||
local romText = require("src.core.RomText")
|
||||
|
||||
local ShopMenu = {}
|
||||
local ShopMenu = { isMenu = true }
|
||||
|
||||
local function txt(game, key, fallback)
|
||||
return game.data.text[key] or fallback
|
||||
@@ -343,6 +343,7 @@ function ShopMenu.new(game, stock, onQuit)
|
||||
drawClerk(self)
|
||||
Menu.draw(self)
|
||||
end
|
||||
menu.isMenu = true
|
||||
return menu
|
||||
end
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ local Screens = require("src.ui.Screens")
|
||||
local Strings = require("src.core.Strings")
|
||||
local Theme = require("src.ui.Theme")
|
||||
|
||||
local StartMenu = {}
|
||||
local StartMenu = { isMenu = true }
|
||||
|
||||
local function sameItems(_, items) return items end
|
||||
|
||||
@@ -229,6 +229,7 @@ function StartMenu.new(game)
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
end
|
||||
end
|
||||
menu.isMenu = true
|
||||
return menu
|
||||
end
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ do
|
||||
end
|
||||
end
|
||||
|
||||
local SummaryMenu = {}
|
||||
local SummaryMenu = { isMenu = true }
|
||||
SummaryMenu.__index = SummaryMenu
|
||||
SummaryMenu.isOpaque = true
|
||||
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ local SpriteRenderer = require("src.render.SpriteRenderer")
|
||||
local Strings = require("src.core.Strings")
|
||||
local Theme = require("src.ui.Theme")
|
||||
|
||||
local TownMap = {}
|
||||
local TownMap = { isMenu = true }
|
||||
TownMap.__index = TownMap
|
||||
TownMap.isOpaque = true
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ local Badges = require("src.inventory.Badges")
|
||||
local Font = require("src.render.Font")
|
||||
local Strings = require("src.core.Strings")
|
||||
|
||||
local TrainerCard = {}
|
||||
local TrainerCard = { isMenu = true }
|
||||
TrainerCard.__index = TrainerCard
|
||||
TrainerCard.isOpaque = true
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ local PartyView = require("src.core.game3.battle.party_view")
|
||||
local RomText = require("src.core.game3.rom_text")
|
||||
local TextIR = require("src.core.game3.scripting.text_ir")
|
||||
|
||||
local BagMenu = {}
|
||||
local BagMenu = { isMenu = true }
|
||||
|
||||
BagMenu.open = false
|
||||
BagMenu.cursor = 1
|
||||
|
||||
@@ -25,7 +25,7 @@ local ItemUse = require("src.core.game3.item_use")
|
||||
local PartyView = require("src.core.game3.battle.party_view")
|
||||
local RomText = require("src.core.game3.rom_text")
|
||||
|
||||
local BerryPouch = {}
|
||||
local BerryPouch = { isMenu = true }
|
||||
|
||||
BerryPouch.open = false
|
||||
BerryPouch.cursor = 1
|
||||
|
||||
@@ -19,7 +19,7 @@ local SummaryMenu = require("src.ui.game3.summary_menu")
|
||||
local Strings = require("src.core.Strings")
|
||||
local RomText = require("src.core.game3.rom_text")
|
||||
|
||||
local BoxStorageUI = {}
|
||||
local BoxStorageUI = { isMenu = true }
|
||||
|
||||
-- pokefirered/src/pokemon_storage_system_data.c:2027
|
||||
local MENU_TEXT = {
|
||||
|
||||
@@ -6,7 +6,7 @@ local Strings = require("src.core.Strings")
|
||||
local BindingsMenu = require("src.ui.BindingsMenu")
|
||||
local ChoiceBox = require("src.ui.ChoiceBox")
|
||||
|
||||
local Controls = {}
|
||||
local Controls = { isMenu = true }
|
||||
|
||||
Controls.ID = "controls"
|
||||
Controls.open = false
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
local Strings = require("src.core.Strings")
|
||||
local RomText = require("src.core.game3.rom_text")
|
||||
|
||||
local DaycareMenu = {}
|
||||
local DaycareMenu = { isMenu = true }
|
||||
|
||||
local DAYCARE_MON_COUNT = 2 -- pokefirered/include/constants/global.h:34
|
||||
local DAYCARE_LEVEL_MENU_EXIT = 5 -- pokefirered/include/constants/daycare.h:20
|
||||
|
||||
@@ -10,7 +10,7 @@ local Chrome = require("src.ui.game3.chrome")
|
||||
local Strings = require("src.core.Strings")
|
||||
local RomText = require("src.core.game3.rom_text")
|
||||
|
||||
local EasyChat = {}
|
||||
local EasyChat = { isMenu = true }
|
||||
|
||||
EasyChat.openFlag = false
|
||||
EasyChat._state = nil
|
||||
|
||||
@@ -7,7 +7,7 @@ local FameChecker = require("src.core.game3.fame_checker")
|
||||
local TextIR = require("src.core.game3.scripting.text_ir")
|
||||
local RomText = require("src.core.game3.rom_text")
|
||||
|
||||
local FameCheckerUi = {}
|
||||
local FameCheckerUi = { isMenu = true }
|
||||
|
||||
local PERSON = FameChecker.PERSON
|
||||
local PICK = FameChecker.PICKSTATE
|
||||
|
||||
@@ -7,7 +7,7 @@ local Pokemon = require("src.core.game3.pokemon")
|
||||
local RomText = require("src.core.game3.rom_text")
|
||||
local HofGfx = require("src.ui.game3.hall_of_fame_gfx")
|
||||
|
||||
local HofPc = {}
|
||||
local HofPc = { isMenu = true }
|
||||
|
||||
HofPc.open = false
|
||||
HofPc._teams = {}
|
||||
|
||||
@@ -7,7 +7,7 @@ local Storage = require("src.core.game3.storage")
|
||||
local RomText = require("src.core.game3.rom_text")
|
||||
local Trig = require("src.core.game3.trig")
|
||||
|
||||
local ItemPc = {}
|
||||
local ItemPc = { isMenu = true }
|
||||
|
||||
ItemPc.open = false
|
||||
ItemPc.mode = "list"
|
||||
|
||||
@@ -11,7 +11,7 @@ local FrlgFont = require("src.ui.game3.frlg_font")
|
||||
local Strings = require("src.core.Strings")
|
||||
local ManagerState = require("src.mods.ManagerState")
|
||||
|
||||
local ModManager = {}
|
||||
local ModManager = { isMenu = true }
|
||||
|
||||
ModManager.open = false
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ local SummaryData = require("src.core.game3.summary_data")
|
||||
local Strings = require("src.core.Strings")
|
||||
local RomText = require("src.core.game3.rom_text")
|
||||
|
||||
local MoveRelearner = {}
|
||||
local MoveRelearner = { isMenu = true }
|
||||
|
||||
MoveRelearner.open = false
|
||||
MoveRelearner.state = "list"
|
||||
|
||||
@@ -14,7 +14,7 @@ local Versions = require("src.import.gba.versions")
|
||||
local RomText = require("src.core.game3.rom_text")
|
||||
local TextIR = require("src.core.game3.scripting.text_ir")
|
||||
|
||||
local Naming = {}
|
||||
local Naming = { isMenu = true }
|
||||
|
||||
Naming.MAX_LEN = 7
|
||||
Naming.openFlag = false
|
||||
|
||||
@@ -4,7 +4,7 @@ local Options = require("src.core.game3.options")
|
||||
local Rows = require("src.ui.game3.option_rows")
|
||||
local RomText = require("src.core.game3.rom_text")
|
||||
|
||||
local OptionMenu = {}
|
||||
local OptionMenu = { isMenu = true }
|
||||
|
||||
OptionMenu.open = false
|
||||
OptionMenu.cursor = 1
|
||||
|
||||
@@ -16,7 +16,7 @@ local Strings = require("src.core.Strings")
|
||||
local RomText = require("src.core.game3.rom_text")
|
||||
local TextIR = require("src.core.game3.scripting.text_ir")
|
||||
|
||||
local PartyMenu = {}
|
||||
local PartyMenu = { isMenu = true }
|
||||
|
||||
PartyMenu.open = false
|
||||
PartyMenu.cursor = 1
|
||||
|
||||
@@ -15,7 +15,7 @@ local Strings = require("src.core.Strings")
|
||||
local RomText = require("src.core.game3.rom_text")
|
||||
local Profile = require("src.core.game3.profile")
|
||||
|
||||
local PcMenu = {}
|
||||
local PcMenu = { isMenu = true }
|
||||
|
||||
local function rse_pc()
|
||||
if Profile.family(PcMenu._session) ~= "rse" then return nil end
|
||||
|
||||
@@ -17,7 +17,7 @@ local PokedexChrome = require("src.ui.game3.pokedex_chrome")
|
||||
local Strings = require("src.core.Strings")
|
||||
local RomText = require("src.core.game3.rom_text")
|
||||
|
||||
local Pokedex = {}
|
||||
local Pokedex = { isMenu = true }
|
||||
|
||||
Pokedex.open = false
|
||||
Pokedex.screen = "mode_select"
|
||||
|
||||
@@ -4,7 +4,7 @@ local Stack = require("src.ui.game3.stack")
|
||||
local Window = require("src.ui.game3.window")
|
||||
local FrlgFont = require("src.ui.game3.frlg_font")
|
||||
|
||||
local PrizeCorner = {}
|
||||
local PrizeCorner = { isMenu = true }
|
||||
|
||||
-- pokefirered/include/constants/menu.h:21
|
||||
PrizeCorner.LIST_POKEMON_PRIZES = 14
|
||||
|
||||
@@ -8,7 +8,7 @@ local Strings = require("src.core.Strings")
|
||||
local Gpu = require("src.ui.game3.region_map_gpu")
|
||||
local Position = require("src.ui.game3.region_map_position")
|
||||
|
||||
local RegionMap = {}
|
||||
local RegionMap = { isMenu = true }
|
||||
|
||||
RegionMap.open = false
|
||||
RegionMap.cursorX = 0
|
||||
|
||||
@@ -5,7 +5,7 @@ local Kit = require("src.ui.game3.rse.scene_kit")
|
||||
local Gfx = require("src.ui.game3.rse.pokeblock_gfx")
|
||||
local BerryTrees = require("src.core.game3.rse.berry_trees")
|
||||
|
||||
local Tag = {}
|
||||
local Tag = { isMenu = true }
|
||||
|
||||
Tag.ID = "rse_berry_tag"
|
||||
Tag.open = false
|
||||
|
||||
@@ -5,7 +5,7 @@ local Kit = require("src.ui.game3.rse.scene_kit")
|
||||
local Gfx = require("src.ui.game3.rse.pokeblock_gfx")
|
||||
local PalFade = require("src.core.game3.pal_fade")
|
||||
|
||||
local Pass = {}
|
||||
local Pass = { isMenu = true }
|
||||
|
||||
Pass.ID = "rse_frontier_pass"
|
||||
Pass.MAP_ID = "rse_frontier_map"
|
||||
|
||||
@@ -5,7 +5,7 @@ local RomText = require("src.core.game3.rom_text")
|
||||
local Storage = require("src.core.game3.storage")
|
||||
local Bag = require("src.core.game3.bag")
|
||||
|
||||
local ItemStorage = {}
|
||||
local ItemStorage = { isMenu = true }
|
||||
|
||||
ItemStorage.open = false
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ local FrlgFont = require("src.ui.game3.frlg_font")
|
||||
local RomText = require("src.core.game3.rom_text")
|
||||
local Mail = require("src.core.game3.mail")
|
||||
|
||||
local Mailbox = {}
|
||||
local Mailbox = { isMenu = true }
|
||||
|
||||
Mailbox.open = false
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ local SummaryData = require("src.core.game3.summary_data")
|
||||
local RomText = require("src.core.game3.rom_text")
|
||||
local Kit = require("src.ui.game3.rse.scene_kit")
|
||||
|
||||
local RseRelearner = {}
|
||||
local RseRelearner = { isMenu = true }
|
||||
|
||||
RseRelearner.SUB = "rse/move_relearner"
|
||||
RseRelearner.open = false
|
||||
|
||||
@@ -7,7 +7,7 @@ local Chrome = require("src.ui.game3.chrome")
|
||||
local Kit = require("src.ui.game3.rse.scene_kit")
|
||||
local Screens = require("src.ui.game3.screens")
|
||||
|
||||
local OptionMenu = {}
|
||||
local OptionMenu = { isMenu = true }
|
||||
|
||||
OptionMenu.ID = "option"
|
||||
OptionMenu.VISIBLE = 7
|
||||
|
||||
@@ -6,7 +6,7 @@ local Kit = require("src.ui.game3.rse.scene_kit")
|
||||
local Gfx = require("src.ui.game3.rse.pokeblock_gfx")
|
||||
local Pokeblock = require("src.core.game3.rse.pokeblock")
|
||||
|
||||
local Case = {}
|
||||
local Case = { isMenu = true }
|
||||
|
||||
Case.ID = "rse_pokeblock_case"
|
||||
Case.open = false
|
||||
|
||||
@@ -2358,7 +2358,7 @@ function Pokedex.draw(s)
|
||||
love.graphics.pop()
|
||||
end
|
||||
|
||||
local Host = {}
|
||||
local Host = { isMenu = true }
|
||||
Pokedex.Host = Host
|
||||
Host._s = nil
|
||||
Host._step = nil
|
||||
|
||||
@@ -50,7 +50,7 @@ Pokenav.SCREENS = SCREENS
|
||||
local Shell = {}
|
||||
Shell.__index = Shell
|
||||
|
||||
local Host = {}
|
||||
local Host = { isMenu = true }
|
||||
Pokenav.Host = Host
|
||||
|
||||
-- pokeemerald/src/pokenav.c:388
|
||||
|
||||
@@ -6,7 +6,7 @@ local RomText = require("src.core.game3.rom_text")
|
||||
local ItemsData = require("src.core.game3.items_data")
|
||||
local Gfx = require("src.ui.game3.rse.pokeblock_gfx")
|
||||
|
||||
local PBag = {}
|
||||
local PBag = { isMenu = true }
|
||||
|
||||
PBag.ID = "rse_pyramid_bag"
|
||||
PBag.open = false
|
||||
|
||||
@@ -606,7 +606,7 @@ function RegionMap.draw(s)
|
||||
love.graphics.pop()
|
||||
end
|
||||
|
||||
local Host = {}
|
||||
local Host = { isMenu = true }
|
||||
RegionMap.Host = Host
|
||||
Host._s = nil
|
||||
Host._step = nil
|
||||
|
||||
@@ -14,7 +14,7 @@ local RomText = require("src.core.game3.rom_text")
|
||||
local Flags = require("src.core.game3.scripting.flags")
|
||||
local Dex = require("src.core.game3.dex")
|
||||
|
||||
local SaveMenu = {}
|
||||
local SaveMenu = { isMenu = true }
|
||||
|
||||
SaveMenu.open = false
|
||||
SaveMenu.cursor = 1 -- 1=YES 2=NO
|
||||
|
||||
@@ -9,7 +9,7 @@ local Bag = require("src.core.game3.bag")
|
||||
local MoneyBox = require("src.ui.game3.money_box")
|
||||
local RomText = require("src.core.game3.rom_text")
|
||||
|
||||
local ShopMenu = {}
|
||||
local ShopMenu = { isMenu = true }
|
||||
|
||||
ShopMenu.open = false
|
||||
ShopMenu.mode = "root"
|
||||
|
||||
@@ -24,6 +24,8 @@ end
|
||||
-- opts.drawUnder: if true, still draw layers below this one.
|
||||
function Stack.push(id, mod, opts)
|
||||
opts = opts or {}
|
||||
local isMenu = opts.isMenu
|
||||
if isMenu == nil then isMenu = mod and mod.isMenu end
|
||||
if opts.hideBelow ~= false then
|
||||
local Message = package.loaded["src.ui.game3.message"]
|
||||
if Message and Message.closeStay then Message.closeStay() end
|
||||
@@ -37,6 +39,7 @@ function Stack.push(id, mod, opts)
|
||||
Stack._layers[#Stack._layers + 1] = {
|
||||
id = id,
|
||||
mod = mod,
|
||||
isMenu = isMenu == true,
|
||||
drawUnder = opts.drawUnder and true or false,
|
||||
hideBelow = opts.hideBelow ~= false, -- default hide layers underneath
|
||||
fullscreen = opts.fullscreen,
|
||||
|
||||
@@ -10,7 +10,7 @@ local RomText = require("src.core.game3.rom_text")
|
||||
local ModRuntime = require("src.mods.Runtime")
|
||||
local FrlgData = require("src.ui.game3.start_menu_frlg")
|
||||
|
||||
local StartMenu = {}
|
||||
local StartMenu = { isMenu = true }
|
||||
|
||||
local function se(id)
|
||||
pcall(function() require("src.core.game3.audio").playSe(require("src.core.game3.se_ids").resolve(id)) end)
|
||||
|
||||
@@ -17,7 +17,7 @@ local Strings = require("src.core.Strings")
|
||||
local RomText = require("src.core.game3.rom_text")
|
||||
local ItemsData = require("src.core.game3.items_data")
|
||||
|
||||
local SummaryMenu = {}
|
||||
local SummaryMenu = { isMenu = true }
|
||||
|
||||
-- pokefirered/src/pokemon_summary_screen.c:2139
|
||||
function SummaryMenu.heldItemText(mon)
|
||||
|
||||
@@ -18,7 +18,7 @@ local SummaryData = require("src.core.game3.summary_data")
|
||||
local SummaryChrome = require("src.ui.game3.summary_chrome")
|
||||
local RomText = require("src.core.game3.rom_text")
|
||||
|
||||
local TmCase = {}
|
||||
local TmCase = { isMenu = true }
|
||||
|
||||
TmCase.open = false
|
||||
TmCase.cursor = 1
|
||||
|
||||
@@ -4,7 +4,7 @@ local Stack = require("src.ui.game3.stack")
|
||||
local FrlgFont = require("src.ui.game3.frlg_font")
|
||||
local RomText = require("src.core.game3.rom_text")
|
||||
|
||||
local TrainerCard = {}
|
||||
local TrainerCard = { isMenu = true }
|
||||
|
||||
TrainerCard.open = false
|
||||
TrainerCard._session = nil
|
||||
|
||||
@@ -232,6 +232,15 @@ local ROWS = {
|
||||
if speed == 1 then return Strings("NORMAL") end
|
||||
return Strings("%dX", speed)
|
||||
end },
|
||||
{ label = Strings.source("ORIENTATION"), key = "orientation", port = true,
|
||||
cycle = function(options, delta)
|
||||
local Orientation = require("src.core.Orientation")
|
||||
options.orientation = Orientation.cycle(options.orientation, delta)
|
||||
Orientation.apply(options.orientation)
|
||||
end,
|
||||
text = function(options)
|
||||
return Strings(require("src.core.Orientation").modeLabel(options.orientation))
|
||||
end },
|
||||
{ label = Strings.source("ZOOM"), key = "zoom", port = true,
|
||||
cycle = function(options, delta, game)
|
||||
local Zoom = require("src.render.Zoom")
|
||||
@@ -500,7 +509,7 @@ local GROUPS = {
|
||||
{ id = "group.speed", label = Strings.source("SPEED"),
|
||||
members = { "textSpeed", "speed" } },
|
||||
{ id = "group.video", label = Strings.source("VIDEO"),
|
||||
members = { "videoMode", "faithfulRes", "screenPos", "fpsCap", "vsync",
|
||||
members = { "videoMode", "orientation", "faithfulRes", "screenPos", "fpsCap", "vsync",
|
||||
"logicClock" } },
|
||||
{ id = "group.graphics", label = Strings.source("GRAPHICS"),
|
||||
members = { "color", "uiLetterbox", "shaderfx", "shaderfx2", "frame" } },
|
||||
@@ -586,6 +595,7 @@ local function buildRows()
|
||||
-- The descriptor and the save key stay, so a build that grows a printer
|
||||
-- only has to drop this test.
|
||||
local hidden = row.key == "print"
|
||||
or (row.key == "orientation" and osName ~= "Android" and osName ~= "iOS")
|
||||
or (isNX and row.key == "videoMode")
|
||||
or (not showTouch and (row.id == "touchControls"
|
||||
or row.id == "touchLayout" or row.id == "haptics"
|
||||
|
||||
@@ -3121,8 +3121,10 @@ function OverworldState:billsHousePokemonList()
|
||||
end
|
||||
table.insert(items, { label = Strings("CANCEL") })
|
||||
-- TextBoxBorder b=10,c=9 at (0,0) -> total tw=11, th=12
|
||||
Game.stack:push(Menu.new(Game, items,
|
||||
{ tx = 0, ty = 0, tw = 11, th = 12 }))
|
||||
local menu = Menu.new(Game, items,
|
||||
{ tx = 0, ty = 0, tw = 11, th = 12 })
|
||||
menu.isMenu = true
|
||||
Game.stack:push(menu)
|
||||
end
|
||||
Game.stack:push(TextBox.new(Game, t._BillsHousePokemonListText1
|
||||
or Strings("BILL's favorite\nPOKéMON list!"), openList))
|
||||
@@ -3693,6 +3695,7 @@ function OverworldState:openPC(onDone)
|
||||
menu = Menu.new(Game, items,
|
||||
{ tx = 0, ty = 0, tw = 16, th = #items * 2 + 2, onCancel = logOff,
|
||||
noSound = true })
|
||||
menu.isMenu = true
|
||||
-- engine/menus/pc.asm:5
|
||||
Game.stack:push(TextBox.new(Game,
|
||||
(Game.data.text or {})._TurnedOnPC1Text
|
||||
|
||||
@@ -2703,7 +2703,8 @@ function World:mapMusicSong(mapId)
|
||||
local def = self.maps and self.maps[mapId]
|
||||
-- ENGINE_ROCKETS_IN_MAHOGANY / _RADIO_TOWER (data/events/engine_flags.asm:40,:36)
|
||||
return World.mapMusicLabel(audio, def and def.music,
|
||||
self:engineFlag(22), self:engineFlag(18))
|
||||
self:engineFlag(self:engineFlagId("ENGINE_ROCKETS_IN_MAHOGANY", 22)),
|
||||
self:engineFlag(self:engineFlagId("ENGINE_ROCKETS_IN_RADIO_TOWER", 18)))
|
||||
end
|
||||
|
||||
function World:playMapMusic()
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
local U = require("tests.drivers.util")
|
||||
local DIR = os.getenv("POKEPORT_SHOT_DIR") or ".bazinga/BSA/10-02-26-00-userreported/shots/2617"
|
||||
|
||||
return function(game)
|
||||
local failures = 0
|
||||
local function check(ok, label)
|
||||
print((ok and "PASS " or "FAIL ") .. label)
|
||||
if not ok then failures = failures + 1 end
|
||||
return ok
|
||||
end
|
||||
local function finish()
|
||||
print("RESULT route110_surf_bridge failures=" .. failures)
|
||||
love.event.quit(failures == 0 and 0 or 1)
|
||||
end
|
||||
for _ = 1, 900 do
|
||||
if game.phase == "boot" and game.boot then break end
|
||||
U.wait(1)
|
||||
end
|
||||
if not check(game.phase == "boot" and game.boot, "boot_ready") then return finish() end
|
||||
game:_handleBootAction({ action = "new_game", name = "NICK", gender = 0 })
|
||||
U.wait(240)
|
||||
local Runtime = require("src.core.game3.runtime")
|
||||
local Map = require("src.core.game3.map")
|
||||
local Player = require("src.core.game3.player")
|
||||
local Collision = require("src.core.game3.collision")
|
||||
local Field = require("src.core.game3.field")
|
||||
local Space = require("src.core.game3.scripting.space")
|
||||
if not check(Runtime.getSession() ~= nil, "session_ready") then return finish() end
|
||||
require("src.core.game3.encounters").onStep = function() return nil end
|
||||
require("src.core.game3.trainer_sight").check = function() return false end
|
||||
Map.load(Runtime._mod, game, "EM_ROUTE110", { x = 15, y = 29, facing = "down" })
|
||||
U.wait(90)
|
||||
if Space.vm and Space.vm.stop then Space.vm:stop() end
|
||||
Field.unlock()
|
||||
Player.surfing, Player.dismounting, Player.surfHopping = true, false, false
|
||||
Player.elevation, Player.currentElevation = 1, 1
|
||||
Player.facing, Player.turnArmed, Player.turnTimer = "down", true, 0
|
||||
if not check(Map.current == "EM_ROUTE110" and Player.cellX == 15 and Player.cellY == 29
|
||||
and Collision.isWater(15, 29), "normal_water_setup") then return finish() end
|
||||
|
||||
local function state(label)
|
||||
print(string.format("STATE %s x=%d y=%d surfing=%s dismounting=%s elevation=%s current=%s behavior=%s coll=%s moving=%s locked=%s",
|
||||
label, Player.cellX, Player.cellY, tostring(Player.surfing), tostring(Player.dismounting),
|
||||
tostring(Player.elevation), tostring(Player.currentElevation),
|
||||
tostring(Collision.behavior(Player.cellX, Player.cellY)), tostring(Collision.cell(Player.cellX, Player.cellY)),
|
||||
tostring(Player.moving), tostring(Field.locked)))
|
||||
end
|
||||
local function step(dir)
|
||||
local x, y = Player.cellX, Player.cellY
|
||||
for _ = 1, 24 do
|
||||
U.hold(game, dir, 1)
|
||||
if Player.moving then break end
|
||||
end
|
||||
for _ = 1, 80 do
|
||||
if not Player.moving then break end
|
||||
U.wait(1)
|
||||
end
|
||||
state(dir)
|
||||
return x ~= Player.cellX or y ~= Player.cellY
|
||||
end
|
||||
|
||||
local Popup = require("src.ui.game3.map_name_popup")
|
||||
for _ = 1, 360 do
|
||||
if not Popup.isActive() then break end
|
||||
U.wait(1)
|
||||
end
|
||||
if not check(not Popup.isActive(), "map_popup_finished") then return finish() end
|
||||
if not check(step("up") and Map.current == "EM_ROUTE110" and Player.cellX == 15
|
||||
and Player.cellY == 28 and Player.surfing and not Player.dismounting
|
||||
and Player.elevation == 1 and Player.currentElevation == 1 and Collision.isWater(15, 28),
|
||||
"before_bridge_clear_water_by_input") then return finish() end
|
||||
state("before_bridge_clear_water")
|
||||
check(U.still(game, DIR .. "/2617_before_bridge_on_water.png"), "shot_before")
|
||||
if not check(step("down") and Map.current == "EM_ROUTE110" and Player.cellX == 15
|
||||
and Player.cellY == 29 and Player.surfing and not Player.dismounting
|
||||
and Player.elevation == 1 and Player.currentElevation == 1 and Collision.isWater(15, 29),
|
||||
"returned_to_causal_setup_by_input") then return finish() end
|
||||
state("before_bridge")
|
||||
if not check(step("down") and Player.cellY == 30, "entered_bridge_by_input") then return finish() end
|
||||
if not check(Player.surfing and not Player.dismounting and Player.elevation == 1,
|
||||
"retain_surf_under_bridge") then return finish() end
|
||||
check(U.still(game, DIR .. "/2617_under_bridge_after_entry.png"), "shot_under_bridge")
|
||||
check(step("down") and Player.cellY == 31 and Player.surfing, "bridge_second_row")
|
||||
check(step("down") and Player.cellY == 32 and Player.surfing, "bridge_third_row")
|
||||
if not check(step("down") and Map.current == "EM_ROUTE110" and Player.cellX == 15
|
||||
and Player.cellY == 33 and Player.surfing and not Player.dismounting
|
||||
and Player.elevation == 1 and Player.currentElevation == 1 and Collision.isWater(15, 33),
|
||||
"exit_bridge_to_south_water") then return finish() end
|
||||
for _, y in ipairs({ 34, 35 }) do
|
||||
if not check(step("down") and Map.current == "EM_ROUTE110" and Player.cellX == 15
|
||||
and Player.cellY == y and Player.surfing and not Player.dismounting
|
||||
and Player.elevation == 1 and Player.currentElevation == 1 and Collision.isWater(15, y),
|
||||
"south_water_row" .. y .. "_retains_surf") then return finish() end
|
||||
end
|
||||
state("south_water_clear_of_bridge")
|
||||
check(U.still(game, DIR .. "/2617_south_water_clear_of_bridge.png"), "shot_exit")
|
||||
finish()
|
||||
end
|
||||
@@ -65,7 +65,17 @@ return function(game)
|
||||
end
|
||||
local function phases(name)
|
||||
U.wait(90)
|
||||
local pairs = { "general__rom_082d4b54", "general__rom_082d4b6c" }
|
||||
local pairs = {}
|
||||
for index, mapId in ipairs({ "FR_ROUTE_19", "FR_ROUTE_20" }) do
|
||||
local def = game.data and game.data.maps and game.data.maps[mapId]
|
||||
local layout = def and Map.ensureMidLayout(game, mapId, def)
|
||||
local pair = layout and layout.pair
|
||||
if type(pair) ~= "string" or pair == "" or pair == pairs[1] then
|
||||
result(false, name .. "_distinct_loaded_pair_" .. mapId)
|
||||
return false
|
||||
end
|
||||
pairs[index] = pair
|
||||
end
|
||||
for index, phase in ipairs({ 2, 4 }) do
|
||||
local target = phase * 16 + 3
|
||||
local ready = false
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
local U = require("tests.drivers.util")
|
||||
local Music = require("src.core.Music")
|
||||
local FlagNames = require("src.core.gen2.FlagNames")
|
||||
|
||||
return function(game)
|
||||
U.wait(60)
|
||||
local world = game.world
|
||||
local failures = 0
|
||||
local function check(ok, label)
|
||||
if not ok then failures = failures + 1 end
|
||||
print((ok and "PASS " or "FAIL ") .. label)
|
||||
end
|
||||
if not (world and world.map) then
|
||||
print("FAIL 2623 gen2 world did not boot")
|
||||
return love.event.quit(1)
|
||||
end
|
||||
check(game.mods and game.mods.safeMode == true, "2623 mods disabled before boot")
|
||||
world.checkTrainerBattle = function() return false end
|
||||
local tower = world:engineFlagId("ENGINE_ROCKETS_IN_RADIO_TOWER", 18)
|
||||
local mahogany = world:engineFlagId("ENGINE_ROCKETS_IN_MAHOGANY", 22)
|
||||
check(type(tower) == "number" and type(mahogany) == "number",
|
||||
"2623 Rocket engine flags resolve")
|
||||
if not (tower and mahogany) then return love.event.quit(1) end
|
||||
world.events:set(FlagNames.events.EVENT_RADIO_TOWER_ROCKET_TAKEOVER, false)
|
||||
world.events:set(FlagNames.events.EVENT_RADIO_TOWER_CIVILIANS_AFTER, true)
|
||||
world.mapScenes.RADIO_TOWER_5F = 2
|
||||
world.mapScenes.MAHOGANY_MART_1F = 0
|
||||
local dir = os.getenv("POKEPORT_SHOT_DIR")
|
||||
local function enter(mapId, x, y)
|
||||
if not world:warpToMapId(mapId, x, y, "up") then
|
||||
check(false, "2623 load " .. mapId)
|
||||
return false
|
||||
end
|
||||
U.wait(120)
|
||||
check(world.map.id == mapId, "2623 entered " .. mapId)
|
||||
return world.map.id == mapId
|
||||
end
|
||||
|
||||
world:setEngineFlag(tower, true)
|
||||
if not enter("GOLDENROD_CITY", 5, 10) then return love.event.quit(1) end
|
||||
check(Music.current() == "Music_GoldenrodCity", "2623 ordinary Goldenrod music")
|
||||
for floor = 1, 5 do
|
||||
local mapId = "RADIO_TOWER_" .. floor .. "F"
|
||||
world:setEngineFlag(tower, true)
|
||||
if not enter(mapId, 2, 6) then return love.event.quit(1) end
|
||||
print("2623 " .. mapId .. " flag=" .. tower
|
||||
.. " active=" .. tostring(world:engineFlag(tower))
|
||||
.. " resolved=" .. tostring(world:mapMusicSong(mapId))
|
||||
.. " playing=" .. tostring(Music.current()))
|
||||
check(world:engineFlag(tower), "2623 takeover active " .. mapId)
|
||||
check(Music.current() == "Music_RocketTheme", "2623 rocket_music_floor_" .. floor)
|
||||
if dir then U.still(game, dir .. "/2623_radio_tower_" .. floor .. "f_takeover.png") end
|
||||
world:setEngineFlag(tower, false)
|
||||
world:playMapMusic()
|
||||
U.wait(120)
|
||||
check(Music.current() == "Music_GoldenrodCity", "2623 cleared_music_floor_" .. floor)
|
||||
end
|
||||
|
||||
world:setEngineFlag(mahogany, true)
|
||||
if not enter("MAHOGANY_MART_1F", 2, 6) then return love.event.quit(1) end
|
||||
check(world:engineFlag(mahogany), "2623 Mahogany Rockets active")
|
||||
check(Music.current() == "Music_RocketHideout", "2623 mahogany_rocket_music")
|
||||
if dir then U.still(game, dir .. "/2623_mahogany_mart_rocket_music.png") end
|
||||
world:setEngineFlag(mahogany, false)
|
||||
world:playMapMusic()
|
||||
U.wait(120)
|
||||
check(Music.current() == "Music_CherrygroveCity", "2623 mahogany_cleared_music")
|
||||
print("RESULT 2623 failures=" .. failures)
|
||||
love.event.quit(failures == 0 and 0 or 1)
|
||||
end
|
||||
@@ -0,0 +1,151 @@
|
||||
local U = require("tests.drivers.util")
|
||||
local DIR = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/menu_speed_bug2578"
|
||||
local failed = 0
|
||||
local function check(ok, label)
|
||||
print((ok and "PASS " or "FAIL ") .. label)
|
||||
if not ok then failed = failed + 1 end
|
||||
return ok
|
||||
end
|
||||
|
||||
local function gen1(game)
|
||||
game.speedOverride = 200
|
||||
game.save.party = { require("src.pokemon.Pokemon").new(game.data, "PIKACHU", 20) }
|
||||
U.teleport(game, "PALLET_TOWN", 10, 12, "down")
|
||||
local Screens = require("src.ui.Screens")
|
||||
local Game = require("src.core.Game")
|
||||
local opts = game.save.options
|
||||
opts.speedOverworld, opts.speedBattle, opts.speedMenu = 4, 10, 2
|
||||
game.speedOverride = nil
|
||||
check(game:logicSpeed() == 4, "2578_gen1_field_speed")
|
||||
for _, name in ipairs({ "StartMenu", "PartyMenu", "BagMenu" }) do
|
||||
Screens.push(game, name)
|
||||
U.wait(20)
|
||||
check(Game.speedCategoryInStack(game.stack) == "menu" and game:logicSpeed() == 2,
|
||||
"2578_gen1_field_" .. name .. "_menu_speed")
|
||||
check(U.still(game, DIR .. "/2578_gen1_field_" .. name .. ".png"), "2578_gen1_" .. name .. "_shot")
|
||||
game.stack:pop()
|
||||
check(game:logicSpeed() == 4, "2578_gen1_" .. name .. "_pop_restores_field")
|
||||
end
|
||||
local Menu = require("src.ui.Menu")
|
||||
local function reachPCMenu()
|
||||
for _ = 1, 600 do
|
||||
local top = game.stack:top()
|
||||
if getmetatable(top) == Menu then return top end
|
||||
U.tap(game, "a")
|
||||
U.wait(2)
|
||||
end
|
||||
error("actual interactive PC menu was not reached")
|
||||
end
|
||||
game.overworld:openPC()
|
||||
local pc = reachPCMenu()
|
||||
check(#pc.items >= 3 and pc.noSound and Game.speedCategoryInStack(game.stack) == "menu"
|
||||
and game:logicSpeed() == 2, "2578_gen1_pc_main_uses_MENU_SPEED")
|
||||
check(U.still(game, DIR .. "/2578_gen1_pc_main.png"), "2578_gen1_pc_main_shot")
|
||||
U.tap(game, "b")
|
||||
U.wait(2)
|
||||
check(game.stack:top() == game.overworld and game:logicSpeed() == 4,
|
||||
"2578_gen1_pc_main_pop_restores_field")
|
||||
|
||||
game.speedOverride = 200
|
||||
U.teleport(game, "BILLS_HOUSE", 2, 3, "up")
|
||||
game.speedOverride = nil
|
||||
game.overworld:billsHousePokemonList()
|
||||
local bill = reachPCMenu()
|
||||
check(#bill.items == 5 and bill.items[1].keepOpen and Game.speedCategoryInStack(game.stack) == "menu"
|
||||
and game:logicSpeed() == 2, "2578_gen1_bill_pc_viewer_uses_MENU_SPEED")
|
||||
check(U.still(game, DIR .. "/2578_gen1_bill_pc_viewer.png"), "2578_gen1_bill_pc_viewer_shot")
|
||||
U.tap(game, "b")
|
||||
U.wait(2)
|
||||
check(game.stack:top() == game.overworld and game:logicSpeed() == 4,
|
||||
"2578_gen1_bill_pc_viewer_pop_restores_field")
|
||||
game.speedOverride = 200
|
||||
U.teleport(game, "PALLET_TOWN", 10, 12, "down")
|
||||
game.speedOverride = nil
|
||||
local battle = require("src.battle.BattleState").newWild(game, "RATTATA", 3)
|
||||
game.overworld:pushBattle(battle)
|
||||
game.speedOverride = 200
|
||||
for _ = 1, 1500 do
|
||||
if game.stack:top() == battle and battle.phase == "menu" then break end
|
||||
U.tap(game, "a")
|
||||
U.wait(2)
|
||||
end
|
||||
game.speedOverride = nil
|
||||
check(Game.speedCategoryInStack(game.stack) == "battle" and game:logicSpeed() == 10,
|
||||
"2578_gen1_actual_battle_speed")
|
||||
for _, name in ipairs({ "PartyMenu", "BagMenu" }) do
|
||||
Screens.push(game, name, { battle = battle })
|
||||
U.wait(20)
|
||||
check(game:logicSpeed() == 2, "2578_gen1_battle_" .. name .. "_menu_speed")
|
||||
check(U.still(game, DIR .. "/2578_gen1_battle_" .. name .. ".png"), "2578_gen1_battle_" .. name .. "_shot")
|
||||
game.stack:pop()
|
||||
check(game:logicSpeed() == 10, "2578_gen1_battle_" .. name .. "_pop_restores_battle")
|
||||
end
|
||||
end
|
||||
|
||||
local function gen3(game)
|
||||
game.speedOverride = 200
|
||||
for _ = 1, 900 do
|
||||
if game.phase == "boot" and game.boot then break end
|
||||
U.wait(1)
|
||||
end
|
||||
game:_handleBootAction({ action = "new_game", name = "RED", gender = 0 })
|
||||
U.wait(240)
|
||||
local Runtime = require("src.core.game3.runtime")
|
||||
local session = Runtime.getSession()
|
||||
assert(session, "new game did not reach field")
|
||||
session.party = {}
|
||||
require("src.core.game3.party").giveMon(session, 25, 20)
|
||||
local Stack = require("src.ui.game3.stack")
|
||||
local Start = require("src.ui.game3.start_menu")
|
||||
local Party = require("src.ui.game3.party_menu")
|
||||
local Bag = require("src.ui.game3.bag_menu")
|
||||
game.options.speedOverworld, game.options.speedBattle, game.options.speedMenu = 4, 10, 2
|
||||
game.speedOverride = nil
|
||||
check(game:logicSpeed() == 4, "2578_gen3_field_speed")
|
||||
local menus = {
|
||||
{ id = "start", open = function() Start.show({ session = session, game = game }) end, close = Start.close },
|
||||
{ id = "party", open = function() Party.show(session.party, nil, { session = session }) end, close = Party.close },
|
||||
{ id = "bag", open = function() Bag.show(session, { session = session }) end, close = Bag.close },
|
||||
}
|
||||
for _, menu in ipairs(menus) do
|
||||
menu.open()
|
||||
U.wait(120)
|
||||
check(Stack.has(menu.id) and game:logicSpeed() == 2, "2578_gen3_field_" .. menu.id .. "_menu_speed")
|
||||
check(U.still(game, DIR .. "/2578_gen3_field_" .. menu.id .. ".png"), "2578_gen3_" .. menu.id .. "_shot")
|
||||
menu.close()
|
||||
check(game:logicSpeed() == 4, "2578_gen3_" .. menu.id .. "_pop_restores_field")
|
||||
end
|
||||
local Battle = require("src.core.game3.battle")
|
||||
local Bridge = require("src.core.game3.battle_bridge")
|
||||
local started, err = Bridge.startWild(Runtime._mod, game, { species = 16, level = 3 }, { fade = false })
|
||||
assert(started, tostring(err))
|
||||
game.speedOverride = 200
|
||||
for _ = 1, 1800 do
|
||||
if Battle._phase == "command" then break end
|
||||
U.tap(game, "a")
|
||||
U.wait(2)
|
||||
end
|
||||
game.speedOverride = nil
|
||||
check(Battle.isActive() and game:logicSpeed() == 10, "2578_gen3_actual_battle_speed")
|
||||
Party.show(session.party, nil, { session = session, battle = true, mode = "battle_switch" })
|
||||
U.wait(120)
|
||||
check(game:logicSpeed() == 2, "2578_gen3_battle_party_menu_speed")
|
||||
check(U.still(game, DIR .. "/2578_gen3_battle_party.png"), "2578_gen3_battle_party_shot")
|
||||
Party.close()
|
||||
check(game:logicSpeed() == 10, "2578_gen3_battle_party_pop_restores_battle")
|
||||
Bag.show(session, { session = session, battle = true })
|
||||
U.wait(120)
|
||||
check(game:logicSpeed() == 2 and Stack.top().fullscreen == false, "2578_gen3_nonfullscreen_battle_bag_menu_speed")
|
||||
check(U.still(game, DIR .. "/2578_gen3_battle_bag.png"), "2578_gen3_battle_bag_shot")
|
||||
Bag.close()
|
||||
check(game:logicSpeed() == 10, "2578_gen3_battle_bag_pop_restores_battle")
|
||||
end
|
||||
|
||||
return function(game)
|
||||
local ok, err = xpcall(function()
|
||||
if game.phase ~= nil then gen3(game) else gen1(game) end
|
||||
end, debug.traceback)
|
||||
if not ok then failed = failed + 1; print("FAIL 2578_driver " .. tostring(err)) end
|
||||
print((failed == 0 and "PASS " or "FAIL ") .. "2578_menu_speed failures=" .. failed)
|
||||
love.event.quit(failed == 0 and 0 or 1)
|
||||
end
|
||||
@@ -70,12 +70,15 @@ do
|
||||
eq(stale.options.speedOverworld, 7, "a stale Runtime game is untouched")
|
||||
check(wrote() > 0, "the change is persisted")
|
||||
g.phase = "field"
|
||||
eq(g:logicSpeed(), 2, "logicSpeed follows the menu change")
|
||||
eq(g:speedCategory(), "menu", "the actual OPTION layer owns its speed while open")
|
||||
eq(g:logicSpeed(), 1, "open OPTION keeps MENU SPEED after changing OVERWORLD SPEED")
|
||||
|
||||
check(selectRow("textSpeed"), "TEXT SPEED row reachable")
|
||||
OptionMenu.handleInput(fakeInput("right"))
|
||||
eq(Options.block(g.options).textSpeed, 2, "TEXT SPEED lands on the game's cart block")
|
||||
OptionMenu.close()
|
||||
eq(g:speedCategory(), "overworld", "closing OPTION restores the field category")
|
||||
eq(g:logicSpeed(), 2, "the changed OVERWORLD SPEED applies after OPTION closes")
|
||||
menu.state = "options"
|
||||
menu:frame({ new = {}, held = {} })
|
||||
eq(state.textSpeed, 2, "closing OPTION refreshes the boot text speed for NEW GAME")
|
||||
|
||||
@@ -11,8 +11,9 @@ local Game3 = require("src.core.Game3")
|
||||
|
||||
local battleActive = false
|
||||
package.loaded["src.core.game3.battle"] = { isActive = function() return battleActive end }
|
||||
local stackBusy = false
|
||||
package.loaded["src.ui.game3.stack"] = { busy = function() return stackBusy end, clear = function() end }
|
||||
local Stack = require("src.ui.game3.stack")
|
||||
local StartMenu = require("src.ui.game3.start_menu")
|
||||
Stack.clear()
|
||||
|
||||
local function newGame3(phase)
|
||||
local g = setmetatable({
|
||||
@@ -29,13 +30,13 @@ Input:applyBindings(nil)
|
||||
|
||||
do
|
||||
local g = newGame3("field")
|
||||
stackBusy = true
|
||||
eq(g:speedCategory(), "overworld", "menu over the field inherits overworld")
|
||||
Stack.push("start", StartMenu)
|
||||
eq(g:speedCategory(), "menu", "an explicit field menu owns MENU SPEED")
|
||||
g:keypressed("1")
|
||||
eq(g.options.speedOverworld, 2, "1 with a field menu open bumps OVERWORLD SPEED")
|
||||
eq(g.options.speedOverworld, 2, "1 with a field menu open syncs OVERWORLD SPEED")
|
||||
eq(g.options.speedMenu, 2, "1 with a field menu open syncs MENU SPEED")
|
||||
eq(g.options.speedBattle, 2, "and syncs BATTLE SPEED")
|
||||
stackBusy = false
|
||||
Stack.pop("start")
|
||||
eq(g:logicSpeed(), 2, "walk speed sticks after the menu closes")
|
||||
battleActive = true
|
||||
eq(g:speedCategory(), "battle", "battle stays battle")
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
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 GameVersion = require("src.core.GameVersion")
|
||||
local MB = require("src.core.game3.mb")
|
||||
local Collision = require("src.core.game3.collision")
|
||||
local InteractionScripts = require("src.core.game3.scripting.interaction_scripts")
|
||||
local session = { version = "emerald", map = "SURF_BRIDGE_TEST" }
|
||||
local stops = 0
|
||||
package.loaded["src.core.game3.runtime"] = { getSession = function() return session end }
|
||||
package.loaded["src.core.game3.audio"] = { stopSurfMusic = function() stops = stops + 1 end }
|
||||
package.loaded["src.core.game3.bike"] = { rse = function() return nil end }
|
||||
package.loaded["src.core.game3.field_effects"] = {
|
||||
leaveTallGrass = function() end,
|
||||
tallGrassAt = function() end,
|
||||
}
|
||||
package.loaded["src.core.game3.field"] = { locked = false }
|
||||
package.loaded["src.core.game3.objects"] = { hasMap = function() return false end }
|
||||
local Player = require("src.core.game3.player")
|
||||
local game = { save = { position = {} } }
|
||||
|
||||
local function setup(version, cells, surfing, elevation)
|
||||
GameVersion.set(version)
|
||||
session.version = version
|
||||
local layout = { width = #cells, height = 1, pair = "surf_bridge_test" }
|
||||
function layout:collAt(x) return cells[x + 1][1] end
|
||||
function layout:elevAt(x) return cells[x + 1][2] end
|
||||
function layout:midAt(x) return x end
|
||||
function layout:collArray()
|
||||
local coll = {}
|
||||
for i, cell in ipairs(cells) do coll[i] = cell[1] end
|
||||
return coll
|
||||
end
|
||||
local behaviors = {}
|
||||
for i, cell in ipairs(cells) do behaviors[i - 1] = cell[3] or MB.NORMAL end
|
||||
InteractionScripts.behaviors[layout.pair] = behaviors
|
||||
Collision.bindMap(game, session.map, { midLayout = layout, pair = layout.pair, warps = {} })
|
||||
game.world = nil
|
||||
Player.reset(0, 0, "right")
|
||||
Player.surfing = surfing == true
|
||||
Player.underwater = false
|
||||
Player.elevation = elevation or cells[1][2]
|
||||
Player.currentElevation = Player.elevation
|
||||
stops = 0
|
||||
end
|
||||
|
||||
local function settle()
|
||||
Player._onStepDone = function() end
|
||||
for _ = 1, 40 do
|
||||
if not Player.moving then break end
|
||||
Player.tick(game)
|
||||
end
|
||||
check(not Player.moving, "step completes")
|
||||
end
|
||||
|
||||
local function step(label)
|
||||
eq(Player.tryMove("right", game, false), "step", label .. " accepted")
|
||||
settle()
|
||||
end
|
||||
|
||||
local water = { 0x29, 1, MB.OCEAN_WATER }
|
||||
local bridge15 = { 0, 15, MB.BRIDGE_OVER_OCEAN }
|
||||
local bridge1 = { 0, 1, MB.BRIDGE_OVER_OCEAN }
|
||||
local shore = { 0, 3, MB.NORMAL }
|
||||
|
||||
-- pokeemerald/src/field_player_avatar.c:693
|
||||
setup("emerald", { water, bridge15, bridge1, water, shore }, true, 1)
|
||||
step("water to multilevel bridge")
|
||||
check(Player.surfing and not Player.dismounting, "retain_surf_under_bridge")
|
||||
eq(Player.currentElevation, 1, "multilevel bridge keeps lower layer")
|
||||
check(not Collision.isWater(1, 0), "bridge collision stays land for upper deck")
|
||||
step("multilevel bridge to lower bridge")
|
||||
check(Player.surfing, "retain_surf_on_elevation1_bridge")
|
||||
step("bridge to water")
|
||||
check(Player.surfing and Player.cellX == 3, "exit_bridge_to_water")
|
||||
eq(stops, 0, "bridge traversal keeps Surf music")
|
||||
eq(Player.tryMove("right", game, false), "step", "elevation3 shore accepted")
|
||||
check(Player.surfing and Player.dismounting and Player.jumping, "shore starts Surf dismount hop")
|
||||
eq(stops, 1, "shore stops Surf music once")
|
||||
settle()
|
||||
check(not Player.surfing and not Player.dismounting, "shore finishes on foot")
|
||||
eq(Player.currentElevation, 3, "shore reaches default elevation")
|
||||
|
||||
setup("emerald", { { 0, 4 }, bridge15, { 0, 4 } }, false, 4)
|
||||
step("upper deck to multilevel bridge")
|
||||
check(not Player.surfing and not Player.jumping, "upper deck remains walking")
|
||||
eq(Player.currentElevation, 4, "upper deck keeps upper layer")
|
||||
step("multilevel bridge to upper deck")
|
||||
check(not Player.surfing, "upper deck never enables Surf")
|
||||
|
||||
setup("emerald", { water, { 0x07, 15, MB.BRIDGE_OVER_OCEAN } }, true, 1)
|
||||
eq(Player.tryMove("right", game, false), "blocked", "bridge support blocks Surf")
|
||||
check(Player.surfing and not Player.moving and Player.cellX == 0, "support bump preserves Surf")
|
||||
|
||||
setup("emerald", { water, { 0, 4 } }, true, 1)
|
||||
local result, why = Player.tryMove("right", game, false)
|
||||
eq(result, "blocked", "wrong elevation land rejects dismount")
|
||||
eq(why, "elevation", "wrong elevation collision reason")
|
||||
check(Player.surfing, "wrong elevation bump preserves Surf")
|
||||
|
||||
setup("emerald", { water, shore }, true, 1)
|
||||
game.world = { npcs = { { cellX = 1, cellY = 0, passable = false } } }
|
||||
result, why = Player.tryMove("right", game, false)
|
||||
eq(result, "blocked", "occupied shore rejects dismount")
|
||||
eq(why, "entity", "occupied shore collision reason")
|
||||
eq(stops, 0, "occupied shore keeps Surf music")
|
||||
|
||||
-- pokeemerald/src/field_player_avatar.c:443
|
||||
setup("emerald", { water, bridge15 }, true, 1)
|
||||
check(Player.forcedStep("right", 8), "forced bridge step accepted")
|
||||
check(not Player.dismounting and not Player.jumping, "forced bridge step keeps Surf movement")
|
||||
eq(Player.stepFrames, 8, "forced bridge retains requested speed")
|
||||
settle()
|
||||
check(Player.surfing, "forced_step_retains_surf_under_bridge")
|
||||
eq(stops, 0, "forced bridge keeps Surf music")
|
||||
|
||||
setup("emerald", { water, shore }, true, 1)
|
||||
check(Player.forcedStep("right", 8), "forced shore step accepted")
|
||||
check(Player.dismounting and Player.jumping, "forced shore starts dismount")
|
||||
eq(Player.stepFrames, 16, "forced shore retains dismount timing")
|
||||
settle()
|
||||
check(not Player.surfing, "forced shore finishes on foot")
|
||||
|
||||
setup("emerald", { water, bridge15 }, true, 1)
|
||||
check(Player.forceStep("right"), "step without dismount setup accepted")
|
||||
check(not Player.dismounting, "forceStep has no dismount setup")
|
||||
settle()
|
||||
check(Player.surfing, "finish_step_retains_surf_without_dismount")
|
||||
|
||||
for _, version in ipairs({ "firered", "leafgreen" }) do
|
||||
-- pokefirered/src/field_player_avatar.c:568
|
||||
setup(version, { water, { 0, 1, MB.PUDDLE }, { 0, 1, MB.SHALLOW_WATER }, water, shore }, true, 1)
|
||||
for i = 1, 3 do
|
||||
step(version .. " shallow step " .. i)
|
||||
check(Player.surfing and not Player.dismounting, version .. " shallow water retains Surf")
|
||||
end
|
||||
step(version .. " shore")
|
||||
check(not Player.surfing and Player.currentElevation == 3, version .. " shore dismount preserved")
|
||||
eq(stops, 1, version .. " shore stops Surf music once")
|
||||
end
|
||||
|
||||
GameVersion.set("firered")
|
||||
T.finish("game3_surf_bridge_bug2617_test")
|
||||
@@ -22,7 +22,8 @@ local function stack(...) return { states = { ... } } end
|
||||
|
||||
local battle = { isBattle = true }
|
||||
local overworld = { isOverworld = true }
|
||||
local overlay = {} -- a party menu/choice box/naming screen/text box: no marker
|
||||
local overlay = {}
|
||||
local menu = { isMenu = true }
|
||||
|
||||
eq(Game.speedCategoryInStack(nil), "menu", "a nil stack falls to menu")
|
||||
eq(Game.speedCategoryInStack(stack()), "menu", "an empty stack falls to menu")
|
||||
@@ -35,16 +36,20 @@ eq(Game.speedCategoryInStack(stack(battle)), "battle",
|
||||
"a battle alone resolves to battle")
|
||||
|
||||
eq(Game.speedCategoryInStack(stack(overworld, overlay)), "overworld",
|
||||
"a menu opened while walking inherits overworld")
|
||||
"unmarked dialogue while walking inherits overworld")
|
||||
eq(Game.speedCategoryInStack(stack(battle, overlay)), "battle",
|
||||
"a menu opened mid-battle inherits battle, not menu")
|
||||
"unmarked battle dialogue inherits battle")
|
||||
eq(Game.speedCategoryInStack(stack(overworld, overlay, overlay)), "overworld",
|
||||
"the inheritance walk sees through more than one stacked overlay")
|
||||
|
||||
eq(Game.speedCategoryInStack(stack(overworld, battle)), "battle",
|
||||
"a battle opened over the overworld reads as battle, not the overworld underneath it")
|
||||
eq(Game.speedCategoryInStack(stack(overworld, battle, overlay)), "battle",
|
||||
"and a menu on top of THAT still reads as battle")
|
||||
"dialogue on top of battle still reads as battle")
|
||||
eq(Game.speedCategoryInStack(stack(overworld, menu)), "menu",
|
||||
"an explicit field menu owns MENU SPEED")
|
||||
eq(Game.speedCategoryInStack(stack(overworld, battle, menu, overlay)), "menu",
|
||||
"dialogue over an explicit battle menu inherits MENU SPEED")
|
||||
|
||||
-- ------- Game:_resolveLogicSpeed: category -> save.options key -> clamp
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
love = require("tests.love_stub")
|
||||
local T = require("tests.harness")
|
||||
local SaveData = require("src.core.SaveData")
|
||||
local Save = require("src.core.gen2.Save")
|
||||
local Orientation = require("src.core.Orientation")
|
||||
local Settings = require("src.import.LauncherSettings")
|
||||
local OptionsMenu = require("src.ui.gen2.OptionsMenu")
|
||||
local Game2 = require("src.core.Game2")
|
||||
local StateStack = require("src.core.StateStack")
|
||||
local realLoad, realSave = SaveData.loadOptions, SaveData.saveOptions
|
||||
local realApply = Orientation.apply
|
||||
local applied
|
||||
Orientation.apply = function(mode) applied = mode; return true end
|
||||
local function memfs()
|
||||
local files = {}
|
||||
return {
|
||||
write = function(path, content) files[path] = content; return true end,
|
||||
read = function(path) return files[path] end,
|
||||
remove = function(path) files[path] = nil; return true end,
|
||||
getInfo = function(path) return files[path] and { type = "file" } end,
|
||||
}
|
||||
end
|
||||
local function orientationRow(model)
|
||||
for _, section in ipairs(model.sections) do
|
||||
for _, row in ipairs(section.rows) do
|
||||
if row.label == "ORIENTATION" then return row end
|
||||
end
|
||||
end
|
||||
end
|
||||
for _, osName in ipairs({ "Android", "iOS", "OS X" }) do
|
||||
love.system.getOS = function() return osName end
|
||||
for _, version in ipairs({ "red", "yellow", "gold", "silver", "crystal", "firered", "emerald" }) do
|
||||
local fs = memfs()
|
||||
local seed = SaveData.defaultOptions()
|
||||
seed.orientation = "landscape"
|
||||
seed.gold = { orientation = "portrait", textSpeed = "FAST" }
|
||||
realSave(seed, fs)
|
||||
SaveData.loadOptions = function() return realLoad(fs) end
|
||||
SaveData.saveOptions = function(opts) return realSave(opts, fs) end
|
||||
local model = Settings.open({}, version)
|
||||
local row = orientationRow(model)
|
||||
T.eq(row ~= nil, osName ~= "OS X", osName .. " " .. version .. " row visibility")
|
||||
if row then
|
||||
T.eq(row.value(), Orientation.modeLabel("landscape"), version .. " launcher reads flat orientation")
|
||||
applied = nil
|
||||
T.eq(row.step(1), true, version .. " orientation row steps")
|
||||
T.eq(applied, "reverseLandscape", version .. " launcher applies selection live")
|
||||
T.eq(model.opts.orientation, "reverseLandscape", version .. " launcher writes shared root")
|
||||
T.eq(model.opts.gold.orientation, "portrait", version .. " launcher ignores stale local copy")
|
||||
model.save()
|
||||
T.eq(realLoad(fs).orientation, "reverseLandscape", version .. " selection persists flat")
|
||||
T.eq(Save.loadOptions(fs).orientation, "reverseLandscape", version .. " shared value loads into Gen2")
|
||||
end
|
||||
SaveData.loadOptions, SaveData.saveOptions = realLoad, realSave
|
||||
end
|
||||
local options = Save.defaultOptions()
|
||||
options.orientation = "landscape"
|
||||
local stack = setmetatable({}, { __index = StateStack })
|
||||
stack:init()
|
||||
local game = { options = options, stack = stack }
|
||||
local menu = OptionsMenu.new(game, { options = options })
|
||||
stack:push(menu)
|
||||
local row
|
||||
for _, item in ipairs(menu.rows) do if item.id == "orientation" then row = item end end
|
||||
T.eq(row ~= nil, osName ~= "OS X", osName .. " in-game orientation visibility")
|
||||
if row then
|
||||
local focused = menu:focusRow("orientation")
|
||||
T.check(focused ~= nil and focused ~= menu, osName .. " orientation lives in VIDEO group")
|
||||
T.eq(focused:row().id, "orientation", osName .. " group focuses orientation")
|
||||
applied = nil
|
||||
focused:cycle(row, 1)
|
||||
T.eq(options.orientation, "reverseLandscape", osName .. " actual menu cycles shared options")
|
||||
T.eq(applied, "reverseLandscape", osName .. " actual menu applies selection live")
|
||||
end
|
||||
end
|
||||
SaveData.loadOptions, SaveData.saveOptions = realLoad, realSave
|
||||
for _, value in ipairs({ "landscape", "reverseLandscape", "bogus" }) do
|
||||
local fs = memfs()
|
||||
local seed = SaveData.defaultOptions()
|
||||
seed.orientation = value
|
||||
seed.gold = { orientation = "portrait", textSpeed = "FAST" }
|
||||
realSave(seed, fs)
|
||||
local options = Save.loadOptions(fs)
|
||||
T.eq(options.orientation, Orientation.normalize(value), "root " .. value .. " wins with normalization")
|
||||
options.orientation = "portrait"
|
||||
T.check(Save.saveOptions(options, fs), "Gen2 orientation persistence returns success")
|
||||
local written = realLoad(fs)
|
||||
T.eq(written.orientation, "portrait", "Gen2 writes host orientation flat")
|
||||
T.eq(written.gold.orientation, nil, "Gen2 write removes stale local orientation")
|
||||
T.eq(written.gold.textSpeed, "FAST", "Gen2 cartridge option remains local")
|
||||
T.eq(Save.loadOptions(fs).orientation, "portrait", "Gen2 host orientation round-trips")
|
||||
end
|
||||
T.eq(Save.loadOptions(memfs()).orientation, "auto", "missing Gen2 orientation defaults to AUTO")
|
||||
love.system.getOS = function() return "OS X" end
|
||||
local game = setmetatable({ options = Save.defaultOptions() }, { __index = Game2 })
|
||||
game.options.orientation = "reverseLandscape"
|
||||
applied = nil
|
||||
game:applyOptions()
|
||||
T.eq(applied, "reverseLandscape", "Game2 applies the loaded host orientation")
|
||||
Orientation.apply = realApply
|
||||
T.finish("Gen2 orientation bug 2586")
|
||||
@@ -63,6 +63,58 @@ eq(World.mapMusicLabel(audio, 72, true, true), nil,
|
||||
eq(World.mapMusicLabel(audio, nil, true, true), nil,
|
||||
"a missing music byte resolves to nothing")
|
||||
|
||||
local function newWorld(engineFlagOrder)
|
||||
local maps = { MAHOGANY_MART_1F = { music = MUSIC_MAHOGANY_MART } }
|
||||
for floor = 1, 5 do
|
||||
maps["RADIO_TOWER_" .. floor .. "F"] = { music = RADIO_TOWER_SENTINEL }
|
||||
end
|
||||
return setmetatable({
|
||||
constants = { engineFlagOrder = engineFlagOrder },
|
||||
maps = maps,
|
||||
game = { save = { engineFlags = {} }, data = { audio = audio } },
|
||||
}, { __index = World })
|
||||
end
|
||||
|
||||
-- pokecrystal/constants/engine_flags.asm:25
|
||||
local crystalOrder = {
|
||||
[19] = "ENGINE_SAFARI_ZONE",
|
||||
[20] = "ENGINE_ROCKETS_IN_RADIO_TOWER",
|
||||
[23] = "ENGINE_REACHED_GOLDENROD",
|
||||
[24] = "ENGINE_ROCKETS_IN_MAHOGANY",
|
||||
}
|
||||
for _, version in ipairs({
|
||||
{ name = "Gold", tower = 18, mahogany = 22 },
|
||||
{ name = "Crystal", tower = 19, mahogany = 23, order = crystalOrder },
|
||||
}) do
|
||||
local world = newWorld(version.order)
|
||||
world.game.save.engineFlags[version.tower] = true
|
||||
world.game.save.engineFlags[version.mahogany] = true
|
||||
for floor = 1, 5 do
|
||||
local mapId = "RADIO_TOWER_" .. floor .. "F"
|
||||
eq(world:mapMusicSong(mapId), "Music_RocketTheme",
|
||||
version.name .. " takeover uses its own engine flag on " .. mapId)
|
||||
end
|
||||
eq(world:mapMusicSong("MAHOGANY_MART_1F"), "Music_RocketHideout",
|
||||
version.name .. " Mahogany Rocket state uses its own engine flag")
|
||||
|
||||
world.game.save.engineFlags = {}
|
||||
for floor = 1, 5 do
|
||||
local mapId = "RADIO_TOWER_" .. floor .. "F"
|
||||
eq(world:mapMusicSong(mapId), "Music_GoldenrodCity",
|
||||
version.name .. " cleared takeover restores city music on " .. mapId)
|
||||
end
|
||||
eq(world:mapMusicSong("MAHOGANY_MART_1F"), "Music_CherrygroveCity",
|
||||
version.name .. " cleared Mahogany restores Cherrygrove music")
|
||||
end
|
||||
|
||||
local crystal = newWorld(crystalOrder)
|
||||
crystal.game.save.engineFlags[18] = true
|
||||
crystal.game.save.engineFlags[22] = true
|
||||
eq(crystal:mapMusicSong("RADIO_TOWER_1F"), "Music_GoldenrodCity",
|
||||
"Crystal Safari flag does not start Radio Tower takeover music")
|
||||
eq(crystal:mapMusicSong("MAHOGANY_MART_1F"), "Music_CherrygroveCity",
|
||||
"Crystal reached-Goldenrod flag does not start Mahogany Rocket music")
|
||||
|
||||
local data = { audio = {
|
||||
songs = {
|
||||
Music_RocketHideout = { file = "rocket_hideout.wav" },
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
love = require("tests.love_stub")
|
||||
|
||||
local T = require("tests.harness")
|
||||
local Game = require("src.core.Game")
|
||||
local Game2 = require("src.core.Game2")
|
||||
local Game3 = require("src.core.Game3")
|
||||
local StateStack = require("src.core.StateStack")
|
||||
local FixedStep = require("src.core.FixedStep")
|
||||
local Stack3 = require("src.ui.game3.stack")
|
||||
local battleActive = false
|
||||
package.loaded["src.core.game3.battle"] = { isActive = function() return battleActive end }
|
||||
|
||||
local g = setmetatable({
|
||||
save = { party = {}, player = { name = "RED" }, inventory = {},
|
||||
flags = {}, pcItems = {}, pokedex = { seen = {}, owned = {} },
|
||||
options = { speedOverworld = 4, speedBattle = 10, speedMenu = 2 } },
|
||||
data = { pokemon = {}, items = {}, maps = {}, text = {}, field = {} },
|
||||
input = { wasPressed = function() return false end },
|
||||
}, { __index = Game })
|
||||
g.stack = setmetatable({}, { __index = StateStack })
|
||||
g.stack:init()
|
||||
local overworld, battle = { isOverworld = true }, { isBattle = true }
|
||||
g.stack:push(overworld)
|
||||
|
||||
g.data.pokemon.PIKACHU = { id = "PIKACHU", dex = 25 }
|
||||
local constructors = {
|
||||
StartMenu = {}, PartyMenu = {}, BagMenu = {}, OptionsMenu = {},
|
||||
PokedexMenu = {}, TrainerCard = {}, BoxMenu = {}, PlayerPC = {},
|
||||
LeaguePC = {}, NamingScreen = {}, FlyMenu = {}, TownMap = {},
|
||||
DexEntryMenu = { "PIKACHU" }, SummaryMenu = { { species = "PIKACHU", moves = {} } },
|
||||
PrizeCounter = { {} },
|
||||
}
|
||||
for name, args in pairs(constructors) do
|
||||
local screen = require("src.ui." .. name).new(g, unpack(args))
|
||||
T.eq(screen.isMenu, true, name .. " returned instance is a menu owner")
|
||||
g.stack:push(screen)
|
||||
T.eq(g:logicSpeed(), 2, name .. " uses MENU SPEED over the overworld")
|
||||
g.stack:push({})
|
||||
T.eq(g:logicSpeed(), 2, name .. " nested dialogue inherits MENU SPEED")
|
||||
g.stack:pop()
|
||||
g.stack:pop()
|
||||
T.eq(g:logicSpeed(), 4, name .. " pop restores OVERWORLD SPEED")
|
||||
end
|
||||
|
||||
local shop = require("src.ui.ShopMenu").new(g, {})
|
||||
T.eq(shop.isMenu, true, "ShopMenu actual returned generic instance owns MENU SPEED")
|
||||
T.eq(Game.speedCategoryInStack({ states = { overworld, shop, {} } }), "menu",
|
||||
"shop greetings and nested item lists inherit the concrete menu owner")
|
||||
|
||||
local Menu = require("src.ui.Menu")
|
||||
local OW = require("src.world.OverworldController")
|
||||
local owGameIndex, owGame
|
||||
for i = 1, 100 do
|
||||
local name, value = debug.getupvalue(OW.openPC, i)
|
||||
if not name then break end
|
||||
if name == "Game" then owGameIndex, owGame = i, value; break end
|
||||
end
|
||||
assert(owGameIndex, "openPC Game binding absent")
|
||||
debug.setupvalue(OW.openPC, owGameIndex, g)
|
||||
local baseInput, pressed = g.input, nil
|
||||
g.input = { wasPressed = function(_, key) return pressed == key end,
|
||||
isDown = function() return false end }
|
||||
local function tap(screen, key)
|
||||
pressed = key
|
||||
screen:update(1 / 60)
|
||||
pressed = nil
|
||||
end
|
||||
local function advanceToMenu()
|
||||
for _ = 1, 600 do
|
||||
local top = g.stack:top()
|
||||
if getmetatable(top) == Menu then return top end
|
||||
tap(top, "a")
|
||||
end
|
||||
error("actual interactive PC menu was not reached")
|
||||
end
|
||||
|
||||
OW:openPC()
|
||||
T.eq(g:logicSpeed(), 4, "PC opening text retains OVERWORLD SPEED")
|
||||
local pc = advanceToMenu()
|
||||
T.eq(#pc.items >= 3 and pc.noSound, true, "actual main PC chooser is reached")
|
||||
T.eq(pc.isMenu, true, "main PC chooser concrete instance owns MENU SPEED")
|
||||
T.eq(Game.speedCategoryInStack(g.stack), "menu", "main PC chooser category is menu")
|
||||
T.eq(g:logicSpeed(), 2, "actual main PC chooser uses MENU SPEED")
|
||||
pc.index = 2
|
||||
tap(pc, "a")
|
||||
T.eq(g.stack:top().isTextBox, true, "main PC chooser opens actual player PC access text")
|
||||
T.eq(g:logicSpeed(), 2, "player PC access text inherits MENU SPEED")
|
||||
local playerPC = advanceToMenu()
|
||||
T.eq(playerPC ~= pc and #playerPC.items == 4, true, "actual player PC submenu is reached")
|
||||
T.eq(g:logicSpeed(), 2, "player PC submenu uses MENU SPEED")
|
||||
tap(playerPC, "b")
|
||||
T.eq(g.stack:top(), pc, "player PC back returns the same main chooser")
|
||||
T.eq(g:logicSpeed(), 2, "returned main PC chooser retains MENU SPEED")
|
||||
tap(pc, "b")
|
||||
T.eq(g.stack:top(), overworld, "main PC cancel returns to field")
|
||||
T.eq(g:logicSpeed(), 4, "main PC cancel restores OVERWORLD SPEED")
|
||||
|
||||
for i, id in ipairs({ "EEVEE", "FLAREON", "JOLTEON", "VAPOREON" }) do
|
||||
g.data.pokemon[id] = { id = id, name = id, dex = 132 + i }
|
||||
end
|
||||
OW:billsHousePokemonList()
|
||||
T.eq(g:logicSpeed(), 4, "Bill viewer opening text retains OVERWORLD SPEED")
|
||||
local bill = advanceToMenu()
|
||||
T.eq(#bill.items == 5 and bill.items[1].keepOpen, true, "actual Bill PC viewer list is reached")
|
||||
T.eq(bill.isMenu, true, "Bill PC viewer concrete instance owns MENU SPEED")
|
||||
T.eq(Game.speedCategoryInStack(g.stack), "menu", "Bill PC viewer category is menu")
|
||||
T.eq(g:logicSpeed(), 2, "actual Bill PC viewer uses MENU SPEED")
|
||||
tap(bill, "a")
|
||||
T.eq(getmetatable(g.stack:top()), require("src.ui.DexEntryMenu"),
|
||||
"Bill viewer selection opens the actual DexEntryMenu")
|
||||
T.eq(g.save.pokedex.seen.EEVEE, true, "Bill viewer preserves the selected species seen flag")
|
||||
T.eq(g:logicSpeed(), 2, "Bill viewer nested DexEntryMenu uses MENU SPEED")
|
||||
for _ = 1, 600 do
|
||||
if g.stack:top() == bill then break end
|
||||
tap(g.stack:top(), "b")
|
||||
end
|
||||
T.eq(g.stack:top(), bill, "DexEntryMenu back returns the same Bill viewer")
|
||||
T.eq(g:logicSpeed(), 2, "returned Bill PC viewer retains MENU SPEED")
|
||||
tap(bill, "b")
|
||||
T.eq(g.stack:top(), overworld, "Bill viewer cancel returns to field")
|
||||
T.eq(g:logicSpeed(), 4, "Bill viewer cancel restores OVERWORLD SPEED")
|
||||
|
||||
g.input = baseInput
|
||||
debug.setupvalue(OW.openPC, owGameIndex, owGame)
|
||||
local generic = Menu.new(g, { { label = "CHOICE" } })
|
||||
T.eq(generic.isMenu, nil, "generic Menu remains transparent")
|
||||
g.stack:push(generic)
|
||||
T.eq(g:logicSpeed(), 4, "unowned generic choice retains OVERWORLD SPEED")
|
||||
g.stack:pop()
|
||||
|
||||
for _, name in ipairs({ "PartyMenu", "BagMenu" }) do
|
||||
g.stack:push(battle)
|
||||
local screen = require("src.ui." .. name).new(g, { battle = { playerParty = {} } })
|
||||
g.stack:push(screen)
|
||||
T.eq(g:logicSpeed(), 2, "battle " .. name .. " uses MENU SPEED")
|
||||
g.stack:pop()
|
||||
T.eq(g:logicSpeed(), 10, "battle " .. name .. " pop restores BATTLE SPEED")
|
||||
g.stack:push({})
|
||||
T.eq(g:logicSpeed(), 10, "battle dialogue stays at BATTLE SPEED")
|
||||
g.stack:pop()
|
||||
g.stack:pop()
|
||||
end
|
||||
|
||||
local party = require("src.ui.PartyMenu").new(g)
|
||||
g.stack:push(party)
|
||||
local function ticks(speed)
|
||||
g.save.options.speedMenu = speed
|
||||
party.blink = 0
|
||||
FixedStep.clock = function() return 0 end
|
||||
FixedStep.refreshPeriod = nil
|
||||
FixedStep:init(function(dt) party:update(dt) end)
|
||||
local multiplier = g:logicSpeed()
|
||||
FixedStep.maxAccum = FixedStep.catchupLimit(multiplier, 1 / 60)
|
||||
for _ = 1, 60 do FixedStep:update(1 / 60, multiplier) end
|
||||
return party.blink
|
||||
end
|
||||
T.eq(ticks(1), 60, "real party advances 60 logic ticks per second at MENU SPEED 1")
|
||||
T.eq(ticks(4), 240, "real party advances 240 logic ticks per second at MENU SPEED 4")
|
||||
g.speedOverride = 20
|
||||
T.eq(g:logicSpeed(), 20, "run override still wins over a menu")
|
||||
g.linkSession = true
|
||||
T.eq(g:logicSpeed(), 1, "link lock still wins over menu and override")
|
||||
g.linkSession = nil
|
||||
g.stack:push({ isFixedSpeed = true })
|
||||
T.eq(g:logicSpeed(), 1, "minigame lock still wins over menu and override")
|
||||
g.stack:pop()
|
||||
g.stack:pop()
|
||||
|
||||
local g3 = setmetatable({ phase = "field",
|
||||
options = { speedOverworld = 4, speedBattle = 10, speedMenu = 2 } }, { __index = Game3 })
|
||||
Stack3.clear()
|
||||
for _, name in ipairs({ "start_menu", "party_menu", "bag_menu", "option_menu", "pokedex",
|
||||
"trainer_card", "summary_menu", "box_storage_ui", "pc_menu", "item_pc",
|
||||
"hall_of_fame_pc", "naming", "region_map", "tm_case", "berry_pouch", "save_menu",
|
||||
"fame_checker", "controls_menu", "mod_manager", "rse.option_menu", "rse.item_storage",
|
||||
"rse.mailbox", "shop_menu", "daycare_menu", "prize_corner", "move_relearner", "easy_chat",
|
||||
"rse.move_relearner", "rse.pokeblock_case", "rse.berry_tag", "rse.frontier_pass", "rse.pyramid_bag" }) do
|
||||
local mod = require("src.ui.game3." .. name)
|
||||
Stack3.push(name, mod, { fullscreen = false })
|
||||
T.eq(g3:speedCategory(), "menu", name .. " owns the actual non-fullscreen layer")
|
||||
T.eq(g3:logicSpeed(), 2, name .. " uses MENU SPEED")
|
||||
Stack3.push("dialogue", {}, { fullscreen = true })
|
||||
T.eq(g3:logicSpeed(), 2, name .. " nested text inherits menu speed")
|
||||
Stack3.pop("dialogue")
|
||||
Stack3.pop(name)
|
||||
T.eq(g3:logicSpeed(), 4, name .. " pop restores field speed")
|
||||
end
|
||||
for _, name in ipairs({ "pokedex", "region_map", "pokenav.init" }) do
|
||||
local mod = require("src.ui.game3.rse." .. name).Host
|
||||
Stack3.push(name, mod)
|
||||
T.eq(g3:logicSpeed(), 2, "RSE " .. name .. " actual delegated Host owns MENU SPEED")
|
||||
Stack3.pop(name)
|
||||
end
|
||||
for _, name in ipairs({ "party_menu", "bag_menu" }) do
|
||||
battleActive = true
|
||||
Stack3.push(name, require("src.ui.game3." .. name), { fullscreen = false })
|
||||
T.eq(g3:logicSpeed(), 2, "Gen3 battle " .. name .. " uses MENU SPEED")
|
||||
Stack3.pop(name)
|
||||
T.eq(g3:logicSpeed(), 10, "Gen3 battle " .. name .. " pop restores BATTLE SPEED")
|
||||
end
|
||||
Stack3.push("battle_message", {}, { fullscreen = true })
|
||||
T.eq(g3:logicSpeed(), 10, "Gen3 unmarked fullscreen battle text stays at BATTLE SPEED")
|
||||
Stack3.clear()
|
||||
battleActive = false
|
||||
Stack3.push("scene", {}, { fullscreen = true })
|
||||
T.eq(g3:logicSpeed(), 4, "Gen3 unmarked field scene stays at OVERWORLD SPEED")
|
||||
Stack3.clear()
|
||||
Stack3.push("party", require("src.ui.game3.party_menu"))
|
||||
g3.speedOverride = 20
|
||||
T.eq(g3:logicSpeed(), 20, "Gen3 run override wins over menu")
|
||||
package.loaded["src.core.game3.link"] = { link = {} }
|
||||
T.eq(g3:logicSpeed(), 1, "Gen3 link lock wins over menu and override")
|
||||
package.loaded["src.core.game3.link"] = nil
|
||||
package.loaded["src.core.game3.minigames.common"] = { isActive = function() return true end }
|
||||
T.eq(g3:logicSpeed(), 1, "Gen3 minigame lock wins over menu and override")
|
||||
package.loaded["src.core.game3.minigames.common"] = nil
|
||||
Stack3.clear()
|
||||
|
||||
local g2 = setmetatable({ options = { speed = 4, speedOverworld = 1, speedMenu = 10 } }, { __index = Game2 })
|
||||
T.eq(g2:logicSpeed(), 4, "Gen2 retains legacy GAME SPEED")
|
||||
T.finish("menu speed bug 2578")
|
||||
@@ -366,7 +366,7 @@ local options = OptionsMenu.new(optionsGame, {
|
||||
})
|
||||
-- The cart's seven rows, then the port's: CONTROLS, audio, PERFORMANCE,
|
||||
-- speed, display, SHADER FX + SHADER FX 2 (the second slot added alongside
|
||||
check("thirty-five rows", #OptionsMenu.ROWS, 35)
|
||||
check("thirty-six row descriptors", #OptionsMenu.ROWS, 36)
|
||||
check("the cart's rows come first", OptionsMenu.ROWS[7].key, "frame")
|
||||
check("then the rebind screen", OptionsMenu.ROWS[8].id, "controls")
|
||||
check("then the port's audio group", OptionsMenu.ROWS[9].key, "musicVol")
|
||||
@@ -378,6 +378,17 @@ local function hasRow(rows, key)
|
||||
for _, row in ipairs(rows) do if row.key == key then return true end end
|
||||
return false
|
||||
end
|
||||
check("ORIENTATION is a descriptor", hasRow(OptionsMenu.ROWS, "orientation"), true)
|
||||
do
|
||||
local oldSystem = love.system
|
||||
for _, osName in ipairs({ "Linux", "Android", "iOS" }) do
|
||||
love.system = { getOS = function() return osName end }
|
||||
local platformOptions = OptionsMenu.new(newGame(Save.newGame()))
|
||||
check(osName .. " orientation row visibility",
|
||||
hasRow(platformOptions.rows, "orientation"), osName ~= "Linux")
|
||||
end
|
||||
love.system = oldSystem
|
||||
end
|
||||
check("PRINT is still a descriptor", hasRow(OptionsMenu.ROWS, "print"), true)
|
||||
check("but never reaches the screen", hasRow(options.rows, "print"), false)
|
||||
check("and the save keeps its value",
|
||||
|
||||
Reference in New Issue
Block a user