diff --git a/main.lua b/main.lua index cd043d6..f683fe1 100644 --- a/main.lua +++ b/main.lua @@ -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 @@ -47,10 +46,10 @@ function love.load() end --sort mode/rule lists local function padnum(d) return ("%03d%s"):format(#d, d) end - table.sort(game_modes, function(a,b) - return tostring(a.name):gsub("%d+",padnum) < tostring(b.name):gsub("%d+",padnum) end) + table.sort(game_modes, function(a,b) + return tostring(a.name):gsub("%d+",padnum) < tostring(b.name):gsub("%d+",padnum) end) 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 @@ -110,13 +109,124 @@ 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 diff --git a/scene.lua b/scene.lua index f2b7a51..809a2df 100644 --- a/scene.lua +++ b/scene.lua @@ -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" diff --git a/scene/config.lua b/scene/config.lua index 630cf63..3de9d0b 100644 --- a/scene/config.lua +++ b/scene/config.lua @@ -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 diff --git a/scene/exit.lua b/scene/exit.lua index ba0f849..bd1fd16 100644 --- a/scene/exit.lua +++ b/scene/exit.lua @@ -16,7 +16,7 @@ end function ExitScene:changeOption(rel) end -function ExitScene:onKeyPress(e) +function ExitScene:onInputPress(e) end return ExitScene diff --git a/scene/game.lua b/scene/game.lua index 9b085e9..17e5a11 100644 --- a/scene/game.lua +++ b/scene/game.lua @@ -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() @@ -60,23 +67,24 @@ 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 +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.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 + 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 diff --git a/scene/game_config.lua b/scene/game_config.lua index 37c8a84..beef971 100644 --- a/scene/game_config.lua +++ b/scene/game_config.lua @@ -5,10 +5,10 @@ ConfigScene.title = "Game Settings" require 'load.save' ConfigScene.options = { - -- this serves as reference to what the options' values mean i guess? - {"manlock", "Manual locking",{"Per ruleset","Per gamemode","Harddrop", "Softdrop"}}, - {"piece_colour", "Piece Colours", {"Per ruleset","Arika" ,"TTC"}}, - {"world_reverse","World Reverse", {"No" ,"SRS only" ,"Always"}}, + -- this serves as reference to what the options' values mean i guess? + {"manlock", "Manual locking",{"Per ruleset","Per gamemode","Harddrop", "Softdrop"}}, + {"piece_colour", "Piece Colours", {"Per ruleset","Arika" ,"TTC"}}, + {"world_reverse","World Reverse", {"No" ,"SRS only" ,"Always"}}, } local optioncount = #ConfigScene.options @@ -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 diff --git a/scene/input_config.lua b/scene/input_config.lua index a16aa23..16efcad 100644 --- a/scene/input_config.lua +++ b/scene/input_config.lua @@ -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,38 +51,116 @@ 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" - ) + 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 enter 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("enter, 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() +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 + -- enter, delete, backspace, tab, arrows, and escape are reserved and can't be remapped + if e.scancode == "escape" and config.input then scene = TitleScene() - elseif e.scancode == "delete" or e.scancode == "backspace" then - self.input_state = 1 + 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 - 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 + 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 diff --git a/scene/mode_select.lua b/scene/mode_select.lua index e810bd3..51ee21e 100755 --- a/scene/mode_select.lua +++ b/scene/mode_select.lua @@ -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 diff --git a/scene/title.lua b/scene/title.lua index a21ce24..0a7dc52 100644 --- a/scene/title.lua +++ b/scene/title.lua @@ -26,7 +26,7 @@ function TitleScene:new() self.main_menu_state = 1 DiscordRPC:update({ details = "In menus", - state = mainmenuidle[math.random(#mainmenuidle)], + state = mainmenuidle[math.random(#mainmenuidle)], }) end @@ -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