Implemented joystick input.

I had to redo how input is done entirely, so more than one source of input can be used for game inputs.

I added new inputs, menu_decide and menu_back. Return and escape still have their reserved status, sending menu_decide and menu_back, respectively. Other keys are reserved too, like arrows, to ensure users can always reconfigure input.
pull/8/head
nightmareci 2020-11-08 12:55:06 -08:00
parent a105086ca6
commit 863c614a4c
9 changed files with 291 additions and 85 deletions

116
main.lua
View File

@ -23,7 +23,6 @@ function love.load()
end
if not config.input then
config.input = {}
scene = InputConfigScene()
else
if config.current_mode then current_mode = config.current_mode end
@ -110,16 +109,127 @@ function love.draw()
love.graphics.pop()
end
function love.keypressed(key, scancode, isrepeat)
function love.keypressed(key, scancode)
-- global hotkeys
if scancode == "f4" then
config["fullscreen"] = not config["fullscreen"]
love.window.setFullscreen(config["fullscreen"])
-- reserved keys, so the user can always get back to configure input
elseif scancode == "return" then
scene:onInputPress({input="menu_decide", type="key", key=key, scancode=scancode})
elseif scancode == "escape" then
scene:onInputPress({input="menu_back", type="key", key=key, scancode=scancode})
elseif scancode == "left" or scancode == "right" or scancode == "up" or scancode == "down" then
scene:onInputPress({input=scancode, type="key", key=key, scancode=scancode})
-- other keys can be configured
else
scene:onKeyPress({key=key, scancode=scancode, isRepeat=isrepeat})
local input_pressed = nil
if config.input and config.input.keys then
input_pressed = config.input.keys[scancode]
end
scene:onInputPress({input=input_pressed, type="key", key=key, scancode=scancode})
end
end
function love.keyreleased(key, scancode)
-- reserved keys, so the user can always get back to configure input
if scancode == "return" then
scene:onInputRelease({input="menu_decide", type="key", key=key, scancode=scancode})
elseif scancode == "escape" then
scene:onInputRelease({input="menu_back", type="key", key=key, scancode=scancode})
elseif scancode == "left" or scancode == "right" or scancode == "up" or scancode == "down" then
scene:onInputRelease({input=scancode, type="key", key=key, scancode=scancode})
-- other keys can be configured
else
local input_released = nil
if config.input and config.input.keys then
input_released = config.input.keys[scancode]
end
scene:onInputRelease({input=input_released, type="key", key=key, scancode=scancode})
end
end
function love.joystickpressed(joystick, button)
local input_pressed = nil
if
config.input and
config.input.joysticks and
config.input.joysticks[joystick:getName()] and
config.input.joysticks[joystick:getName()].buttons
then
input_pressed = config.input.joysticks[joystick:getName()].buttons[button]
end
scene:onInputPress({input=input_pressed, type="joybutton", name=joystick:getName(), button=button})
end
function love.joystickreleased(joystick, button)
local input_released = nil
if
config.input and
config.input.joysticks and
config.input.joysticks[joystick:getName()] and
config.input.joysticks[joystick:getName()].buttons
then
input_released = config.input.joysticks[joystick:getName()].buttons[button]
end
scene:onInputRelease({input=input_released, type="joybutton", name=joystick:getName(), button=button})
end
function love.joystickaxis(joystick, axis, value)
local input_pressed = nil
local positive_released = nil
local negative_released = nil
if
config.input and
config.input.joysticks and
config.input.joysticks[joystick:getName()] and
config.input.joysticks[joystick:getName()].axes and
config.input.joysticks[joystick:getName()].axes[axis]
then
if math.abs(value) >= 0.5 then
input_pressed = config.input.joysticks[joystick:getName()].axes[axis][value >= 0.5 and "positive" or "negative"]
end
positive_released = config.input.joysticks[joystick:getName()].axes[axis].positive
negative_released = config.input.joysticks[joystick:getName()].axes[axis].negative
end
if math.abs(value) >= 0.5 then
scene:onInputPress({input=input_pressed, type="joyaxis", name=joystick:getName(), axis=axis, value=value})
else
scene:onInputRelease({input=positive_released, type="joyaxis", name=joystick:getName(), axis=axis, value=value})
scene:onInputRelease({input=negative_released, type="joyaxis", name=joystick:getName(), axis=axis, value=value})
end
end
function love.joystickhat(joystick, hat, direction)
local input_pressed = nil
local has_hat = false
if
config.input and
config.input.joysticks and
config.input.joysticks[joystick:getName()] and
config.input.joysticks[joystick:getName()].hats and
config.input.joysticks[joystick:getName()].hats[hat]
then
if direction ~= "c" then
input_pressed = config.input.joysticks[joystick:getName()].hats[hat][direction]
end
has_hat = true
end
if input_pressed then
scene:onInputPress({input=input_pressed, type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
elseif has_hat then
for i, direction in ipairs{"d", "l", "ld", "lu", "r", "rd", "ru", "u"} do
scene:onInputRelease({input=config.input.joysticks[joystick:getName()].hats[hat][direction], type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
end
elseif direction ~= "c" then
scene:onInputPress({input=nil, type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
else
for i, direction in ipairs{"d", "l", "ld", "lu", "r", "rd", "ru", "u"} do
scene:onInputRelease({input=nil, type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
end
end
end
function love.focus(f)
if f then
resumeBGM()

View File

@ -5,7 +5,8 @@ Scene = Object:extend()
function Scene:new() end
function Scene:update() end
function Scene:render() end
function Scene:onKeyPress() end
function Scene:onInputPress() end
function Scene:onInputRelease() end
ExitScene = require "scene.exit"
GameScene = require "scene.game"

View File

@ -17,7 +17,7 @@ function ConfigScene:changeOption(rel)
self.main_menu_state = (self.main_menu_state + len + rel - 1) % len + 1
end
function ConfigScene:onKeyPress(e)
function ConfigScene:onInputPress(e)
end
return ConfigScene

View File

@ -16,7 +16,7 @@ end
function ExitScene:changeOption(rel)
end
function ExitScene:onKeyPress(e)
function ExitScene:onInputPress(e)
end
return ExitScene

View File

@ -2,9 +2,23 @@ local GameScene = Scene:extend()
require 'load.save'
function GameScene:new(game_mode, ruleset)
self.retry_mode = game_mode
self.retry_ruleset = ruleset
self.game = game_mode()
self.ruleset = ruleset()
self.game:initialize(self.ruleset)
self.inputs = {
left=false,
right=false,
up=false,
down=false,
rotate_left=false,
rotate_left2=false,
rotate_right=false,
rotate_right2=false,
rotate_180=false,
hold=false,
}
DiscordRPC:update({
details = self.game.rpc_details,
state = self.game.name,
@ -13,18 +27,11 @@ end
function GameScene:update()
if love.window.hasFocus() then
self.game:update({
left = love.keyboard.isScancodeDown(config.input.left),
right = love.keyboard.isScancodeDown(config.input.right),
up = love.keyboard.isScancodeDown(config.input.up),
down = love.keyboard.isScancodeDown(config.input.down),
rotate_left = love.keyboard.isScancodeDown(config.input.rotate_left),
rotate_left2 = love.keyboard.isScancodeDown(config.input.rotate_left2),
rotate_right = love.keyboard.isScancodeDown(config.input.rotate_right),
rotate_right2 = love.keyboard.isScancodeDown(config.input.rotate_right2),
rotate_180 = love.keyboard.isScancodeDown(config.input.rotate_180),
hold = love.keyboard.isScancodeDown(config.input.hold),
}, self.ruleset)
local inputs = {}
for input, value in pairs(self.inputs) do
inputs[input] = value
end
self.game:update(inputs, self.ruleset)
end
self.game.grid:update()
@ -59,24 +66,25 @@ function GameScene:render()
end
function GameScene:onKeyPress(e)
if (self.game.completed) and
(e.scancode == "return" or e.scancode == "escape") and e.isRepeat == false then
highscore_entry = self.game:getHighscoreData()
highscore_hash = self.game.hash .. "-" .. self.ruleset.hash
submitHighscore(highscore_hash, highscore_entry)
scene = ModeSelectScene()
elseif (e.scancode == config.input.retry) then
-- fuck this, this is hacky but the way this codebase is setup prevents anything else
-- it seems like all the values that get touched in the child gamemode class
-- stop being linked to the values of the GameMode superclass because of how `mt.__index` works
-- not even sure this is the actual problem, but I don't want to have to rebuild everything about
-- the core organisation of everything. this hacky way will have to do until someone figures out something.
love.keypressed("escape", "escape", false)
love.keypressed("return", "return", false)
elseif e.scancode == "escape" then
scene = ModeSelectScene()
end
function GameScene:onInputPress(e)
if self.game.completed and (e.input == "menu_decide" or e.input == "menu_back") then
highscore_entry = self.game:getHighscoreData()
highscore_hash = self.game.hash .. "-" .. self.ruleset.hash
submitHighscore(highscore_hash, highscore_entry)
scene = ModeSelectScene()
elseif e.input == "retry" then
scene = GameScene(self.retry_mode, self.retry_ruleset)
elseif e.input == "menu_back" then
scene = ModeSelectScene()
elseif e.input and string.sub(e.input, 1, 5) ~= "menu_" then
self.inputs[e.input] = true
end
end
function GameScene:onInputRelease(e)
if e.input and string.sub(e.input, 1, 5) ~= "menu_" then
self.inputs[e.input] = false
end
end
function submitHighscore(hash, data)

View File

@ -51,26 +51,26 @@ function ConfigScene:render()
end
end
function ConfigScene:onKeyPress(e)
if e.scancode == "return" and e.isRepeat == false then
function ConfigScene:onInputPress(e)
if e.input == "menu_decide" then
playSE("mode_decide")
saveConfig()
scene = TitleScene()
elseif (e.scancode == config.input["up"] or e.scancode == "up") and e.isRepeat == false then
elseif e.input == "up" then
playSE("cursor")
self.highlight = Mod1(self.highlight-1, optioncount)
elseif (e.scancode == config.input["down"] or e.scancode == "down") and e.isRepeat == false then
elseif e.input == "down" then
playSE("cursor")
self.highlight = Mod1(self.highlight+1, optioncount)
elseif (e.scancode == config.input["left"] or e.scancode == "left") and e.isRepeat == false then
elseif e.input == "left" then
playSE("cursor_lr")
local option = ConfigScene.options[self.highlight]
config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]-1, #option[3])
elseif (e.scancode == config.input["right"] or e.scancode == "right") and e.isRepeat == false then
elseif e.input == "right" then
playSE("cursor_lr")
local option = ConfigScene.options[self.highlight]
config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]+1, #option[3])
elseif e.scancode == "escape" then
elseif e.input == "menu_back" then
loadSave()
scene = TitleScene()
end

View File

@ -5,6 +5,8 @@ ConfigScene.title = "Input Config"
require 'load.save'
local configurable_inputs = {
"menu_decide",
"menu_back",
"left",
"right",
"up",
@ -18,10 +20,18 @@ local configurable_inputs = {
"retry",
}
local function newSetInputs()
local set_inputs = {}
for i, input in ipairs(configurable_inputs) do
set_inputs[input] = false
end
return set_inputs
end
function ConfigScene:new()
-- load current config
self.config = config.input
self.input_state = 1
self.set_inputs = newSetInputs()
self.new_input = {}
DiscordRPC:update({
details = "In menus",
@ -41,40 +51,118 @@ function ConfigScene:render()
)
love.graphics.setFont(font_3x5_2)
for i, input in pairs(configurable_inputs) do
for i, input in ipairs(configurable_inputs) do
love.graphics.printf(input, 40, 50 + i * 20, 200, "left")
if config.input[input] then
love.graphics.printf(
love.keyboard.getKeyFromScancode(config.input[input]) .. " (" .. config.input[input] .. ")",
240, 50 + i * 20, 200, "left"
)
end
end
if self.set_inputs[input] then
love.graphics.printf(self.set_inputs[input], 240, 50 + i * 20, 300, "left")
end
end
if self.input_state > table.getn(configurable_inputs) then
love.graphics.print("press enter to confirm, delete to retry")
love.graphics.print("press return to confirm, delete/backspace to retry" .. (config.input and ", escape to cancel" or ""))
else
love.graphics.print("press key for " .. configurable_inputs[self.input_state])
love.graphics.print("press key or joystick input for " .. configurable_inputs[self.input_state] .. ", tab to skip" .. (config.input and ", escape to cancel" or ""), 0, 0)
love.graphics.print("return, delete, backspace, tab, arrows, and escape can't be changed", 0, 20)
end
end
function ConfigScene:onKeyPress(e)
if self.input_state > table.getn(configurable_inputs) then
if e.scancode == "return" then
-- save, then load next scene
saveConfig()
scene = TitleScene()
elseif e.scancode == "delete" or e.scancode == "backspace" then
self.input_state = 1
end
else
if e.scancode == "escape" then
loadSave()
scene = TitleScene()
else
config.input[configurable_inputs[self.input_state]] = e.scancode
self.input_state = self.input_state + 1
end
end
local function addJoystick(input, name)
if not input.joysticks then
input.joysticks = {}
end
if not input.joysticks[name] then
input.joysticks[name] = {}
end
end
function ConfigScene:onInputPress(e)
if e.type == "key" then
-- return, delete, backspace, tab, arrows, and escape are reserved and can't be remapped
if e.scancode == "escape" and config.input then
scene = TitleScene()
elseif self.input_state > table.getn(configurable_inputs) then
if e.scancode == "return" then
-- save new input, then load next scene
config.input = self.new_input
saveConfig()
scene = TitleScene()
elseif e.scancode == "delete" or e.scancode == "backspace" then
-- retry
self.input_state = 1
self.set_inputs = newSetInputs()
self.new_input = {}
elseif e.scancode == "escape" and config.input then
-- cancel only if there was an input config already
scene = TitleScene()
end
elseif
e.scancode ~= "delete" and
e.scancode ~= "backspace" and
e.scancode ~= "return" and
e.scancode ~= "left" and
e.scancode ~= "right" and
e.scancode ~= "up" and
e.scancode ~= "down"
then
if e.scancode == "tab" then
self.set_inputs[configurable_inputs[self.input_state]] = "skipped"
self.input_state = self.input_state + 1
else
if not self.new_input.keys then
self.new_input.keys = {}
end
self.set_inputs[configurable_inputs[self.input_state]] = "key " .. love.keyboard.getKeyFromScancode(e.scancode) .. " (" .. e.scancode .. ")"
self.new_input.keys[e.scancode] = configurable_inputs[self.input_state]
self.input_state = self.input_state + 1
end
end
elseif string.sub(e.type, 1, 3) == "joy" then
if self.input_state <= table.getn(configurable_inputs) then
if e.type == "joybutton" then
addJoystick(self.new_input, e.name)
if not self.new_input.joysticks[e.name].buttons then
self.new_input.joysticks[e.name].buttons = {}
end
self.set_inputs[configurable_inputs[self.input_state]] =
"jbtn " ..
e.button ..
" " .. string.sub(e.name, 1, 10) .. (string.len(e.name) > 10 and "..." or "")
self.new_input.joysticks[e.name].buttons[e.button] = configurable_inputs[self.input_state]
self.input_state = self.input_state + 1
elseif e.type == "joyaxis" then
if math.abs(e.value) >= 0.5 then
addJoystick(self.new_input, e.name)
if not self.new_input.joysticks[e.name].axes then
self.new_input.joysticks[e.name].axes = {}
end
if not self.new_input.joysticks[e.name].axes[e.axis] then
self.new_input.joysticks[e.name].axes[e.axis] = {}
end
self.set_inputs[configurable_inputs[self.input_state]] =
"jaxis " ..
(e.value >= 0.5 and "+" or "-") .. e.axis ..
" " .. string.sub(e.name, 1, 10) .. (string.len(e.name) > 10 and "..." or "")
self.new_input.joysticks[e.name].axes[e.axis][e.value >= 0.5 and "positive" or "negative"] = configurable_inputs[self.input_state]
self.input_state = self.input_state + 1
end
elseif e.type == "joyhat" then
if e.direction ~= "c" then
addJoystick(self.new_input, e.name)
if not self.new_input.joysticks[e.name].hats then
self.new_input.joysticks[e.name].hats = {}
end
if not self.new_input.joysticks[e.name].hats[e.hat] then
self.new_input.joysticks[e.name].hats[e.hat] = {}
end
self.set_inputs[configurable_inputs[self.input_state]] =
"jhat " ..
e.hat .. " " .. e.direction ..
" " .. string.sub(e.name, 1, 10) .. (string.len(e.name) > 10 and "..." or "")
self.new_input.joysticks[e.name].hats[e.hat][e.direction] = configurable_inputs[self.input_state]
self.input_state = self.input_state + 1
end
end
end
end
end
return ConfigScene

View File

@ -58,8 +58,8 @@ function ModeSelectScene:render()
end
end
function ModeSelectScene:onKeyPress(e)
if e.scancode == "return" and e.isRepeat == false then
function ModeSelectScene:onInputPress(e)
if e.input == "menu_decide" then
current_mode = self.menu_state.mode
current_ruleset = self.menu_state.ruleset
config.current_mode = current_mode
@ -67,17 +67,16 @@ function ModeSelectScene:onKeyPress(e)
playSE("mode_decide")
saveConfig()
scene = GameScene(game_modes[self.menu_state.mode], rulesets[self.menu_state.ruleset])
elseif (e.scancode == config.input["up"] or e.scancode == "up") and e.isRepeat == false then
elseif e.input == "up" then
self:changeOption(-1)
playSE("cursor")
elseif (e.scancode == config.input["down"] or e.scancode == "down") and e.isRepeat == false then
elseif e.input == "down" then
self:changeOption(1)
playSE("cursor")
elseif (e.scancode == config.input["left"] or e.scancode == "left") or
(e.scancode == config.input["right"] or e.scancode == "right") then
elseif e.input == "left" or e.input == "right" then
self:switchSelect()
playSE("cursor_lr")
elseif e.scancode == "escape" then
elseif e.input == "menu_back" then
scene = TitleScene()
end
end

View File

@ -57,17 +57,17 @@ function TitleScene:changeOption(rel)
self.main_menu_state = (self.main_menu_state + len + rel - 1) % len + 1
end
function TitleScene:onKeyPress(e)
if e.scancode == "return" and e.isRepeat == false then
function TitleScene:onInputPress(e)
if e.input == "menu_decide" then
playSE("main_decide")
scene = main_menu_screens[self.main_menu_state]()
elseif (e.scancode == config.input["up"] or e.scancode == "up") and e.isRepeat == false then
elseif e.input == "up" then
self:changeOption(-1)
playSE("cursor")
elseif (e.scancode == config.input["down"] or e.scancode == "down") and e.isRepeat == false then
elseif e.input == "down" then
self:changeOption(1)
playSE("cursor")
elseif e.scancode == "escape" and e.isRepeat == false then
elseif e.input == "menu_back" then
love.event.quit()
end
end