diff --git a/res/bgm/blitz.mp3 b/res/bgm/blitz.mp3 new file mode 100644 index 0000000..b820b80 Binary files /dev/null and b/res/bgm/blitz.mp3 differ diff --git a/res/se/multiplier_up.wav b/res/se/multiplier_up.wav new file mode 100644 index 0000000..68e2a46 Binary files /dev/null and b/res/se/multiplier_up.wav differ diff --git a/tetris/modes/blitz.lua b/tetris/modes/blitz.lua new file mode 100644 index 0000000..fba9e92 --- /dev/null +++ b/tetris/modes/blitz.lua @@ -0,0 +1,448 @@ +--[[ +Blitz Mode +Design by MattMayuga +Spaghetti code by terpyderp +Music from Bejeweled Twist +HSV to RGB code copied from https://gist.github.com/GigsD4X/8513963 because I'm lazy and stupid lol. +]]-- + +require 'funcs' + +local GameMode = require 'tetris.modes.gamemode' +local Piece = require 'tetris.components.piece' + +local Bag7Randomiser = require 'tetris.randomizers.bag7noSZOstart' + +local Blitz = GameMode:extend() + +sounds.blitz = { + multi_up = love.audio.newSource("res/se/multiplier_up.wav", "static") +} + +bgm.blitz = { + blitz = love.audio.newSource("res/bgm/blitz.mp3", "stream"), +} + +Blitz.name = "Blitz" +Blitz.hash = "Blitz" +Blitz.tagline = "How many points can you get in 5 minutes? Do cool tricks, get All Clears, increase your score multiplier and bring your best game to reach higher speed levels and gain more points in this action-packed mode!" + +local scoreTable = { + 250, -- Single + 750, + 1250, + 2000, -- Quadruple + 3750, -- Quintuple + 1000, -- T-Spin Zero + 2000, + 3000, + 4000, -- T-Spin Triple + 250, -- T-Spin Mini Zero + 750, + 1000, -- T-Spin Mini Double + 2000, -- All Clear Single + 3000, + 4500, + 5000, + 6000, -- All Clear Quintuple + 8000, -- B2B AC Quadruple + 9000 -- B2B AC Quintuple +} + +local chainActions = { + 3, 4, 6, 8, 10, 10, 10, 10, 10, 10, 10 +} + +local SRSTPiece = { + { {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=0, y=-1} }, + { {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=1, y=0} }, + { {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=0, y=1} }, + { {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=0} }, +} +local ARSTPiece = { + { {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=0, y=-1} }, + { {x=0, y=-1}, {x=0, y=0}, {x=1, y=-1}, {x=0, y=-2} }, + { {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=-1}, {x=0, y=0} }, + { {x=0, y=-1}, {x=0, y=0}, {x=-1, y=-1}, {x=0, y=-2} }, +} + +function Blitz:new() + Blitz.super:new() + + self.lines = 0 + self.frames = 18000 + self.pieces = 0 + self.roll_frames = 0 + self.upstacked = false + self.activeTime = 0 + self.framesButBackwards = 0 + + -- blitz mode variables + self.score = 0 + self.b2b = 0 + self.b2bMulti = 1 + self.multi = 1 + self.chain = 0 + self.lastLineCount = 4 + self.lastPieceTime = 0 + self.rainbowMode = 0 -- 0 = not started, 1 = active, 2 = finished + self.tSpin = 0 -- 0 = none, 1 = normal, 2 = mini + + self.randomizer = Bag7Randomiser() + self.lock_drop = true + self.lock_hard_drop = true + self.instant_hard_drop = true + self.instant_soft_drop = false + self.enable_hold = true + self.next_queue_length = 5 + self.immobile_spin_bonus = true +end + + +function Blitz:getDropSpeed() return 20 end +function Blitz:getARR() return config.arr end +function Blitz:getARE() return 0 end +function Blitz:getLineARE() return self:getARE() end +function Blitz:getDasLimit() return config.das end +function Blitz:getLineClearDelay() return 0 end +function Blitz:getLockDelay() return 30 end +function Blitz:getGravity() return 1/64 end +function Blitz:getDasCutDelay() return config.dcd end +function Blitz:getBackground() return 3 end + + +function Blitz:advanceOneFrame() + if self.ready_frames == 0 then + if self.frames == 18000 then + switchBGM("blitz", "blitz") + end + end + + if self.clear then + self.roll_frames = self.roll_frames + 1 + if self.roll_frames > 360 then + self.completed = true + end + return false + elseif self.ready_frames == 0 then + self.frames = self.frames - 1 + if self.frames <= 0 then + self.clear = true + end + end + self.framesButBackwards = self.framesButBackwards + 1 + return true +end + + +function Blitz:onPieceLock(piece, cleared_row_count) + self.super:onPieceLock() + self.pieces = self.pieces + 1 + allClear = self.grid:checkForBravo(cleared_row_count) + + + -- detect what kind of T piece the ruleset has (either 0-SRS, 1-ARS, or 2-idk) (pain) + if piece.shape == "T" then + SRS = true + for i,v in ipairs(self.ruleset.block_offsets.T) do -- for every rotation of the T + for ii,vv in ipairs(v) do -- for every block in the T + if #(v) == 4 then + if vv.x ~= SRSTPiece[i][ii].x and vv.y ~= SRSTPiece[i][ii].y then + SRS = false + break + end + else + SRS = false + break + end + end + end + if SRS then + self.rulesetType = 0 + else + ARS = true + for i,v in ipairs(self.ruleset.block_offsets.T) do -- for every rotation of the T + for ii,vv in ipairs(v) do -- for every block in the T + if #(v) == 4 then + if vv.x ~= ARSTPiece[i][ii].x and vv.y ~= ARSTPiece[i][ii].y then + ARS = false + break + end + else + ARS = false + break + end + end + end + if ARS then + self.rulesetType = 1 + else + self.rulesetType = 2 + end + end + end + + -- 4-corner T-Spin detection (also pain) + if self.rulesetType == 2 then -- just be lazy and do immobile if it's not SRS or ARS + if self.piece.spin and self.piece.shape == "T" then + self.tSpin = 1 + else + self.tSpin = 0 + end + else + if self.rulesetType == 0 then tempYOffset = 0 + else tempYOffset = -1 + end + + if piece.shape == "T" and piece.last_rotated then + topCount = 0 + bottomCount = 0 + if self.grid:isOccupied(piece.position.x+1, piece.position.y+1) then + if piece.rotation <= 1 then bottomCount = bottomCount + 1 + else topCount = topCount + 1 + end + end + if self.grid:isOccupied(piece.position.x+1, piece.position.y-1+tempYOffset) then + if piece.rotation <= 1 then topCount = topCount + 1 + else bottomCount = bottomCount + 1 + end + end + if self.grid:isOccupied(piece.position.x-1, piece.position.y+1+tempYOffset) then + if piece.rotation == 1 or piece.rotation == 2 then topCount = topCount + 1 + else bottomCount = bottomCount + 1 + end + end + if self.grid:isOccupied(piece.position.x-1, piece.position.y-1+tempYOffset) then + if piece.rotation == 1 or piece.rotation == 2 then bottomCount = bottomCount + 1 + else topCount = topCount + 1 + end + end + if topCount == 2 and bottomCount >= 1 then + self.tSpin = 1 -- normal + elseif topCount == 1 and bottomCount == 2 then + self.tSpin = 2 -- mini + else + self.tSpin = 0 -- none + end + else + self.tSpin = 0 + end + end + + -- updates the multiplier for back to back + if self.b2b > 0 then self.b2bMulti = 1.5 + else self.b2bMulti = 1 + end + + -- updates the score + -- if it's a T spin + if self.tSpin == 1 then + self.score = self.score + scoreTable[cleared_row_count+6] * self.b2bMulti * self.multi + -- if it's a T spin mini + elseif self.tSpin == 2 then + self.score = self.score + scoreTable[cleared_row_count+10] * self.b2bMulti * self.multi + -- if it's an all clear + elseif self.grid:checkForBravo(cleared_row_count) then + if self.b2b > 0 and cleared_row_count >= 4 then + self.score = self.score + scoreTable[cleared_row_count+14] * self.multi + else + self.score = self.score + scoreTable[cleared_row_count+12] * self.multi + end + -- if it's a quad or quin + elseif cleared_row_count >= 4 then + self.score = self.score + scoreTable[cleared_row_count] * self.b2bMulti * self.multi + -- if it's none of the above and it clears at least one line + elseif cleared_row_count > 0 then + self.score = self.score + scoreTable[cleared_row_count] * self.multi + end + + -- raises the chain count if necessary + -- if it's an all clear + if allClear then + self.chain = self.chain + 12 + -- if at least 4 lines are cleared or it's a T-Spin + elseif self.tSpin ~= 0 or cleared_row_count >= 4 then + if self.b2b > 10 then + self.chain = self.chain + 2 + else + self.chain = self.chain + 1 + end + -- if 3 lines are cleared + elseif cleared_row_count >= 3 then + self.chain = self.chain + 1 + end + + -- decreases the multiplier or chain meter if necessary + if self.lastLineCount < 3 and (cleared_row_count < 3 and cleared_row_count ~= 0) and not allClear then + if self.chain < 1 then + if self.multi > 1 then + self.multi = self.multi - 1 + self.rainbowMode = 0 + end + else + if self.rainbowMode == 2 then + self.rainbowMode = 0 + self.multi = 9 + end + self.chain = 0 + end + elseif self.lastPieceTime >= 180 and self.activeTime >= 180 and self.multi > 1 then + self.multi = self.multi - 1 + end + + -- increases the multiplier if necessary + if self.multi < 10 then + while self.chain >= chainActions[self.multi] do + self.chain = self.chain - chainActions[self.multi] + self.multi = self.multi + 1 + playSE("blitz", "multi_up") + end + elseif self.rainbowMode == 0 then + if self.chain >= chainActions[10] then + self.rainbowMode = 1 + self.chain = self.chain - chainActions[10] + playSE("blitz", "multi_up") + end + elseif self.rainbowMode == 1 then + if self.chain >= chainActions[11] then + self.score = self.score + 300000 + self.chain = 10 + self.rainbowMode = 2 + playSE("blitz", "multi_up") + end + end + + -- updates back to back + if self.tSpin ~= 0 or cleared_row_count >= 4 then + self.b2b = self.b2b + 1 + elseif cleared_row_count > 0 and cleared_row_count < 4 then + self.b2b = 0 + end + + -- print("chain: " .. tostring(self.chain) .. " multiplier: " .. tostring(self.multi) .. " rainbow mode: " .. tostring(self.rainbowMode) .. " lastLineCount: " .. tostring(self.lastLineCount)) + + + -- update some other stuff + self.lastPieceTime = self.activeTime + self.activeTime = 0 + if self.tSpin ~= 0 then + self.lastLineCount = 4 -- if it works it's not stupid right? + elseif cleared_row_count ~= 0 then + self.lastLineCount = cleared_row_count + end + if self.rainbowMode == 2 then + self.chain = chainActions[11] + end +end + + +function Blitz:onLineClear(cleared_row_count) + if not self.clear then + self.lines = self.lines + cleared_row_count + end +end + + +function Blitz:drawGrid(ruleset) + self.grid:draw() + if self.piece ~= nil then + self:drawGhostPiece(ruleset) + end +end + + +function Blitz:getHighscoreData() + return { + level = self.level, + frames = self.frames, + } +end + + +function Blitz:drawScoringInfo() + + love.graphics.setColor(1, 1, 1, 1) + + if config["side_next"] then + love.graphics.printf("NEXT", 240, 72, 40, "left") + else + love.graphics.printf("NEXT", 64, 40, 40, "left") + end + + local text_x = config["side_next"] and 316 or 240 + + love.graphics.setFont(font_3x5_2) + love.graphics.printf("SCORE", text_x, 72, 80, "left") + love.graphics.printf("PPS", text_x, 132, 80, "left") + love.graphics.printf("LINES", text_x, 192, 40, "left") + + love.graphics.setFont(font_3x5_3) + love.graphics.printf(self.score, text_x, 92, 80, "left") + love.graphics.printf(string.format("%.04f", self.pieces / math.max(1, self.framesButBackwards) * 60), text_x, 152, 80, "left") + love.graphics.printf(math.max(0, self.lines), text_x, 212, 40, "left") + + -- draw chain meter + if self.rainbowMode == 1 then + self.red, self.green, self.blue = Blitz:HSVToRGB(((-self.frames+180000)*2)%360, 1, 1) + else + self.red, self.green, self.blue = Blitz:HSVToRGB((-self.multi*36)+395, 1, 1) + end + love.graphics.setColor(self.red/4, self.blue/4, self.green/4) + love.graphics.rectangle("fill", 240, 382, 160, 24) + love.graphics.setColor(self.red, self.blue, self.green) + love.graphics.rectangle("fill", 240, 382, (self.chain/chainActions[self.multi])*160, 24) + love.graphics.setColor(0, 0, 0) + love.graphics.setLineWidth(4) + love.graphics.rectangle("line", 240, 382, 160, 24) + love.graphics.setColor(1, 1, 1, 1) + love.graphics.setFont(font_3x5_2) + love.graphics.printf(tostring(self.chain) .. " / " .. tostring(chainActions[self.multi]), 240, 412, 160, "center") + + -- draw multiplier + love.graphics.setFont(font_3x5_3) + love.graphics.printf("MULTI: X" .. tostring(self.multi), 240, 352, 400, "left") + + love.graphics.setColor(1, 1, 1, 1) +end + + +function Blitz:whilePieceActive() + self.activeTime = self.activeTime + 1 +end + + +-- copied from https://gist.github.com/GigsD4X/8513963 +function Blitz:HSVToRGB( hue, saturation, value ) + -- Returns the RGB equivalent of the given HSV-defined color + -- (adapted from some code found around the web) + + -- If it's achromatic, just return the value + if saturation == 0 then + return value, value, value; + end; + + -- Get the hue sector + local hue_sector = math.floor( hue / 60 ); + local hue_sector_offset = ( hue / 60 ) - hue_sector; + + local p = value * ( 1 - saturation ); + local q = value * ( 1 - saturation * hue_sector_offset ); + local t = value * ( 1 - saturation * ( 1 - hue_sector_offset ) ); + + if hue_sector == 0 then + return value, t, p; + elseif hue_sector == 1 then + return q, value, p; + elseif hue_sector == 2 then + return p, value, t; + elseif hue_sector == 3 then + return p, q, value; + elseif hue_sector == 4 then + return t, p, value; + elseif hue_sector == 5 then + return value, p, q; + end; +end; + + +return Blitz