diff --git a/res/bgm/mus_x_undyne.mp3 b/res/bgm/mus_x_undyne.mp3 new file mode 100644 index 0000000..0ec5be2 Binary files /dev/null and b/res/bgm/mus_x_undyne.mp3 differ diff --git a/res/se/0000299c.wav b/res/se/0000299c.wav new file mode 100644 index 0000000..f036b8b Binary files /dev/null and b/res/se/0000299c.wav differ diff --git a/res/se/000029aa.wav b/res/se/000029aa.wav new file mode 100644 index 0000000..8a58733 Binary files /dev/null and b/res/se/000029aa.wav differ diff --git a/res/se/000029c3.wav b/res/se/000029c3.wav new file mode 100644 index 0000000..9b00a9e Binary files /dev/null and b/res/se/000029c3.wav differ diff --git a/tetris/modes/hero.lua b/tetris/modes/hero.lua new file mode 100644 index 0000000..211927d --- /dev/null +++ b/tetris/modes/hero.lua @@ -0,0 +1,294 @@ +local GameMode = require 'tetris.modes.gamemode' + +local HistoryRandomizer = require 'tetris.randomizers.history_6rolls_35bag' + +local TheTrueHero = GameMode:extend() + +bgm.undyne = love.audio.newSource("res/bgm/mus_x_undyne.mp3", "stream") + +sounds.undyne = { + ding = love.audio.newSource("res/se/000029aa.wav", "static"), + pike = love.audio.newSource("res/se/0000299c.wav", "static"), + damage = love.audio.newSource("res/se/000029c3.wav", "static") +} + +TheTrueHero.name = "The True Hero" +TheTrueHero.hash = "TheTrueHero" +TheTrueHero.tagline = "A tribute to the Puzzle Maker." + +function TheTrueHero:new() + self.super:new() + + self.randomizer = HistoryRandomizer() + + self.attacks = { + "PIECE GOAL", + "LINE GOAL", + "GARBAGE", + "HIDDEN PREVIEWS", + "TO THE BEAT", + "INVISIBLE" + } + + self.attack_number = 0 + self.current_attack = 0 + self.var = 0 + + self.section_frames = 0 + self.section_times = { + 385, + 385, + 385, + 386, + 386, + 385, + 381, + 384, + 384, + 384, + 384, + 385, + 385, + 384, + 384, + 288, + 288, + 288, + 288, + 288, + 288, + 288, + 288, + 288, + 288, + 288, + 290, + } + + self.queue_age = 0 + self.grounded_time = 0 + + self.lock_drop = true + self.lock_hard_drop = true + self.enable_hold = true + self.next_queue_length = 5 + + self.irs = false + self.ihs = false +end + +function TheTrueHero:getDropSpeed() + return 20 +end + +function TheTrueHero:getARR() + return config.arr +end + +function TheTrueHero:getARE() + return 0 +end + +function TheTrueHero:getLineARE() + return 0 +end + +function TheTrueHero:getDasLimit() + return config.das +end + +function TheTrueHero:getLineClearDelay() + return 0 +end + +function TheTrueHero:getLockDelay() + return 30 +end + +function TheTrueHero:getGravity() + return self.current_attack == 5 and 20 or 1.1 ^ (self.attack_number - 1) +end + +function TheTrueHero:getDasCutDelay() + return config.dcd +end + +function TheTrueHero:advanceOneFrame(inputs, ruleset) + if self.ready_frames == 0 then + if self.frames == 0 then + switchBGMLoop("undyne") + end + if self.section_frames >= self.section_times[Mod1(self.attack_number, #self.section_times)] or self.frames == 0 then + if (self.current_attack == 1 or self.current_attack == 2) and self.var > 0 then + playSE("undyne", "damage") + self.game_over = true + return false + end + self.section_frames = 0 + self.attack_number = self.attack_number + 1 + local prev_attack = self.current_attack + local attack_rolls = 0 + repeat + attack_rolls = attack_rolls + 1 + if self.attack_number > 20 then + self.current_attack = math.random(#self.attacks) + else + self.current_attack = math.random(4) + end + until prev_attack ~= self.current_attack and ( + self.current_attack ~= 5 or attack_rolls == 3 + ) + if self.current_attack == 1 then + self.var = math.floor(3 + math.random( + math.floor(self.attack_number / 4), + math.floor(self.attack_number / 3) + ) * + self.section_times[Mod1(self.attack_number, #self.section_times)] / 342) + elseif self.current_attack == 2 then + self.var = math.floor(1 + math.random( + math.floor(self.attack_number / 6), + math.floor(self.attack_number / 4) + ) * + self.section_times[Mod1(self.attack_number, #self.section_times)] / 342) + elseif self.current_attack == 3 then + self.var = math.max(10 - math.random( + math.floor(self.attack_number / 8), + math.floor(self.attack_number / 4) + ), 3) + end + end + self.frames = self.frames + 1 + self.section_frames = self.section_frames + 1 + if self.current_attack == 5 then + self.lock_on_soft_drop = false + self.lock_on_hard_drop = false + if self.section_frames % 24 == 0 and self.section_frames >= 25 then + self.piece.locked = true + playSE("undyne", "pike") + end + else + 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 + end + return true +end + +function TheTrueHero:onPieceEnter() + self.queue_age = 0 + self.grounded_time = 0 +end + +function TheTrueHero:onHold() + self.grounded_time = 0 +end + +function TheTrueHero:whilePieceActive() + if self.piece:isDropBlocked(self.grid) then + self.grounded_time = self.grounded_time + 1 + if self.grounded_time >= 120 then + self.piece.locked = true + end + end + self.queue_age = self.queue_age + 1 +end + +function TheTrueHero:onPieceLock(piece, cleared_row_count) + self.super:onPieceLock() + if self.current_attack == 1 or self.current_attack == 3 then + self.var = math.max(self.var - 1, 0) + self:advanceBottomRow(1) + playSE("undyne", "ding") + elseif self.current_attack == 2 then + self.var = math.max(self.var - cleared_row_count, 0) + if cleared_row_count ~= 0 then + playSE("undyne", "ding") + end + end +end + +function TheTrueHero:advanceBottomRow(dx) + if self.var <= 0 and self.current_attack == 3 then + self.grid:copyBottomRow() + self.var = math.max(10 - math.random( + math.floor(self.attack_number / 8), + math.floor(self.attack_number / 4) + ), 3) + playSE("undyne", "pike") + end +end + +function TheTrueHero:setNextOpacity(i) + if self.current_attack == 4 then + local hidden_next_pieces = math.ceil(self.attack_number / 15) + if i < hidden_next_pieces then + love.graphics.setColor(1, 1, 1, 0) + elseif i == hidden_next_pieces then + love.graphics.setColor(1, 1, 1, 1 - math.min(1, self.queue_age / 15)) + else + love.graphics.setColor(1, 1, 1, 1) + end + else + love.graphics.setColor(1, 1, 1, 1) + end +end + +local function invisible(game, block, x, y, age) + return 0.5, 0.5, 0.5, 1 - age / (300 - game.attack_number * 5.5), 0 +end + +function TheTrueHero:drawGrid() + if self.current_attack == 6 then + self.grid:drawCustom(invisible, self) + else + self.grid:draw() + end + self:drawGhostPiece() +end + +function TheTrueHero:drawScoringInfo() + self.super.drawScoringInfo(self) + + love.graphics.setColor(1, 1, 1, 1) + + love.graphics.setFont(font_3x5_2) + love.graphics.printf("ATTACK NUMBER", 240, 150, 200, "left") + love.graphics.printf("ATTACK", 240, 230, 200, "left") + + love.graphics.setFont(font_3x5_3) + if self.attack_number > 0 then + love.graphics.printf(self.attack_number .. " - " .. ( + string.sub(formatTime( + self.section_times[Mod1(self.attack_number, #self.section_times)] - self.section_frames + ), -4, -1) + ), 240, 170, 200, "left") + end + love.graphics.printf( + self.attacks[self.current_attack] or "", + 240, 250, 200, "left" + ) + + love.graphics.setFont(font_3x5_4) + if self.current_attack >= 1 and self.current_attack <= 3 then + love.graphics.setColor( + (not self.game_over and self.frames % 4 < 2) and + ( + self.var == 0 and {0.3, 1, 0.3, 1} or {1, 0.3, 0.3, 1} + ) or {1, 1, 1, 1} + ) + love.graphics.printf(self.var, 240, 280, 200, "left") + love.graphics.setColor(1, 1, 1, 1) + end +end + +function TheTrueHero:getHighscoreData() + return { + frames = self.frames, + } +end + +function TheTrueHero:getBackground() + return math.max(0, self.attack_number - 1) % 20 +end + +return TheTrueHero \ No newline at end of file diff --git a/tetris/modes/marathon_gf.lua b/tetris/modes/marathon_gf.lua index 5de90d4..6a601e7 100644 --- a/tetris/modes/marathon_gf.lua +++ b/tetris/modes/marathon_gf.lua @@ -34,6 +34,7 @@ function MarathonGFGame:getLineARE() return 6 end function MarathonGFGame:getLineClearDelay() return 24 end function MarathonGFGame:getDasLimit() return config.das end function MarathonGFGame:getARR() return config.arr end +function MarathonGFGame:getDasCutDelay() return config.dcd end function MarathonGFGame:getGravity() if self.lines < 180 then diff --git a/tetris/modes/non.lua b/tetris/modes/non.lua index 017452c..8e8aac3 100644 --- a/tetris/modes/non.lua +++ b/tetris/modes/non.lua @@ -36,6 +36,7 @@ function NightOfNights:getLineARE() return 0 end function NightOfNights:getLineClearDelay() return 0 end function NightOfNights:getDasLimit() return config.das end function NightOfNights:getARR() return config.arr end +function NightOfNights:getDasCutDelay() return config.dcd end function NightOfNights:getGravity() return 20 end function NightOfNights:advanceOneFrame() diff --git a/tetris/modes/pacer_test.lua b/tetris/modes/pacer_test.lua index 533993e..a9843b6 100644 --- a/tetris/modes/pacer_test.lua +++ b/tetris/modes/pacer_test.lua @@ -83,13 +83,12 @@ function PacerTest:getGravity() return 1/64 end -function PacerTest:getSection() - return math.floor(level / 100) + 1 +function PacerTest:getDasCutDelay() + return config.dcd end -function PacerTest:onPieceEnter() - self.irs = false - self.ihs = false +function PacerTest:getSection() + return math.floor(level / 100) + 1 end function PacerTest:advanceOneFrame() diff --git a/tetris/modes/pro_tl.lua b/tetris/modes/pro_tl.lua index cf8afe1..6bb73c7 100644 --- a/tetris/modes/pro_tl.lua +++ b/tetris/modes/pro_tl.lua @@ -27,6 +27,7 @@ function ProGame:getLineARE() return 6 end function ProGame:getLineClearDelay() return 6 end function ProGame:getDasLimit() return config.das end function ProGame:getARR() return config.arr end +function ProGame:getDasCutDelay() return config.dcd end function ProGame:getDropSpeed() return 20 end function ProGame:getGravity() diff --git a/tetris/modes/race_40.lua b/tetris/modes/race_40.lua index 082829e..5fadb67 100644 --- a/tetris/modes/race_40.lua +++ b/tetris/modes/race_40.lua @@ -70,6 +70,10 @@ function Race40Game:getGravity() return 1/64 end +function Race40Game:getDasCutDelay() + return config.dcd +end + function Race40Game:advanceOneFrame() if self.clear then self.roll_frames = self.roll_frames + 1 @@ -83,11 +87,6 @@ function Race40Game:advanceOneFrame() return true end -function Race40Game:onPieceEnter() - self.irs = false - self.ihs = false -end - function Race40Game:onPieceLock() self.super:onPieceLock() self.pieces = self.pieces + 1 diff --git a/tetris/modes/speed.lua b/tetris/modes/speed.lua index 2ee1dc9..29be168 100644 --- a/tetris/modes/speed.lua +++ b/tetris/modes/speed.lua @@ -39,6 +39,7 @@ function LudicrousSpeed:getLineARE() return 0 end function LudicrousSpeed:getLineClearDelay() return 0 end function LudicrousSpeed:getDasLimit() return config.das end function LudicrousSpeed:getARR() return config.arr end +function LudicrousSpeed:getDasCutDelay() return config.dcd end function LudicrousSpeed:getDropSpeed() return 20 end local function mean(t) @@ -74,11 +75,6 @@ function LudicrousSpeed:advanceOneFrame() return true end -function LudicrousSpeed:onPieceEnter() - self.irs = false - self.ihs = false -end - function LudicrousSpeed:onPieceLock() self.super:onPieceLock() self.pieces = self.pieces + 1 diff --git a/tetris/modes/zen.lua b/tetris/modes/zen.lua new file mode 100644 index 0000000..45f69a9 --- /dev/null +++ b/tetris/modes/zen.lua @@ -0,0 +1,176 @@ +require 'funcs' + +local Race40Game = require 'tetris.modes.race_40' + +local ZenMode = Race40Game:extend() + +ZenMode.name = "Marathon WZ" +ZenMode.hash = "Zen" +ZenMode.tagline = "Attempt to score as many points as you can!" + +function ZenMode:new() + self.super:new() + + self.score = bigint.new(0) + self.line_goal = 200 + self.pieces = 0 + self.bravos = 0 + self.combo = 0 + self.b2b = 0 + self.message_timer = 0 + self.immobile_spin_bonus = true +end + +function ZenMode:initialize(ruleset) + self.super.initialize(self, ruleset) + ruleset.onPieceDrop = function(self, piece) piece.lock_delay = 0 end + ruleset.onPieceMove = function(self, piece) piece.lock_delay = 0 end + ruleset.onPieceRotate = function(self, piece) piece.lock_delay = 0 end +end + +function ZenMode:onHardDrop(dropped_row_count) + if dropped_row_count > 0 then + self.score = self.score + bigint.new(dropped_row_count) + end +end + +function ZenMode:getColor(i) + local color_table = { + {255/255, 128/255, 128/255, 1}, + {255/255, 191/255, 128/255, 1}, + {255/255, 255/255, 128/255, 1}, + {128/255, 255/255, 191/255, 1}, + {128/255, 255/255, 255/255, 1}, + {128/255, 128/255, 191/255, 1}, + {191/255, 128/255, 255/255, 1}, + } + if i == 0 then + return {1, 1, 1, 1} + else + return color_table[Mod1(i, #color_table)] + end +end + +function ZenMode:updateScore(level, drop_bonus, cleared_row_count) + local score_to_add = bigint.new(1) + if cleared_row_count ~= 0 then + if self.piece.spin then + score_to_add = score_to_add * ( + bigint.new(400 + 400 * cleared_row_count) * + (bigint.new(2) ^ bigint.new(self.b2b + self.combo)) + ) + self.b2b = self.b2b + 1 + elseif cleared_row_count >= 4 then + score_to_add = score_to_add * ( + bigint.new(100 * (cleared_row_count * cleared_row_count / 4 + cleared_row_count) - 25 * (cleared_row_count % 2)) * + (bigint.new(2) ^ bigint.new(self.b2b + self.combo)) + ) + self.b2b = self.b2b + 1 + else + score_to_add = score_to_add * ( + bigint.new(({100, 300, 500})[cleared_row_count]) * + (bigint.new(2) ^ bigint.new(self.combo)) + ) + self.b2b = 0 + end + self.combo = self.combo + 1 + else + if self.piece.spin then + score_to_add = score_to_add * ( + bigint.new(2) ^ (bigint.new(self.b2b)) * (bigint.new(400)) + ) + else + score_to_add = bigint.new(0) + end + self.combo = 0 + end + self.score = self.score + score_to_add * bigint.new(16) ^ bigint.new(self.bravos) + if self.grid:checkForBravo(cleared_row_count) then + self.score = self.score + bigint.new(10 ^ 6) * bigint.new(16) ^ bigint.new(self.bravos) + self.bravos = self.bravos + 1 + end +end + +function ZenMode:onPieceLock(piece, cleared_row_count) + self.super:onPieceLock() + self.pieces = self.pieces + 1 + self.score = self.score + bigint.new(self:getLockDelay() - piece.lock_delay) + if self.grid:checkForBravo(cleared_row_count) then + self.message = "ALL CLEAR!" + elseif piece.spin then + self.message = (self.b2b > 0 and cleared_row_count ~= 0 and "B2B " or "") .. + (type(piece.shape) == "string" and piece.shape .. "-" or "") .. + "SPIN " .. cleared_row_count .. "!" + elseif cleared_row_count >= 4 then + self.message = (self.b2b > 0 and "B2B " or "") .. + string.upper(string.sub(number_names[cleared_row_count * 3 + 3], 1, -7)) .. "A!" + else + self.message = "" + end + self.message_timer = 60 +end + +function ZenMode:drawScoringInfo() + local text_x = config["side_next"] and 320 or 240 + + 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) + ) + + love.graphics.printf("SCORE", text_x, 100, 40, "left") + love.graphics.printf("PIECES", text_x, 280, 80, "left") + love.graphics.printf("LINES", text_x, 340, 40, "left") + love.graphics.printf("B2B / COMBO", text_x, 160, 160, "left") + love.graphics.printf("PERFECT CLEARS", text_x, 220, 160, "left") + if self.message_timer > 0 then + love.graphics.printf(self.message, 64, 400, 160, "center") + self.message_timer = self.message_timer - 1 + end + local sg = self.grid:checkSecretGrade() + if sg >= 7 or self.upstacked then + love.graphics.printf("SECRET GRADE", 240, 430, 180, "left") + end + + love.graphics.setFont(font_3x5_3) + love.graphics.printf( + {self:getColor(self.b2b), formatBigNum(bigint.unserialize(self.score, "s"))}, + text_x, 120, 400, "left" + ) + love.graphics.printf(self.pieces, text_x, 300, 80, "left") + love.graphics.printf(self.b2b .. " / " .. self.combo, text_x, 180, 80, "left") + love.graphics.printf(self.bravos, text_x, 240, 80, "left") + if sg >= 7 or self.upstacked then + love.graphics.printf(self:getSecretGrade(sg), 240, 450, 180, "left") + end + + love.graphics.setFont(font_3x5_4) + love.graphics.printf(math.max(0, self.line_goal - self.lines), text_x, 360, 80, "left") + + love.graphics.setFont(font_8x11) + love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center") +end + +function ZenMode:getBackground() + return math.floor(self.lines / 10) % 20 +end + +function ZenMode:getHighscoreData() + return { + score = self.score, + lines = self.lines, + frames = self.frames, + } +end + +return ZenMode \ No newline at end of file