Merge pull request #8 from nightmareci/master

Implemented joystick input.
This commit is contained in:
Joe Zeng 2020-11-08 17:17:30 -05:00 committed by GitHub
commit 6b77ad8547
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 287 additions and 81 deletions

122
main.lua
View File

@ -23,7 +23,6 @@ function love.load()
end end
if not config.input then if not config.input then
config.input = {}
scene = InputConfigScene() scene = InputConfigScene()
else else
if config.current_mode then current_mode = config.current_mode end if config.current_mode then current_mode = config.current_mode end
@ -47,10 +46,10 @@ function love.load()
end end
--sort mode/rule lists --sort mode/rule lists
local function padnum(d) return ("%03d%s"):format(#d, d) end local function padnum(d) return ("%03d%s"):format(#d, d) end
table.sort(game_modes, function(a,b) table.sort(game_modes, function(a,b)
return tostring(a.name):gsub("%d+",padnum) < tostring(b.name):gsub("%d+",padnum) end) return tostring(a.name):gsub("%d+",padnum) < tostring(b.name):gsub("%d+",padnum) end)
table.sort(rulesets, function(a,b) table.sort(rulesets, function(a,b)
return tostring(a.name):gsub("%d+",padnum) < tostring(b.name):gsub("%d+",padnum) end) return tostring(a.name):gsub("%d+",padnum) < tostring(b.name):gsub("%d+",padnum) end)
end end
@ -110,13 +109,124 @@ function love.draw()
love.graphics.pop() love.graphics.pop()
end end
function love.keypressed(key, scancode, isrepeat) function love.keypressed(key, scancode)
-- global hotkeys -- global hotkeys
if scancode == "f4" then if scancode == "f4" then
config["fullscreen"] = not config["fullscreen"] config["fullscreen"] = not config["fullscreen"]
love.window.setFullscreen(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 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
end end

View File

@ -5,7 +5,8 @@ Scene = Object:extend()
function Scene:new() end function Scene:new() end
function Scene:update() end function Scene:update() end
function Scene:render() end function Scene:render() end
function Scene:onKeyPress() end function Scene:onInputPress() end
function Scene:onInputRelease() end
ExitScene = require "scene.exit" ExitScene = require "scene.exit"
GameScene = require "scene.game" 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 self.main_menu_state = (self.main_menu_state + len + rel - 1) % len + 1
end end
function ConfigScene:onKeyPress(e) function ConfigScene:onInputPress(e)
end end
return ConfigScene return ConfigScene

View File

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

View File

@ -2,9 +2,23 @@ local GameScene = Scene:extend()
require 'load.save' require 'load.save'
function GameScene:new(game_mode, ruleset) function GameScene:new(game_mode, ruleset)
self.retry_mode = game_mode
self.retry_ruleset = ruleset
self.game = game_mode() self.game = game_mode()
self.ruleset = ruleset() self.ruleset = ruleset()
self.game:initialize(self.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({ DiscordRPC:update({
details = self.game.rpc_details, details = self.game.rpc_details,
state = self.game.name, state = self.game.name,
@ -13,18 +27,11 @@ end
function GameScene:update() function GameScene:update()
if love.window.hasFocus() then if love.window.hasFocus() then
self.game:update({ local inputs = {}
left = love.keyboard.isScancodeDown(config.input.left), for input, value in pairs(self.inputs) do
right = love.keyboard.isScancodeDown(config.input.right), inputs[input] = value
up = love.keyboard.isScancodeDown(config.input.up), end
down = love.keyboard.isScancodeDown(config.input.down), self.game:update(inputs, self.ruleset)
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)
end end
self.game.grid:update() self.game.grid:update()
@ -60,23 +67,24 @@ function GameScene:render()
end end
function GameScene:onKeyPress(e) function GameScene:onInputPress(e)
if (self.game.completed) and if self.game.completed and (e.input == "menu_decide" or e.input == "menu_back") then
(e.scancode == "return" or e.scancode == "escape") and e.isRepeat == false then
highscore_entry = self.game:getHighscoreData() highscore_entry = self.game:getHighscoreData()
highscore_hash = self.game.hash .. "-" .. self.ruleset.hash highscore_hash = self.game.hash .. "-" .. self.ruleset.hash
submitHighscore(highscore_hash, highscore_entry) submitHighscore(highscore_hash, highscore_entry)
scene = ModeSelectScene() scene = ModeSelectScene()
elseif (e.scancode == config.input.retry) then elseif e.input == "retry" then
-- fuck this, this is hacky but the way this codebase is setup prevents anything else scene = GameScene(self.retry_mode, self.retry_ruleset)
-- it seems like all the values that get touched in the child gamemode class elseif e.input == "menu_back" then
-- 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() 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
end end

View File

@ -5,10 +5,10 @@ ConfigScene.title = "Game Settings"
require 'load.save' require 'load.save'
ConfigScene.options = { ConfigScene.options = {
-- this serves as reference to what the options' values mean i guess? -- this serves as reference to what the options' values mean i guess?
{"manlock", "Manual locking",{"Per ruleset","Per gamemode","Harddrop", "Softdrop"}}, {"manlock", "Manual locking",{"Per ruleset","Per gamemode","Harddrop", "Softdrop"}},
{"piece_colour", "Piece Colours", {"Per ruleset","Arika" ,"TTC"}}, {"piece_colour", "Piece Colours", {"Per ruleset","Arika" ,"TTC"}},
{"world_reverse","World Reverse", {"No" ,"SRS only" ,"Always"}}, {"world_reverse","World Reverse", {"No" ,"SRS only" ,"Always"}},
} }
local optioncount = #ConfigScene.options local optioncount = #ConfigScene.options
@ -51,26 +51,26 @@ function ConfigScene:render()
end end
end end
function ConfigScene:onKeyPress(e) function ConfigScene:onInputPress(e)
if e.scancode == "return" and e.isRepeat == false then if e.input == "menu_decide" then
playSE("mode_decide") playSE("mode_decide")
saveConfig() saveConfig()
scene = TitleScene() scene = TitleScene()
elseif (e.scancode == config.input["up"] or e.scancode == "up") and e.isRepeat == false then elseif e.input == "up" then
playSE("cursor") playSE("cursor")
self.highlight = Mod1(self.highlight-1, optioncount) 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") playSE("cursor")
self.highlight = Mod1(self.highlight+1, optioncount) 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") playSE("cursor_lr")
local option = ConfigScene.options[self.highlight] local option = ConfigScene.options[self.highlight]
config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]-1, #option[3]) 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") playSE("cursor_lr")
local option = ConfigScene.options[self.highlight] local option = ConfigScene.options[self.highlight]
config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]+1, #option[3]) config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]+1, #option[3])
elseif e.scancode == "escape" then elseif e.input == "menu_back" then
loadSave() loadSave()
scene = TitleScene() scene = TitleScene()
end end

View File

@ -5,6 +5,8 @@ ConfigScene.title = "Input Config"
require 'load.save' require 'load.save'
local configurable_inputs = { local configurable_inputs = {
"menu_decide",
"menu_back",
"left", "left",
"right", "right",
"up", "up",
@ -18,10 +20,18 @@ local configurable_inputs = {
"retry", "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() function ConfigScene:new()
-- load current config
self.config = config.input
self.input_state = 1 self.input_state = 1
self.set_inputs = newSetInputs()
self.new_input = {}
DiscordRPC:update({ DiscordRPC:update({
details = "In menus", details = "In menus",
@ -41,38 +51,116 @@ function ConfigScene:render()
) )
love.graphics.setFont(font_3x5_2) 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") love.graphics.printf(input, 40, 50 + i * 20, 200, "left")
if config.input[input] then if self.set_inputs[input] then
love.graphics.printf( love.graphics.printf(self.set_inputs[input], 240, 50 + i * 20, 300, "left")
love.keyboard.getKeyFromScancode(config.input[input]) .. " (" .. config.input[input] .. ")",
240, 50 + i * 20, 200, "left"
)
end end
end end
if self.input_state > table.getn(configurable_inputs) then if self.input_state > table.getn(configurable_inputs) then
love.graphics.print("press enter to confirm, delete to retry") love.graphics.print("press enter to confirm, delete/backspace to retry" .. (config.input and ", escape to cancel" or ""))
else 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("enter, delete, backspace, tab, arrows, and escape can't be changed", 0, 20)
end end
end end
function ConfigScene:onKeyPress(e) local function addJoystick(input, name)
if self.input_state > table.getn(configurable_inputs) then if not input.joysticks then
if e.scancode == "return" then input.joysticks = {}
-- save, then load next scene end
saveConfig() if not input.joysticks[name] then
input.joysticks[name] = {}
end
end
function ConfigScene:onInputPress(e)
if e.type == "key" then
-- enter, delete, backspace, tab, arrows, and escape are reserved and can't be remapped
if e.scancode == "escape" and config.input then
scene = TitleScene() scene = TitleScene()
elseif e.scancode == "delete" or e.scancode == "backspace" then elseif self.input_state > table.getn(configurable_inputs) then
self.input_state = 1 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 end
else elseif string.sub(e.type, 1, 3) == "joy" then
if e.scancode == "escape" then if self.input_state <= table.getn(configurable_inputs) then
loadSave() if e.type == "joybutton" then
scene = TitleScene() addJoystick(self.new_input, e.name)
else if not self.new_input.joysticks[e.name].buttons then
config.input[configurable_inputs[self.input_state]] = e.scancode self.new_input.joysticks[e.name].buttons = {}
self.input_state = self.input_state + 1 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 end
end end

View File

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

View File

@ -26,7 +26,7 @@ function TitleScene:new()
self.main_menu_state = 1 self.main_menu_state = 1
DiscordRPC:update({ DiscordRPC:update({
details = "In menus", details = "In menus",
state = mainmenuidle[math.random(#mainmenuidle)], state = mainmenuidle[math.random(#mainmenuidle)],
}) })
end end
@ -57,17 +57,17 @@ function TitleScene:changeOption(rel)
self.main_menu_state = (self.main_menu_state + len + rel - 1) % len + 1 self.main_menu_state = (self.main_menu_state + len + rel - 1) % len + 1
end end
function TitleScene:onKeyPress(e) function TitleScene:onInputPress(e)
if e.scancode == "return" and e.isRepeat == false then if e.input == "menu_decide" then
playSE("main_decide") playSE("main_decide")
scene = main_menu_screens[self.main_menu_state]() 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) self:changeOption(-1)
playSE("cursor") 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) self:changeOption(1)
playSE("cursor") playSE("cursor")
elseif e.scancode == "escape" and e.isRepeat == false then elseif e.input == "menu_back" then
love.event.quit() love.event.quit()
end end
end end