diff --git a/tetris/modes/highstacker.lua b/tetris/modes/highstacker.lua new file mode 100644 index 0000000..301144f --- /dev/null +++ b/tetris/modes/highstacker.lua @@ -0,0 +1,569 @@ +require 'funcs' + +local GameMode = require 'tetris.modes.gamemode' +local Piece = require 'tetris.components.piece' + +local Bag7NoSZOStartRandomizer = require 'tetris.randomizers.bag7noSZOstart' + +local HighStackerGame = GameMode:extend() + +HighStackerGame.name = "High Stacker" +HighStackerGame.hash = "HighStacker" +HighStackerGame.tagline = "Play risky on the higher fields and don't be a chicken!" + +--Official Release v1.0 +--Concept & Coding by Xx_Henry_xX (Xx_Henry_xX#0296) +--Code mostly & shamelessly stolen from... + --Marathon A2 / A3 + --Survival 2020 +--[[ + changelog + Official Release v1.0 + -2'-ALL ending appearing on other ALLs fixed + -transition bug is fixed AT LAST!!!!!!!!!!!!! + + Official Release v0.2 + -null piece bug fixed + -some pointless bool == bool stuffs edited + + Official Release v0.1 + -total delay is now ura loop 2 exclusive + -edited total delay values + -end of game bonus details added + -2'-ALL ending added + -roll bgm added + + Discord Beta v2.0 + -scoring rehaul (mostly on REN) + -slot is gone + -current section score & section cool requirement display added + + Discord Beta v1.2 + -adjusted section cool cutoffs and ura 2nd loop cutoff + -fixed end of game bonus being given endlessly (kudos 2 infinifen#1129) + + Discord Beta v1.1 + -bugfixes on section score text color + -end of game bonus added + + Discord Beta v1.0 + -initial semi-public release +]] + +function HighStackerGame:new() + HighStackerGame.super:new() + + self.roll_frames = 0 + self.combo = 0 + self.randomizer = Bag7NoSZOStartRandomizer() + self.grade = 0 + self.grade_points = 0 + self.grade_point_decay_counter = 0 + self.section_start_score = 0 + self.section_scores = { [0] = 0 } + self.loop = 0 --0 == loop 1 / 1 == omote / 2 == ura + self.total_lines = 0 + self.section_cools = 0 + self.cool_timer = 0 + self.ground_touched = false + self.total_delay = 300 + self.show_loop = false + self.actually_cue_the_rolls = false + self.ren_on = false + + self.line_details = { } + self.line_details_second = { } + self.detail_timer = 0 + self.ending = 0 + self.clear_bonus_given = false + + --debug + --[[ + self.level = 499 + self.section_cools = 4 + self.score = 50000000 + self.section_scores = { + [0] = 10000000, + [1] = 10000000, + [2] = 10000000, + [3] = 10000000 + } + ]] + + self.additive_gravity = false + self.lock_drop = false + self.lock_hard_drop = false + self.enable_hold = true + self.next_queue_length = 3 +end + +function HighStackerGame:getARE() + if self.level < 700 then return 27 + elseif self.level < 800 then return 18 + else return 14 end +end + +function HighStackerGame:getLineARE() + if self.level < 600 then return 27 + elseif self.level < 700 then return 18 + elseif self.level < 800 then return 14 + else return 8 end +end + +function HighStackerGame:getDasLimit() + if self.level < 500 then return 15 + elseif self.level < 900 then return 9 + else return 7 end +end + +function HighStackerGame:getLineClearDelay() + if self.level < 500 then return 40 + elseif self.level < 600 then return 25 + elseif self.level < 700 then return 16 + elseif self.level < 800 then return 12 + else return 6 end +end + +function HighStackerGame:getLockDelay() + if self.level < 900 then return 30 + else return 17 end +end + +function HighStackerGame:getGravity() + if (self.level < 30) then return 4/256 + elseif (self.level < 35) then return 6/256 + elseif (self.level < 40) then return 8/256 + elseif (self.level < 50) then return 10/256 + elseif (self.level < 60) then return 12/256 + elseif (self.level < 70) then return 16/256 + elseif (self.level < 80) then return 32/256 + elseif (self.level < 90) then return 48/256 + elseif (self.level < 100) then return 64/256 + elseif (self.level < 120) then return 80/256 + elseif (self.level < 140) then return 96/256 + elseif (self.level < 160) then return 112/256 + elseif (self.level < 170) then return 128/256 + elseif (self.level < 200) then return 144/256 + elseif (self.level < 220) then return 4/256 + elseif (self.level < 230) then return 32/256 + elseif (self.level < 233) then return 64/256 + elseif (self.level < 236) then return 96/256 + elseif (self.level < 239) then return 128/256 + elseif (self.level < 243) then return 160/256 + elseif (self.level < 247) then return 192/256 + elseif (self.level < 251) then return 224/256 + elseif (self.level < 300) then return 1 + elseif (self.level < 330) then return 2 + elseif (self.level < 360) then return 3 + elseif (self.level < 400) then return 4 + elseif (self.level < 420) then return 5 + elseif (self.level < 450) then return 4 + elseif (self.level < 500) then return 3 + else return 20 + end +end + +function HighStackerGame:getTotalDelay() + if self.loop ~= 2 then return 300 + elseif self.level < 530 then return 270 + elseif self.level < 600 then return 240 + elseif self.level < 700 then return 210 + elseif self.level < 800 then return 180 + elseif self.level < 900 then return 150 + else return 120 end +end + +function HighStackerGame:advanceOneFrame() + if self.clear then + self.roll_frames = self.roll_frames + 1 + if not self.actually_cue_the_rolls then + if self.roll_frames < -120 then + return false + elseif self.roll_frames >= 0 then + if self.loop ~= 0 then + self.clear = false + self.ready_frames = 100 + self.show_loop = true + end + return true + end + return false + elseif self.roll_frames < 0 then + if self.roll_frames + 1 == 0 then + switchBGM("credit_roll", "gm3") + end + return false + elseif self.roll_frames > 3701 then + self.completed = true + switchBGM(nil) + end + elseif self.ready_frames == 0 then + self.frames = self.frames + 1 + if self.piece ~= nil then + if self.ground_touched and self.loop == 2 then + self.total_delay = self.total_delay + 1 + if self.total_delay >= self:getTotalDelay() and self.piece:isDropBlocked(self.grid) then + self.piece.locked = true + end + elseif self.piece:isDropBlocked(self.grid) then self.ground_touched = true end + end + end + if self.detail_timer > 0 then + self.detail_timer = self.detail_timer - 1 + end + return true +end + +function HighStackerGame:onPieceEnter() + self.total_delay = 0 + self.ground_touched = false + if (self.level % 100 ~= 99) and not self.clear and self.frames ~= 0 and not self.ren_on then + self.level = self.level + 1 + end +end + +local line_count_multiplier = {1, 2, 4, 6} +local line_position_score = { + 15000, + 15000, + 15000, + 15000, --lowest off-field row + 15000, + 10000, + 9000, + 8000, + 7000, + 6000, + 5000, + 4000, + 3000, + 2000, + 1000, + 900, + 800, + 700, + 600, + 500, + 40, + 30, + 20, + 10 +} + +function HighStackerGame:updateScore(level, drop_bonus, cleared_lines) + local ae, cleared_lines_table = self.grid:getClearedRowCount() + self.total_lines = self.total_lines + cleared_lines + if cleared_lines > 0 then + self.detail_timer = 120 + self.line_details = { } + self.line_details_second = { } + local basescore = self.combo + local levelscore = self.level * 10 + table.insert(self.line_details_second, levelscore) + if basescore > 0 then + table.insert(self.line_details_second, basescore) + self.ren_on = true + end + if self.loop == 1 then levelscore = levelscore - 5000 end + basescore = basescore + levelscore + for key, pos in ipairs(cleared_lines_table) do + basescore = basescore + line_position_score[pos] + table.insert(self.line_details, line_position_score[pos]) + end + self.score = self.score + basescore * line_count_multiplier[cleared_lines] + self.combo = basescore + + else + self.combo = 0 + self.ren_on = false + end + self.drop_bonus = 0 + if self.clear then self.lines = self.lines + cleared_lines end --reminder that this is for roll lines +end + +function HighStackerGame:onLineClear(cleared_row_count) + if self.ren_on == false then + self:updateSectionScores(self.level, self.level + cleared_row_count) + self.level = math.min(self.level + cleared_row_count, 1000) + end + if self.level >= 500 and not self.clear and self.loop == 0 then + self.level = 500 + self.clear = true + self.grid:clear() + self.roll_frames = -150 + if self:nextloopcheck() then + self.roll_frames = self.roll_frames - 120 + self.lcd = 0 + self.are = 0 + else + self.actually_cue_the_rolls = true + end + elseif self.level == 1000 and not self.clear then + self.clear = true + self.grid:clear() + self.actually_cue_the_rolls = true + self.roll_frames = -150 + end + self.lock_drop = self.level >= 900 + self.lock_hard_drop = self.level >= 900 +end + +local lowest_section_cool_cutoff = 800000 + +function HighStackerGame:updateSectionScores(old_level, new_level) + if self.clear or self.loop ~= 0 then return end + if math.floor(old_level / 100) < math.floor(new_level / 100) or + new_level >= 500 then + -- record new section + section_score = self.score - self.section_start_score + self.section_scores[math.floor(old_level / 100)] = section_score + self.section_start_score = self.score + if self.loop == 0 then + if section_score >= lowest_section_cool_cutoff + 50000 * (math.floor(old_level / 100) + self.section_cools) then + self.section_cools = self.section_cools + 1 + self.cool_timer = 300 + end + end + end +end + +function HighStackerGame:nextloopcheck() + if not self.clear then return false end + if self.section_cools >= 5 and self.score >= 6000000 then + self.loop = 2 + return true + elseif self.section_cools >= 3 and self.score >= 4000000 then + self.loop = 1 + return true + end + return false +end + +function HighStackerGame:getSkin() + return (self.level >= 701 and self.loop == 2) and "bone" or "2tie" +end + +function HighStackerGame:onGameComplete() + if not self.clear_bonus_given then + self.clear_bonus_given = true + if self.loop == 2 then + self.score = self.score + self.total_lines * 100 + self.lines * 1000 + elseif self.loop == 1 then + self.score = self.score + self.total_lines * 100 + self.lines * 500 + else + self.score = self.score + self.total_lines * 10 + self.lines * 100 + end + end + self:onGameOver() +end + +function HighStackerGame:onGameOver() + switchBGM(nil) + if not self.clear_bonus_given then + love.graphics.setColor(1, 1, 1, 1) + love.graphics.setFont(font_3x5_3) + love.graphics.printf("GAME\nOVER", 64, 200, 160, "center") + else + if self.game_over_frames >= 300 then + local alpha = 0 + local animation_length = 120 + if self.game_over_frames - 300 < animation_length then + -- Show field for a bit, then fade out. + alpha = math.pow(2048, (self.game_over_frames - 300)/animation_length - 1) + else + 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 + if self.game_over_frames >= 420 and loop == 2 then + love.graphics.setColor(1, 1, 1, 1) + love.graphics.setFont(font_3x5_2) + love.graphics.printf( + [[And so, the journey +for the true end of +this absurd scoring +mode is over... + +From now on, +aim for an even + higher score. + +May fortune + be with you. + + -Xx_Henry_xX]], 67, math.max(125, 1000 - self.game_over_frames), 160, "left") + end + end +end + +HighStackerGame.rollOpacityFunction = function(age) + if age < 240 then return 1 + elseif age > 300 then return 0 + else return 1 - (age - 240) / 60 end +end + +HighStackerGame.mRollOpacityFunction = function(age) + if age > 4 then return 0 + else return 1 - age / 4 end +end + +function HighStackerGame:drawGrid() + if self.clear and not (self.completed or self.game_over) then + if self.loop == 2 then + self.grid:drawInvisible(self.mRollOpacityFunction, nil, false) + elseif self.loop == 1 then + self.grid:drawInvisible(self.rollOpacityFunction, nil, false) + else + self.grid:draw() + end + else + self.grid:draw() + if self.piece ~= nil then + self:drawGhostPiece(ruleset) + end + end +end + +function HighStackerGame:drawScoringInfo() + love.graphics.setColor(1, 1, 1, 1) + love.graphics.setFont(font_3x5_2) + + --y is dis debug text everywhere + --[[ + love.graphics.setFont(font_3x5_2) + love.graphics.print( + self.das.direction .. " " .. + self.das.frames .. " " .. + strTrueValues(self.prev_inputs) + ) + ]] + + love.graphics.printf("NEXT", 126, 6, 40, "left") + love.graphics.printf("HOLD", 46, 6, 40, "left") + --love.graphics.printf("SCORE", 240, 200, 40, "left") + local vertpos = 320 + if self.show_loop then vertpos = 290 end + love.graphics.printf("LEVEL", 240, vertpos, 40, "left") + + love.graphics.setFont(font_3x5_3) + if self.clear and not self.actually_cue_the_rolls and self.roll_frames >= -120 then + if self.loop == 2 then + love.graphics.printf("WELCOME TO THE SPECIAL ROUND", 64, 200, 160, "center") + else + love.graphics.printf("Let's try everything again", 64, 200, 160, "center") + end + end + + if self.clear_bonus_given then + if self.loop == 0 then + love.graphics.setFont(font_3x5_3) + love.graphics.printf("ALL CLEAR!", 44, 100, 200, "center") + love.graphics.setFont(font_3x5) + love.graphics.printf("(probably)", 44, 125, 200, "center") + + love.graphics.setFont(font_3x5_2) + love.graphics.printf("TOTAL LINES", 80, 240, 120, "left") + love.graphics.printf(self.total_lines .. " x 10\n" .. (self.total_lines * 10), 80, 260, 120, "right") + love.graphics.printf("ROLL LINES", 80, 320, 120, "left") + love.graphics.printf(self.lines .. " x 100\n" .. (self.lines * 100), 80, 340, 120, "right") + elseif self.loop == 1 then + love.graphics.setFont(font_3x5_3) + love.graphics.printf("ALL CLEAR!", 44, 100, 200, "center") + love.graphics.setFont(font_3x5) + love.graphics.printf("(probably)", 44, 125, 200, "center") + + love.graphics.setFont(font_3x5_2) + love.graphics.printf("TOTAL LINES", 80, 240, 120, "left") + love.graphics.printf(self.total_lines .. " x 100\n" .. (self.total_lines * 100), 80, 260, 120, "right") + love.graphics.printf("ROLL LINES", 80, 320, 120, "left") + love.graphics.printf(self.lines .. " x 500\n" .. (self.lines * 500), 80, 340, 120, "right") + elseif self.game_over_frames < 300 then + love.graphics.printf("CONGRATULATIONS!\nTRUE 2-ALL\nCLEAR!!!", 44, 100, 200, "center") + + love.graphics.setFont(font_3x5_2) + love.graphics.printf("TOTAL LINES", 80, 240, 120, "left") + love.graphics.printf(self.total_lines .. " x 100\n" .. (self.total_lines * 100), 80, 260, 120, "right") + love.graphics.printf("ROLL LINES", 80, 320, 120, "left") + love.graphics.printf(self.lines .. " x 1000\n" .. (self.lines * 1000), 80, 340, 120, "right") + end + end + + if(self.cool_timer > 0) then + love.graphics.printf("COOL!!", 64, 400, 160, "center") + self.cool_timer = self.cool_timer - 1 + end + + love.graphics.setFont(font_8x11) + love.graphics.setColor(1, 1, 1, 1) + love.graphics.printf(self.score, 48, 420, 180, "right") + love.graphics.setFont(font_3x5_3) + if self.show_loop then + if self.loop == 2 then + love.graphics.printf("2'-", 240, 310, 40, "right") + elseif self.loop == 1 then + love.graphics.printf("2-", 240, 310, 40, "left") + end + end + love.graphics.printf(self:getLevel(), 240, 340, 40, "right") + love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right") + love.graphics.setFont(font_3x5_2) + love.graphics.printf(formatTime(self.frames), 500, 420, 180, "left") + + love.graphics.setColor(1, 1, 1, 1) + if self.loop == 0 then + local prevSectionTotal = 0 + for i = 0, #self.section_scores do + prevSectionTotal = prevSectionTotal + self.section_scores[i] + end + love.graphics.printf(self.score - prevSectionTotal, 240, 420, 70, "right") + love.graphics.printf("/" .. lowest_section_cool_cutoff + 50000 * (math.floor(self.level / 100) + self.section_cools), 240, 436, 70, "right") + end + + cool_counter = 0 + for i = 0, #self.section_scores do + love.graphics.setColor(1, 1, 1, 1) + if self.section_scores[i] > lowest_section_cool_cutoff + 50000 * (i + cool_counter) then + love.graphics.setColor(0, 1, 0, 1) + cool_counter = cool_counter + 1 + end + love.graphics.printf(self.section_scores[i], 400, 80 + 16 * i, 180, "right") + end + + love.graphics.setColor(1, 1, 1, 1) + if self.detail_timer > 0 then + for i = 1, #self.line_details do + love.graphics.printf(self.line_details[i] .. "x" .. line_count_multiplier[#self.line_details], 240, 134 + 16 * i, 60, "right") + end + for i = 1, #self.line_details_second do + love.graphics.printf(self.line_details_second[i] .. "x" .. line_count_multiplier[#self.line_details], 240, 196 + 16 * i, 60, "right") + end + end +end + +function HighStackerGame:getHighscoreData() + return { + score = self.score, + level = self.level, + frames = self.frames, + } +end + +function HighStackerGame:getLevel() + if self.loop ~=0 then return self.level - 500 end + return self.level +end + +function HighStackerGame:getSectionEndLevel() + if self.loop ~=0 then return math.min(math.floor((self.level - 500) / 100 + 1) * 100, 500) end + return math.min(math.floor(self.level / 100 + 1) * 100, 500) +end + +function HighStackerGame:getBackground() + return math.floor(self.level / 100) +end + +return HighStackerGame