-- home/joypad2.asm:16-53 (#1898) 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") package.loaded["src.render.Font"] = { BORDER = { tl = 1, tr = 2, bl = 3, br = 4, h = 5, v = 6 }, draw = function() end, drawCode = function() end, drawBox = function() end, width = function(text) return #tostring(text) * 8 end, split = function(text) local spans = {} for i = 1, #tostring(text) do spans[i] = { from = i, to = i } end return spans end, spansFitting = function(spans) return #spans end, } local MenuRepeat = require("src.ui.MenuRepeat") local ListMenu = require("src.ui.ListMenu") local function newInput() local input = { edges = {}, held = {} } function input:press(button) self.edges[button] = true self.held[button] = true end function input:release(button) self.edges[button] = nil self.held[button] = nil end function input:wasPressed(button) if self.edges[button] then self.edges[button] = nil return true end return false end function input:isDown(button) return self.held[button] == true end return input end do eq(MenuRepeat.GEN1_DELAY, 30, "half a second before the first repeat") eq(MenuRepeat.GEN1_RATE, 5, "then 1/12 of a second between repeats") eq(MenuRepeat.GEN2_DELAY, 15, "JoyTextDelay seeds wTextDelayFrames with 15") eq(MenuRepeat.GEN2_RATE, 5, "and reseeds it with 5") local input = newInput() local state = MenuRepeat.new(MenuRepeat.GEN1_DELAY, MenuRepeat.GEN1_RATE) input:press("down") local dir, edge = MenuRepeat.direction(state, input, { "up", "down" }) eq(dir, "down", "the press itself reports immediately") eq(edge, true, "and is flagged as the edge") local fired = {} for frame = 1, 60 do local d = MenuRepeat.direction(state, input, { "up", "down" }) if d then fired[#fired + 1] = frame end end eq(fired[1], 30, "the hold is ignored for half a second") eq(fired[2], 35, "then repeats every five frames") eq(fired[3], 40, "and keeps repeating") eq(#fired, 7, "seven repeats in the second after the press") input:release("down") eq(MenuRepeat.direction(state, input, { "up", "down" }), nil, "letting go stops it") eq(state.frames, 0, "and clears the counter, so the next hold waits again") local off = MenuRepeat.new(MenuRepeat.GEN1_DELAY, MenuRepeat.GEN1_RATE, false) input:press("down") MenuRepeat.direction(off, input, { "up", "down" }) local repeated = false for _ = 1, 120 do if MenuRepeat.direction(off, input, { "up", "down" }) then repeated = true end end check(not repeated, "a disabled state stays edge-only") input:release("down") end do local items = {} for i = 1, 10 do items[i] = { value = i, label = "ITEM " .. i } end local input = newInput() local game = { input = input, stack = { push = function() end, pop = function() end, top = function() end } } local list = ListMenu.new(game, "ITEMS", items, { kind = "bag", itemBox = true }) eq(list.keyRepeat, true, "DisplayListMenuID runs with hJoy7 = 1, with no mod hook in sight") eq(list.repeatDelay, 30, "at the cart's half-second delay") eq(list.repeatRate, 5, "and the cart's repeat rate") input:press("down") list:update(1 / 60) eq(list.index, 2, "the press moves the cursor one row") for _ = 1, 29 do list:update(1 / 60) end eq(list.index, 2, "a short hold moves nothing more") list:update(1 / 60) eq(list.index, 3, "the 30th held frame is the first repeat") for _ = 1, 5 do list:update(1 / 60) end eq(list.index, 4, "and every fifth frame after that") for _ = 1, 60 do list:update(1 / 60) end eq(list.index, 10, "holding Down walks to the end of the list") eq(list.scroll, 7, "with the window scrolled to the bottom") input:release("down") input:press("up") list:update(1 / 60) for _ = 1, 200 do list:update(1 / 60) end eq(list.index, 1, "holding Up walks back to the top") eq(list.scroll, 0, "and the window comes with it") end do local data = { pokemon = {}, constants = { dexSize = 151, dexDigits = 3 } } for n = 1, 151 do local id = ("DEXMON_%03d"):format(n) data.pokemon[id] = { id = id, name = ("MON%03d"):format(n), dex = n } end local save = { pokedex = { seen = {}, owned = {} } } for n = 1, 20 do save.pokedex.seen[("DEXMON_%03d"):format(n)] = true end local input = newInput() local game = { data = data, save = save, input = input, stack = { push = function() end, pop = function() end, top = function() end } } local PokedexMenu = require("src.ui.PokedexMenu") local dex = PokedexMenu.new(game, {}) eq(#dex.items, 20, "twenty seen species in the list") input:press("down") dex:update(1 / 60) eq(dex.index, 2, "ShowPokedexMenu's own list moves on the press") for _ = 1, 29 do dex:update(1 / 60) end eq(dex.index, 2, "and waits out the same half second") dex:update(1 / 60) eq(dex.index, 3, "before the first repeat") for _ = 1, 200 do dex:update(1 / 60) end eq(dex.index, 20, "a long hold reaches the last seen species") input:release("down") input:press("right") dex:update(1 / 60) eq(dex.scroll, 13, "Right pages the scroll offset and clamps at the end") for _ = 1, 60 do dex:update(1 / 60) end eq(dex.scroll, 13, "holding Right at the bottom cannot page past it") input:release("right") input:press("left") dex:update(1 / 60) eq(dex.scroll, 6, "Left pages back seven rows") for _ = 1, 40 do dex:update(1 / 60) end eq(dex.scroll, 0, "and a held Left runs the list back to the top") input:release("left") end do local PackMenu = require("src.ui.gen2.PackMenu") local Save = require("src.core.gen2.Save") local items = {} local order = {} for i = 1, 8 do local id = "FAKEITEM_" .. i items[id] = { id = id, name = "ITEM " .. i, pocket = "ITEM", index = i, canToss = true, canSelect = false, fieldMenu = "ITEMMENU_CLOSE" } order[i] = id end local save = Save.newGame() save.inventory = {} for _, id in ipairs(order) do save.inventory[id] = 1 end save.bagOrder = order local input = newInput() local game = { input = input, save = save, options = save.options, data = { items = items, moves = {}, pokemon = {}, audio = { sfx = {} } }, stack = { push = function() end, pop = function() end } } local pack = PackMenu.new(game, { save = save, pocket = "ITEM", onClose = function() end, world = { useFieldItem = function() end } }) pack.gfx = { available = function() return false end, draw = function() end, colorsAt = function() return nil end } eq(pack.hold.delay, 15, "the pocket list runs at JoyTextDelay's cadence") eq(pack.hold.rate, 5, "with the cart's five-frame repeat") local total = pack:total() check(total >= 9, "eight items plus the CANCEL row") input:press("down") pack:update(1 / 60) eq(pack.index, 2, "the press moves one row") for _ = 1, 14 do pack:update(1 / 60) end eq(pack.index, 2, "fifteen frames of delay first") pack:update(1 / 60) eq(pack.index, 3, "then the first repeat") for _ = 1, 200 do pack:update(1 / 60) end eq(pack.index, total, "a held Down stops on the last row rather than wrapping round") input:release("down") end do local Gen2Dex = require("src.ui.gen2.PokedexMenu") local entries, seen = {}, {} for n = 1, 30 do local id = ("G2MON_%03d"):format(n) entries[id] = { dex = n, species = id, name = ("MON%03d"):format(n) } seen[id] = true end local input = newInput() local save = { pokedex = { seen = seen, caught = {} } } local game = { data = {}, save = save, input = input } local dex = Gen2Dex.new(game, { save = save, pokedex = { entries = entries }, pokemon = {} }) eq(dex.hold.delay, 15, "the listing runs at JoyTextDelay's cadence") check(#dex.rows >= 30, "thirty seen species in the listing") input:press("down") dex:update(1 / 60) eq(dex.index, 2, "the press moves one row") for _ = 1, 14 do dex:update(1 / 60) end eq(dex.index, 2, "after the same fifteen-frame delay") dex:update(1 / 60) eq(dex.index, 3, "the hold starts repeating") for _ = 1, 400 do dex:update(1 / 60) end eq(dex.index, #dex.rows, "Pokedex_ListingMoveCursorDown stops at the end of the listing") input:release("down") end T.finish("menu hold-to-scroll bug 1898")