Files
gen1recomp/tests/engine/game3_speed_hotkey_test.lua
bryanthaboi c65fcf939d battle speed
2026-09-25 14:20:34 -04:00

207 lines
7.8 KiB
Lua

package.path = "./?.lua;./?/init.lua;" .. package.path
if not _G.love then _G.love = require("tests.love_stub") end
local T = require("tests.harness")
local check, eq = T.check, T.eq
local Input = require("src.core.Input")
local GamepadMap = require("src.core.GamepadMap")
local Game = require("src.core.Game")
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 function newGame3(phase)
local g = setmetatable({
phase = phase or "field",
options = { speedOverworld = 1, speedBattle = 1, speedMenu = 1 },
input = Input,
writeOptions = function() end,
}, { __index = Game3 })
return g
end
Input:init()
Input:applyBindings(nil)
do
local g = newGame3("field")
stackBusy = true
eq(g:speedCategory(), "overworld", "menu over the field inherits overworld")
g:keypressed("1")
eq(g.options.speedOverworld, 2, "1 with a field menu open bumps OVERWORLD SPEED")
eq(g.options.speedMenu, 1, "1 with a field menu open leaves MENU SPEED alone")
stackBusy = false
eq(g:logicSpeed(), 2, "walk speed sticks after the menu closes")
battleActive = true
eq(g:speedCategory(), "battle", "battle stays battle")
g.options.speedBattle = 10
eq(g:logicSpeed(), 10, "a local gen3 battle runs at BATTLE SPEED")
g:keypressed("1")
check(g.options.speedBattle ~= 10, "1 in battle cycles BATTLE SPEED")
eq(g.options.speedOverworld, 2, "and leaves OVERWORLD SPEED alone")
battleActive = false
eq(newGame3("boot"):speedCategory(), "menu", "boot phase stays menu")
eq(newGame3("quest_log"):speedCategory(), "menu", "quest log stays menu")
end
do
local g = newGame3("field")
g:keypressed("kp1")
eq(g.options.speedOverworld, 2, "numpad 1 cycles speed")
local realKb = love.keyboard
love.keyboard = setmetatable({
getScancodeFromKey = function(key) return key == "&" and "1" or key end,
}, { __index = realKb })
g:keypressed("&")
eq(g.options.speedOverworld, 3, "AZERTY top-row 1 cycles speed by scancode")
love.keyboard = realKb
eq(Input.hotkeyKey("kp3"), "3", "numpad 3 normalizes")
eq(Input.hotkeyKey("kp4"), "4", "numpad 4 normalizes")
eq(Input.hotkeyKey("z"), "z", "letters pass through")
end
do
Input:applyBindings({
left = { key = "kp4" }, down = { key = "kp2" }, up = { key = "kp8" },
right = { key = "kp6" }, a = { key = "&" }, b = { key = "kp1" },
})
local realKb = love.keyboard
love.keyboard = setmetatable({
getScancodeFromKey = function(key) return key == "&" and "1" or key end,
}, { __index = realKb })
eq(Input.hotkeyKey("kp4"), "kp4", "numpad 4 bound to LEFT is not a hotkey")
eq(Input.hotkeyKey("kp2"), "kp2", "numpad 2 bound to DOWN is not a hotkey")
eq(Input.hotkeyKey("&"), "&", "AZERTY & bound to A is not a hotkey")
eq(Input.hotkeyKey("kp3"), "3", "unbound numpad 3 is still a hotkey")
Input:reset()
local g = newGame3("field")
g:keypressed("kp4")
Input:step()
check(Input:isDown("left"), "numpad 4 bound to LEFT walks left in FRLG")
g:keyreleased("kp4")
g:keypressed("kp1")
eq(g.options.speedOverworld, 1, "numpad 1 bound to B leaves speed alone")
Input:step()
check(Input:isDown("b"), "numpad 1 bound to B presses B")
g:keyreleased("kp1")
g:keypressed("&")
eq(g.options.speedOverworld, 1, "AZERTY & bound to A leaves speed alone")
Input:step()
check(Input:isDown("a"), "AZERTY & bound to A presses A")
g:keyreleased("&")
love.keyboard = realKb
Input:applyBindings(nil)
Input:reset()
end
do
local trigger = Input:triggerAxis("triggerright", 1)
eq(Input:padAction(trigger), "speedUp", "RT axis name resolves to speedUp")
Input:triggerAxis("triggerright", 0)
local left = Input:triggerAxis("triggerleft", 1)
eq(Input:padAction(left), "speedDown", "LT axis name resolves to speedDown")
Input:triggerAxis("triggerleft", 0)
eq(GamepadMap.DEFAULT_PAD_ACTIONS.righttrigger, nil, "no legacy trigger key in defaults")
end
do
Input:reset()
local g = newGame3("field")
g:gamepadaxis(nil, "triggerright", 1)
eq(g.options.speedOverworld, 2, "gen3 RT bumps speed")
g:gamepadaxis(nil, "triggerright", 0)
g:gamepadaxis(nil, "triggerleft", 1)
eq(g.options.speedOverworld, 1, "gen3 LT lowers speed")
g:gamepadaxis(nil, "triggerleft", 0)
g:gamepadpressed(nil, "rightshoulder")
eq(g.options.speedOverworld, 1, "gen3 R shoulder never changes speed")
Input:step()
check(Input:isDown("r"), "gen3 R shoulder still presses R")
g:gamepadreleased(nil, "rightshoulder")
end
do
Input:applyBindings({ speedUp = { pad = "y" } })
Input:reset()
local g = newGame3("field")
g:gamepadpressed(nil, "y")
eq(g.options.speedOverworld, 2, "gen3 honors a SPEED + rebind to Y")
g:gamepadreleased(nil, "y")
Input:applyBindings({ speedUp = { pad = "righttrigger" }, a = { pad = "lefttrigger" } })
eq(Input:padAction("triggerright"), "speedUp", "legacy righttrigger speed rebind still lands")
eq(Input.padBindings.triggerleft, "a", "legacy lefttrigger button rebind still lands")
Input:applyBindings(nil)
end
do
local g1 = setmetatable({ save = { options = { speedOverworld = 1 } }, stack = { states = {}, top = function() return nil end },
writeOptions = function() end }, { __index = Game })
Input:reset()
g1:gamepadaxis(nil, "triggerright", 1)
eq(g1.save.options.speedMenu, 2, "Gen 1 RT reaches _cycleSpeed")
g1:gamepadaxis(nil, "triggerright", 0)
end
do
local g = newGame3("field")
g.speedOverride = 8
g.options.speedOverworld = 4
eq(g:logicSpeed(), 8, "override applies off-link")
package.loaded["src.core.game3.link"] = { link = {} }
eq(g:logicSpeed(), 1, "gen3 link play is locked to 1X")
package.loaded["src.core.game3.link"] = { link = nil }
package.loaded["src.core.game3.minigames.common"] = { isActive = function() return true end }
eq(g:logicSpeed(), 1, "gen3 minigames are locked to 1X")
package.loaded["src.core.game3.minigames.common"] = { isActive = function() return false end }
g.phase = "arena"
eq(g:logicSpeed(), 1, "gen3 arena is locked to 1X")
package.loaded["src.core.game3.link"] = nil
package.loaded["src.core.game3.minigames.common"] = nil
end
do
local g = newGame3("field")
g.options.speedOverworld = 10
eq(g:logicSpeed(), 10, "plain field walk honors OVERWORLD SPEED")
local unionOn = true
package.loaded["src.core.game3.link.union_room"] = {
isActive = function() return unionOn end,
}
eq(g:logicSpeed(), 1, "an active Union Room is locked to 1X")
g:keypressed("1")
eq(g.options.speedOverworld, 10, "1 in the Union Room is ignored")
unionOn = false
eq(g:logicSpeed(), 10, "leaving the Union Room restores the walk speed")
package.loaded["src.core.game3.link.union_room"] = nil
package.loaded["src.core.game3.map"] = { current = "FR_UNION_ROOM" }
eq(g:logicSpeed(), 1, "standing on the Union Room map is locked to 1X")
package.loaded["src.core.game3.map"] = { current = "FR_PALLET_TOWN" }
eq(g:logicSpeed(), 10, "another map is not")
package.loaded["src.core.game3.map"] = nil
local room = 1
package.loaded["src.core.game3.link"] = {
inLinkRoom = function() return room ~= 0 end,
}
eq(g:logicSpeed(), 1, "a cable club link room is locked to 1X")
room = 0
eq(g:logicSpeed(), 10, "and outside it the walk speed is back")
package.loaded["src.core.game3.link"] = nil
local menuOpen, directOpen = true, false
package.loaded["src.ui.game3.link_menu"] = {
isOpen = function() return menuOpen end,
Direct = { isOpen = function() return directOpen end },
}
eq(g:logicSpeed(), 1, "the wireless link menu is locked to 1X")
menuOpen, directOpen = false, true
eq(g:logicSpeed(), 1, "the Direct Corner chooser is locked to 1X")
directOpen = false
eq(g:logicSpeed(), 10, "closed link menus release the lock")
package.loaded["src.ui.game3.link_menu"] = nil
end
T.finish()