1072 lines
29 KiB
Lua
1072 lines
29 KiB
Lua
local Object = require 'libs.classic'
|
|
require 'funcs'
|
|
|
|
local playedReadySE = false
|
|
local playedGoSE = false
|
|
|
|
local Grid = require 'tetris.components.grid'
|
|
local Randomizer = require 'tetris.randomizers.randomizer'
|
|
local BagRandomizer = require 'tetris.randomizers.bag'
|
|
local binser = require 'libs.binser'
|
|
|
|
local GameMode = Object:extend()
|
|
|
|
GameMode.name = ""
|
|
GameMode.hash = ""
|
|
GameMode.tagline = ""
|
|
GameMode.rollOpacityFunction = function(age) return 0 end
|
|
|
|
function GameMode:new(secret_inputs)
|
|
self.grid = Grid(10, 24)
|
|
self.randomizer = Randomizer()
|
|
self.piece = nil
|
|
self.ready_frames = 100
|
|
self.frames = 0
|
|
self.game_over_frames = 0
|
|
self.score = 0
|
|
self.level = 0
|
|
self.lines = 0
|
|
self.squares = 0
|
|
self.drop_bonus = 0
|
|
self.are = 0
|
|
self.lcd = 0
|
|
self.das = { direction = "none", frames = -1 }
|
|
self.move = "none"
|
|
self.prev_inputs = {}
|
|
self.next_queue = {}
|
|
self.game_over = false
|
|
self.clear = false
|
|
self.completed = false
|
|
-- configurable parameters
|
|
self.lock_drop = false
|
|
self.lock_hard_drop = false
|
|
self.instant_hard_drop = false
|
|
self.instant_soft_drop = true
|
|
self.enable_hold = false
|
|
self.enable_hard_drop = true
|
|
self.next_queue_length = 1
|
|
self.additive_gravity = true
|
|
self.classic_lock = false
|
|
self.draw_section_times = false
|
|
self.draw_secondary_section_times = false
|
|
self.big_mode = false
|
|
self.irs = true
|
|
self.ihs = true
|
|
self.square_mode = false
|
|
self.immobile_spin_bonus = false
|
|
self.rpc_details = "In game"
|
|
self.SGnames = {
|
|
"9", "8", "7", "6", "5", "4", "3", "2", "1",
|
|
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
|
|
"GM"
|
|
}
|
|
-- variables related to configurable parameters
|
|
self.drop_locked = false
|
|
self.hard_drop_locked = false
|
|
self.lock_on_soft_drop = false
|
|
self.lock_on_hard_drop = false
|
|
self.cleared_block_table = {}
|
|
self.last_lcd = 0
|
|
self.used_randomizer = nil
|
|
self.hold_queue = nil
|
|
self.held = false
|
|
self.section_start_time = 0
|
|
self.section_times = { [0] = 0 }
|
|
self.secondary_section_times = { [0] = 0 }
|
|
self.replay_inputs = {}
|
|
self.replay_pieces = {}
|
|
end
|
|
|
|
function GameMode:getARR() return 1 end
|
|
function GameMode:getDropSpeed() return 1 end
|
|
function GameMode:getARE() return 25 end
|
|
function GameMode:getLineARE() return 25 end
|
|
function GameMode:getLockDelay() return 30 end
|
|
function GameMode:getLineClearDelay() return 40 end
|
|
function GameMode:getDasLimit() return 15 end
|
|
function GameMode:getDasCutDelay() return 0 end
|
|
function GameMode:getGravity() return 1/64 end
|
|
|
|
function GameMode:getNextPiece(ruleset)
|
|
local shape = self.used_randomizer:nextPiece()
|
|
self.replay_pieces[#self.replay_pieces + 1] = shape
|
|
return {
|
|
skin = self:getSkin(),
|
|
shape = shape,
|
|
orientation = ruleset:getDefaultOrientation(shape),
|
|
}
|
|
end
|
|
|
|
function GameMode:getSkin()
|
|
return "2tie"
|
|
end
|
|
|
|
function GameMode:initialize(ruleset)
|
|
local dummy_entry = {}
|
|
dummy_entry["inputs"] = {}
|
|
dummy_entry["frames"] = 0
|
|
self.replay_inputs[#self.replay_inputs + 1] = dummy_entry
|
|
-- generate next queue
|
|
self.used_randomizer = (
|
|
table.equalvalues(
|
|
table.keys(ruleset.colourscheme),
|
|
self.randomizer.possible_pieces
|
|
) and
|
|
self.randomizer or BagRandomizer(table.keys(ruleset.colourscheme))
|
|
)
|
|
self.ruleset = ruleset
|
|
for i = 1, math.max(self.next_queue_length, 1) do
|
|
table.insert(self.next_queue, self:getNextPiece(ruleset))
|
|
end
|
|
self.lock_on_soft_drop = ({ruleset.softdrop_lock, self.instant_soft_drop, false, true })[config.gamesettings.manlock]
|
|
self.lock_on_hard_drop = ({ruleset.harddrop_lock, self.instant_hard_drop, true, false})[config.gamesettings.manlock]
|
|
end
|
|
|
|
function GameMode:update(inputs, ruleset)
|
|
if self.game_over or self.completed then
|
|
self.game_over_frames = self.game_over_frames + 1
|
|
return
|
|
end
|
|
|
|
if config.gamesettings.diagonal_input == 2 then
|
|
if inputs["left"] or inputs["right"] then
|
|
inputs["up"] = false
|
|
inputs["down"] = false
|
|
elseif inputs["down"] then
|
|
inputs["up"] = false
|
|
end
|
|
end
|
|
|
|
-- check if inputs have changed since last frame
|
|
if self.prev_inputs["left"] ~= inputs["left"] or self.prev_inputs["right"] ~= inputs["right"]
|
|
or self.prev_inputs["down"] ~= inputs["down"] or self.prev_inputs["up"] ~= inputs["up"]
|
|
or self.prev_inputs["rotate_left"] ~= inputs["rotate_left"] or self.prev_inputs["rotate_right"] ~= inputs["rotate_right"]
|
|
or self.prev_inputs["hold"] ~= inputs["hold"] or self.prev_inputs["rotate_180"] ~= inputs["rotate_180"]
|
|
or self.prev_inputs["rotate_left2"] ~= inputs["rotate_left2"] or self.prev_inputs["rotate_right2"] ~= inputs["rotate_right2"] then
|
|
-- insert new inputs into replay inputs table
|
|
local new_inputs = {}
|
|
new_inputs["inputs"] = inputs
|
|
new_inputs["frames"] = 0
|
|
self.replay_inputs[#self.replay_inputs + 1] = new_inputs
|
|
else
|
|
-- add 1 to input frame counter
|
|
self.replay_inputs[#self.replay_inputs]["frames"] = self.replay_inputs[#self.replay_inputs]["frames"] + 1
|
|
end
|
|
|
|
-- advance one frame
|
|
if self:advanceOneFrame(inputs, ruleset) == false then return end
|
|
|
|
self:chargeDAS(inputs, self:getDasLimit(), self:getARR())
|
|
|
|
-- set attempt flags
|
|
if inputs["left"] or inputs["right"] then self:onAttemptPieceMove(self.piece, self.grid) end
|
|
if (
|
|
inputs["rotate_left"] or inputs["rotate_right"] or
|
|
inputs["rotate_left2"] or inputs["rotate_right2"] or
|
|
inputs["rotate_180"]
|
|
) then
|
|
self:onAttemptPieceRotate(self.piece, self.grid)
|
|
end
|
|
|
|
if self.piece == nil then
|
|
self:processDelays(inputs, ruleset)
|
|
else
|
|
-- perform active frame actions such as fading out the next queue
|
|
self:whilePieceActive()
|
|
|
|
if self.enable_hold and inputs["hold"] == true and self.held == false and self.prev_inputs["hold"] == false then
|
|
self:hold(inputs, ruleset)
|
|
self.prev_inputs = inputs
|
|
return
|
|
end
|
|
|
|
if (self.lock_drop or (
|
|
not ruleset.are or self:getARE() == 0
|
|
)) and inputs["down"] ~= true then
|
|
self.drop_locked = false
|
|
end
|
|
|
|
if (self.lock_hard_drop or (
|
|
not ruleset.are or self:getARE() == 0
|
|
)) and inputs["up"] ~= true then
|
|
self.hard_drop_locked = false
|
|
end
|
|
|
|
-- diff vars to use in checks
|
|
local piece_y = self.piece.position.y
|
|
local piece_x = self.piece.position.x
|
|
local piece_rot = self.piece.rotation
|
|
|
|
ruleset:processPiece(
|
|
inputs, self.piece, self.grid, self:getGravity(), self.prev_inputs,
|
|
(
|
|
inputs.up and self.lock_on_hard_drop and not self.hard_drop_locked
|
|
) and "none" or self.move,
|
|
self:getLockDelay(), self:getDropSpeed(),
|
|
self.drop_locked, self.hard_drop_locked,
|
|
self.enable_hard_drop, self.additive_gravity, self.classic_lock
|
|
)
|
|
|
|
local piece_dy = self.piece.position.y - piece_y
|
|
local piece_dx = self.piece.position.x - piece_x
|
|
local piece_drot = self.piece.rotation - piece_rot
|
|
|
|
-- das cut
|
|
if (
|
|
(piece_dy ~= 0 and (inputs.up or inputs.down)) or
|
|
(piece_drot ~= 0 and (
|
|
inputs.rotate_left or inputs.rotate_right or
|
|
inputs.rotate_left2 or inputs.rotate_right2 or
|
|
inputs.rotate_180
|
|
))
|
|
) then
|
|
self:dasCut()
|
|
end
|
|
|
|
if (piece_dx ~= 0) then
|
|
self.piece.last_rotated = false
|
|
self:onPieceMove(self.piece, self.grid, piece_dx)
|
|
end
|
|
if (piece_dy ~= 0) then
|
|
self.piece.last_rotated = false
|
|
self:onPieceDrop(self.piece, self.grid, piece_dy)
|
|
end
|
|
if (piece_drot ~= 0) then
|
|
self.piece.last_rotated = true
|
|
self:onPieceRotate(self.piece, self.grid, piece_drot)
|
|
end
|
|
|
|
if inputs["up"] == true and
|
|
self.piece:isDropBlocked(self.grid) and
|
|
not self.hard_drop_locked then
|
|
self:onHardDrop(piece_dy)
|
|
if self.lock_on_hard_drop then
|
|
self.piece_hard_dropped = true
|
|
self.piece.locked = true
|
|
end
|
|
end
|
|
|
|
if inputs["down"] == true then
|
|
if not (
|
|
self.piece:isDropBlocked(self.grid) and
|
|
piece_drot ~= 0
|
|
) then
|
|
self:onSoftDrop(piece_dy)
|
|
end
|
|
if self.piece:isDropBlocked(self.grid) and
|
|
not self.drop_locked and
|
|
self.lock_on_soft_drop
|
|
then
|
|
self.piece.locked = true
|
|
self.piece_soft_locked = true
|
|
end
|
|
end
|
|
|
|
if self.piece.locked == true then
|
|
-- spin detection, immobile only for now
|
|
if self.immobile_spin_bonus and
|
|
self.piece.last_rotated and (
|
|
self.piece:isDropBlocked(self.grid) and
|
|
self.piece:isMoveBlocked(self.grid, { x=-1, y=0 }) and
|
|
self.piece:isMoveBlocked(self.grid, { x=1, y=0 }) and
|
|
self.piece:isMoveBlocked(self.grid, { x=0, y=-1 })
|
|
) then
|
|
self.piece.spin = true
|
|
end
|
|
|
|
self.grid:applyPiece(self.piece)
|
|
|
|
-- mark squares (can be overridden)
|
|
if self.square_mode then
|
|
self.squares = self.squares + self.grid:markSquares()
|
|
end
|
|
|
|
local cleared_row_count = self.grid:getClearedRowCount()
|
|
self:onPieceLock(self.piece, cleared_row_count)
|
|
self:updateScore(self.level, self.drop_bonus, cleared_row_count)
|
|
|
|
self.cleared_block_table = self.grid:markClearedRows()
|
|
self.piece = nil
|
|
if self.enable_hold then
|
|
self.held = false
|
|
end
|
|
|
|
if cleared_row_count > 0 then
|
|
local row_count_names = {"single","double","triple","quad"}
|
|
playSE("erase",row_count_names[cleared_row_count] or "quad")
|
|
self.lcd = self:getLineClearDelay()
|
|
self.last_lcd = self.lcd
|
|
self.are = (
|
|
ruleset.are and self:getLineARE() or 0
|
|
)
|
|
if self.lcd == 0 then
|
|
self.grid:clearClearedRows()
|
|
self:afterLineClear(cleared_row_count)
|
|
if self.are == 0 then
|
|
self:initializeOrHold(inputs, ruleset)
|
|
end
|
|
end
|
|
self:onLineClear(cleared_row_count)
|
|
else
|
|
if self:getARE() == 0 or not ruleset.are then
|
|
self:initializeOrHold(inputs, ruleset)
|
|
else
|
|
self.are = self:getARE()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
self.prev_inputs = inputs
|
|
end
|
|
|
|
function GameMode:updateScore() end
|
|
|
|
function GameMode:advanceOneFrame()
|
|
if self.clear then
|
|
self.completed = true
|
|
elseif self.ready_frames == 0 then
|
|
self.frames = self.frames + 1
|
|
end
|
|
end
|
|
|
|
-- event functions
|
|
function GameMode:whilePieceActive() end
|
|
function GameMode:onAttemptPieceMove(piece, grid) end
|
|
function GameMode:onAttemptPieceRotate(piece, grid) end
|
|
function GameMode:onPieceMove(piece, grid, dx) end
|
|
function GameMode:onPieceRotate(piece, grid, drot) end
|
|
function GameMode:onPieceDrop(piece, grid, dy) end
|
|
function GameMode:onPieceLock(piece, cleared_row_count)
|
|
playSE("lock")
|
|
end
|
|
|
|
function GameMode:onLineClear(cleared_row_count) end
|
|
function GameMode:afterLineClear(cleared_row_count) end
|
|
|
|
function GameMode:onPieceEnter() end
|
|
function GameMode:onHold() end
|
|
|
|
function GameMode:onSoftDrop(dropped_row_count)
|
|
self.drop_bonus = self.drop_bonus + (
|
|
(self.piece.big and 2 or 1) * dropped_row_count
|
|
)
|
|
end
|
|
|
|
function GameMode:onHardDrop(dropped_row_count)
|
|
self:onSoftDrop(dropped_row_count * 2)
|
|
end
|
|
|
|
function GameMode:onGameOver()
|
|
switchBGM(nil)
|
|
local alpha = 0
|
|
local animation_length = 120
|
|
if self.game_over_frames == 1 then
|
|
alpha = 1
|
|
-- Save replay.
|
|
local replay = {}
|
|
replay["inputs"] = self.replay_inputs
|
|
replay["pieces"] = self.replay_pieces
|
|
replay["mode"] = self.name
|
|
replay["ruleset"] = self.ruleset.name
|
|
replay["timer"] = self.frames
|
|
replay["score"] = self.score
|
|
replay["level"] = self.level
|
|
replay["lines"] = self.lines
|
|
replay["gamesettings"] = config.gamesettings
|
|
replay["timestamp"] = os.time()
|
|
if love.filesystem.getInfo("replays") == nil then
|
|
love.filesystem.createDirectory("replays")
|
|
end
|
|
local replay_files = love.filesystem.getDirectoryItems("replays")
|
|
-- Select replay filename that doesn't collide with an existing one
|
|
local replay_number = 0
|
|
local collision = true
|
|
while collision do
|
|
collision = false
|
|
replay_number = replay_number + 1
|
|
for key, file in pairs(replay_files) do
|
|
if file == replay_number..".rply" then
|
|
collision = true
|
|
break
|
|
end
|
|
end
|
|
end
|
|
love.filesystem.write("replays/"..replay_number..".rply", binser.serialize(replay))
|
|
elseif self.game_over_frames < animation_length then
|
|
-- Show field for a bit, then fade out.
|
|
alpha = math.pow(2048, self.game_over_frames/animation_length - 1)
|
|
elseif self.game_over_frames < 2 * animation_length then
|
|
-- Keep field hidden for a short time, then pop it back in (for screenshots).
|
|
alpha = 1
|
|
end
|
|
love.graphics.setColor(0, 0, 0, alpha)
|
|
love.graphics.rectangle(
|
|
"fill", 64, 80,
|
|
16 * self.grid.width, 16 * (self.grid.height - 4)
|
|
)
|
|
end
|
|
|
|
function GameMode:onGameComplete()
|
|
self:onGameOver()
|
|
end
|
|
|
|
function GameMode:onExit() end
|
|
|
|
-- DAS functions
|
|
|
|
function GameMode:startRightDAS()
|
|
self.move = "right"
|
|
self.das = { direction = "right", frames = 0 }
|
|
if self:getDasLimit() == 0 then
|
|
self:continueDAS()
|
|
end
|
|
end
|
|
|
|
function GameMode:startLeftDAS()
|
|
self.move = "left"
|
|
self.das = { direction = "left", frames = 0 }
|
|
if self:getDasLimit() == 0 then
|
|
self:continueDAS()
|
|
end
|
|
end
|
|
|
|
function GameMode:continueDAS()
|
|
local das_frames = self.das.frames + 1
|
|
if das_frames >= self:getDasLimit() then
|
|
if self.das.direction == "left" then
|
|
self.move = (self:getARR() == 0 and "speed" or "") .. "left"
|
|
self.das.frames = self:getDasLimit() - self:getARR()
|
|
elseif self.das.direction == "right" then
|
|
self.move = (self:getARR() == 0 and "speed" or "") .. "right"
|
|
self.das.frames = self:getDasLimit() - self:getARR()
|
|
end
|
|
else
|
|
self.move = "none"
|
|
self.das.frames = das_frames
|
|
end
|
|
end
|
|
|
|
function GameMode:stopDAS()
|
|
self.move = "none"
|
|
self.das = { direction = "none", frames = -1 }
|
|
end
|
|
|
|
function GameMode:chargeDAS(inputs)
|
|
if config.gamesettings.das_last_key == 2 then
|
|
if inputs["right"] == true and self.das.direction ~= "right" and not self.prev_inputs["right"] then
|
|
self:startRightDAS()
|
|
elseif inputs["left"] == true and self.das.direction ~= "left" and not self.prev_inputs["left"] then
|
|
self:startLeftDAS()
|
|
elseif inputs[self.das.direction] == true then
|
|
self:continueDAS()
|
|
else
|
|
self:stopDAS()
|
|
end
|
|
else -- default behaviour, das first key pressed
|
|
if inputs[self.das.direction] == true then
|
|
self:continueDAS()
|
|
elseif inputs["right"] == true then
|
|
self:startRightDAS()
|
|
elseif inputs["left"] == true then
|
|
self:startLeftDAS()
|
|
else
|
|
self:stopDAS()
|
|
end
|
|
end
|
|
end
|
|
|
|
function GameMode:dasCut()
|
|
self.das.frames = math.max(
|
|
self.das.frames - self:getDasCutDelay(),
|
|
-(self:getDasCutDelay() + 1)
|
|
)
|
|
end
|
|
|
|
function GameMode:areCancel(inputs, ruleset)
|
|
if ruleset.are_cancel and strTrueValues(inputs) ~= "" and
|
|
not self.prev_inputs.up and
|
|
(self.piece_hard_dropped or
|
|
(self.piece_soft_locked and not self.prev_inputs.down)) then
|
|
self.lcd = 0
|
|
self.are = 0
|
|
end
|
|
end
|
|
|
|
function GameMode:checkBufferedInputs(inputs)
|
|
if (
|
|
config.gamesettings.buffer_lock ~= 1 and
|
|
not self.prev_inputs["up"] and inputs["up"] and
|
|
self.enable_hard_drop
|
|
) then
|
|
self.buffer_hard_drop = true
|
|
end
|
|
if (
|
|
config.gamesettings.buffer_lock ~= 1 and
|
|
not self.prev_inputs["down"] and inputs["down"]
|
|
) then
|
|
self.buffer_soft_drop = true
|
|
end
|
|
end
|
|
|
|
function GameMode:processDelays(inputs, ruleset, drop_speed)
|
|
if self.ready_frames == 100 then
|
|
playedReadySE = false
|
|
playedGoSE = false
|
|
end
|
|
if self.ready_frames > 0 then
|
|
self:checkBufferedInputs(inputs)
|
|
if not playedReadySE then
|
|
playedReadySE = true
|
|
playSEOnce("ready")
|
|
end
|
|
self.ready_frames = self.ready_frames - 1
|
|
if self.ready_frames == 50 and not playedGoSE then
|
|
playedGoSE = true
|
|
playSEOnce("go")
|
|
end
|
|
if self.ready_frames == 0 then
|
|
self:initializeOrHold(inputs, ruleset)
|
|
end
|
|
elseif self.lcd > 0 then
|
|
self:checkBufferedInputs(inputs)
|
|
self.lcd = self.lcd - 1
|
|
self:areCancel(inputs, ruleset)
|
|
if self.lcd == 0 then
|
|
local cleared_row_count = self.grid:getClearedRowCount()
|
|
self.grid:clearClearedRows()
|
|
self:afterLineClear(cleared_row_count)
|
|
playSE("fall")
|
|
if self.are == 0 then
|
|
self:initializeOrHold(inputs, ruleset)
|
|
end
|
|
end
|
|
elseif self.are > 0 then
|
|
self:checkBufferedInputs(inputs)
|
|
self.are = self.are - 1
|
|
self:areCancel(inputs, ruleset)
|
|
if self.are == 0 then
|
|
self:initializeOrHold(inputs, ruleset)
|
|
end
|
|
end
|
|
end
|
|
|
|
function GameMode:initializeOrHold(inputs, ruleset)
|
|
if (
|
|
(self.frames == 0 or (ruleset.are and self:getARE() ~= 0))
|
|
and self.ihs or false
|
|
) and self.enable_hold and inputs["hold"] == true then
|
|
self:hold(inputs, ruleset, true)
|
|
else
|
|
self:initializeNextPiece(inputs, ruleset, self.next_queue[1])
|
|
end
|
|
self:onPieceEnter()
|
|
self:onEnterOrHold(inputs, ruleset)
|
|
end
|
|
|
|
function GameMode:hold(inputs, ruleset, ihs)
|
|
local data = copy(self.hold_queue)
|
|
if self.piece == nil then
|
|
self.hold_queue = self.next_queue[1]
|
|
table.remove(self.next_queue, 1)
|
|
table.insert(self.next_queue, self:getNextPiece(ruleset))
|
|
else
|
|
self.hold_queue = {
|
|
skin = self.piece.skin,
|
|
shape = self.piece.shape,
|
|
orientation = ruleset:getDefaultOrientation(self.piece.shape),
|
|
}
|
|
end
|
|
if data == nil then
|
|
self:initializeNextPiece(inputs, ruleset, self.next_queue[1])
|
|
else
|
|
self:initializeNextPiece(inputs, ruleset, data, false)
|
|
end
|
|
self.held = true
|
|
self:onHold()
|
|
if ihs then
|
|
playSE("ihs")
|
|
else
|
|
playSE("hold")
|
|
self:onEnterOrHold(inputs, ruleset)
|
|
end
|
|
end
|
|
|
|
function GameMode:onEnterOrHold(inputs, ruleset)
|
|
if not self.grid:canPlacePiece(self.piece) then
|
|
self.game_over = true
|
|
return
|
|
elseif self.piece:isDropBlocked(self.grid) then
|
|
playSE("bottom")
|
|
end
|
|
ruleset:dropPiece(
|
|
inputs, self.piece, self.grid, self:getGravity(),
|
|
self:getDropSpeed(), self.drop_locked, self.hard_drop_locked
|
|
)
|
|
end
|
|
|
|
function GameMode:initializeNextPiece(
|
|
inputs, ruleset, piece_data, generate_next_piece
|
|
)
|
|
if not self.buffer_soft_drop and self.lock_drop or (
|
|
not ruleset.are or self:getARE() == 0
|
|
) then
|
|
self.drop_locked = true
|
|
end
|
|
if not self.buffer_hard_drop and self.lock_hard_drop or (
|
|
not ruleset.are or self:getARE() == 0
|
|
) then
|
|
self.hard_drop_locked = true
|
|
end
|
|
self.piece = ruleset:initializePiece(
|
|
inputs, piece_data, self.grid, self:getGravity(),
|
|
self.prev_inputs, self.move,
|
|
self:getLockDelay(), self:getDropSpeed(),
|
|
self.drop_locked, self.hard_drop_locked, self.big_mode,
|
|
(
|
|
self.frames == 0 or (ruleset.are and self:getARE() ~= 0)
|
|
) and self.irs or false
|
|
)
|
|
if config.gamesettings.buffer_lock == 3 then
|
|
if self.buffer_hard_drop then
|
|
local prev_y = self.piece.position.y
|
|
self.piece:dropToBottom(self.grid)
|
|
self.piece.locked = self.lock_on_hard_drop
|
|
self:onHardDrop(self.piece.position.y - prev_y)
|
|
end
|
|
if (
|
|
self.buffer_soft_drop and
|
|
self.lock_on_soft_drop and
|
|
self:getGravity() >= self.grid.height - 4
|
|
) then
|
|
self.piece.locked = true
|
|
end
|
|
end
|
|
self.piece_hard_dropped = false
|
|
self.piece_soft_locked = false
|
|
self.buffer_hard_drop = false
|
|
self.buffer_soft_drop = false
|
|
if generate_next_piece == nil then
|
|
table.remove(self.next_queue, 1)
|
|
table.insert(self.next_queue, self:getNextPiece(ruleset))
|
|
end
|
|
self:playNextSound(ruleset)
|
|
end
|
|
|
|
function GameMode:playNextSound(ruleset)
|
|
playSE("blocks", ruleset.next_sounds[self.next_queue[1].shape])
|
|
end
|
|
|
|
function GameMode:getHighScoreData()
|
|
return {
|
|
score = self.score
|
|
}
|
|
end
|
|
|
|
function GameMode:animation(x, y, skin, colour)
|
|
-- Animation progress where 0 = start and 1 = end
|
|
local progress = 1
|
|
if self.last_lcd ~= 0 then
|
|
progress = (self.last_lcd - self.lcd) / self.last_lcd
|
|
end
|
|
-- Convert progress through the animation into an alpha value, with easing
|
|
local alpha = 1 - progress ^ 2
|
|
return {
|
|
1, 1, 1,
|
|
alpha,
|
|
skin, colour,
|
|
48 + x * 16, y * 16
|
|
}
|
|
end
|
|
|
|
function GameMode:canDrawLCA()
|
|
return self.lcd > 0
|
|
end
|
|
|
|
function GameMode:drawLineClearAnimation()
|
|
-- animation function
|
|
-- params: block x, y, skin, colour
|
|
-- returns: table with RGBA, skin, colour, x, y
|
|
|
|
-- Quadratic Fadeout (default)
|
|
--[[
|
|
function animation(x, y, skin, colour)
|
|
-- Animation progress where 0 = start and 1 = end
|
|
local progress = 1
|
|
if self.last_lcd ~= 0 then
|
|
progress = (self.last_lcd - self.lcd) / self.last_lcd
|
|
end
|
|
-- Convert progress through the animation into an alpha value, with easing
|
|
local alpha = 1 - progress ^ 2
|
|
return {
|
|
1, 1, 1,
|
|
alpha,
|
|
skin, colour,
|
|
48 + x * 16, y * 16
|
|
}
|
|
end
|
|
--]]
|
|
|
|
-- Flashy Fadeout
|
|
--[[
|
|
function animation(x, y, skin, colour)
|
|
-- Animation progress where 0 = start and 1 = end
|
|
local progress = 1
|
|
if self.last_lcd ~= 0 then
|
|
progress = (self.last_lcd - self.lcd) / self.last_lcd
|
|
end
|
|
-- Change this number to change "bounciness"
|
|
local bounce = 13
|
|
-- Convert progress through the animation into an alpha value
|
|
local alpha = 0
|
|
-- Cutoff is arbitrary: corresponds to level 500 in Marathon A2
|
|
if self.last_lcd > 25 then
|
|
-- Goes up and down: looks better when animation is long
|
|
alpha = 1 - (bounce * progress^3 - 1.5 * bounce * progress^2 + (0.5 * bounce + 1) * progress)
|
|
else
|
|
-- Always decreasing: looks better when animation is short
|
|
alpha = 1 - progress * progress
|
|
end
|
|
return {
|
|
1, 1, 1,
|
|
alpha,
|
|
skin, colour,
|
|
48 + x * 16, y * 16
|
|
}
|
|
end
|
|
--]]
|
|
|
|
-- Fadeout
|
|
--[[
|
|
function animation(x, y, skin, colour)
|
|
return {
|
|
1, 1, 1,
|
|
-0.25 + 1.25 * (self.lcd / self.last_lcd),
|
|
skin, colour,
|
|
48 + x * 16, y * 16
|
|
}
|
|
end
|
|
--]]
|
|
|
|
-- Flash
|
|
--[[
|
|
function animation(x, y, skin, colour)
|
|
return {
|
|
1, 1, 1,
|
|
self.lcd % 6 < 3 and 1 or 0.25,
|
|
skin, colour,
|
|
48 + x * 16, y * 16
|
|
}
|
|
end
|
|
--]]
|
|
|
|
-- TGM1 pop-out
|
|
--[[
|
|
function animation(x, y, skin, colour)
|
|
local p = 0.5
|
|
local l = (
|
|
(self.last_lcd - self.lcd) / self.last_lcd
|
|
)
|
|
local dx = l * (x - (1 + self.grid.width) / 2)
|
|
local dy = l * (y - (1 + self.grid.height) / 2)
|
|
return {
|
|
1, 1, 1, 1, skin, colour,
|
|
48 + (x + dx) * 16,
|
|
(y + dy) * 16 + (464 / (p - 1)) * l * (p - l)
|
|
}
|
|
end
|
|
--]]
|
|
|
|
for y, row in pairs(self.cleared_block_table) do
|
|
for x, block in pairs(row) do
|
|
local animation_table = self:animation(x, y, block.skin, block.colour)
|
|
love.graphics.setColor(
|
|
animation_table[1], animation_table[2],
|
|
animation_table[3], animation_table[4]
|
|
)
|
|
love.graphics.draw(
|
|
blocks[animation_table[5]][animation_table[6]],
|
|
animation_table[7], animation_table[8]
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
function GameMode:drawPiece()
|
|
if self.piece ~= nil then
|
|
local b = (
|
|
self.classic_lock and
|
|
(
|
|
self.piece:isDropBlocked(self.grid) and
|
|
1 - self.piece.gravity or 1
|
|
) or
|
|
1 - (self.piece.lock_delay / self:getLockDelay())
|
|
)
|
|
self.piece:draw(1, 0.25 + 0.75 * b, self.grid)
|
|
end
|
|
end
|
|
|
|
function GameMode:drawGhostPiece(ruleset)
|
|
if self.piece == nil or not self.grid:canPlacePiece(self.piece) then
|
|
return
|
|
end
|
|
local ghost_piece = self.piece:withOffset({x=0, y=0})
|
|
ghost_piece.ghost = true
|
|
ghost_piece:dropToBottom(self.grid)
|
|
ghost_piece:draw(0.5)
|
|
end
|
|
|
|
function GameMode:drawNextQueue(ruleset)
|
|
local colourscheme
|
|
if table.equalvalues(
|
|
self.used_randomizer.possible_pieces,
|
|
{"I", "J", "L", "O", "S", "T", "Z"}
|
|
) then
|
|
colourscheme = ({ruleset.colourscheme, ColourSchemes.Arika, ColourSchemes.TTC})[config.gamesettings.piece_colour]
|
|
else
|
|
colourscheme = ruleset.colourscheme
|
|
end
|
|
function drawPiece(piece, skin, offsets, pos_x, pos_y)
|
|
for index, offset in pairs(offsets) do
|
|
local x = offset.x + ruleset:getDrawOffset(piece, rotation).x + ruleset.spawn_positions[piece].x
|
|
local y = offset.y + ruleset:getDrawOffset(piece, rotation).y + 4.7
|
|
love.graphics.draw(blocks[skin][colourscheme[piece]], pos_x+x*16, pos_y+y*16)
|
|
end
|
|
end
|
|
for i = 1, self.next_queue_length do
|
|
self:setNextOpacity(i)
|
|
local next_piece = self.next_queue[i].shape
|
|
local skin = self.next_queue[i].skin
|
|
local rotation = self.next_queue[i].orientation
|
|
if config.side_next then -- next at side
|
|
drawPiece(next_piece, skin, ruleset.block_offsets[next_piece][rotation], 192, -16+i*48)
|
|
else -- next at top
|
|
drawPiece(next_piece, skin, ruleset.block_offsets[next_piece][rotation], -16+i*80, -32)
|
|
end
|
|
end
|
|
if self.hold_queue ~= nil and self.enable_hold then
|
|
self:setHoldOpacity()
|
|
drawPiece(
|
|
self.hold_queue.shape,
|
|
self.hold_queue.skin,
|
|
ruleset.block_offsets[self.hold_queue.shape][self.hold_queue.orientation],
|
|
-16, -32
|
|
)
|
|
end
|
|
return false
|
|
end
|
|
|
|
function GameMode:setNextOpacity(i)
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
end
|
|
|
|
function GameMode:setHoldOpacity()
|
|
local colour = self.held and 0.6 or 1
|
|
love.graphics.setColor(colour, colour, colour, 1)
|
|
end
|
|
|
|
function GameMode:getBackground()
|
|
return 0
|
|
end
|
|
|
|
function GameMode:getHighscoreData()
|
|
return {}
|
|
end
|
|
|
|
function GameMode:drawGrid()
|
|
self.grid:draw()
|
|
end
|
|
|
|
function GameMode:drawScoringInfo()
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
love.graphics.setFont(font_3x5_2)
|
|
|
|
if config["side_next"] then
|
|
love.graphics.printf("NEXT", 240, 72, 40, "left")
|
|
else
|
|
love.graphics.printf("NEXT", 64, 40, 40, "left")
|
|
end
|
|
|
|
love.graphics.print(
|
|
self.das.direction .. " " ..
|
|
self.das.frames .. " " ..
|
|
strTrueValues(self.prev_inputs) ..
|
|
self.drop_bonus
|
|
)
|
|
|
|
love.graphics.setFont(font_8x11)
|
|
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")
|
|
end
|
|
|
|
function GameMode:drawSectionTimes(current_section)
|
|
local section_x = 530
|
|
|
|
for section, time in pairs(self.section_times) do
|
|
if section > 0 then
|
|
love.graphics.printf(formatTime(time), section_x, 40 + 20 * section, 90, "left")
|
|
end
|
|
end
|
|
|
|
love.graphics.printf(formatTime(self.frames - self.section_start_time), section_x, 40 + 20 * current_section, 90, "left")
|
|
end
|
|
|
|
function GameMode:sectionColourFunction(section)
|
|
return { 1, 1, 1, 1 }
|
|
end
|
|
|
|
function GameMode:drawSectionTimesWithSecondary(current_section, section_limit)
|
|
section_limit = section_limit or math.huge
|
|
local section_x = 530
|
|
local section_secondary_x = 440
|
|
|
|
for section, time in pairs(self.section_times) do
|
|
if section > 0 then
|
|
love.graphics.printf(formatTime(time), section_x, 40 + 20 * section, 90, "left")
|
|
end
|
|
end
|
|
|
|
for section, time in pairs(self.secondary_section_times) do
|
|
love.graphics.setColor(self:sectionColourFunction(section))
|
|
if section > 0 then
|
|
love.graphics.printf(formatTime(time), section_secondary_x, 40 + 20 * section, 90, "left")
|
|
end
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
end
|
|
|
|
local current_x
|
|
if table.getn(self.section_times) < table.getn(self.secondary_section_times) then
|
|
current_x = section_x
|
|
else
|
|
current_x = section_secondary_x
|
|
end
|
|
|
|
if current_section <= section_limit then
|
|
love.graphics.printf(formatTime(self.frames - self.section_start_time), current_x, 40 + 20 * current_section, 90, "left")
|
|
end
|
|
end
|
|
|
|
function GameMode:drawSectionTimesWithSplits(current_section, section_limit)
|
|
section_limit = section_limit or math.huge
|
|
|
|
local section_x = 440
|
|
local split_x = 530
|
|
|
|
local split_time = 0
|
|
|
|
for section, time in pairs(self.section_times) do
|
|
if section > 0 then
|
|
love.graphics.setColor(self:sectionColourFunction(section))
|
|
love.graphics.printf(formatTime(time), section_x, 40 + 20 * section, 90, "left")
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
split_time = split_time + time
|
|
love.graphics.printf(formatTime(split_time), split_x, 40 + 20 * section, 90, "left")
|
|
end
|
|
end
|
|
|
|
if (current_section <= section_limit) then
|
|
love.graphics.printf(formatTime(self.frames - self.section_start_time), section_x, 40 + 20 * current_section, 90, "left")
|
|
love.graphics.printf(formatTime(self.frames), split_x, 40 + 20 * current_section, 90, "left")
|
|
end
|
|
end
|
|
|
|
function GameMode:drawBackground()
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
love.graphics.draw(
|
|
backgrounds[self:getBackground()],
|
|
0, 0, 0,
|
|
0.5, 0.5
|
|
)
|
|
end
|
|
|
|
function GameMode:drawFrame()
|
|
-- game frame
|
|
if self.grid.width == 10 and self.grid.height == 24 then
|
|
love.graphics.draw(misc_graphics["frame"], 48, 64)
|
|
else
|
|
love.graphics.setColor(174/255, 83/255, 76/255, 1)
|
|
love.graphics.setLineWidth(8)
|
|
love.graphics.line(
|
|
60,76,
|
|
68+16*self.grid.width,76,
|
|
68+16*self.grid.width,84+16*(self.grid.height-4),
|
|
60,84+16*(self.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.grid.width,76,
|
|
68+16*self.grid.width,84+16*(self.grid.height-4),
|
|
60,84+16*(self.grid.height-4),
|
|
60,76
|
|
)
|
|
love.graphics.setLineWidth(1)
|
|
love.graphics.setColor(0, 0, 0, 200)
|
|
love.graphics.rectangle(
|
|
"fill", 64, 80,
|
|
16 * self.grid.width, 16 * (self.grid.height - 4)
|
|
)
|
|
end
|
|
end
|
|
|
|
function GameMode:drawReadyGo()
|
|
-- ready/go graphics
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
|
|
if self.ready_frames <= 100 and self.ready_frames > 52 then
|
|
love.graphics.draw(misc_graphics["ready"], 144 - 50, 240 - 14)
|
|
elseif self.ready_frames <= 50 and self.ready_frames > 2 then
|
|
love.graphics.draw(misc_graphics["go"], 144 - 27, 240 - 14)
|
|
end
|
|
end
|
|
|
|
function GameMode:drawCustom() end
|
|
|
|
function GameMode:drawIfPaused()
|
|
love.graphics.setFont(font_3x5_3)
|
|
love.graphics.printf("GAME PAUSED!", 64, 160, 160, "center")
|
|
end
|
|
|
|
-- transforms specified in here will transform the whole screen
|
|
-- if you want a transform for a particular component, push the
|
|
-- default transform by using love.graphics.push(), do your
|
|
-- transform, and then love.graphics.pop() at the end of that
|
|
-- component's draw call!
|
|
function GameMode:transformScreen() end
|
|
|
|
function GameMode:draw(paused)
|
|
self:transformScreen()
|
|
self:drawBackground()
|
|
self:drawFrame()
|
|
self:drawGrid()
|
|
self:drawPiece()
|
|
if self:canDrawLCA() then
|
|
self:drawLineClearAnimation()
|
|
end
|
|
self:drawNextQueue(self.ruleset)
|
|
self:drawScoringInfo()
|
|
self:drawReadyGo()
|
|
self:drawCustom()
|
|
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
love.graphics.setFont(font_3x5_2)
|
|
if config.gamesettings.display_gamemode == 1 then
|
|
love.graphics.printf(
|
|
self.name .. " - " .. self.ruleset.name,
|
|
0, 460, 640, "left"
|
|
)
|
|
end
|
|
|
|
if paused then
|
|
self:drawIfPaused()
|
|
end
|
|
|
|
if self.completed then
|
|
self:onGameComplete()
|
|
elseif self.game_over then
|
|
self:onGameOver()
|
|
end
|
|
end
|
|
|
|
return GameMode
|