From 2384ceb3bff3fe23c774a1bcd789a8108c3162f7 Mon Sep 17 00:00:00 2001 From: MarkGamed7794 <47731570+MarkGamed7794@users.noreply.github.com> Date: Tue, 19 Oct 2021 19:32:16 -0400 Subject: [PATCH] Hopefully this is the folder support it better be --- load/bgm.lua | 41 ++++-- load/graphics.lua | 20 ++- load/save.lua | 40 ++++- load/sounds.lua | 54 ++++--- load/version.lua | 1 + main.lua | 285 ++++++++++++++++++++++++++---------- scene.lua | 2 + scene/credits.lua | 64 +++++--- scene/game.lua | 90 +++--------- scene/game_config.lua | 25 ++-- scene/input_config.lua | 180 ++++++----------------- scene/key_config.lua | 100 +++++++++++++ scene/mode_select.lua | 219 +++++++++++++++++++++++---- scene/settings.lua | 15 +- scene/stick_config.lua | 159 ++++++++++++++++++++ scene/title.lua | 15 +- scene/tuning.lua | 2 +- tetris/components/grid.lua | 47 +++--- tetris/components/piece.lua | 41 ++++-- 19 files changed, 971 insertions(+), 429 deletions(-) create mode 100644 load/version.lua create mode 100644 scene/key_config.lua create mode 100644 scene/stick_config.lua diff --git a/load/bgm.lua b/load/bgm.lua index 21aa1d2..1081da0 100644 --- a/load/bgm.lua +++ b/load/bgm.lua @@ -2,38 +2,46 @@ bgm = { credit_roll = { gm3 = love.audio.newSource("res/bgm/tgm_credit_roll.mp3", "stream"), }, - pacer_test = love.audio.newSource("res/bgm/pacer_test.mp3", "stream"), + pacer_test = love.audio.newSource("res/bgm/pacer_test.mp3", "stream") } local current_bgm = nil local bgm_locked = false +local unfocused = false function switchBGM(sound, subsound) - if bgm_locked then return end if current_bgm ~= nil then current_bgm:stop() end - if subsound ~= nil then - current_bgm = bgm[sound][subsound] - resetBGMFadeout() + if bgm_locked or config.bgm_volume <= 0 then + current_bgm = nil elseif sound ~= nil then - current_bgm = bgm[sound] - resetBGMFadeout() + if subsound ~= nil then + current_bgm = bgm[sound][subsound] + else + current_bgm = bgm[sound] + end else current_bgm = nil end + if current_bgm ~= nil then + resetBGMFadeout() + end end function switchBGMLoop(sound, subsound) - if bgm_locked then return end switchBGM(sound, subsound) - current_bgm:setLooping(true) + if current_bgm then current_bgm:setLooping(true) end end function lockBGM() bgm_locked = true end +function unlockBGM() + bgm_locked = false +end + local fading_bgm = false local fadeout_time = 0 local total_fadeout_time = 0 @@ -49,11 +57,11 @@ end function resetBGMFadeout(time) current_bgm:setVolume(config.bgm_volume) fading_bgm = false - current_bgm:play() + resumeBGM() end function processBGMFadeout(dt) - if fading_bgm then + if current_bgm and fading_bgm then fadeout_time = fadeout_time - dt if fadeout_time < 0 then fadeout_time = 0 @@ -63,13 +71,20 @@ function processBGMFadeout(dt) end end -function pauseBGM() +function pauseBGM(f) + if f then + unfocused = true + end if current_bgm ~= nil then current_bgm:pause() end end -function resumeBGM() +function resumeBGM(f) + if f and scene.paused and unfocused then + unfocused = false + return + end if current_bgm ~= nil then current_bgm:play() end diff --git a/load/graphics.lua b/load/graphics.lua index 73fd409..e1297d1 100644 --- a/load/graphics.lua +++ b/load/graphics.lua @@ -25,6 +25,15 @@ backgrounds = { game_config = love.graphics.newImage("res/backgrounds/options-game.png"), } +-- in order, the colors are: +-- red, orange, yellow, green, cyan, blue +-- magenta (or purple), white, black +-- the next three don't have colors tied to them +-- F is used for lock flash +-- A is a garbage block +-- X is an invisible "block" +-- don't use these for piece colors when making a ruleset +-- all the others are fine to use blocks = { ["2tie"] = { R = love.graphics.newImage("res/img/s1.png"), @@ -34,6 +43,8 @@ blocks = { C = love.graphics.newImage("res/img/s2.png"), B = love.graphics.newImage("res/img/s4.png"), M = love.graphics.newImage("res/img/s5.png"), + W = love.graphics.newImage("res/img/s9.png"), + D = love.graphics.newImage("res/img/s8.png"), F = love.graphics.newImage("res/img/s9.png"), A = love.graphics.newImage("res/img/s8.png"), X = love.graphics.newImage("res/img/s9.png"), @@ -46,6 +57,8 @@ blocks = { C = love.graphics.newImage("res/img/bone.png"), B = love.graphics.newImage("res/img/bone.png"), M = love.graphics.newImage("res/img/bone.png"), + W = love.graphics.newImage("res/img/bone.png"), + D = love.graphics.newImage("res/img/bone.png"), F = love.graphics.newImage("res/img/bone.png"), A = love.graphics.newImage("res/img/bone.png"), X = love.graphics.newImage("res/img/bone.png"), @@ -58,13 +71,16 @@ blocks = { C = love.graphics.newImage("res/img/gem2.png"), B = love.graphics.newImage("res/img/gem4.png"), M = love.graphics.newImage("res/img/gem5.png"), + W = love.graphics.newImage("res/img/gem9.png"), + D = love.graphics.newImage("res/img/gem9.png"), F = love.graphics.newImage("res/img/gem9.png"), A = love.graphics.newImage("res/img/gem9.png"), X = love.graphics.newImage("res/img/gem9.png"), }, ["square"] = { - F = love.graphics.newImage("res/img/squares.png"), + W = love.graphics.newImage("res/img/squares.png"), Y = love.graphics.newImage("res/img/squareg.png"), + F = love.graphics.newImage("res/img/squares.png"), X = love.graphics.newImage("res/img/squares.png"), } } @@ -87,7 +103,7 @@ ColourSchemes = { Z = "R", O = "Y", T = "M", - }, + } } for name, blockset in pairs(blocks) do diff --git a/load/save.lua b/load/save.lua index 92cdb68..40da2d1 100644 --- a/load/save.lua +++ b/load/save.lua @@ -6,19 +6,53 @@ function loadSave() end function loadFromFile(filename) - local save_data, len = binser.readFile(filename) + local file_data = love.filesystem.read(filename) + if file_data == nil then + return {} -- new object + end + local save_data = binser.deserialize(file_data) if save_data == nil then return {} -- new object end return save_data[1] end +function initConfig() + if not config.das then config.das = 10 end + if not config.arr then config.arr = 2 end + if not config.dcd then config.dcd = 0 end + if not config.sfx_volume then config.sfx_volume = 0.5 end + if not config.bgm_volume then config.bgm_volume = 0.5 end + + if config.fullscreen == nil then config.fullscreen = false end + if config.secret == nil then config.secret = false end + if not config.gamesettings then config.gamesettings = {} end + for _, option in ipairs(GameConfigScene.options) do + if not config.gamesettings[option[1]] then + config.gamesettings[option[1]] = 1 + end + end + + if not config.input then + scene = InputConfigScene() + else + if config.current_mode then current_mode = config.current_mode end + if config.current_ruleset then current_ruleset = config.current_ruleset end + if not config.mode_path then config.mode_path = {} end + if not config.rule_path then config.rule_path = {} end + scene = TitleScene() + end +end function saveConfig() - binser.writeFile('config.sav', config) + love.filesystem.write( + 'config.sav', binser.serialize(config) + ) end function saveHighscores() - binser.writeFile('highscores.sav', highscores) + love.filesystem.write( + 'highscores.sav', binser.serialize(highscores) + ) end diff --git a/load/sounds.lua b/load/sounds.lua index 7e0b80b..cf62dac 100644 --- a/load/sounds.lua +++ b/load/sounds.lua @@ -22,38 +22,48 @@ sounds = { go = love.audio.newSource("res/se/go.wav", "static"), irs = love.audio.newSource("res/se/irs.wav", "static"), ihs = love.audio.newSource("res/se/ihs.wav", "static"), + clears = { + single = love.audio.newSource("res/se/single.wav", "static"), + double = love.audio.newSource("res/se/double.wav", "static"), + triple = love.audio.newSource("res/se/triple.wav", "static"), + quad = love.audio.newSource("res/se/quad.wav", "static") + }, -- a secret sound! welcome = love.audio.newSource("res/se/welcomeToCambridge.wav", "static"), } function playSE(sound, subsound) - if subsound == nil then - sounds[sound]:setVolume(config.sfx_volume) - if sounds[sound]:isPlaying() then - sounds[sound]:stop() + if sound ~= nil then + if subsound ~= nil then + sounds[sound][subsound]:setVolume(config.sfx_volume) + if sounds[sound][subsound]:isPlaying() then + sounds[sound][subsound]:stop() + end + sounds[sound][subsound]:play() + else + sounds[sound]:setVolume(config.sfx_volume) + if sounds[sound]:isPlaying() then + sounds[sound]:stop() + end + sounds[sound]:play() end - sounds[sound]:play() - else - sounds[sound][subsound]:setVolume(config.sfx_volume) - if sounds[sound][subsound]:isPlaying() then - sounds[sound][subsound]:stop() - end - sounds[sound][subsound]:play() end end function playSEOnce(sound, subsound) - if subsound == nil then - sounds[sound]:setVolume(config.sfx_volume) - if sounds[sound]:isPlaying() then - return + if sound ~= nil then + if subsound ~= nil then + sounds[sound][subsound]:setVolume(config.sfx_volume) + if sounds[sound][subsound]:isPlaying() then + return + end + sounds[sound][subsound]:play() + else + sounds[sound]:setVolume(config.sfx_volume) + if sounds[sound]:isPlaying() then + return + end + sounds[sound]:play() end - sounds[sound]:play() - else - sounds[sound][subsound]:setVolume(config.sfx_volume) - if sounds[sound][subsound]:isPlaying() then - return - end - sounds[sound][subsound]:play() end end \ No newline at end of file diff --git a/load/version.lua b/load/version.lua new file mode 100644 index 0000000..995ec4e --- /dev/null +++ b/load/version.lua @@ -0,0 +1 @@ +version = "v0.3-beta7" diff --git a/main.lua b/main.lua index 0af75ab..ec2e6fe 100644 --- a/main.lua +++ b/main.lua @@ -8,106 +8,115 @@ function love.load() require "load.bgm" require "load.save" require "load.bigint" + require "load.version" loadSave() + require "funcs" require "scene" + --config["side_next"] = false --config["reverse_rotate"] = true - config["fullscreen"] = false + --config["das_last_key"] = false + --config["fullscreen"] = false love.window.setMode(love.graphics.getWidth(), love.graphics.getHeight(), {resizable = true}); + + -- used for screenshots + GLOBAL_CANVAS = love.graphics.newCanvas() -- init config - if not config.das then config.das = 10 end - if not config.arr then config.arr = 2 end - if not config.dcd then config.dcd = 0 end - if not config.sfx_volume then config.sfx_volume = 0.5 end - if not config.bgm_volume then config.bgm_volume = 0.5 end - - if config.secret == nil then config.secret = false - elseif config.secret == true then playSE("welcome") end + initConfig() - if not config.gamesettings then - config.gamesettings = {} - config["das_last_key"] = false + love.window.setFullscreen(config["fullscreen"]) + if config.secret then playSE("welcome") end + + -- import custom modules + initModules() +end + +function loadFiles(path,name) + local file = love.filesystem.getInfo(path) + if(file.type == "directory") then + local files_in = {} + local file_list = love.filesystem.getDirectoryItems(path) + --print("Encountered directory "..path) + table.insert(files_in,{type="back",name="(..)"}) + for i,v in ipairs(file_list) do + if(v ~= "gamemode.lua") then table.insert(files_in, loadFiles(path.."/"..v,v)) end -- gamemode.lua is the base, not a mode + end + return {type="directory",name=name,content=files_in} else - config["das_last_key"] = config.gamesettings.das_last_key == 2 + --print("Loaded file "..path) + return {type="file",content=require(string.gsub(string.gsub(path,"/","."),"%.lua",""))} end - for _, option in ipairs(GameConfigScene.options) do - if not config.gamesettings[option[1]] then - config.gamesettings[option[1]] = 1 +end + +function sort_folder_algo(a,b) + local function padnum(d) return ("%03d%s"):format(#d, d) end + if(a.type == "back" or b.type == "back") then + -- back always goes first + return a.type == "back" + elseif(a.type == "directory" and b.type == "directory") then + -- if both are directories, sort by name + return tostring(a.name) < tostring(b.name) + elseif(a.type == "directory" or b.type == "directory") then + -- if one is a directory and the other's a file, then the directory always goes first + return a.type == "directory" + end + -- otherwise, they're both files, sort them by name + return tostring(a.content.name):gsub("%d+",padnum) < tostring(b.content.name):gsub("%d+",padnum) +end + +function sortFolder(folder) + -- step 1, sort the main level + table.sort(folder, sort_folder_algo) + + -- step 2, recursively do it + for i,v in ipairs(folder) do + if(v.type == "directory") then + sortFolder(v.content) end end - - if not config.input then - scene = InputConfigScene() - else - if config.current_mode then current_mode = config.current_mode end - if config.current_ruleset then current_ruleset = config.current_ruleset end - scene = TitleScene() - end +end +function initModules() game_modes = {} mode_list = love.filesystem.getDirectoryItems("tetris/modes") + for i,v in ipairs(mode_list) do + if(v ~= "gamemode.lua") then table.insert(game_modes, loadFiles("tetris/modes/"..v,v)) end + end + --[[ + mode_list = love.filesystem.getDirectoryItems("tetris/modes") for i=1,#mode_list do - if(mode_list[i] ~= "gamemode.lua" and mode_list[i] ~= "unrefactored_modes") then + + if(mode_list[i] ~= "gamemode.lua" and string.sub(mode_list[i], -4) == ".lua") then game_modes[#game_modes+1] = require ("tetris.modes."..string.sub(mode_list[i],1,-5)) end end + --]] rulesets = {} rule_list = love.filesystem.getDirectoryItems("tetris/rulesets") + for i,v in ipairs(rule_list) do + if(v ~= "ruleset.lua") then table.insert(rulesets, loadFiles("tetris/rulesets/"..v,v)) end + end + --[[ for i=1,#rule_list do - if(rule_list[i] ~= "ruleset.lua" and rule_list[i] ~= "unrefactored_rulesets") then + if(rule_list[i] ~= "ruleset.lua" and string.sub(rule_list[i], -4) == ".lua") then rulesets[#rulesets+1] = require ("tetris.rulesets."..string.sub(rule_list[i],1,-5)) end 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(rulesets, function(a,b) - return tostring(a.name):gsub("%d+",padnum) < tostring(b.name):gsub("%d+",padnum) end) - -end -local TARGET_FPS = 60 -local SAMPLE_SIZE = 60 - -local rolling_samples = {} -local rolling_total = 0 -local average_n = 0 -local frame = 0 - -function getSmoothedDt(dt) - rolling_total = rolling_total + dt - frame = frame + 1 - if frame > SAMPLE_SIZE then frame = frame - SAMPLE_SIZE end - if average_n == SAMPLE_SIZE then - rolling_total = rolling_total - rolling_samples[frame] - else - average_n = average_n + 1 - end - rolling_samples[frame] = dt - return rolling_total / average_n -end - -local update_time = 0.52 - -function love.update(dt) - processBGMFadeout(dt) - local old_update_time = update_time - update_time = update_time + getSmoothedDt(dt) * TARGET_FPS - updates = 0 - while (update_time >= 1.02) do - scene:update() - updates = updates + 1 - update_time = update_time - 1 - end - if math.abs(update_time - old_update_time) < 0.02 then - update_time = old_update_time - end + sortFolder(game_modes) + sortFolder(rulesets) end function love.draw() + love.graphics.setCanvas(GLOBAL_CANVAS) + love.graphics.clear() + love.graphics.push() -- get offset matrix @@ -120,15 +129,25 @@ function love.draw() (height - scale_factor * 480) / 2 ) love.graphics.scale(scale_factor) - + scene:render() + + love.graphics.setFont(font_3x5_2) + love.graphics.setColor(1, 1, 1, 1) + love.graphics.printf(version, 0, 460, 635, "right") + love.graphics.pop() + + love.graphics.setCanvas() + love.graphics.setColor(1,1,1,1) + love.graphics.draw(GLOBAL_CANVAS) end function love.keypressed(key, scancode) -- global hotkeys - if scancode == "f4" then + if scancode == "f11" then config["fullscreen"] = not config["fullscreen"] + saveConfig() love.window.setFullscreen(config["fullscreen"]) elseif scancode == "f2" and scene.title ~= "Input Config" and scene.title ~= "Game" then scene = InputConfigScene() @@ -140,6 +159,16 @@ function love.keypressed(key, scancode) scene.restart_message = true if config.secret then playSE("mode_decide") else playSE("erase") end + -- f12 is reserved for saving screenshots + elseif scancode == "f12" then + local ss_name = os.date("ss/%Y-%m-%d_%H-%M-%S.png") + local info = love.filesystem.getInfo("ss", "directory") + if not info then + love.filesystem.remove("ss") + love.filesystem.createDirectory("ss") + end + print("Saving screenshot as "..ss_name) + GLOBAL_CANVAS:newImageData():encode("png", ss_name) -- function keys are reserved elseif string.match(scancode, "^f[1-9]$") or string.match(scancode, "^f[1-9][0-9]+$") then return @@ -210,13 +239,13 @@ function love.joystickaxis(joystick, axis, value) 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"] + if math.abs(value) >= 1 then + input_pressed = config.input.joysticks[joystick:getName()].axes[axis][value >= 1 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 + if math.abs(value) >= 1 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}) @@ -224,6 +253,14 @@ function love.joystickaxis(joystick, axis, value) end end +local last_hat_direction = "" +local directions = { + ["u"] = "up", + ["d"] = "down", + ["l"] = "left", + ["r"] = "right", +} + function love.joystickhat(joystick, hat, direction) local input_pressed = nil local has_hat = false @@ -240,24 +277,116 @@ function love.joystickhat(joystick, hat, direction) has_hat = true end if input_pressed then - scene:onInputPress({input=input_pressed, type="joyhat", name=joystick:getName(), hat=hat, direction=direction}) + for i = 1, #direction do + local char = direction:sub(i, i) + local _, count = last_hat_direction:gsub(char, char) + if count == 0 then + scene:onInputPress({input=config.input.joysticks[joystick:getName()].hats[hat][char], type="joyhat", name=joystick:getName(), hat=hat, direction=char}) + end + end + for i = 1, #last_hat_direction do + local char = last_hat_direction:sub(i, i) + local _, count = direction:gsub(char, char) + if count == 0 then + scene:onInputRelease({input=config.input.joysticks[joystick:getName()].hats[hat][char], type="joyhat", name=joystick:getName(), hat=hat, direction=char}) + end + end + last_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 + last_hat_direction = "" elseif direction ~= "c" then - scene:onInputPress({input=nil, type="joyhat", name=joystick:getName(), hat=hat, direction=direction}) + for i = 1, #direction do + local char = direction:sub(i, i) + local _, count = last_hat_direction:gsub(char, char) + if count == 0 then + scene:onInputPress({input=directions[char], type="joyhat", name=joystick:getName(), hat=hat, direction=char}) + end + end + for i = 1, #last_hat_direction do + local char = last_hat_direction:sub(i, i) + local _, count = direction:gsub(char, char) + if count == 0 then + scene:onInputRelease({input=directions[char], type="joyhat", name=joystick:getName(), hat=hat, direction=char}) + end + end + last_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 + last_hat_direction = "" end end +function love.wheelmoved(x, y) + scene:onInputPress({input=nil, type="wheel", x=x, y=y}) +end + function love.focus(f) - if f and (scene.title ~= "Game" or not scene.paused) then - resumeBGM() + if f then + resumeBGM(true) else - pauseBGM() + pauseBGM(true) + end +end + +function love.resize(w, h) + GLOBAL_CANVAS:release() + GLOBAL_CANVAS = love.graphics.newCanvas(w, h) +end + +local TARGET_FPS = 60 + +function love.run() + if love.load then love.load(love.arg.parseGameArguments(arg), arg) end + + if love.timer then love.timer.step() end + + local dt = 0 + + local last_time = love.timer.getTime() + local time_accumulator = 0 + return function() + if love.event then + love.event.pump() + for name, a,b,c,d,e,f in love.event.poll() do + if name == "quit" then + if not love.quit or not love.quit() then + return a or 0 + end + end + love.handlers[name](a,b,c,d,e,f) + end + end + + if love.timer then + processBGMFadeout(love.timer.step()) + end + + if scene and scene.update and love.timer then + scene:update() + + local frame_duration = 1.0 / TARGET_FPS + if time_accumulator < frame_duration then + if love.graphics and love.graphics.isActive() and love.draw then + love.graphics.origin() + love.graphics.clear(love.graphics.getBackgroundColor()) + love.draw() + love.graphics.present() + end + local end_time = last_time + frame_duration + local time = love.timer.getTime() + while time < end_time do + love.timer.sleep(0.001) + time = love.timer.getTime() + end + time_accumulator = time_accumulator + time - last_time + end + time_accumulator = time_accumulator - frame_duration + end + last_time = love.timer.getTime() end end diff --git a/scene.lua b/scene.lua index a2e4c74..aaa81b8 100644 --- a/scene.lua +++ b/scene.lua @@ -11,6 +11,8 @@ function Scene:onInputRelease() end ExitScene = require "scene.exit" GameScene = require "scene.game" ModeSelectScene = require "scene.mode_select" +KeyConfigScene = require "scene.key_config" +StickConfigScene = require "scene.stick_config" InputConfigScene = require "scene.input_config" GameConfigScene = require "scene.game_config" TuningScene = require "scene.tuning" diff --git a/scene/credits.lua b/scene/credits.lua index 8529dbe..c3ecbe7 100644 --- a/scene/credits.lua +++ b/scene/credits.lua @@ -4,23 +4,33 @@ CreditsScene.title = "Credits" function CreditsScene:new() self.frames = 0 + -- higher = slower + self.scroll_speed = 1.85 switchBGM("credit_roll", "gm3") + + DiscordRPC:update({ + details = "Watching the credits", + state = "Thanks for playing the game!", + largeImageKey = "ingame-1900", + }) end function CreditsScene:update() if love.window.hasFocus() then self.frames = self.frames + 1 end - if self.frames >= 4200 then + if self.frames >= 2100 * self.scroll_speed then playSE("mode_decide") scene = TitleScene() switchBGM(nil) - elseif self.frames == 3600 then + elseif self.frames == math.floor(1950 * self.scroll_speed) then fadeoutBGM(2) end end function CreditsScene:render() + local offset = self.frames / self.scroll_speed + love.graphics.setColor(1, 1, 1, 1) love.graphics.draw( backgrounds[19], @@ -29,27 +39,43 @@ function CreditsScene:render() ) love.graphics.setFont(font_3x5_4) - love.graphics.print("Cambridge Credits", 320, 500 - self.frames / 2) - love.graphics.print("THANK YOU\nFOR PLAYING!", 320, math.max(1500 - self.frames / 2, 240)) + love.graphics.print("Cambridge Credits", 320, 500 - offset) + love.graphics.print("THANK YOU\nFOR PLAYING!", 320, math.max(2050 - offset, 240)) love.graphics.setFont(font_3x5_3) - love.graphics.print("Game Developers", 320, 550 - self.frames / 2) - love.graphics.print("Project Heads", 320, 640 - self.frames / 2) - love.graphics.print("Other Game Developers", 320, 730 - self.frames / 2) - love.graphics.print("Special Thanks", 320, 900 - self.frames / 2) - love.graphics.print("- SashLilac / SpinTriple", 320, math.max(2000 - self.frames / 2, 320)) + love.graphics.print("Game Developers", 320, 550 - offset) + love.graphics.print("Project Heads", 320, 640 - offset) + love.graphics.print("Notable Game Developers", 320, 750 - offset) + love.graphics.print("Special Thanks", 320, 1020 - offset) + love.graphics.print("- Milla", 320, math.max(2130 - offset, 320)) love.graphics.setFont(font_3x5_2) - love.graphics.print("Oshisaure\nJoe Zeng", 320, 590 - self.frames / 2) - love.graphics.print("Mizu\nHailey", 320, 680 - self.frames / 2) - love.graphics.print("Axel Fox - Multimino\nMine - Tetra Online\nDr Ocelot - Tetra Legends\nFelicity / nightmareci - Shiromino\n2Tie - TGMsim\nPhoenix Flare - Master of Blocks", 320, 770 - self.frames / 2) + love.graphics.print("Oshisaure\nJoe Zeng", 320, 590 - offset) + love.graphics.print("Mizu\nMarkGamed\nHailey", 320, 680 - offset) love.graphics.print( - "RocketLanterns\nCylinderKnot\nHammrTime\nKirby703\nMattMayuga\nMyPasswordIsWeak\n" .. - "Nikki Karissa\noffwo\nsinefuse\nTetro48\nTimmSkiller\nuser74003\nAgentBasey\n" .. - "CheeZed_Fish\neightsixfivezero\nEricICX\ngizmo4487\nM1ssing0\nMarkGamed7794\n" .. - "pokemonfan1937\nSimon\nstratus\nZaptorZap\nThe Absolute PLUS Discord\nTetra Legends Discord\n" .. - "Tetra Online Discord\nMultimino Discord\nCambridge Discord\nAnd to you, the player!", - 320, 940 - self.frames / 2 + "2Tie - TGMsim\nAxel Fox - Multimino\nDr Ocelot - Tetra Legends\n" .. + "Electra - ZTrix\nFelicity/nightmareci/kdex - Shiromino\n" .. + "Mine - Tetra Online\nMrZ - Techmino\nosk - TETR.IO\n" .. + "Phoenix Flare - Master of Blocks\nRayRay26 - Spirit Drop\n" .. + "Rin - Puzzle Trial\nsinefuse - stackfuse", + 320, 790 - offset + ) + love.graphics.print( + "321MrHaatz\nAdventium\nAgentBasey\nArchina\nAurora\n" .. + "Caithness\nCheez\ncolour_thief\nCommando\nCublex\n" .. + "CylinderKnot\neightsixfivezero\nEricICX\nGesomaru\n" .. + "gizmo4487\nJBroms\nKirby703\nKitaru\n" .. + "M1ssing0\nMattMayuga\nMyPasswordIsWeak\n" .. + "Nikki Karissa\noffwo\nOliver\nPineapple\npokemonfan1937\n" .. + "Pyra Neoxi\nRDST64\nRocketLanterns\nRustyFoxxo\n" .. + "saphie\nShelleloch\nSimon\nstratus\nSuper302\n" .. + "switchpalacecorner\nterpyderp\nTetrian22\nTetro48\nThatCookie\n" .. + "TimmSkiller\nTrixciel\nuser74003\nZaptorZap\nZircean\n" .. + "All other contributors and friends!\nThe Absolute PLUS Discord\n" .. + "Tetra Legends Discord\nTetra Online Discord\nMultimino Discord\n" .. + "Hard Drop Discord\nRusty's Systemspace\nCambridge Discord\n" .. + "And to you, the player!", + 320, 1060 - offset ) end @@ -61,4 +87,4 @@ function CreditsScene:onInputPress(e) end end -return CreditsScene \ No newline at end of file +return CreditsScene diff --git a/scene/game.lua b/scene/game.lua index 4c73647..dd5b9f4 100644 --- a/scene/game.lua +++ b/scene/game.lua @@ -7,10 +7,10 @@ require 'load.save' function GameScene:new(game_mode, ruleset, inputs) self.retry_mode = game_mode self.retry_ruleset = ruleset - self.secret_inputs = copy(inputs) + self.secret_inputs = inputs self.game = game_mode(self.secret_inputs) - self.ruleset = ruleset() - self.game:initialize(self.ruleset, self.secret_inputs) + self.ruleset = ruleset(self.game) + self.game:initialize(self.ruleset) self.inputs = { left=false, right=false, @@ -27,6 +27,7 @@ function GameScene:new(game_mode, ruleset, inputs) DiscordRPC:update({ details = self.game.rpc_details, state = self.game.name, + largeImageKey = "ingame-"..self.game:getBackground().."00" }) end @@ -38,94 +39,39 @@ function GameScene:update() end self.game:update(inputs, self.ruleset) self.game.grid:update() + DiscordRPC:update({ + largeImageKey = "ingame-"..self.game:getBackground().."00" + }) end end function GameScene:render() - love.graphics.setColor(1, 1, 1, 1) - love.graphics.draw( - backgrounds[self.game:getBackground()], - 0, 0, 0, - 0.5, 0.5 - ) - - -- game frame - if self.game.grid.width == 10 and self.game.grid.height == 24 then - love.graphics.draw(misc_graphics["frame"], 48, 64) - end - - love.graphics.setColor(0, 0, 0, 200) - love.graphics.rectangle( - "fill", 64, 80, - 16 * self.game.grid.width, 16 * (self.game.grid.height - 4) - ) - - if self.game.grid.width ~= 10 or self.game.grid.height ~= 24 then - love.graphics.setColor(174/255, 83/255, 76/255, 1) - love.graphics.setLineWidth(8) - love.graphics.line( - 60,76, - 68+16*self.game.grid.width,76, - 68+16*self.game.grid.width,84+16*(self.game.grid.height-4), - 60,84+16*(self.game.grid.height-4), - 60,76 - ) - love.graphics.setColor(203/255, 137/255, 111/255, 1) - love.graphics.setLineWidth(4) - love.graphics.line( - 60,76, - 68+16*self.game.grid.width,76, - 68+16*self.game.grid.width,84+16*(self.game.grid.height-4), - 60,84+16*(self.game.grid.height-4), - 60,76 - ) - love.graphics.setLineWidth(1) - end - - self.game:drawGrid() - self.game:drawPiece() - self.game:drawNextQueue(self.ruleset) - self.game:drawScoringInfo() - - -- ready/go graphics - - if self.game.ready_frames <= 100 and self.game.ready_frames > 52 then - love.graphics.draw(misc_graphics["ready"], 144 - 50, 240 - 14) - elseif self.game.ready_frames <= 50 and self.game.ready_frames > 2 then - love.graphics.draw(misc_graphics["go"], 144 - 27, 240 - 14) - end - - self.game:drawCustom() - - love.graphics.setFont(font_3x5_2) - if config.gamesettings.display_gamemode == 1 then - love.graphics.printf(self.game.name .. " - " .. self.ruleset.name, 0, 460, 640, "left") - end - - love.graphics.setFont(font_3x5_3) - if self.paused then love.graphics.print("PAUSED!", 80, 100) end - - if self.game.completed then - self.game:onGameComplete() - elseif self.game.game_over then - self.game:onGameOver() - end + self.game:draw(self.paused) end function GameScene:onInputPress(e) - if (self.game.game_over or self.game.completed) and (e.input == "menu_decide" or e.input == "menu_back" or e.input == "retry") then + if ( + self.game.game_over or self.game.completed + ) and ( + e.input == "menu_decide" or + e.input == "menu_back" or + e.input == "retry" + ) then highscore_entry = self.game:getHighscoreData() highscore_hash = self.game.hash .. "-" .. self.ruleset.hash submitHighscore(highscore_hash, highscore_entry) + self.game:onExit() scene = e.input == "retry" and GameScene(self.retry_mode, self.retry_ruleset, self.secret_inputs) or ModeSelectScene() elseif e.input == "retry" then switchBGM(nil) + self.game:onExit() scene = GameScene(self.retry_mode, self.retry_ruleset, self.secret_inputs) elseif e.input == "pause" and not (self.game.game_over or self.game.completed) then self.paused = not self.paused if self.paused then pauseBGM() else resumeBGM() end elseif e.input == "menu_back" then + self.game:onExit() scene = ModeSelectScene() elseif e.input and string.sub(e.input, 1, 5) ~= "menu_" then self.inputs[e.input] = true diff --git a/scene/game_config.lua b/scene/game_config.lua index f14cd4f..a161cf8 100644 --- a/scene/game_config.lua +++ b/scene/game_config.lua @@ -11,13 +11,13 @@ ConfigScene.options = { {"manlock", "Manual Locking", false, {"Per ruleset", "Per gamemode", "Harddrop", "Softdrop"}}, {"piece_colour", "Piece Colours", false, {"Per ruleset", "Arika", "TTC"}}, {"world_reverse", "A Button Rotation", false, {"Left", "Auto", "Right"}}, - {"spawn_positions", "Spawn Positions", false, {"In field", "Out of field"}}, + {"spawn_positions", "Spawn Positions", false, {"Per ruleset", "In field", "Out of field"}}, {"display_gamemode", "Display Gamemode", false, {"On", "Off"}}, - {"das_last_key", "DAS Switch", false, {"Default", "Instant"}}, + {"das_last_key", "DAS Last Key", false, {"Off", "On"}}, {"smooth_movement", "Smooth Piece Drop", false, {"On", "Off"}}, {"synchroes_allowed", "Synchroes", false, {"Per ruleset", "On", "Off"}}, {"diagonal_input", "Diagonal Input", false, {"On", "Off"}}, - {"buffer_lock", "Buffer Lock Inputs", false, {"On", "Off"}}, + {"buffer_lock", "Buffer Drop Type", false, {"Off", "Hold", "Tap"}}, {"sfx_volume", "SFX", true, "sfxSlider"}, {"bgm_volume", "BGM", true, "bgmSlider"}, } @@ -27,9 +27,9 @@ function ConfigScene:new() -- load current config self.config = config.input self.highlight = 1 - + DiscordRPC:update({ - details = "In menus", + details = "In settings", state = "Changing game settings", }) @@ -38,7 +38,6 @@ function ConfigScene:new() end function ConfigScene:update() - config["das_last_key"] = config.gamesettings.das_last_key == 2 self.sfxSlider:update() self.bgmSlider:update() end @@ -53,7 +52,7 @@ function ConfigScene:render() love.graphics.setFont(font_3x5_4) love.graphics.print("GAME SETTINGS", 80, 40) - + --Lazy check to see if we're on the SFX or BGM slider. Probably will need to be rewritten if more options get added. love.graphics.setColor(1, 1, 1, 0.5) if not ConfigScene.options[self.highlight][3] then @@ -61,7 +60,7 @@ function ConfigScene:render() else love.graphics.rectangle("fill", 65 + (1+self.highlight-#self.options) * 300, 342, 215, 33) end - + love.graphics.setFont(font_3x5_2) for i, option in ipairs(ConfigScene.options) do if not option[3] then @@ -101,9 +100,10 @@ function ConfigScene:onInputPress(e) local option = ConfigScene.options[self.highlight] config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]-1, #option[4]) else + local sld = self[self.options[self.highlight][4]] + sld.value = math.max(sld.min, math.min(sld.max, (sld:getValue() - 5) / (sld.max - sld.min))) + sld:update() playSE("cursor") - sld = self[self.options[self.highlight][4]] - sld.value = math.max(sld.min, math.min(sld.max, (sld:getValue() - 3) / (sld.max - sld.min))) end elseif e.input == "right" or e.scancode == "right" then if not self.options[self.highlight][3] then @@ -111,9 +111,10 @@ function ConfigScene:onInputPress(e) local option = ConfigScene.options[self.highlight] config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]+1, #option[4]) else - playSE("cursor") sld = self[self.options[self.highlight][4]] - sld.value = math.max(sld.min, math.min(sld.max, (sld:getValue() + 3) / (sld.max - sld.min)))--math.max(0, (math.floor(sld:getValue())+2)/(sld.max-sld.min)) + sld.value = math.max(sld.min, math.min(sld.max, (sld:getValue() + 5) / (sld.max - sld.min))) + sld:update() + playSE("cursor") end elseif e.input == "menu_back" or e.scancode == "delete" or e.scancode == "backspace" then loadSave() diff --git a/scene/input_config.lua b/scene/input_config.lua index 7560f15..7b488f8 100644 --- a/scene/input_config.lua +++ b/scene/input_config.lua @@ -2,161 +2,65 @@ local ConfigScene = Scene:extend() ConfigScene.title = "Input Config" -require 'load.save' - -local configurable_inputs = { - "menu_decide", - "menu_back", - "left", - "right", - "up", - "down", - "rotate_left", - "rotate_left2", - "rotate_right", - "rotate_right2", - "rotate_180", - "hold", - "retry", - "pause", +local menu_screens = { + KeyConfigScene, + StickConfigScene } -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() - self.input_state = 1 - self.set_inputs = newSetInputs() - self.new_input = {} - self.axis_timer = 0 - - DiscordRPC:update({ - details = "In menus", - state = "Changing input config", - }) + self.menu_state = 1 + DiscordRPC:update({ + details = "In settings", + state = "Changing input config", + largeImageKey = "settings-input" + }) end -function ConfigScene:update() -end +function ConfigScene:update() end function ConfigScene:render() - love.graphics.setColor(1, 1, 1, 1) - love.graphics.draw( + love.graphics.setColor(1, 1, 1, 1) + love.graphics.draw( backgrounds["input_config"], 0, 0, 0, 0.5, 0.5 - ) + ) - love.graphics.setFont(font_3x5_2) - for i, input in ipairs(configurable_inputs) do - love.graphics.printf(input, 40, 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/backspace to retry" .. (config.input and ", escape to cancel" or "")) - else - 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("function keys (F1, F2, etc.), escape, and tab can't be changed", 0, 20) - end + love.graphics.setFont(font_3x5_4) + love.graphics.print("INPUT CONFIG", 80, 40) - self.axis_timer = self.axis_timer + 1 + love.graphics.setFont(font_3x5_2) + love.graphics.print("Which controls do you want to configure?", 80, 90) + + love.graphics.setColor(1, 1, 1, 0.5) + love.graphics.rectangle("fill", 75, 118 + 50 * self.menu_state, 200, 33) + + love.graphics.setFont(font_3x5_3) + love.graphics.setColor(1, 1, 1, 1) + for i, screen in pairs(menu_screens) do + love.graphics.printf(screen.title, 80, 120 + 50 * i, 200, "left") + 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 +function ConfigScene:changeOption(rel) + local len = table.getn(menu_screens) + self.menu_state = (self.menu_state + len + rel - 1) % len + 1 end function ConfigScene:onInputPress(e) - if e.type == "key" then - -- function keys, escape, and tab are reserved and can't be remapped - if e.scancode == "escape" and config.input then - -- cancel only if there was an input config already - scene = SettingsScene() - 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 = {} - end - elseif e.scancode == "tab" then - self.set_inputs[configurable_inputs[self.input_state]] = "skipped" - self.input_state = self.input_state + 1 - elseif e.scancode ~= "escape" then - -- all other keys can be configured - 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 - 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 (e.axis ~= self.last_axis or self.axis_timer > 30) and math.abs(e.value) >= 1 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 >= 1 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 >= 1 and "positive" or "negative"] = configurable_inputs[self.input_state] - self.input_state = self.input_state + 1 - self.last_axis = e.axis - self.axis_timer = 0 - 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 + if e.input == "menu_decide" or e.scancode == "return" then + playSE("main_decide") + scene = menu_screens[self.menu_state]() + elseif e.input == "up" or e.scancode == "up" then + self:changeOption(-1) + playSE("cursor") + elseif e.input == "down" or e.scancode == "down" then + self:changeOption(1) + playSE("cursor") + elseif config.input and ( + e.input == "menu_back" or e.scancode == "backspace" or e.scancode == "delete" + ) then + scene = SettingsScene() end end diff --git a/scene/key_config.lua b/scene/key_config.lua new file mode 100644 index 0000000..419ebb5 --- /dev/null +++ b/scene/key_config.lua @@ -0,0 +1,100 @@ +local KeyConfigScene = Scene:extend() + +KeyConfigScene.title = "Key Config" + +require 'load.save' + +local configurable_inputs = { + "menu_decide", + "menu_back", + "left", + "right", + "up", + "down", + "rotate_left", + "rotate_left2", + "rotate_right", + "rotate_right2", + "rotate_180", + "hold", + "retry", + "pause", +} + +local function newSetInputs() + local set_inputs = {} + for i, input in ipairs(configurable_inputs) do + set_inputs[input] = false + end + return set_inputs +end + +function KeyConfigScene:new() + self.input_state = 1 + self.set_inputs = newSetInputs() + self.new_input = {} + + DiscordRPC:update({ + details = "In settings", + state = "Changing key config", + }) +end + +function KeyConfigScene:update() +end + +function KeyConfigScene:render() + love.graphics.setColor(1, 1, 1, 1) + love.graphics.draw( + backgrounds["input_config"], + 0, 0, 0, + 0.5, 0.5 + ) + + love.graphics.setFont(font_3x5_2) + for i, input in ipairs(configurable_inputs) do + love.graphics.printf(input, 40, 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/backspace to retry" .. (config.input and ", escape to cancel" or "")) + else + love.graphics.print("press key input for " .. configurable_inputs[self.input_state] .. ", tab to skip, escape to cancel", 0, 0) + love.graphics.print("function keys (F1, F2, etc.), escape, and tab can't be changed", 0, 20) + end +end + +function KeyConfigScene:onInputPress(e) + if e.type == "key" then + -- function keys, escape, and tab are reserved and can't be remapped + if e.scancode == "escape" then + scene = InputConfigScene() + elseif self.input_state > table.getn(configurable_inputs) then + if e.scancode == "return" then + -- save new input, then load next scene + local had_config = config.input ~= nil + if not config.input then config.input = {} end + config.input.keys = self.new_input + saveConfig() + scene = had_config and InputConfigScene() or TitleScene() + elseif e.scancode == "delete" or e.scancode == "backspace" then + -- retry + self.input_state = 1 + self.set_inputs = newSetInputs() + self.new_input = {} + end + elseif e.scancode == "tab" then + self.set_inputs[configurable_inputs[self.input_state]] = "skipped" + self.input_state = self.input_state + 1 + elseif e.scancode ~= "escape" and not self.new_input[e.scancode] then + -- all other keys can be configured + self.set_inputs[configurable_inputs[self.input_state]] = "key " .. love.keyboard.getKeyFromScancode(e.scancode) .. " (" .. e.scancode .. ")" + self.new_input[e.scancode] = configurable_inputs[self.input_state] + self.input_state = self.input_state + 1 + end + end +end + +return KeyConfigScene diff --git a/scene/mode_select.lua b/scene/mode_select.lua index f4d4c7d..412ba45 100755 --- a/scene/mode_select.lua +++ b/scene/mode_select.lua @@ -6,27 +6,113 @@ current_mode = 1 current_ruleset = 1 function ModeSelectScene:new() + -- reload custom modules + initModules() + if table.getn(game_modes) == 0 or table.getn(rulesets) == 0 then + self.display_warning = true + current_mode = 1 + current_ruleset = 1 + else + self.display_warning = false + if current_mode > table.getn(game_modes) then + current_mode = 1 + end + if current_ruleset > table.getn(rulesets) then + current_ruleset = 1 + end + end + self.menu_state = { mode = current_mode, + mode_list = game_modes, + mode_list_history = {}, + mode_path = (config.mode_path or {}), ruleset = current_ruleset, + rule_list = rulesets, + rule_list_history = {}, + rule_path = config.rule_path, select = "mode", } - self.secret_inputs = { - rotate_left = false, - rotate_left2 = false, - rotate_right = false, - rotate_right2 = false, - rotate_180 = false, - hold = false, - } + self.secret_inputs = {} + self.das = 0 DiscordRPC:update({ details = "In menus", state = "Choosing a mode", + largeImageKey = "ingame-000" }) + + -- grab the filepaths from config to ensure you're dropped off on the correct mode and ruleset + local able_to_find = true + for _,looking_for in pairs(self.menu_state.mode_path) do + if(able_to_find) then + local found = false + for idx,file in pairs(self.menu_state.mode_list) do + if(not found) then + if(file.type == "directory" and file.name == looking_for) then + table.insert(self.menu_state.mode_list_history,self.menu_state.mode_list) + self.menu_state.mode_list = self.menu_state.mode_list[idx].content + found = true + end + end + end + able_to_find = (found and able_to_find) + end + end + if(not able_to_find) then + --directory doesn't exist, revert to default + self.menu_state.mode_list = game_modes + self.menu_state.mode_list_history = {} + self.menu_state.mode_path = {} + self.menu_state.mode = 1 + current_mode = 1 + end + + -- do the same with the ruleset + local able_to_find = true + for _,looking_for in pairs(self.menu_state.rule_path) do + if(able_to_find) then + local found = false + for idx,file in pairs(self.menu_state.rule_list) do + if(not found) then + if(file.type == "directory" and file.name == looking_for) then + table.insert(self.menu_state.rule_list_history,self.menu_state.rule_list) + self.menu_state.rule_list = self.menu_state.rule_list[idx].content + found = true + end + end + end + able_to_find = (found and able_to_find) + end + end + if(not able_to_find) then + --directory doesn't exist, revert to default + self.menu_state.rule_list = game_modes + self.menu_state.rule_list_history = {} + self.menu_state.rule_path = {} + self.menu_state.rule = 1 + current_mode = 1 + end end function ModeSelectScene:update() switchBGM(nil) -- experimental + + if self.das_up or self.das_down then + self.das = self.das + 1 + else + self.das = 0 + end + + if self.das >= 15 then + self:changeOption(self.das_up and -1 or 1) + self.das = self.das - 4 + end + + DiscordRPC:update({ + details = "In menus", + state = "Choosing a " .. self.menu_state.select, + largeImageKey = "ingame-000" + }) end function ModeSelectScene:render() @@ -36,6 +122,23 @@ function ModeSelectScene:render() 0.5, 0.5 ) + love.graphics.draw(misc_graphics["select_mode"], 20, 40) + + if self.display_warning then + love.graphics.setFont(font_3x5_3) + love.graphics.printf( + "You have no modes or rulesets.", + 80, 200, 480, "center" + ) + love.graphics.setFont(font_3x5_2) + love.graphics.printf( + "Come back to this menu after getting more modes or rulesets. " .. + "Press any button to return to the main menu.", + 80, 250, 480, "center" + ) + return + end + if self.menu_state.select == "mode" then love.graphics.setColor(1, 1, 1, 0.5) elseif self.menu_state.select == "ruleset" then @@ -52,39 +155,87 @@ function ModeSelectScene:render() love.graphics.setColor(1, 1, 1, 1) - love.graphics.draw(misc_graphics["select_mode"], 20, 40) - love.graphics.setFont(font_3x5_2) - for idx, mode in pairs(game_modes) do + for idx, mode in pairs(self.menu_state.mode_list) do if(idx >= self.menu_state.mode-9 and idx <= self.menu_state.mode+9) then - love.graphics.printf(mode.name, 40, (260 - 20*(self.menu_state.mode)) + 20 * idx, 200, "left") + if(mode.type == "directory") then + love.graphics.setColor(1,0.5,0,1) + elseif(mode.type == "back") then + love.graphics.setColor(1,1,0,1) + else + love.graphics.setColor(1,1,1,1) + end + love.graphics.printf((mode.type == "file" and mode.content.name or mode.name), 40, (260 - 20*(self.menu_state.mode)) + 20 * idx, 200, "left") end end - for idx, ruleset in pairs(rulesets) do + for idx, ruleset in pairs(self.menu_state.rule_list) do if(idx >= self.menu_state.ruleset-9 and idx <= self.menu_state.ruleset+9) then - love.graphics.printf(ruleset.name, 360, (260 - 20*(self.menu_state.ruleset)) + 20 * idx, 160, "left") + if(ruleset.type == "directory") then + love.graphics.setColor(1,0.5,0,1) + elseif(ruleset.type == "back") then + love.graphics.setColor(1,1,0,1) + else + love.graphics.setColor(1,1,1,1) + end + love.graphics.printf((ruleset.type == "file" and ruleset.content.name or ruleset.name), 360, (260 - 20*(self.menu_state.ruleset)) + 20 * idx, 200, "left") end end end function ModeSelectScene:onInputPress(e) - if e.input == "menu_decide" or e.scancode == "return" then - current_mode = self.menu_state.mode - current_ruleset = self.menu_state.ruleset - config.current_mode = current_mode - config.current_ruleset = current_ruleset - playSE("mode_decide") - saveConfig() - scene = GameScene(game_modes[self.menu_state.mode], rulesets[self.menu_state.ruleset], self.secret_inputs) + if self.display_warning and e.input then + scene = TitleScene() + elseif e.type == "wheel" then + if e.x % 2 == 1 then + self:switchSelect() + end + if e.y ~= 0 then + self:changeOption(-e.y) + end + elseif e.input == "menu_decide" or e.scancode == "return" then + if self.menu_state.mode_list[self.menu_state.mode].type == "directory" and self.menu_state.select == "mode" then + table.insert(self.menu_state.mode_list_history,self.menu_state.mode_list) + table.insert(self.menu_state.mode_path,self.menu_state.mode_list[self.menu_state.mode].name) + self.menu_state.mode_list = self.menu_state.mode_list[self.menu_state.mode].content + self.menu_state.mode = 1 + elseif self.menu_state.mode_list[self.menu_state.mode].type == "back" and self.menu_state.select == "mode" then + table.remove(self.menu_state.mode_path) + self.menu_state.mode_list = table.remove(self.menu_state.mode_list_history) + self.menu_state.mode = 1 + elseif self.menu_state.rule_list[self.menu_state.ruleset].type == "directory" and self.menu_state.select == "ruleset" then + table.insert(self.menu_state.rule_list_history,self.menu_state.rule_list) + table.insert(self.menu_state.rule_path,self.menu_state.rule_list[self.menu_state.ruleset].name) + self.menu_state.rule_list = self.menu_state.rule_list[self.menu_state.ruleset].content + self.menu_state.ruleset = 1 + elseif self.menu_state.rule_list[self.menu_state.ruleset].type == "back" and self.menu_state.select == "ruleset" then + table.remove(self.menu_state.rule_path) + self.menu_state.rule_list = table.remove(self.menu_state.rule_list_history) + self.menu_state.ruleset = 1 + elseif(self.menu_state.mode_list[self.menu_state.mode].type == "file" and self.menu_state.rule_list[self.menu_state.ruleset].type == "file") then -- make sure neither option is on a folder + current_mode = self.menu_state.mode + current_ruleset = self.menu_state.ruleset + config.current_mode = current_mode + config.mode_path = self.menu_state.mode_path + config.current_ruleset = current_ruleset + config.rule_path = self.menu_state.rule_path + playSE("mode_decide") + saveConfig() + scene = GameScene( + self.menu_state.mode_list[self.menu_state.mode].content, + self.menu_state.rule_list[self.menu_state.ruleset].content, + self.secret_inputs + ) + end elseif e.input == "up" or e.scancode == "up" then self:changeOption(-1) - playSE("cursor") + self.das_up = true + self.das_down = nil elseif e.input == "down" or e.scancode == "down" then self:changeOption(1) - playSE("cursor") + self.das_down = true + self.das_up = nil elseif e.input == "left" or e.input == "right" or e.scancode == "left" or e.scancode == "right" then self:switchSelect() - playSE("cursor_lr") elseif e.input == "menu_back" or e.scancode == "delete" or e.scancode == "backspace" then scene = TitleScene() elseif e.input then @@ -93,7 +244,11 @@ function ModeSelectScene:onInputPress(e) end function ModeSelectScene:onInputRelease(e) - if e.input == "hold" or (e.input and string.sub(e.input, 1, 7) == "rotate_") then + if e.input == "up" or e.scancode == "up" then + self.das_up = nil + elseif e.input == "down" or e.scancode == "down" then + self.das_down = nil + elseif e.input then self.secret_inputs[e.input] = false end end @@ -104,24 +259,26 @@ function ModeSelectScene:changeOption(rel) elseif self.menu_state.select == "ruleset" then self:changeRuleset(rel) end + playSE("cursor") end -function ModeSelectScene:switchSelect(rel) +function ModeSelectScene:switchSelect() if self.menu_state.select == "mode" then self.menu_state.select = "ruleset" elseif self.menu_state.select == "ruleset" then self.menu_state.select = "mode" end + playSE("cursor_lr") end function ModeSelectScene:changeMode(rel) - local len = table.getn(game_modes) - self.menu_state.mode = (self.menu_state.mode + len + rel - 1) % len + 1 + local len = table.getn(self.menu_state.mode_list) + self.menu_state.mode = Mod1(self.menu_state.mode + rel, len) end function ModeSelectScene:changeRuleset(rel) - local len = table.getn(rulesets) - self.menu_state.ruleset = (self.menu_state.ruleset + len + rel - 1) % len + 1 + local len = table.getn(self.menu_state.rule_list) + self.menu_state.ruleset = Mod1(self.menu_state.ruleset + rel, len) end return ModeSelectScene diff --git a/scene/settings.lua b/scene/settings.lua index 1187a03..c9663aa 100644 --- a/scene/settings.lua +++ b/scene/settings.lua @@ -8,11 +8,20 @@ local menu_screens = { TuningScene } +local settingsidle = { + "Tweaking some knobs", + "Tuning up", + "Adjusting options", + "Setting up", + "Setting the settings" +} + function SettingsScene:new() self.menu_state = 1 DiscordRPC:update({ - details = "In menus", - state = "Changing settings", + details = "In settings", + state = settingsidle[math.random(#settingsidle)], + largeImageKey = "settings", }) end @@ -62,4 +71,4 @@ function SettingsScene:onInputPress(e) end end -return SettingsScene \ No newline at end of file +return SettingsScene diff --git a/scene/stick_config.lua b/scene/stick_config.lua new file mode 100644 index 0000000..3e7be01 --- /dev/null +++ b/scene/stick_config.lua @@ -0,0 +1,159 @@ +local StickConfigScene = Scene:extend() + +StickConfigScene.title = "Joystick Config" + +require 'load.save' + +local configurable_inputs = { + "menu_decide", + "menu_back", + "left", + "right", + "up", + "down", + "rotate_left", + "rotate_left2", + "rotate_right", + "rotate_right2", + "rotate_180", + "hold", + "retry", + "pause", +} + +local function newSetInputs() + local set_inputs = {} + for i, input in ipairs(configurable_inputs) do + set_inputs[input] = false + end + return set_inputs +end + +function StickConfigScene:new() + self.input_state = 1 + self.set_inputs = newSetInputs() + self.new_input = {} + self.axis_timer = 0 + + DiscordRPC:update({ + details = "In settings", + state = "Changing joystick config", + }) +end + +function StickConfigScene:update() +end + +function StickConfigScene:render() + love.graphics.setColor(1, 1, 1, 1) + love.graphics.draw( + backgrounds["input_config"], + 0, 0, 0, + 0.5, 0.5 + ) + + love.graphics.setFont(font_3x5_2) + for i, input in ipairs(configurable_inputs) do + love.graphics.printf(input, 40, 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/backspace to retry" .. (config.input and ", escape to cancel" or "")) + else + love.graphics.print("press joystick input for " .. configurable_inputs[self.input_state] .. ", tab to skip, escape to cancel", 0, 0) + end + + self.axis_timer = self.axis_timer + 1 +end + +local function addJoystick(input, name) + if not input[name] then + input[name] = {} + end +end + +function StickConfigScene:onInputPress(e) + if e.type == "key" then + -- function keys, escape, and tab are reserved and can't be remapped + if e.scancode == "escape" then + scene = InputConfigScene() + elseif self.input_state > table.getn(configurable_inputs) then + if e.scancode == "return" then + -- save new input, then load next scene + local had_config = config.input ~= nil + if not config.input then config.input = {} end + config.input.joysticks = self.new_input + saveConfig() + scene = had_config and InputConfigScene() or TitleScene() + elseif e.scancode == "delete" or e.scancode == "backspace" then + -- retry + self.input_state = 1 + self.set_inputs = newSetInputs() + self.new_input = {} + end + elseif e.scancode == "tab" then + self.set_inputs[configurable_inputs[self.input_state]] = "skipped" + self.input_state = self.input_state + 1 + 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[e.name].buttons then + self.new_input[e.name].buttons = {} + end + if self.new_input[e.name].buttons[e.button] then return 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[e.name].buttons[e.button] = configurable_inputs[self.input_state] + self.input_state = self.input_state + 1 + elseif e.type == "joyaxis" then + if (e.axis ~= self.last_axis or self.axis_timer > 30) and math.abs(e.value) >= 1 then + addJoystick(self.new_input, e.name) + if not self.new_input[e.name].axes then + self.new_input[e.name].axes = {} + end + if not self.new_input[e.name].axes[e.axis] then + self.new_input[e.name].axes[e.axis] = {} + end + if ( + self.new_input[e.name].axes[e.axis][e.value >= 1 and "positive" or "negative"] + ) then return end + self.set_inputs[configurable_inputs[self.input_state]] = + "jaxis " .. + (e.value >= 1 and "+" or "-") .. e.axis .. + " " .. string.sub(e.name, 1, 10) .. (string.len(e.name) > 10 and "..." or "") + self.new_input[e.name].axes[e.axis][e.value >= 1 and "positive" or "negative"] = configurable_inputs[self.input_state] + self.input_state = self.input_state + 1 + self.last_axis = e.axis + self.axis_timer = 0 + end + elseif e.type == "joyhat" then + if e.direction ~= "c" then + addJoystick(self.new_input, e.name) + if not self.new_input[e.name].hats then + self.new_input[e.name].hats = {} + end + if not self.new_input[e.name].hats[e.hat] then + self.new_input[e.name].hats[e.hat] = {} + end + if self.new_input[e.name].hats[e.hat][e.direction] then + return + 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[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 StickConfigScene diff --git a/scene/title.lua b/scene/title.lua index ee43375..b9f12b4 100644 --- a/scene/title.lua +++ b/scene/title.lua @@ -23,6 +23,15 @@ local mainmenuidle = { "Having a nap", "In menus", "Bottom text", + "Trying to see all the funny rpc messages (maybe)", + "Not not not playing", + "AFK", + "Preparing for their next game", + "Who are those people on that boat?", + "Welcome to Cambridge!", + "who even reads these", + "Made with love in LOVE!", + "This is probably the longest RPC string out of every possible RPC string that can be displayed." } function TitleScene:new() @@ -35,6 +44,8 @@ function TitleScene:new() DiscordRPC:update({ details = "In menus", state = mainmenuidle[math.random(#mainmenuidle)], + largeImageKey = "icon2", + largeImageText = version }) end @@ -82,7 +93,6 @@ function TitleScene:render() for i, screen in pairs(main_menu_screens) do love.graphics.printf(screen.title, 40, 280 + 20 * i, 120, "left") end - end function TitleScene:changeOption(rel) @@ -106,6 +116,9 @@ function TitleScene:onInputPress(e) self.text = self.text .. (e.scancode ~= nil and e.scancode or "") if self.text == "ffffff" then self.text_flag = true + DiscordRPC:update({ + largeImageKey = "snow" + }) end end end diff --git a/scene/tuning.lua b/scene/tuning.lua index 69e58eb..ef0f4ee 100644 --- a/scene/tuning.lua +++ b/scene/tuning.lua @@ -16,7 +16,7 @@ local optioncount = #TuningScene.options function TuningScene:new() DiscordRPC:update({ - details = "In menus", + details = "In settings", state = "Changing tuning settings", }) self.highlight = 1 diff --git a/tetris/components/grid.lua b/tetris/components/grid.lua index d6c81da..d8c2bd1 100644 --- a/tetris/components/grid.lua +++ b/tetris/components/grid.lua @@ -111,18 +111,24 @@ function Grid:getClearedRowCount() end function Grid:markClearedRows() + local block_table = {} for row = 1, self.height do if self:isRowFull(row) then + block_table[row] = {} for x = 1, self.width do + block_table[row][x] = { + skin = self.grid[row][x].skin, + colour = self.grid[row][x].colour, + } self.grid[row][x] = { skin = self.grid[row][x].skin, colour = "X" } - self.grid_age[row][x] = 0 + --self.grid_age[row][x] = 0 end end end - return true + return block_table end function Grid:clearClearedRows() @@ -201,7 +207,7 @@ function Grid:applyBigPiece(piece) y = piece.position.y + offset.y for a = 1, 2 do for b = 1, 2 do - if y*2+a > 0 then + if y*2+a > 0 and y*2 < self.height then self.grid[y*2+a][x*2+b] = { skin = piece.skin, colour = piece.colour @@ -343,7 +349,7 @@ function Grid:markSquares() elseif i == 2 then for j = 0, 3 do for k = 0, 3 do - self.grid[y+j][x+k].colour = "F" + self.grid[y+j][x+k].colour = "W" self.grid[y+j][x+k].skin = "square" end @@ -388,15 +394,16 @@ end function Grid:draw() for y = 5, self.height do for x = 1, self.width do - if self.grid[y][x] ~= empty then + if blocks[self.grid[y][x].skin] and + blocks[self.grid[y][x].skin][self.grid[y][x].colour] then if self.grid_age[y][x] < 2 then love.graphics.setColor(1, 1, 1, 1) love.graphics.draw(blocks[self.grid[y][x].skin]["F"], 48+x*16, y*16) else - if self.grid[y][x].skin == "bone" then + if self.grid[y][x].colour == "X" then + love.graphics.setColor(0, 0, 0, 0) + elseif self.grid[y][x].skin == "bone" then love.graphics.setColor(1, 1, 1, 1) - elseif self.grid[y][x].colour == "X" then - love.graphics.setColor(0.5, 0.5, 0.5, 1 - self.grid_age[y][x] / 15) else love.graphics.setColor(0.5, 0.5, 0.5, 1) end @@ -405,11 +412,11 @@ function Grid:draw() if self.grid[y][x].skin ~= "bone" and self.grid[y][x].colour ~= "X" then love.graphics.setColor(0.8, 0.8, 0.8, 1) love.graphics.setLineWidth(1) - if y > 1 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then + if y > 5 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16) end if y < self.height and self.grid[y+1][x] == empty or - (y + 1 > self.height or self.grid[y+1][x].colour == "X") then + (y + 1 <= self.height and self.grid[y+1][x].colour == "X") then love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16) end if x > 1 and self.grid[y][x-1] == empty then @@ -427,18 +434,14 @@ end function Grid:drawOutline() for y = 5, self.height do for x = 1, self.width do - if self.grid[y][x].colour == "X" then - love.graphics.setColor(0.5, 0.5, 0.5, 1 - self.grid_age[y][x] / 15) - love.graphics.draw(blocks[self.grid[y][x].skin][self.grid[y][x].colour], 48+x*16, y*16) - end if self.grid[y][x] ~= empty and self.grid[y][x].colour ~= "X" then love.graphics.setColor(0.8, 0.8, 0.8, 1) love.graphics.setLineWidth(1) - if y > 1 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then + if y > 5 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16) end if y < self.height and self.grid[y+1][x] == empty or - (y + 1 > self.height or self.grid[y+1][x].colour == "X") then + (y + 1 <= self.height and self.grid[y+1][x].colour == "X") then love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16) end if x > 1 and self.grid[y][x-1] == empty then @@ -459,7 +462,7 @@ function Grid:drawInvisible(opacity_function, garbage_opacity_function, lock_fla for x = 1, self.width do if self.grid[y][x] ~= empty then if self.grid[y][x].colour == "X" then - opacity = 1 - self.grid_age[y][x] / 15 + opacity = 0 elseif garbage_opacity_function and self.grid[y][x].colour == "A" then opacity = garbage_opacity_function(self.grid_age[y][x]) else @@ -471,11 +474,11 @@ function Grid:drawInvisible(opacity_function, garbage_opacity_function, lock_fla if opacity > 0 and self.grid[y][x].colour ~= "X" then love.graphics.setColor(0.64, 0.64, 0.64) love.graphics.setLineWidth(1) - if y > 1 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then + if y > 5 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16) end if y < self.height and self.grid[y+1][x] == empty or - (y + 1 > self.height or self.grid[y+1][x].colour == "X") then + (y + 1 <= self.height and self.grid[y+1][x].colour == "X") then love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16) end if x > 1 and self.grid[y][x-1] == empty then @@ -506,18 +509,18 @@ function Grid:drawCustom(colour_function, gamestate) if block ~= empty then local R, G, B, A, outline = colour_function(gamestate, block, x, y, self.grid_age[y][x]) if self.grid[y][x].colour == "X" then - A = 1 - self.grid_age[y][x] / 15 + A = 0 end love.graphics.setColor(R, G, B, A) love.graphics.draw(blocks[self.grid[y][x].skin][self.grid[y][x].colour], 48+x*16, y*16) if outline > 0 and self.grid[y][x].colour ~= "X" then love.graphics.setColor(0.64, 0.64, 0.64, outline) love.graphics.setLineWidth(1) - if y > 1 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then + if y > 5 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16) end if y < self.height and self.grid[y+1][x] == empty or - (y + 1 > self.height or self.grid[y+1][x].colour == "X") then + (y + 1 <= self.height and self.grid[y+1][x].colour == "X") then love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16) end if x > 1 and self.grid[y][x-1] == empty then diff --git a/tetris/components/piece.lua b/tetris/components/piece.lua index 3b6b34a..9be8743 100644 --- a/tetris/components/piece.lua +++ b/tetris/components/piece.lua @@ -104,9 +104,8 @@ function Piece:dropToBottom(grid) self:dropSquares(math.huge, grid) self.gravity = 0 if self.position.y > piece_y then - -- if it got dropped any, also reset lock delay if self.ghost == false then playSE("bottom") end - self.lock_delay = 0 + -- self.lock_delay = 0 end return self end @@ -118,19 +117,37 @@ function Piece:lockIfBottomed(grid) return self end -function Piece:addGravity(gravity, grid) +function Piece:addGravity(gravity, grid, classic_lock) + gravity = gravity / (self.big and 2 or 1) local new_gravity = self.gravity + gravity if self:isDropBlocked(grid) then - self.gravity = math.min(1, new_gravity) - self.lock_delay = self.lock_delay + 1 - else - local dropped_squares = math.floor(new_gravity) - local new_frac_gravity = new_gravity - dropped_squares - self.gravity = new_frac_gravity - self:dropSquares(dropped_squares, grid) - if self:isDropBlocked(grid) then - playSE("bottom") + if classic_lock then + self.gravity = new_gravity + else + self.gravity = 0 + self.lock_delay = self.lock_delay + 1 end + elseif not ( + self:isMoveBlocked(grid, { x=0, y=-1 }) and gravity < 0 + ) then + local dropped_squares = math.floor(math.abs(new_gravity)) + if gravity >= 0 then + local new_frac_gravity = new_gravity - dropped_squares + self.gravity = new_frac_gravity + self:dropSquares(dropped_squares, grid) + if self:isDropBlocked(grid) then + playSE("bottom") + end + else + local new_frac_gravity = new_gravity + dropped_squares + self.gravity = new_frac_gravity + self:moveInGrid({ x=0, y=-1 }, dropped_squares, grid) + if self:isMoveBlocked(grid, { x=0, y=-1 }) then + playSE("bottom") + end + end + else + self.gravity = 0 end return self end