mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-07 17:31:35 -04:00
477 lines
25 KiB
Lua
477 lines
25 KiB
Lua
local root = assert(os.getenv("BOX_UI_REPO"))
|
|
io.stdout:setvbuf("no")
|
|
package.path = root .. "/?.lua;" .. root .. "/?/init.lua;" .. package.path
|
|
local imp
|
|
local co, pending, controls, checks = nil, nil, {}, 0
|
|
local awards={}
|
|
local function check(value, label) checks = checks + 1; assert(value, label) end
|
|
function love.load()
|
|
local CacheFs = require("src.import.CacheFs")
|
|
local CacheBlob = require("src.import.CacheBlob")
|
|
CacheFs.readAt = function(path)
|
|
local file
|
|
for cache in assert(os.getenv("BOX_UI_CACHES")):gmatch("[^:]+") do
|
|
file=io.open(cache.."/"..path,"rb")
|
|
if file then break end
|
|
end
|
|
if not file then return nil end
|
|
local bytes = file:read("*a"); file:close()
|
|
return CacheBlob.decode(path, bytes)
|
|
end
|
|
local SaveData = require("src.core.SaveData")
|
|
local Serializer = require("src.core.SaveSerializer")
|
|
local Store = require("src.box.Store")
|
|
local Service = require("src.box.Service")
|
|
local Catalog = require("src.box.Catalog")
|
|
local state = Store.new()
|
|
local save = { version = "emerald", generation = 3, engine = "game3", name = "Demo",
|
|
trainerId=42,secretId=97,flags={},vars={},modData={},bag={pockets={}},
|
|
party = { { species = 25, level = 20, hp = 52, otId = 42, otSecretId = 97, personality = 345 } },
|
|
storage = { currentBox = 1, boxes = { { name = "Demo PC", wallpaper = 1, mons = {} } } } }
|
|
local species = {1,4,7,25,133,152,155,158,175,246,151,251,52,54,56,58,60,63,66,69,72,74,77,79,81,83,84,86,88,90,92,95,96,98,100,102,104,108,109,111,113,114,115,116,118,120,122,123,127,128,129,131,132,137,147,149,154,157,160,165}
|
|
for i, id in ipairs(species) do
|
|
local mon = { species = id, level = 12 + i, hp = 40, personality = i * 1001,
|
|
otId = 42, otSecretId = 97, otName = "Demo", moves = { 33,45 }, pp={30,35}, friendship=70, language=2,
|
|
ivs={hp=15,atk=15,def=15,spe=15,spa=15,spd=15},evs={},heldItem=0,
|
|
exp=require("src.core.game3.summary_data").expForLevel(Catalog.get("emerald").meta[id].growthRate,12+i), metGame=3,metLevel=5,metLocation=7,pokeball=4 }
|
|
if i == 2 then
|
|
mon.markings, mon.pokerus, mon.modernFatefulEncounter = 5, 0x23, false
|
|
mon.contest = { cool=0, beauty=80, cute=70, smart=45, tough=54, sheen=15 }
|
|
mon.ribbons = { cool=2, champion=1, artist=1 }
|
|
end
|
|
if i <= 24 then save.storage.boxes[1].mons[i] = Store.copy(mon) end
|
|
state.boxes[1].mons[i] = { id = i, version = "emerald", generation = 3,
|
|
mon = mon, display = Catalog.describe("emerald", mon) }
|
|
end
|
|
state.boxes[1].name, state.boxes[2].name, state.boxes[3].name = "Kanto collection", "Starters", "Rare finds"
|
|
for i = 1, 12 do
|
|
local entry = Store.copy(state.boxes[1].mons[i]); entry.id = 60 + i
|
|
state.boxes[2].mons[i] = entry
|
|
end
|
|
for i = 1, 6 do
|
|
local entry = Store.copy(state.boxes[1].mons[i + 8]); entry.id = 72 + i
|
|
state.boxes[3].mons[i * 3] = entry
|
|
end
|
|
state.nextId = 79
|
|
local Showcase=require("src.box.Showcase")
|
|
local stage=Showcase.new();stage.name="Starter garden";stage.pattern="Dots"
|
|
stage=assert(Showcase.add(stage,{{box=1,slot=1},{box=1,slot=2},{box=1,slot=3}},state))
|
|
for i,piece in ipairs(stage.pieces) do piece.x=.28+(i-1)*.22;piece.y=.5;piece.scale=1.4 end
|
|
stage.pieces[#stage.pieces+1]={kind="Platform",x=.5,y=.75,scale=2,rotation=0,flip=false}
|
|
state=assert(Showcase.save(state,1,stage))
|
|
local fs = SaveData.persistenceFs()
|
|
fs.createDirectory("box"); fs.createDirectory("saves/emerald")
|
|
fs.write(Store.PATH, Serializer.encode(state))
|
|
fs.write("saves/emerald/slot1.lua", Serializer.encode(save))
|
|
local second = Store.copy(save); second.name = "Other trainer"; second.trainerId = 99
|
|
if os.getenv("BOX_UI_ID_SYNC_ONLY") then
|
|
second.name, second.secretId = "May", 123
|
|
for _, mon in ipairs(require("src.core.TrainerIdSync").monsOf(second, 3)) do
|
|
mon.otId, mon.otSecretId, mon.otName = 99, 123, "May"
|
|
mon.personality = require("bit").bxor(99, 123, 12345) * 65536 + 12345
|
|
end
|
|
local opts = SaveData.loadOptions()
|
|
opts.saveSlots = opts.saveSlots or {}
|
|
opts.saveSlots.emerald = { active = "slot1", list = { "slot1", "slot2" } }
|
|
SaveData.saveOptions(opts)
|
|
end
|
|
fs.write("saves/emerald/slot2.lua", Serializer.encode(second))
|
|
imp = require("src.import.RomImporter").new(function() end, { launcher = true })
|
|
imp.tab = "box"
|
|
imp.mods, imp.modStraysChecked = {}, true
|
|
imp.findErrors, imp._findFetch, imp.findLoading, imp.findNotice = {}, nil, false, nil
|
|
imp._refreshFindSources = function() end
|
|
imp._refreshFind = function() end
|
|
imp.findLoaded, imp.findSources = true, { { feed = "https://example.invalid/mods.json" } }
|
|
imp.findIndex = { mods = {}, carts = {} }
|
|
imp._ensureMods = function() end
|
|
imp._ensureFind = function() end
|
|
local pcRows = {}
|
|
for i, mon in ipairs(save.storage.boxes[1].mons) do
|
|
pcRows[i] = { where = "box", box = 1, index = i, key = "box:1:" .. i,
|
|
source = "Demo PC", mon = mon, entry = { version = "emerald", generation = 3,
|
|
mon = mon, display = Catalog.describe("emerald", mon) } }
|
|
end
|
|
imp._boxState = { service = assert(Service.open()), sources = {
|
|
{ version = "emerald", slotId = "slot1", path = "saves/emerald/slot1.lua", label = "Emerald · Demo" },
|
|
{ version = "emerald", slotId = "slot2", path = "saves/emerald/slot2.lua", label = "Emerald · Other trainer" } },
|
|
sourceIndex = 1, box = 1, pcBox = 1, query = "", sort = "slot", pcRows = pcRows,
|
|
pcBody = Serializer.encode(save), selectedBox = {}, selectedPC = {}, pageBox = 1,
|
|
pagePC = 1, sourcePage = 1, pcSave = save, multi = true, view = "box" }
|
|
for _, slot in ipairs({ 1, 2 }) do imp._boxState.selectedBox["1:" .. slot] = { box = 1, slot = slot } end
|
|
imp._boxState.inspect = imp._boxState.service.state.boxes[1].mons[2]
|
|
local View = require("src.import.LauncherView")
|
|
local original = View.btn
|
|
View.btn = function(owner, x, y, w, h, id, label, opts)
|
|
controls[id] = { x=x, y=y, w=w, h=h, label=label, enabled=opts.enabled ~= false, icon=opts.icon }
|
|
return original(owner, x, y, w, h, id, label, opts)
|
|
end
|
|
local Icons=require("src.ui.kit.Icons")
|
|
local drawIcon=Icons.draw
|
|
Icons.draw=function(icon,x,y,...)
|
|
if icon=="award" then awards[#awards+1]={x=x,y=y} end
|
|
return drawIcon(icon,x,y,...)
|
|
end
|
|
co = coroutine.create(run)
|
|
|
|
end
|
|
local UI = require("src.import.BoxUI")
|
|
local Transition = require("src.ui.kit.Transition")
|
|
local function wait(seconds)
|
|
local untilAt = love.timer.getTime() + (seconds or .3)
|
|
repeat coroutine.yield() until love.timer.getTime() >= untilAt
|
|
end
|
|
local function click(id)
|
|
wait()
|
|
local o=imp._boxState and imp._boxState.organizer
|
|
local r = assert(controls[id], "missing control " .. id.." (rule "..tostring(o and o.ruleIndex)..
|
|
", mode "..tostring(o and o.config.mode)..", page "..tostring(imp._boxState and imp._boxState.toolsPage)..")")
|
|
if not imp._boxPopup and not imp._modConfirm and not imp._idSyncResult then
|
|
local view = imp._tabRegionRect
|
|
if r.y < view.y or r.y+r.h > view.y+view.h then
|
|
imp._tabScroll.box = math.max(0, math.min(imp._tabScrollMax.box, imp._tabScroll.box + r.y - view.y - 20))
|
|
wait(); r = assert(controls[id])
|
|
end
|
|
end
|
|
check(r.w > 0 and r.y >= 0 and r.y+r.h <= love.graphics.getHeight(), id .. " reachable")
|
|
imp:mousepressed(r.x+r.w/2, r.y+r.h/2, 1)
|
|
if imp.mousereleased then imp:mousereleased(r.x+r.w/2, r.y+r.h/2, 1) end
|
|
wait()
|
|
end
|
|
local function shot(name)
|
|
wait(); pending = name
|
|
repeat coroutine.yield() until pending == nil
|
|
wait(.05)
|
|
end
|
|
local function close()
|
|
imp:keypressed("escape"); wait()
|
|
check(not imp._boxPopup, "escape closed the popup")
|
|
end
|
|
local function page(name)
|
|
imp._tabScroll.box = 0
|
|
if name=="Filters" then
|
|
imp._boxState.toolsPage=nil;wait();click("box-filters")
|
|
check(imp._boxState.toolsPage=="Filters","filter button opens filters")
|
|
return
|
|
end
|
|
click("box-tools-menu")
|
|
local option
|
|
for i,row in ipairs(imp._boxPopup.options) do if row.id == name then option=i end end
|
|
assert(option, name)
|
|
imp._boxPopup.index, imp._boxPopup.reveal = option, true
|
|
imp:keypressed("return"); wait()
|
|
check(imp._boxState.toolsPage == (name ~= "Pokémon" and name or nil), "tool picker chose " .. name)
|
|
end
|
|
local function chooseValue(id, value)
|
|
click(id)
|
|
local index
|
|
for i, option in ipairs(imp._boxPopup.options) do if option.id == value then index = i; break end end
|
|
assert(index, "missing choice " .. tostring(value))
|
|
imp._boxPopup.index, imp._boxPopup.reveal = index, true
|
|
imp:keypressed("return"); wait()
|
|
end
|
|
local function boxInventory(state)
|
|
local out = {}
|
|
for _, box in ipairs(state.boxes) do
|
|
for _, entry in pairs(box.mons) do out[entry.id] = require("src.core.SaveSerializer").encode(entry.mon) end
|
|
end
|
|
return require("src.core.SaveSerializer").encode(out)
|
|
end
|
|
local function pcInventory(save)
|
|
local out = {}
|
|
for _, row in ipairs(require("src.box.Records").candidates(save, 3)) do
|
|
if row.where == "box" then out[#out + 1] = require("src.core.SaveSerializer").encode(row.mon) end
|
|
end
|
|
table.sort(out)
|
|
return require("src.core.SaveSerializer").encode(out)
|
|
end
|
|
function run()
|
|
wait()
|
|
if os.getenv("BOX_UI_STUDIO_ONLY") then
|
|
page("Showcase")
|
|
shot("ui-40-showcase-editor-desktop.png")
|
|
click("box-stage-add-pokemon")
|
|
check(#imp._boxPopup.options>50,"Pokémon picker makes stored collection available")
|
|
shot("ui-41-showcase-add-pokemon-desktop.png")
|
|
imp._boxPopup.index=2;imp:keypressed("return");wait()
|
|
local s=imp._boxState
|
|
check(#s.stageDraft.pieces==5,"picker adds a Pokémon directly to the stage")
|
|
click("box-stage-add-scenery")
|
|
local tree
|
|
for i,row in ipairs(imp._boxPopup.options) do if row.label=="Tree" then tree=i end end
|
|
imp._boxPopup.index=tree;imp:keypressed("return");wait()
|
|
check(s.stageDraft.pieces[s.stagePiece].kind=="Tree","new scenery is a real tree")
|
|
local r=s.stageRect
|
|
local p=s.stageDraft.pieces[s.stagePiece]
|
|
local cx,cy=r.x+p.x*r.w,r.y+p.y*r.h
|
|
imp:mousepressed(cx,cy,1);imp:mousemoved(cx+30,cy-20);imp:mousereleased(cx+30,cy-20,1);wait()
|
|
check(p.x>.5 and p.y<.5,"native scene dragging changes placement")
|
|
local handles=s.stageHandles
|
|
local resize=handles[1]
|
|
imp:mousepressed(resize.x+resize.w/2,resize.y+resize.h/2,1)
|
|
imp:mousemoved(resize.x+resize.w/2+35,resize.y+resize.h/2-10)
|
|
imp:mousereleased(0,0,1);wait()
|
|
check(p.scale>1.5,"hold resize changes scenery scale")
|
|
local flip=s.stageHandles[3]
|
|
imp:mousepressed(flip.x+flip.w/2,flip.y+flip.h/2,1);wait()
|
|
check(p.flipY,"vertical flip toggles from pop-out")
|
|
flip=s.stageHandles[3]
|
|
imp:mousepressed(flip.x+flip.w/2,flip.y+flip.h/2,1);wait()
|
|
check(not p.flipY,"vertical flip can be restored from pop-out")
|
|
click("box-tools-stage-back")
|
|
check(s.stagePiece==#s.stageDraft.pieces-1,"layer arrow sends selected object backward")
|
|
shot("ui-42-showcase-selected-tools-desktop.png")
|
|
chooseValue("box-tools-stageSection","Scene")
|
|
chooseValue("box-tools-stage-music","Night")
|
|
local Showcase=require("src.box.Showcase")
|
|
local fingerprints={}
|
|
for i,pattern in ipairs({"Crosses","Bricks","Grid"}) do
|
|
chooseValue("box-tools-stage-pattern",pattern)
|
|
imp._tabScroll.box=0
|
|
shot("ui-"..(46+i).."-showcase-"..pattern:lower().."-desktop.png")
|
|
local canvas=love.graphics.newCanvas(400,200)
|
|
love.graphics.push("all");love.graphics.setCanvas(canvas);love.graphics.origin();love.graphics.setScissor()
|
|
local empty=Showcase.new();empty.pattern=pattern;Showcase.draw(s.service.state,empty,0,0,400,200)
|
|
love.graphics.pop()
|
|
local data=canvas:newImageData();fingerprints[i]=love.data.hash("sha256",data:getString())
|
|
data:release();canvas:release()
|
|
end
|
|
check(fingerprints[1]~=fingerprints[2] and fingerprints[2]~=fingerprints[3] and fingerprints[1]~=fingerprints[3],
|
|
"Crosses, Bricks and Grid render different native pixels")
|
|
click("box-stage-options")
|
|
imp._boxPopup.index=1;imp:keypressed("return");wait()
|
|
check(s.service.state.boxes[1].theme=="Showcase","saved scene is applied as Box theme")
|
|
check(s.service.fs.getInfo(s.service.state.boxes[1].wallpaper),"scene wallpaper PNG exists")
|
|
check(s.service.state.boxes[1].showcaseMusic=="Night","applying scene saves its chosen music")
|
|
page("Pokémon")
|
|
local track,owner,music=Showcase.currentMusic()
|
|
check(track=="Night" and owner=="box" and music:isPlaying(),"applied scene music plays on native Box board")
|
|
shot("ui-50-applied-stage-desktop.png")
|
|
page("Themes");click("box-theme-picker")
|
|
local saved
|
|
for i,row in ipairs(imp._boxPopup.options) do if row.label:find("Stage 1",1,true) then saved=i end end
|
|
check(saved~=nil,"saved stages are selectable from Themes")
|
|
shot("ui-43-saved-stage-theme-desktop.png");close()
|
|
page("Showcase");love.window.setMode(390,844,{resizable=true});imp._tabScroll.box=0;wait()
|
|
shot("ui-44-showcase-editor-portrait.png")
|
|
page("Pokémon")
|
|
s.query="bULba";wait();shot("ui-45-search-context-portrait.png")
|
|
check(#require("src.box.Store").search(s.service.state,s.query,"slot")>0,"mixed-case Pokémon lookup matches")
|
|
page("List");shot("ui-46-list-sprites-portrait.png")
|
|
love.window.setMode(1360,860,{resizable=true});s.query="";page("Pokémon")
|
|
local Store=require("src.box.Store")
|
|
local edited=Store.copy(s.service.state);edited.revision=edited.revision+1
|
|
local shiny=edited.boxes[1].mons[1]
|
|
shiny.mon.personality=require("bit").bxor(42,97,12345)*65536+12345
|
|
shiny.display=require("src.box.Catalog").describe("emerald",shiny.mon)
|
|
check(shiny.display.shiny,"move fixture is actually shiny")
|
|
assert(s.service:commit(edited));s.selectedBox={["1:1"]={box=1,slot=1}};s.inspect=nil;wait()
|
|
local function marked(r)
|
|
for _,a in ipairs(awards) do if a.x>=r.x and a.x<=r.x+r.w and a.y>=r.y and a.y<=r.y+r.h then return true end end
|
|
return false
|
|
end
|
|
check(marked(controls["box-box-1:1"]),"occupied shiny slot has its marker")
|
|
click("box-options");imp._boxPopup.index=1;imp:keypressed("return");wait()
|
|
chooseValue("box-active-box-picker",2);click("box-box-2:20")
|
|
chooseValue("box-active-box-picker",1)
|
|
check(s.service.state.boxes[1].mons[1]==nil,"moving Pokémon empties its old slot")
|
|
check(not marked(controls["box-box-1:1"]),"empty former slot has no leftover shiny marker")
|
|
shot("ui-51-moved-shiny-empty-slot-desktop.png")
|
|
imp:_switchTab("mods");wait()
|
|
imp:_switchTab("box")
|
|
local tr=assert(Transition.get("tabs"),"entering Box keeps its slide")
|
|
check(tr.from=="mods" and tr.to=="box" and love.timer.getTime()-tr.t0<.05,"native Box slide starts after refresh")
|
|
wait(.03)
|
|
tr=assert(Transition.get("tabs"),"default Box transition survives initial native rendering")
|
|
check(tr.p>0 and tr.p<1,"default native Box slide renders an intermediate frame")
|
|
tr.t0=love.timer.getTime()
|
|
tr.dur=1.5
|
|
shot("ui-52-box-tab-slide-desktop.png")
|
|
tr.dur=.18;wait()
|
|
print("BOX_UI_NATIVE_PASS "..checks.." checks; "..love.filesystem.getSaveDirectory())
|
|
love.event.quit(0);return
|
|
end
|
|
if os.getenv("BOX_UI_ID_SYNC_ONLY") then
|
|
imp.activeSlot.emerald = "slot1"
|
|
imp:askIdSync("emerald"); wait()
|
|
check(imp._modConfirm.lines[1]:find("Demo", 1, true), "confirmation names actual trainer")
|
|
check(imp._modConfirm.lines[2]:find("secret ID", 1, true), "confirmation covers full trainer identity")
|
|
shot("ui-36-id-sync-confirm-desktop.png")
|
|
love.window.setMode(390,844,{resizable=true}); wait()
|
|
shot("ui-37-id-sync-confirm-portrait.png")
|
|
local accept = imp._modConfirm.onYes
|
|
imp._modConfirm = nil
|
|
accept()
|
|
while imp._idSync do wait(.05) end
|
|
check(imp._idSyncResult and imp._idSyncResult.title == "ID Sync", "native launcher job succeeds")
|
|
local SaveData = require("src.core.SaveData")
|
|
local other = SaveData.decode(SaveData.readSlotSource("emerald", "slot2"))
|
|
check(other.name == "Demo" and other.trainerId == 42 and other.secretId == 97,
|
|
"native disk save matches selected trainer")
|
|
check(not require("src.core.game3.pokemon").isTradedMon(other.party[1], other),
|
|
"native mon obeys after reload")
|
|
check(require("src.core.game3.pokemon").isShiny(other.party[1]), "native shiny remains shiny after reload")
|
|
check(not require("src.core.TrainerIdSync").newJob({scope="emerald",slot="slot1"}).error,
|
|
"completed journal leaves storage usable")
|
|
shot("ui-38-id-sync-result-portrait.png")
|
|
love.window.setMode(1360,860,{resizable=true}); wait()
|
|
shot("ui-39-id-sync-result-desktop.png")
|
|
print("BOX_UI_NATIVE_PASS "..checks.." checks; "..love.filesystem.getSaveDirectory())
|
|
love.event.quit(0)
|
|
return
|
|
end
|
|
if not os.getenv("BOX_UI_ORGANIZER_ONLY") then
|
|
if not os.getenv("BOX_UI_DETAILS_ONLY") then
|
|
check(controls["box-choose-save"].y == controls["box-tools-menu"].y, "desktop toolbar shares one row")
|
|
check(controls["box-withdraw"].icon ~= nil, "transfer has an icon")
|
|
check(controls["box-withdraw"].label:find("Send 2 to game",1,true), "transfer direction is named")
|
|
shot("ui-01-board-desktop.png")
|
|
click("box-choose-save"); check(#imp._boxPopup.options==2,"game picker shows playthroughs")
|
|
shot("ui-02-game-picker-desktop.png"); close()
|
|
click("box-help-Game"); check(imp._boxPopup.message==UI.HELP.Game,"game help explains the PC")
|
|
shot("ui-03-game-help-desktop.png"); close()
|
|
click("box-tools-menu"); check(#imp._boxPopup.options==11,"all tools remain reachable; filters have their own button")
|
|
shot("ui-04-tool-picker-desktop.png"); close()
|
|
page("Filters"); shot("ui-05-filters-desktop.png")
|
|
click("box-tools-filter-type"); shot("ui-06-filter-picker-desktop.png"); close()
|
|
page("Gifts"); shot("ui-07-gifts-desktop.png")
|
|
page("Dashboard"); shot("ui-08-dashboard-desktop.png")
|
|
page("Showcase"); shot("ui-09-showcase-desktop.png")
|
|
click("box-tools-stageSection"); imp._boxPopup.index=2; imp:keypressed("return"); wait()
|
|
check(imp._boxState.stageSection=="Placement","placement section chosen")
|
|
shot("ui-10-placement-desktop.png")
|
|
page("Pokémon")
|
|
click("box-options"); shot("ui-11-options-desktop.png"); close()
|
|
love.window.setMode(390,844,{resizable=true}); imp._tabScroll.box=0; wait()
|
|
shot("ui-12-board-portrait.png")
|
|
click("box-tools-menu"); shot("ui-13-tool-picker-portrait.png"); close()
|
|
page("Filters"); shot("ui-14-filters-portrait.png")
|
|
click("box-help-Game"); shot("ui-15-help-portrait.png"); close()
|
|
page("Showcase"); shot("ui-16-showcase-portrait.png")
|
|
page("Pokémon")
|
|
click("box-view-pc"); check(imp._boxState.view=="pc","game PC view chosen")
|
|
imp._tabScroll.box=0; shot("ui-17-game-pc-portrait.png")
|
|
click("box-view-box")
|
|
click("box-transfer-target"); shot("ui-18-destination-portrait.png")
|
|
imp:keypressed("end"); wait(); imp:keypressed("return"); wait()
|
|
check(imp._boxState.pcBox==14,"destination picker reaches the last PC box")
|
|
shot("ui-19-transfer-toolbar-portrait.png")
|
|
imp._tabScroll.box=0
|
|
Transition.armed=true; UI.change(imp,imp._boxState,"view","pc",1)
|
|
local tr=assert(Transition.get("box")); tr.t0=love.timer.getTime()-.03
|
|
pending="ui-20-slide-portrait.png"; coroutine.yield()
|
|
wait()
|
|
check(not Transition.active("box"),"slide settles")
|
|
end
|
|
love.window.setMode(1360,860,{resizable=true}); imp._tabScroll.box=0; wait()
|
|
click("box-summary"); check(imp._boxState.summary,"summary opens")
|
|
shot("ui-21-summary-desktop.png")
|
|
click("box-summary-section"); imp._boxPopup.index=2; imp:keypressed("return"); wait()
|
|
check(imp._boxState.summarySection=="Moves","moves section chosen")
|
|
shot("ui-22-moves-desktop.png")
|
|
click("box-summary-section"); imp._boxPopup.index=3; imp:keypressed("return"); wait()
|
|
check(imp._boxState.summarySection=="Stats","stats section chosen")
|
|
shot("ui-23-stats-desktop.png")
|
|
click("box-summary-section"); imp._boxPopup.index=4; imp:keypressed("return"); wait()
|
|
shot("ui-24-origin-desktop.png")
|
|
local r=assert(controls["box-summary-back"])
|
|
local viewport=imp._tabRegionRect
|
|
imp._tabScroll.box=math.max(0,math.min(imp._tabScrollMax.box,imp._tabScroll.box+r.y-viewport.y-20)); wait()
|
|
shot("ui-25-ribbons-desktop.png")
|
|
love.window.setMode(390,844,{resizable=true}); imp._tabScroll.box=0; wait()
|
|
click("box-summary-section"); imp._boxPopup.index=1; imp:keypressed("return"); wait()
|
|
shot("ui-26-overview-portrait.png")
|
|
click("box-summary-section"); imp._boxPopup.index=2; imp:keypressed("return"); wait()
|
|
shot("ui-27-moves-portrait.png")
|
|
click("box-summary-section"); imp._boxPopup.index=3; imp:keypressed("return"); wait()
|
|
shot("ui-28-stats-portrait.png")
|
|
r=assert(controls["box-summary-back"]); viewport=imp._tabRegionRect
|
|
imp._tabScroll.box=math.max(0,math.min(imp._tabScrollMax.box,imp._tabScroll.box+r.y-viewport.y-20)); wait()
|
|
shot("ui-29-contest-portrait.png")
|
|
end
|
|
if not os.getenv("BOX_UI_DETAILS_ONLY") then
|
|
if imp._boxState.summary then click("box-summary-back") end
|
|
love.window.setMode(390,844,{resizable=true}); imp._tabScroll.box = 0; wait()
|
|
page("Arrange")
|
|
chooseValue("box-tools-arrangeSection", "Auto Box")
|
|
chooseValue("box-organize-first-box", 2)
|
|
chooseValue("box-organize-field", "species")
|
|
chooseValue("box-organize-value", "PIKACHU")
|
|
check(imp._boxState.organizer.config.rules[1].firstBox == 2, "Auto Box destination is selected")
|
|
check(imp._boxState.organizer.config.rules[1].conditions[1].value == "PIKACHU", "Auto Box species condition is selected")
|
|
imp._tabScroll.box = 0; shot("ui-30-auto-box-portrait.png")
|
|
local warehouseBefore = boxInventory(imp._boxState.service.state)
|
|
click("box-organize-preview")
|
|
check(boxInventory(imp._boxState.service.state) == warehouseBefore, "rule preview leaves warehouse unchanged")
|
|
check(imp._boxState.organizer.preview.report.moved > 0, "rule preview reports actual moves")
|
|
check(imp._boxState.organizer.ruleIndex==1,"sticky preview does not click rule underneath")
|
|
shot("ui-31-auto-box-preview-portrait.png")
|
|
close()
|
|
love.window.setMode(1360,860,{resizable=true}); imp._tabScroll.box = 0; wait()
|
|
shot("ui-32-auto-box-desktop.png")
|
|
check(imp._boxState.organizer.preview ~= nil, "closing movement review keeps the plan")
|
|
click("box-organize-apply")
|
|
check(boxInventory(imp._boxState.service.state) == warehouseBefore, "Auto Box preserves every native record and entry ID")
|
|
local at = require("src.box.Store").find(imp._boxState.service.state, 4)
|
|
check(at.box == 2, "Auto Box applies species rule")
|
|
click("box-organize-presets")
|
|
imp._boxPopup.index = #imp._boxPopup.options; imp:keypressed("return"); wait()
|
|
check(imp._boxPrompt ~= nil, "preset uses the existing name prompt")
|
|
imp:textinput("Pikachu box"); imp:keypressed("return"); wait()
|
|
check(imp._boxState.service.state.organizerProfiles[1].name == "Pikachu box", "preset saved from UI")
|
|
chooseValue("box-organize-field", "egg")
|
|
click("box-organize-presets")
|
|
imp._boxPopup.index = 1; imp:keypressed("return"); wait()
|
|
check(imp._boxState.organizer.config.rules[1].conditions[1].value == "PIKACHU", "saved preset reloads its conditions")
|
|
click("box-organize-rule-options")
|
|
imp._boxPopup.index = 1; imp:keypressed("return"); wait()
|
|
check(#imp._boxState.organizer.config.rules[1].conditions == 2, "rule editor adds a second condition")
|
|
click("box-organize-rule-options")
|
|
imp._boxPopup.index = 4; imp:keypressed("return"); wait()
|
|
check(imp._boxState.organizer.config.rules[1].match == "any", "rule editor switches to any condition")
|
|
click("box-organize-rule-options")
|
|
imp._boxPopup.index = 2; imp:keypressed("return"); wait()
|
|
check(#imp._boxState.organizer.config.rules[1].conditions == 1, "rule editor removes selected condition")
|
|
chooseValue("box-tools-arrangeSection", "Sort")
|
|
click("box-organize-target-pc")
|
|
imp._tabScroll.box = 0; shot("ui-33-game-sort-desktop.png")
|
|
local pcBefore = pcInventory(imp._boxState.pcSave)
|
|
local partyBefore = require("src.core.SaveSerializer").encode(imp._boxState.pcSave.party)
|
|
click("box-organize-preview")
|
|
check(imp._boxState.organizer.preview.report.moved > 0, "Game PC preview reports actual moves")
|
|
check(pcInventory(imp._boxState.pcSave) == pcBefore, "Game PC preview leaves save unchanged")
|
|
shot("ui-34-game-sort-preview-desktop.png")
|
|
close()
|
|
click("box-organize-apply")
|
|
check(pcInventory(imp._boxState.pcSave) == pcBefore, "Game PC apply preserves all native records")
|
|
check(require("src.core.SaveSerializer").encode(imp._boxState.pcSave.party) == partyBefore, "Game PC sort leaves party untouched")
|
|
local source = imp._boxState.sources[imp._boxState.sourceIndex]
|
|
check(imp._boxState.service.fs.getInfo(source.path .. ".box-bak"), "Game PC sort keeps a native save backup")
|
|
page("Pokémon"); click("box-view-pc")
|
|
imp._boxState.pcBox = 1; imp._tabScroll.box = 0
|
|
shot("ui-35-sorted-game-pc-desktop.png")
|
|
end
|
|
require("src.box.Cry").stop(); require("src.box.Showcase").stopMusic()
|
|
print("BOX_UI_NATIVE_PASS "..checks.." checks; "..love.filesystem.getSaveDirectory())
|
|
love.event.quit(0)
|
|
end
|
|
function love.update(dt)
|
|
if imp then imp:update(dt) end
|
|
if co and coroutine.status(co)~="dead" then
|
|
local ok, why=coroutine.resume(co)
|
|
if not ok then print("BOX_UI_NATIVE_FAIL "..tostring(why)); love.event.quit(1) end
|
|
end
|
|
end
|
|
function love.draw()
|
|
if imp then controls={};awards={};imp:draw() end
|
|
if pending then
|
|
local name=pending; pending=nil
|
|
love.graphics.captureScreenshot(function(data)
|
|
assert(love.filesystem.write(name,data:encode("png"):getString()))
|
|
end)
|
|
end
|
|
end
|
|
function love.errorhandler(why)
|
|
print("BOX_UI_NATIVE_FAIL "..tostring(why)); return function() return 1 end
|
|
end
|