mirror of
https://github.com/DramaticShape/DramaticShapeVoxelMod.git
synced 2026-08-12 09:40:50 +02:00
update tests/lib
This commit is contained in:
@@ -117,6 +117,20 @@ function StadiumInstall.romHint()
|
|||||||
return base .. "/" .. StadiumInstall.ROM_DIR
|
return base .. "/" .. StadiumInstall.ROM_DIR
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- The same thing with a FILENAME on the end, which is what a player actually
|
||||||
|
-- needs: a folder alone leaves them guessing what to call the file, and the
|
||||||
|
-- guess is not obviously "baserom.z64".
|
||||||
|
--
|
||||||
|
-- Taken from the head of NAMED rather than retyped, so the name shown is by
|
||||||
|
-- construction the first name looked for. It is not the ONLY one that works
|
||||||
|
-- -- `.n64` and `.v64` are accepted, and so is any other name carrying one
|
||||||
|
-- of those extensions -- but an instruction that names one file is one a
|
||||||
|
-- player can follow, and an instruction that lists every possibility is one
|
||||||
|
-- they have to interpret.
|
||||||
|
function StadiumInstall.romHintFile()
|
||||||
|
return StadiumInstall.romHint() .. "/" .. (NAMED[1]:match("[^/]+$") or "")
|
||||||
|
end
|
||||||
|
|
||||||
-- ------- the marker
|
-- ------- the marker
|
||||||
|
|
||||||
local function readMarker()
|
local function readMarker()
|
||||||
|
|||||||
+85
-17
@@ -96,26 +96,42 @@ local function commandOutput(cmd)
|
|||||||
return (out ~= "") and out or nil
|
return (out ~= "") and out or nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ------- can this machine open one at all
|
-- ------- can this machine open a DIALOG
|
||||||
--
|
--
|
||||||
-- Desktop only, and honestly so. On ANDROID the picker is a native bridge
|
-- Desktop only, and honestly so.
|
||||||
-- (love.system.pickFile) whose kind -> filename mapping is a fixed list of
|
|
||||||
-- three in the engine's own C++, and an unrecognised kind falls through to
|
|
||||||
-- `picked_rom.gb` -- which is the file the engine's Game Boy importer is
|
|
||||||
-- watching. Calling it for a 32 MB N64 ROM would hand that to the wrong
|
|
||||||
-- importer, so it is not called.
|
|
||||||
--
|
--
|
||||||
-- Android does not need it as badly, either: conf.lua points the save
|
-- On ANDROID the picker is a native bridge (love.system.pickFile) whose
|
||||||
-- directory at the app's external-files folder, so `baseroms/` there is
|
-- kind -> filename mapping is a fixed list of three in the engine's own C++,
|
||||||
-- reachable over USB or any file manager with no root and no permission
|
-- and an unrecognised kind falls through to `picked_rom.gb`. That is not
|
||||||
-- prompt, which is the flow the engine's own comment describes for
|
-- merely the wrong name -- it is the file the engine's Game Boy importer is
|
||||||
-- picker-less builds.
|
-- watching, and reading that code settles it: the importer's size test only
|
||||||
function StadiumRomPick.available()
|
-- SKIPS a 1 MB file it has already imported, so a 32 MB N64 ROM landing
|
||||||
|
-- there falls straight through to `love.filesystem.remove` and
|
||||||
|
-- `startData` -- deleted, and then reported to the player as a broken Game
|
||||||
|
-- Boy ROM. So the bridge is not called until it learns the kind, which is a
|
||||||
|
-- two-line change in System.cpp and an APK rebuild (see README).
|
||||||
|
--
|
||||||
|
-- Android is not stuck without it: conf.lua points the save directory at the
|
||||||
|
-- app's external-files folder, so `baseroms/` there is reachable over USB or
|
||||||
|
-- any file manager with no root and no permission prompt. What Android
|
||||||
|
-- lacked was being TOLD that -- the row vanished, and the folder's absolute
|
||||||
|
-- path was only ever written to a console no phone shows. That is what the
|
||||||
|
-- note below is for.
|
||||||
|
function StadiumRomPick.canDialog()
|
||||||
if not (haveShell() and haveFiles()) then return false end
|
if not (haveShell() and haveFiles()) then return false end
|
||||||
local p = osName()
|
local p = osName()
|
||||||
return p == "Windows" or p == "OS X" or p == "Linux"
|
return p == "Windows" or p == "OS X" or p == "Linux"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Kept as the old name for callers that only wanted "is there a dialog".
|
||||||
|
StadiumRomPick.available = StadiumRomPick.canDialog
|
||||||
|
|
||||||
|
-- Where a SAF pick would land if the native bridge grows a Stadium kind.
|
||||||
|
-- Watched unconditionally (see poll): on a build that never writes it this
|
||||||
|
-- costs one getInfo a frame, and on one that does the mod needs no further
|
||||||
|
-- change to use it.
|
||||||
|
StadiumRomPick.PICKED = "picked_stadium.z64"
|
||||||
|
|
||||||
-- Open the dialog. Returns the chosen absolute path, or nil when the player
|
-- Open the dialog. Returns the chosen absolute path, or nil when the player
|
||||||
-- cancelled or no dialog could be opened.
|
-- cancelled or no dialog could be opened.
|
||||||
function StadiumRomPick.choose()
|
function StadiumRomPick.choose()
|
||||||
@@ -184,10 +200,22 @@ end
|
|||||||
-- that quietly goes on saying IMPORT.
|
-- that quietly goes on saying IMPORT.
|
||||||
function StadiumRomPick.import(game)
|
function StadiumRomPick.import(game)
|
||||||
if StadiumInstall.status.state == "building" then return false end
|
if StadiumInstall.status.state == "building" then return false end
|
||||||
|
local StadiumScreen = V.require("StadiumScreen")
|
||||||
|
|
||||||
|
-- No dialog on this platform: say where the file goes, on screen, because
|
||||||
|
-- that is the whole of what the player is missing and the console is not
|
||||||
|
-- somewhere they can read it.
|
||||||
|
if not StadiumRomPick.canDialog() then
|
||||||
|
if game and game.stack then
|
||||||
|
game.stack:push(StadiumScreen.newNote(game, "STADIUM ROM",
|
||||||
|
"PUT STADIUM US 1.0 HERE:",
|
||||||
|
StadiumInstall.romHintFile()))
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
local path = StadiumRomPick.choose()
|
local path = StadiumRomPick.choose()
|
||||||
if not path then return false end
|
if not path then return false end
|
||||||
|
|
||||||
local StadiumScreen = V.require("StadiumScreen")
|
|
||||||
local function fail(why)
|
local function fail(why)
|
||||||
StadiumInstall.status.state = "failed"
|
StadiumInstall.status.state = "failed"
|
||||||
StadiumInstall.status.error = why
|
StadiumInstall.status.error = why
|
||||||
@@ -223,13 +251,16 @@ end
|
|||||||
-- nil where no dialog can be opened, which takes the row off the menu
|
-- nil where no dialog can be opened, which takes the row off the menu
|
||||||
-- entirely rather than offering a button that cannot do anything.
|
-- entirely rather than offering a button that cannot do anything.
|
||||||
function StadiumRomPick.row()
|
function StadiumRomPick.row()
|
||||||
if not StadiumRomPick.available() then return nil end
|
|
||||||
return {
|
return {
|
||||||
id = StadiumRomPick.ID,
|
id = StadiumRomPick.ID,
|
||||||
label = StadiumRomPick.LABEL,
|
label = StadiumRomPick.LABEL,
|
||||||
value = function()
|
value = function()
|
||||||
if StadiumInstall.status.state == "building" then return "BUILDING" end
|
if StadiumInstall.status.state == "building" then return "BUILDING" end
|
||||||
return StadiumInstall.available() and "READY" or "IMPORT"
|
if StadiumInstall.available() then return "READY" end
|
||||||
|
-- WHERE, not IMPORT, where pressing it can only tell you the folder:
|
||||||
|
-- a row that says IMPORT and then does not import is a worse row than
|
||||||
|
-- one that says what it actually does
|
||||||
|
return StadiumRomPick.canDialog() and "IMPORT" or "WHERE?"
|
||||||
end,
|
end,
|
||||||
step = function(game)
|
step = function(game)
|
||||||
pcall(StadiumRomPick.import, game)
|
pcall(StadiumRomPick.import, game)
|
||||||
@@ -238,4 +269,41 @@ function StadiumRomPick.row()
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- ------- a pick that landed while we were not looking
|
||||||
|
--
|
||||||
|
-- The desktop dialog BLOCKS, so `import` above can read the answer on the
|
||||||
|
-- next line. A SAF pick cannot work that way: it is a separate activity,
|
||||||
|
-- Android is free to destroy the game while it is up, and the file appears
|
||||||
|
-- some frames later -- so the only way to notice one is to look for it.
|
||||||
|
--
|
||||||
|
-- Nothing writes this filename today (see canDialog). It is watched anyway so
|
||||||
|
-- that teaching the native bridge one more kind is the whole of the Android
|
||||||
|
-- picker work, with no second change needed here.
|
||||||
|
--
|
||||||
|
-- Consumed and DELETED either way: a 32 MB file left in the save directory
|
||||||
|
-- would be imported again on the next boot, and kept forever if the import
|
||||||
|
-- failed.
|
||||||
|
function StadiumRomPick.poll(game)
|
||||||
|
local f = love and love.filesystem
|
||||||
|
if not (f and f.getInfo) then return false end
|
||||||
|
if StadiumInstall.status.state == "building" then return false end
|
||||||
|
local ok, info = pcall(f.getInfo, StadiumRomPick.PICKED, "file")
|
||||||
|
if not (ok and info) then return false end
|
||||||
|
|
||||||
|
local okRead, bytes = pcall(f.read, StadiumRomPick.PICKED)
|
||||||
|
pcall(f.remove, StadiumRomPick.PICKED)
|
||||||
|
if not (okRead and type(bytes) == "string") then return false end
|
||||||
|
|
||||||
|
local StadiumScreen = V.require("StadiumScreen")
|
||||||
|
local started, err = StadiumInstall.beginFrom(bytes, StadiumRomPick.PICKED)
|
||||||
|
if not started then
|
||||||
|
StadiumInstall.status.state = "failed"
|
||||||
|
StadiumInstall.status.error = tostring(err)
|
||||||
|
end
|
||||||
|
if game and game.stack then
|
||||||
|
game.stack:push(StadiumScreen.new(game, true))
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
return StadiumRomPick
|
return StadiumRomPick
|
||||||
|
|||||||
+77
-30
@@ -70,31 +70,37 @@ end
|
|||||||
-- overflow tidily off one side, it clips off BOTH and loses its first word as
|
-- overflow tidily off one side, it clips off BOTH and loses its first word as
|
||||||
-- well as its last ("that is not a Pokemon Stadium ROM" came out as "at is
|
-- well as its last ("that is not a Pokemon Stadium ROM" came out as "at is
|
||||||
-- not a Pokemon").
|
-- not a Pokemon").
|
||||||
|
--
|
||||||
|
-- Fixed, and it stays fixed: shrinking the text to fit more in was tried and
|
||||||
|
-- the font will not take it. These are 1-bit 8x8 bitmaps, so a fractional
|
||||||
|
-- downscale drops whole pixel rows out of every glyph -- at 0.75 the last
|
||||||
|
-- line of an Android save path came out as mush. Long strings get more LINES
|
||||||
|
-- instead (see the note layout in draw).
|
||||||
local COLS = 20
|
local COLS = 20
|
||||||
|
|
||||||
-- Break a reason into lines that fit, on word boundaries, and never more than
|
-- Break a string into lines that fit, on word boundaries, and never more than
|
||||||
-- `limit` of them -- the plate has room for two and a message nobody can read
|
-- `limit` of them.
|
||||||
-- the end of is not improved by adding a third.
|
local function wrapped(str, limit, cols)
|
||||||
local function wrapped(str, limit)
|
|
||||||
limit = limit or 2
|
limit = limit or 2
|
||||||
|
cols = cols or COLS
|
||||||
local lines, line = {}, nil
|
local lines, line = {}, nil
|
||||||
local function push(text)
|
local function push(text)
|
||||||
if #lines < limit then lines[#lines + 1] = text end
|
if #lines < limit then lines[#lines + 1] = text end
|
||||||
end
|
end
|
||||||
for word in tostring(str):gmatch("%S+") do
|
for word in tostring(str):gmatch("%S+") do
|
||||||
local try = line and (line .. " " .. word) or word
|
local try = line and (line .. " " .. word) or word
|
||||||
if #try <= COLS then
|
if #try <= cols then
|
||||||
line = try
|
line = try
|
||||||
else
|
else
|
||||||
if line then push(line) end
|
if line then push(line) end
|
||||||
-- A word longer than the line is BROKEN ACROSS lines rather than cut.
|
-- A word longer than the line is BROKEN ACROSS lines rather than cut.
|
||||||
-- It is always a path, and a path is the one thing here that has to be
|
-- It is always a path, and a path is the one thing here that has to be
|
||||||
-- readable in full -- an absolute Android save directory runs to about
|
-- readable in full -- an absolute Android save directory runs to
|
||||||
-- seventy characters and has no spaces in it at all, so truncating at
|
-- ninety-odd characters with no spaces in it at all, so truncating at
|
||||||
-- twenty told the player almost nothing.
|
-- twenty told the player almost nothing.
|
||||||
while #word > COLS do
|
while #word > cols do
|
||||||
push(word:sub(1, COLS))
|
push(word:sub(1, cols))
|
||||||
word = word:sub(COLS + 1)
|
word = word:sub(cols + 1)
|
||||||
end
|
end
|
||||||
line = word
|
line = word
|
||||||
end
|
end
|
||||||
@@ -149,8 +155,9 @@ end
|
|||||||
--
|
--
|
||||||
-- Same state shape and the same plate as the build screen, so there is one
|
-- Same state shape and the same plate as the build screen, so there is one
|
||||||
-- look and one set of stack manners rather than two.
|
-- look and one set of stack manners rather than two.
|
||||||
function StadiumScreen.newNote(game, title, body)
|
function StadiumScreen.newNote(game, title, lead, body)
|
||||||
return setmetatable({ game = game, note = { title = title, body = body } },
|
return setmetatable({ game = game,
|
||||||
|
note = { title = title, lead = lead, body = body } },
|
||||||
StadiumScreen)
|
StadiumScreen)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -168,8 +175,38 @@ function StadiumScreen:enter()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- The buttons that dismiss a note. Every face button and START, because the
|
||||||
|
-- prompt says ANY and a player who has to hunt for the right one on a phone
|
||||||
|
-- has been lied to.
|
||||||
|
local DISMISS = { "a", "b", "start", "select" }
|
||||||
|
|
||||||
function StadiumScreen:update()
|
function StadiumScreen:update()
|
||||||
if self.note then return end -- a note waits for a key, not a clock
|
-- ------- a note is dismissed by a BUTTON, not by a key
|
||||||
|
--
|
||||||
|
-- `onKeyPressed` is the keyboard, and a phone has none: the touch overlay
|
||||||
|
-- feeds the engine's Input as virtual buttons (Input.overlayPressed), so a
|
||||||
|
-- state that only listens for keys cannot be closed by touch at all. That
|
||||||
|
-- stranded a player on this screen with no way off it -- the one screen in
|
||||||
|
-- the mod whose entire job is to tell somebody something and then get out
|
||||||
|
-- of the way.
|
||||||
|
--
|
||||||
|
-- Polled here rather than handled as an event because `wasPressed` is the
|
||||||
|
-- edge test the engine's own battle screens use, and it is fed by the
|
||||||
|
-- keyboard, the gamepad AND the overlay through one path.
|
||||||
|
if self.note then
|
||||||
|
local input = self.game and self.game.input
|
||||||
|
if input and input.wasPressed then
|
||||||
|
for _, btn in ipairs(DISMISS) do
|
||||||
|
if input:wasPressed(btn) then
|
||||||
|
if self.game.stack and self.game.stack:top() == self then
|
||||||
|
self.game.stack:pop()
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
local status = StadiumInstall.status
|
local status = StadiumInstall.status
|
||||||
if status.state == "building" then
|
if status.state == "building" then
|
||||||
if not StadiumInstall.step() then
|
if not StadiumInstall.step() then
|
||||||
@@ -206,8 +243,9 @@ local function pop(self)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function StadiumScreen:onKeyPressed(key)
|
function StadiumScreen:onKeyPressed(key)
|
||||||
-- a note is dismissed by ANY key: it is telling the player something, and
|
-- A note takes any key too. This is the KEYBOARD path and it is not the
|
||||||
-- making them guess which button acknowledges it would be its own joke
|
-- one that matters on a phone -- see update, which polls the engine's
|
||||||
|
-- Input so the touch overlay's virtual buttons work as well.
|
||||||
if self.note then pop(self) return true end
|
if self.note then pop(self) return true end
|
||||||
if key == "escape" or key == "x" or key == "backspace" then
|
if key == "escape" or key == "x" or key == "backspace" then
|
||||||
StadiumInstall.cancel()
|
StadiumInstall.cancel()
|
||||||
@@ -225,12 +263,20 @@ function StadiumScreen:draw()
|
|||||||
love.graphics.rectangle("fill", 0, 0, W, H)
|
love.graphics.rectangle("fill", 0, 0, W, H)
|
||||||
|
|
||||||
if self.note then
|
if self.note then
|
||||||
centred(self.note.title, 20)
|
centred(self.note.title, 12)
|
||||||
-- six lines is the plate's room; an absolute path on Android runs to
|
-- The sentence is kept SHORT so the path can have the rest of the plate
|
||||||
-- about seventy characters, which is four of them
|
-- at full size. Shrinking the path was tried first and does not survive
|
||||||
local lines = wrapped(self.note.body, 6)
|
-- the font: these are 1-bit 8x8 bitmaps, so a fractional downscale drops
|
||||||
for i, line in ipairs(lines) do centred(line, 44 + (i - 1) * 10) end
|
-- whole pixel rows out of every glyph and the last line came out as
|
||||||
centred("PRESS ANY KEY", 124)
|
-- mush. Nine rows of twenty characters is 180, which is longer than any
|
||||||
|
-- real save path, so nothing has to be shrunk to fit.
|
||||||
|
local lead = wrapped(self.note.lead or "", 2)
|
||||||
|
for i, line in ipairs(lead) do centred(line, 30 + (i - 1) * 10) end
|
||||||
|
local y = 30 + #lead * 10 + 6
|
||||||
|
for i, line in ipairs(wrapped(self.note.body or "", 9)) do
|
||||||
|
centred(line, y + (i - 1) * 9)
|
||||||
|
end
|
||||||
|
centred("PRESS ANY KEY", 130)
|
||||||
love.graphics.setColor(1, 1, 1, 1)
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -319,16 +365,17 @@ function StadiumScreen.maybePush()
|
|||||||
-- no restart. The folder is still said, once, for the platforms with no
|
-- no restart. The folder is still said, once, for the platforms with no
|
||||||
-- dialog (Android, a handheld Linux with neither zenity nor kdialog)
|
-- dialog (Android, a handheld Linux with neither zenity nor kdialog)
|
||||||
-- and for anyone who would rather drop a file than click through one.
|
-- and for anyone who would rather drop a file than click through one.
|
||||||
|
-- The STADIUM ROM row is on the OPTIONS menu on every platform now, so
|
||||||
|
-- point at it rather than reciting a path here: where a file dialog can
|
||||||
|
-- be opened it opens one, and where it cannot it shows this same folder
|
||||||
|
-- on screen -- which is the part a phone could not otherwise find out.
|
||||||
local okPick, pick = pcall(V.require, "StadiumRomPick")
|
local okPick, pick = pcall(V.require, "StadiumRomPick")
|
||||||
local canPick = okPick and pick and pick.available()
|
local label = (okPick and pick and pick.LABEL) or "STADIUM ROM"
|
||||||
V.mod.log:info("stadium: no Pokemon Stadium ROM found, so the STADIUM "
|
local how = (okPick and pick and pick.canDialog())
|
||||||
.. "battle rungs are off. %s",
|
and "opens a file picker" or "says where to put one"
|
||||||
canPick
|
V.mod.log:info("stadium: no Pokemon Stadium (US) 1.0 ROM found, so the "
|
||||||
and ("Import one from OPTIONS -> " .. pick.LABEL
|
.. "STADIUM battle rungs are off. OPTIONS -> %s %s; the "
|
||||||
.. ", or put a .z64/.n64/.v64 in "
|
.. "folder is %s", label, how, StadiumInstall.romHint())
|
||||||
.. StadiumInstall.romHint() .. " and restart.")
|
|
||||||
or ("Put one (.z64/.n64/.v64) in "
|
|
||||||
.. StadiumInstall.romHint() .. " and restart."))
|
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -208,6 +208,11 @@ mod.content.render_pipelines:register("voxel", {
|
|||||||
-- world, so it is never fighting the engine's own launcher for the
|
-- world, so it is never fighting the engine's own launcher for the
|
||||||
-- screen.
|
-- screen.
|
||||||
pcall(function() V.require("StadiumScreen").maybePush() end)
|
pcall(function() V.require("StadiumScreen").maybePush() end)
|
||||||
|
-- and a ROM the system file picker dropped in the save directory while
|
||||||
|
-- we were not the top activity (Android; see StadiumRomPick.poll)
|
||||||
|
pcall(function()
|
||||||
|
V.require("StadiumRomPick").poll(require("src.core.Game"))
|
||||||
|
end)
|
||||||
-- The horde, on the same always-running tick and for the same reason:
|
-- The horde, on the same always-running tick and for the same reason:
|
||||||
-- it owns no pass of the frame, it is a MODE over the overworld, and
|
-- it owns no pass of the frame, it is a MODE over the overworld, and
|
||||||
-- it has to keep thinking while a warp's wipe covers the screen (the
|
-- it has to keep thinking while a warp's wipe covers the screen (the
|
||||||
@@ -802,6 +807,10 @@ mod.hooks:wrap("ui.options.rows", function(next, game, rows)
|
|||||||
-- to restore on the next boot, so it is appended here rather than living in
|
-- to restore on the next boot, so it is appended here rather than living in
|
||||||
-- SETTINGS. nil on a platform with no file dialog, which takes it off the
|
-- SETTINGS. nil on a platform with no file dialog, which takes it off the
|
||||||
-- menu rather than offering a button that cannot do anything.
|
-- menu rather than offering a button that cannot do anything.
|
||||||
|
-- On EVERY platform. Where there is no file dialog it says WHERE? and
|
||||||
|
-- shows the folder to put the cartridge in, which is the one thing a
|
||||||
|
-- player on a phone could not otherwise find out -- the row used to vanish
|
||||||
|
-- there, which reads as the feature being missing rather than manual.
|
||||||
local okPick, importRow = pcall(function()
|
local okPick, importRow = pcall(function()
|
||||||
return V.require("StadiumRomPick").row()
|
return V.require("StadiumRomPick").row()
|
||||||
end)
|
end)
|
||||||
|
|||||||
@@ -400,7 +400,12 @@ end
|
|||||||
Pipelines.setLevel("voxel", 2)
|
Pipelines.setLevel("voxel", 2)
|
||||||
local hookedRows = Runtime.call("ui.options.rows", function(_, r) return r end,
|
local hookedRows = Runtime.call("ui.options.rows", function(_, r) return r end,
|
||||||
{ data = Data }, { { id = "text_speed" } })
|
{ data = Data }, { { id = "text_speed" } })
|
||||||
T.eq(#hookedRows, 9, "the options hook added a row per setting")
|
-- one per setting, plus the STADIUM ROM action row -- which is not a setting
|
||||||
|
-- (nothing to store, nothing for the mod manager to persist) and is offered
|
||||||
|
-- on every platform, saying WHERE? rather than IMPORT where there is no file
|
||||||
|
-- dialog to open
|
||||||
|
T.eq(#hookedRows, 10, "the options hook added a row per setting, plus the "
|
||||||
|
.. "STADIUM ROM action row")
|
||||||
local grid, curve, water = hookedRows[2], hookedRows[3], hookedRows[4]
|
local grid, curve, water = hookedRows[2], hookedRows[3], hookedRows[4]
|
||||||
local battles, backRow, daytime = hookedRows[5], hookedRows[6], hookedRows[7]
|
local battles, backRow, daytime = hookedRows[5], hookedRows[6], hookedRows[7]
|
||||||
-- the AA row is hookedRows[8]; it is read in its own block below, because
|
-- the AA row is hookedRows[8]; it is read in its own block below, because
|
||||||
@@ -1184,22 +1189,27 @@ Battles.setting:setGate(BATTLE_ROW_GATE)
|
|||||||
local Install =
|
local Install =
|
||||||
run.loader.exports.DRAMATIC_SHAPE.lib.require("StadiumInstall")
|
run.loader.exports.DRAMATIC_SHAPE.lib.require("StadiumInstall")
|
||||||
|
|
||||||
T.check(type(Pick.available()) == "boolean",
|
T.check(type(Pick.canDialog()) == "boolean",
|
||||||
"the picker reports whether this platform has a file dialog at all")
|
"the picker reports whether this platform has a file dialog at all")
|
||||||
|
|
||||||
|
-- The row is offered on EVERY platform. It used to be dropped where no
|
||||||
|
-- dialog could be opened, which on Android read as the feature being
|
||||||
|
-- missing rather than manual -- and the folder path it needed to show was
|
||||||
|
-- only ever written to a console a phone does not have.
|
||||||
local row = Pick.row()
|
local row = Pick.row()
|
||||||
if not Pick.available() then
|
T.check(row ~= nil, "the row is offered whatever the platform can do")
|
||||||
T.eq(row, nil,
|
T.eq(row.label, "STADIUM ROM", "and says what it is for")
|
||||||
"with no dialog available there is no row -- a button that cannot open "
|
T.check(type(row.step) == "function", "and does something when pressed")
|
||||||
.. "anything is worse than the folder instruction it would replace")
|
T.eq(row.value(),
|
||||||
else
|
Install.available() and "READY" or (Pick.canDialog() and "IMPORT" or "WHERE?"),
|
||||||
T.check(row ~= nil, "and where there is one, there is a row")
|
"reading READY once installed, IMPORT where a dialog can be opened, and "
|
||||||
T.eq(row.label, "STADIUM ROM", "which says what it is for")
|
.. "WHERE? where pressing it can only name the folder -- a row that said "
|
||||||
T.check(type(row.step) == "function", "and does something when pressed")
|
.. "IMPORT and then did not import would be the worse lie")
|
||||||
-- the value is the STATE, so a player can see whether it worked
|
|
||||||
T.eq(row.value(), Install.available() and "READY" or "IMPORT",
|
-- and the drop folder it would name is an absolute path, which the note
|
||||||
"reading READY once the models are installed and IMPORT before that")
|
-- screen has to be able to show in full
|
||||||
end
|
T.check(type(Install.romHint()) == "string" and #Install.romHint() > 0,
|
||||||
|
"there is a folder to name")
|
||||||
|
|
||||||
-- A ROM that carries no models must be refused BEFORE anything is written.
|
-- A ROM that carries no models must be refused BEFORE anything is written.
|
||||||
-- An empty build otherwise completes with nothing attempted and therefore
|
-- An empty build otherwise completes with nothing attempted and therefore
|
||||||
|
|||||||
Reference in New Issue
Block a user