local Object = require 'libs.classic' local bit = require("bit") local lualzw = require 'libs.lualzw' local playedReadySE = false local hiscore_pos local Grid = require 'game.grid' local Randomizer = require 'game.randomizer' local GameMode = Object:extend() function GameMode:new(player_name, input_file, replay_grade) VCTRL.toggle(MOBILE and not input_file and not SETTINGS.tvMode) VCTRL.reset() if player_name == nil then self.training = true else self.training = false end if input_file ~= nil then input_file = love.filesystem.read(REPLAY_DIR..input_file) input_file = lualzw.decompress(input_file) local seed = self:getInputPieceSeq(input_file) self.replay_inputs = self:getReplayInputs(input_file) self.randomizer = Randomizer(false, tonumber(seed)) self.input_playback = true self.grade = replay_grade self.frames = 1 elseif self.training then player_name = 'TRN' replay_grade = 'N/A' self.randomizer = Randomizer(false, nil) self.input_playback = false self.frames = 0 else self.randomizer = Randomizer(false, nil) self.input_playback = false self.frames = 0 end self.player_name = player_name self.grid = Grid(10, 20) self.piece = nil self.ready_frames = 100 self.game_over_frames = 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 self.next_queue_length = 1 self.irs = true self.drop_locked = false self.hard_drop_locked = false self.cleared_block_table = {} self.last_lcd = 0 self.grade_score = 0 self.did_grades = false self.active_frames = 0 self.total_lines = 0 self.lineClearPoints = {[0]=0, 0, 1667, 3750, 6668, 8335} self.gradeNames = { "19k", "18k", "17k", "16k", "15k", "14k", "13k", "12k", "11k", "10k", "9k", "8k", "7k", "6k", "5k", "4k", "3k", "2k", "1k", "1D", "2D", "3D", "4D", "5D", "6D", "7D", "8D", "9D" } self.promo_table = { 26666, 53333, 79999, 106666, 133333, 159999, 186666, 213333, 239999, 266666, 293333, 319999, 346666, 373333, 399999, 426666, 453333, 479999, 506666, 533333, 559999, 586666, 613333, 639999, 666666, 693333, 719999, 719999 } self.autopromo_table = { 79999, 106666, 133333, 159999, 186666, 213333, 239999, 266666, 293333, 319999, 346666, 373333, 399999, 426666, 453333, 479999, 506666, 533333, 559999, 586666, 613333, 639999, 666666, 693333, 719999, 746666, 773333, 1000000 } self.demo_table = { -1, 13332, 25000, 40000, 50000, 60000, 60000, 120000, 120000, 120000, 180000, 180000, 240000, 240000, 300000, 300000, 360000, 360000, 360000, 420000, 420000, 480000, 480000, 480000, 480000, 540000, 540000, 540000 } self.speed_divisor = 10000 self.line_clear_flash = 0 self.lines_cleared = 0 SOUNDS['bgm_firsthalf']:setVolume(0.3) SOUNDS['bgm_secondhalf']:setVolume(0.35) self.audio_stopped = false self.recorded_inputs = {} self.input_saved = false self.end_grid_clear = false self.last_active = 0 self.move_count = 0 self.target = 0 self.last_percent = 0 self.total_speed_loss = 0 self.grade_change_flash = 1 self.grade_change_color = {1,1,1,1} self.point_flash = 0 self.point_flash_color = {1,1,1,1} self.end_game_sound = nil self.speed_level = 0 self.last_level = 0 self.nextbgmflag = false self.firstbgmlooped = false self.last_holes = 0 self.last_meter = 0 self.speed_table = {} self.holes_bonus_freeze = 0 self.holes_bonus = 0 self.last_speed = 0 self.holes_bonus_lines = 0 self.bonus_components = {speed=1666,clean=750} self.score_totals = {clean=0,speed=0,lines=0} self.score_percents = {clean=0,speed=0,lines=0} self.directions_pressed = {} self.lines_bonus = 0 self.moved = false self.midspeed_flash = false self.lastdir = 1 self.spin_rng = {0,0,0,0,0,0,0,0,0,0} self.lastlock = 0 self.up_lock = false -- gravity, are, lock, das self.delay_table = { [0]={0.013, 20, 30, 12},{0.015, 20, 30, 12},{0.016, 20, 30, 12},{0.018, 20, 30, 12},{0.021, 20, 30, 12},{0.025, 20, 30, 12}, {0.027, 20, 30, 12},{0.030, 20, 30, 12},{0.033, 20, 30, 12},{0.038, 20, 30, 12},{0.043, 20, 30, 12},{0.051, 20, 30, 12}, {0.056, 20, 30, 12},{0.062, 20, 30, 12},{0.070, 20, 30, 12},{0.079, 20, 30, 12},{0.092, 20, 30, 12},{0.109, 20, 30, 12}, {0.120, 20, 30, 12},{0.134, 20, 30, 12},{0.152, 20, 30, 12},{0.175, 20, 30, 12},{0.206, 20, 30, 12},{0.250, 20, 30, 12}, {0.281, 20, 30, 12},{0.319, 20, 30, 12},{0.370, 20, 30, 12},{0.441, 20, 30, 12},{0.545, 20, 30, 12},{0.712, 20, 30, 12}, {0.842, 20, 30, 12},{1.029, 20, 30, 12},{1.323, 20, 30, 12},{1.852, 20, 30, 11},{3.086, 20, 30, 10},{9.259, 20, 30, 9}, {20, 20, 30, 8},{20, 20, 30, 8},{20, 19, 28, 8},{20, 18, 27, 8},{20, 17, 26, 8},{20, 17, 25, 8}, {20, 16, 24, 8},{20, 16, 23, 8},{20, 15, 23, 8},{20, 15, 22, 8},{20, 14, 21, 8},{20, 14, 21, 8}, {20, 14, 20, 8},{20, 13, 20, 8},{20, 13, 20, 8},{20, 13, 19, 8},{20, 13, 19, 8},{20, 13, 19, 8}, {20, 12, 18, 8} } if not self.input_playback and not self.training and not PENTO_MODE then self:readGradeHistory() end end function GameMode:readGradeHistory() if love.filesystem.getInfo(SAVE_DIR..self.player_name.."_grade_history.sav") then self.grade_history = FILE.read(SAVE_DIR..self.player_name.."_grade_history.sav") else self.grade_history = {1,2,0,0} end self.grade = self.grade_history[1] self.starting_grade = self.grade if self.grade > 1 then local temp_grade = copy(self.grade_history); temp_grade[2] = 0 FILE.write(SAVE_DIR..self.player_name.."_grade_history.sav", temp_grade) end end function GameMode:readHiScores() if love.filesystem.getInfo(HIscoreFILE) then self.hi_scores = FILE.read(HIscoreFILE) else self.hi_scores = {"TRO",0,"MIT",0,"ROM",0,"ITR",0,"OMI",0} end end function GameMode:updateGradeHistory() if self.grade_score >= self.promo_table[self.grade] then if (self.grade == 28 and self.grade_history[2] < 4) or self.grade < 28 then self.grade_history[2] = self.grade_history[2] + 1 end self.point_flash = 60 self.point_flash_color = {0,1,0,1} elseif self.grade_score <= self.demo_table[self.grade] then if (self.grade == 1 and self.grade_history[2] > 0) or self.grade > 1 then self.grade_history[2] = self.grade_history[2] - 1 end self.point_flash = 60 self.point_flash_color = {1,0,0,1} end local auto_flag = false while self.grade_score >= self.autopromo_table[self.grade] and self.grade < 28 do self.grade = self.grade + 1 self.point_flash = 1 self.grade_change_flash = 120 self.grade_change_color = {0,0,1,1} self.end_game_sound = "autopromote" auto_flag = true end if self.grade_history[2] >= 5 and self.grade < 28 and auto_flag == false then self.grade = self.grade + 1 self.point_flash = 1 self.grade_change_flash = 120 self.grade_change_color = {0,1,0,1} self.end_game_sound = "promote" elseif self.grade_history[2] < 0 and self.grade > 1 and auto_flag == false then self.grade = self.grade - 1 self.point_flash = 1 self.grade_change_flash = 120 self.grade_change_color = {1,0,0,1} self.end_game_sound = "demote" end if self.starting_grade ~= self.grade then self.grade_history[1] = self.grade self.grade_history[2] = 2 end self.grade_history[4] = self.grade_history[4] + 1 end function GameMode:updateHiScores() self:readHiScores() local hiscore_pos = {0,0} local i = 2 local score_position = -1 while i <= 10 do if score_position == -1 and self.hi_scores[i] < self.grade_score then score_position = i end i = i + 2 end if score_position ~= -1 then i = 8 while i >= score_position-1 do self.hi_scores[i+2] = self.hi_scores[i] i = i - 1 end self.hi_scores[score_position-1] = self.player_name:upper() self.hi_scores[score_position] = self.grade_score hiscore_pos = {score_position-1, score_position} end FILE.write(HIscoreFILE, self.hi_scores) return hiscore_pos end function GameMode:getARR() return 1 end function GameMode:getDropSpeed() return 1 end function GameMode:getARE() if self.training then return 20 end if self.speed_level <= #self.delay_table then return self.delay_table[self.speed_level][2] else return self.delay_table[#self.delay_table][2] end end function GameMode:getLineARE() return self:getARE() end function GameMode:getLockDelay() if self.training then return 99999999999 end if self.speed_level <= #self.delay_table then return self.delay_table[self.speed_level][3] else return self.delay_table[#self.delay_table][3] end end function GameMode:getLineClearDelay() return self:getARE() end function GameMode:getDasLimit() if self.training then return 8 end if self.speed_level <= #self.delay_table then return self.delay_table[self.speed_level][4] else return self.delay_table[#self.delay_table][4] end end function GameMode:getDasCutDelay() return 0 end function GameMode:getGravity() if self.training then return 20 end if self.speed_level <= #self.delay_table then return self.delay_table[self.speed_level][1] else return self.delay_table[#self.delay_table][1] end end function GameMode:getNextPiece(ruleset) local shape = self.randomizer:nextPiece() return { skin = self:getSkin(), shape = shape, orientation = ruleset:getDefaultOrientation(shape), } end function GameMode:getSkin() return "2tie" end function GameMode:initialize(ruleset) self.ruleset = ruleset for i = 1, math.max(self.next_queue_length, 1) do table.insert(self.next_queue, self:getNextPiece(ruleset)) end end function GameMode:saveInputs() local input_string = 'NEW'..string.format("%16d", self.randomizer.seed)..';' for frame, input_set in pairs(self.recorded_inputs) do input_string = input_string..string.format('%03d', tostring(input_set)) end local inputfile = love.filesystem.newFile(REPLAY_DIR..tostring(os.time()).."_"..self.player_name.."_"..self.starting_grade.."_"..self.grade_score.."_replay"..'.sav', 'w') inputfile:write(lualzw.compress(input_string)) inputfile:close() end local GAME_input_code_list = { right = 1 , rotate_right2 = 2 , up = 4 , rotate_left = 8 , left = 16 , down = 32 , rotate_right = 64 , rotate_left2 = 128, } function GameMode:storeInput(input_set) local current_frame_input = 0 for key, code in pairs(GAME_input_code_list) do if input_set[key] then current_frame_input = bit.bor(current_frame_input, code) end end self.recorded_inputs[self.frames] = current_frame_input end function GameMode:getReplayInputs(input_file) local replay_inputs = {} local semicolon_position = string.find(input_file, ';', 1, true) if semicolon_position then for i = semicolon_position + 1, #input_file, 3 do local input_list = {right = false, rotate_right2 = false, up = false, rotate_left = false, left = false, down = false, rotate_right = false, rotate_left2 = false} local coded_input = tonumber(string.sub(input_file, i, i+2)) for key, code in pairs(GAME_input_code_list) do ---@diagnostic disable-next-line: param-type-mismatch if bit.band(coded_input, code) == code then input_list[key] = true end end table.insert(replay_inputs, input_list) end end return replay_inputs end function GameMode:getInputPieceSeq(input_file) local string_start if string.sub(input_file, 1, 3) == 'NEW' then string_start = 4 self.old_replay = false else string_start = 1 self.old_replay = true end return string.sub( input_file, string_start, string.find(input_file, ';', string_start, true) - 1 ) -- seed end function GameMode:update(inputs, ruleset) if self.input_playback and self.replay_inputs[self.frames] ~= nil then inputs = self.replay_inputs[self.frames] end if self.grade_change_flash > 0 and self.game_over_frames >= 2 then self.grade_change_flash = self.grade_change_flash - 1 end if self.point_flash > 0 and self.game_over_frames >= 2 then self.point_flash = self.point_flash - 1 end if self.game_over_frames == 2 then PlaySEOnce(self.end_game_sound) end if self.game_over or self.completed then self.game_over_frames = self.game_over_frames + 1 if self.game_over_frames == 1 then self:storeInput(inputs) end if self.lcd > 0 then self.lcd = self.lcd - 1 elseif self.game_over_frames == 30 then local cleared_row_count = self.grid:getClearedRowCount() self.grid:clearClearedRows() self:afterLineClear(cleared_row_count) elseif not self.input_playback and not self.input_saved and not self.training and not PENTO_MODE and self.game_over_frames == 50 then self:saveInputs() self.input_saved = true end return end if inputs["up"] and self.training and not self.up_lock then table.remove(self.next_queue, 1) table.insert(self.next_queue, self:getNextPiece(ruleset)) self.up_lock = true end if not inputs["up"] then self.up_lock = false end local dir_list = {"down", "left", "right"} for i = 1, #dir_list do if inputs[dir_list[i]] and not table.contains(self.directions_pressed, dir_list[i]) then table.insert(self.directions_pressed, dir_list[i]) elseif not inputs[dir_list[i]] then for j=1, #self.directions_pressed do if self.directions_pressed[j] == dir_list[i] then table.remove(self.directions_pressed, j) end end end end if #self.directions_pressed > 0 then for i=1, #self.directions_pressed-1 do inputs[self.directions_pressed[i]] = false end inputs[self.directions_pressed[#self.directions_pressed]] = true if inputs['left'] then self.lastdir = -1 elseif inputs['right'] then self.lastdir = 1 end 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"] ) then self:onAttemptPieceRotate(self.piece, self.grid) end if self.piece == nil then self:processDelays(inputs, ruleset) else self:whilePieceActive() local gravity = self:getGravity() -- 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, gravity, self.prev_inputs, self.move, self:getLockDelay(), self:getDropSpeed(), self.drop_locked, self.hard_drop_locked, false, false, false, self.lastdir ) 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 )) ) 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["down"] == true then if self.piece:isDropBlocked(self.grid) and not self.drop_locked then self.lastlock = self.piece.lock_delay self.piece.locked = true self.piece_soft_locked = true end end if self.piece.locked == true then local last_x = self.piece.position.x self.grid:applyPiece(self.piece) local cleared_row_count = self.grid:getClearedRowCount() self:onPieceLock(self.piece, cleared_row_count) if not self.training then self:updateScore(cleared_row_count) end self.cleared_block_table = self.grid:markClearedRows() self.piece = nil if cleared_row_count > 0 then for i=1, #self.spin_rng do local spin = math.abs(i-(last_x+3)) --if spin < 0 then spin = 0 end self.spin_rng[i] = spin end PlaySE("erase") self.lcd = self:getLineClearDelay() self.last_lcd = self.lcd self.are = self:getLineARE() 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 self.are = self:getARE() end end end self.moved = false if not self.input_playback and not self.game_over and not self.completed and not self.training and not PENTO_MODE then self:storeInput(inputs) end self.prev_inputs = inputs end function GameMode:detectHoles(x, y) if y < 1 then return true end -- reached the top, no hole local cell = self.grid:getCell(x, y) if table.contains(self.visited, x..'.'..y) then return false else table.insert(self.visited, x..'.'..y) end if cell.colour ~= "" or cell.oob then return false end if self:detectHoles(x+1, y) then return true end if self:detectHoles(x-1, y) then return true end if self:detectHoles(x, y+1) then return true end if self:detectHoles(x, y-1) then return true end return false end function GameMode:stackQualityCheck() local hole_num = 0 self.visited = {} for x=1, 10 do for y = 1, 20 do if self.grid:getCell(x, y).colour == "" and not table.contains(self.visited, x..'.'..y) then local stack_clean = self:detectHoles(x, y) if not stack_clean then hole_num = hole_num + 1 end end end end return hole_num end function GameMode:doStackQuality() local contiguous_holes = self:stackQualityCheck() local total_speed = 0 if contiguous_holes > self.last_holes then self.speed_table[1] = 0 self.last_percent = 0 end for i=1, #self.speed_table do total_speed = total_speed + self.speed_table[i] end if total_speed <= 0 then total_speed = 0.001 end self.total_speed_loss = total_speed / #self.speed_table if #self.speed_table == 0 then self.total_speed_loss = 0 end self.last_holes = contiguous_holes end function GameMode:doSpeedCheck() self.target = self.move_count local speed = (self.target/self.active_frames) if speed > 1 then speed = 1 end table.insert(self.speed_table, 1, speed) while #self.speed_table > 50 do table.remove(self.speed_table, #self.speed_table) end local total_speed = 0 for i=1, #self.speed_table do total_speed = total_speed + self.speed_table[i] end if total_speed <= 0 then total_speed = 0.001 end self.total_speed_loss = total_speed / #self.speed_table if #self.speed_table == 0 then self.total_speed_loss = 0 end self.last_active = self.active_frames self.last_percent = speed self.active_frames = 0 self.move_count = 0 end function GameMode:updateScore(cleared_lines) self:doSpeedCheck() if cleared_lines >= 1 then while cleared_lines+self.total_lines > 300 do cleared_lines = cleared_lines - 1 end self.last_speed = math.ceil(self.total_speed_loss * self.bonus_components['speed'] * cleared_lines) self.line_clear_flash = 240 self.lines_cleared = cleared_lines self.score_to_add = self.lineClearPoints[cleared_lines] + self.last_speed self.grade_score = self.grade_score + self.score_to_add self.last_meter = self.holes_bonus + self.last_speed self.speed_level = math.floor(self.grade_score / self.speed_divisor) self.score_totals['speed'] = self.score_totals['speed'] + self.last_speed self.score_totals['lines'] = self.score_totals['lines'] + self.lineClearPoints[cleared_lines] end end function GameMode:advanceOneFrame() if self.ready_frames ~= 0 and not self.audio_stopped and not self.training then if SOUNDS['bgm_title']:getVolume() > 0.01 then SOUNDS['bgm_title']:setVolume(SOUNDS['bgm_title']:getVolume()-0.01) end if self.ready_frames == 5 then love.audio.stop() self.audio_stopped = true end end if self.training and not SOUNDS['bgm_title']:isPlaying() and SETTINGS["music"] then SOUNDS['bgm_title']:play() end if not self.training then if self.nextbgmflag and SOUNDS['bgm_firsthalf']:isPlaying() then if SOUNDS['bgm_firsthalf']:getVolume() > 0.1 then SOUNDS['bgm_firsthalf']:setVolume(SOUNDS['bgm_firsthalf']:getVolume()-0.01) else SOUNDS['bgm_firsthalf']:stop() end end if self.ready_frames < 1 and not SOUNDS['bgm_firsthalf']:isPlaying() and not SOUNDS['bgm_secondhalf']:isPlaying() and SETTINGS["music"] then if not self.nextbgmflag then SOUNDS['bgm_firsthalf']:play() elseif self.total_lines < 296 then SOUNDS['bgm_secondhalf']:play() end end if self.total_lines >= 296 then SOUNDS['bgm_firsthalf']:stop() SOUNDS['bgm_secondhalf']:stop() end end if self.clear then self.completed = true end self.frames = self.frames + 1 if self.input_playback == true and self.old_replay == true and self.ready_frames ~= 0 then self.frames = 0 end if self.line_clear_flash > 0 then self.line_clear_flash = self.line_clear_flash - 1 end if self.line_clear_flash == 0 then self.lines_cleared = 0 self.score_to_add = 0 self.last_meter = 0 self.holes_bonus = 0 self.last_speed = 0 self.holes_bonus_lines = 0 self.lines_bonus_lines = 0 self.lines_bonus = 0 end return true end -- event functions function GameMode:whilePieceActive() self.active_frames = self.active_frames + 1 end function GameMode:onAttemptPieceMove(piece, grid) end function GameMode:onAttemptPieceRotate(piece, grid) end function GameMode:onPieceMove(piece, grid, dx) if not self.moved then self.move_count = self.move_count + 1 self.moved = true end end function GameMode:onPieceRotate(piece, grid, drot) if not self.moved then self.move_count = self.move_count + 1 self.moved = true end end function GameMode:onPieceDrop(piece, grid, dy) if not self.moved then self.move_count = self.move_count + 1 self.moved = true end end function GameMode:onPieceLock(piece, cleared_row_count) if not self.moved then self.move_count = self.move_count + 1 self.moved = true end self.lastdir = 0 PlaySE("lock") end function GameMode:onLineClear(cleared_row_count) if not self.training then self.total_lines = math.min(self.total_lines + cleared_row_count, 300) end if self.total_lines == 300 and not self.clear then self.clear = true end end function GameMode:afterLineClear(cleared_row_count) end function GameMode:onPieceEnter() self:doStackQuality() end function GameMode:onGameOver() if not self.training then VCTRL.toggle(false) end if not self.input_playback and not self.training and not PENTO_MODE then if not self.did_grades then self.grade_score = self.grade_score + self.speed_level if #self.speed_table >= 49 then self:updateGradeHistory() end hiscore_pos = self:updateHiScores() FILE.write(SAVE_DIR..self.player_name.."_grade_history.sav", self.grade_history) self.did_grades = true end self:drawEndScoringInfo() elseif not self.did_grades then self.grade_score = self.grade_score + self.speed_level self.did_grades = true end if PENTO_MODE and not self.training then self:drawEndScoringInfo() end end function GameMode:drawEndScoringInfo() love.graphics.setColor(1, 1, 1, 1) drawText("Score: ", 247, 135, 1000, "left") drawBigText(string.format("%s", self.grade_score), 247, 150, 100, "center") if not PENTO_MODE then drawText("Best scores:", 247, 220, 1000, "left") local i = 2 while i <= 10 do if i == hiscore_pos[1] or i == hiscore_pos[2] then drawText(self.hi_scores[i-1]..' - '..self.hi_scores[i], 255, 220+(i*10), 1000, "left", {0,1,0,1}) else drawText(self.hi_scores[i-1]..' - '..self.hi_scores[i], 255, 220+(i*10), 1000, "left") end i = i + 2 end end end function GameMode:onGameComplete() self:onGameOver() 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 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 function GameMode:dasCut() self.das.frames = math.max( self.das.frames - self:getDasCutDelay(), -(self:getDasCutDelay() + 1) ) 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 if not playedReadySE then playedReadySE = true PlaySEOnce("ready") end self.ready_frames = self.ready_frames - 1 if self.ready_frames == 0 then self:initializeOrHold(inputs, ruleset) end elseif self.lcd > 0 then self.lcd = self.lcd - 1 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.are = self.are - 1 if self.are == 0 then self:initializeOrHold(inputs, ruleset) end end end function GameMode:initializeOrHold(inputs, ruleset) self:initializeNextPiece(inputs, ruleset, self.next_queue[1]) self:onPieceEnter() if not self.grid:canPlacePiece(self.piece) then self.game_over = true 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) 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, false, ( self.frames == 0 or self:getARE() ~= 0 ) and self.irs or false, self.lastdir ) self.piece_hard_dropped = false self.piece_soft_locked = false if self.piece:isDropBlocked(self.grid) and self.grid:canPlacePiece(self.piece) then PlaySE("bottom") end if generate_next_piece == nil then table.remove(self.next_queue, 1) table.insert(self.next_queue, self:getNextPiece(ruleset)) end end function GameMode:animation(x, y, skin, colour) return { 1, 1, 1, -0.25 + 1.25 * (self.lcd / self.last_lcd), skin, colour, 200 + x * 16, y * 16 } end function GameMode:canDrawLCA() return self.lcd > 0 end function GameMode:drawLineClearAnimation() local grid_position = {x=200,y=64} for y, row in pairs(self.cleared_block_table) do for x, block in pairs(row) do local real_x = (x * 16) + grid_position['x'] local real_y = (y * 16) + grid_position['y'] local drift = self.spin_rng[x]*-1 local fall_timer = (self:getLineClearDelay()-self.lcd)/2 fall_timer = (fall_timer*fall_timer)+drift fade_timer = self.lcd/20 love.graphics.setColor(1,1,1,fade_timer) love.graphics.draw(BLOCKS[block.skin][block.colour..'_d'], real_x, real_y+fall_timer) if self.lcd > self:getLineClearDelay() - 5 then love.graphics.setColor(1,1,1,fade_timer*0.3) love.graphics.draw(BLOCKS[block.skin]['W'], real_x, real_y+fall_timer) end end end end function GameMode:drawPiece() if self.piece ~= nil then local b = ( 1 - (self.piece.lock_delay / self:getLockDelay()) ) self.piece:draw(0.50 + 0.50 * b, 0.50 + 0.50 * b, self.grid) end end function GameMode:drawNextQueue(ruleset) local colourscheme 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][COLOUR_SCHEMES.Arika[piece]], pos_x+x*16, pos_y+y*16) end end for i = 1, self.next_queue_length do love.graphics.setColor(1, 1, 1, 1) local highness = -54 local next_piece = self.next_queue[i].shape local skin = self.next_queue[i].skin local rotation = self.next_queue[i].orientation drawPiece(next_piece, skin, ruleset.block_offsets[next_piece][rotation], 136+i*80, highness) end return false end function GameMode:getBackground() bg = math.floor(self.speed_level/6) if bg > 9 then bg = 9 end if (self.total_lines >= 130 or self.frames >= 23640) and self.last_level ~= bg then self.nextbgmflag = true end self.last_level = bg return bg end function GameMode:drawGrid() local greyscale = false if self.game_over or self.completed then greyscale = true end self.grid:draw(greyscale, (self.game_over_frames/50)) end function GameMode:drawInputDisplay(left, top) if self.replay_inputs[self.frames] ~= nil then drawText("•", left+7.5, top+ 8, 1000, "left") drawText(CHAR.key.down , left+ 5, top+18, 1000, "left",{1-boolToInt(self.replay_inputs[self.frames]['down']),1,1-boolToInt(self.replay_inputs[self.frames]['down']),1}) drawText(CHAR.key.left , left- 5, top+ 8, 1000, "left",{1-boolToInt(self.replay_inputs[self.frames]['left']),1,1-boolToInt(self.replay_inputs[self.frames]['left']),1}) drawText(CHAR.key.right, left+ 15, top+ 8, 1000, "left",{1-boolToInt(self.replay_inputs[self.frames]['right']),1,1-boolToInt(self.replay_inputs[self.frames]['right']),1}) drawText("L", left+ 35, top+ 8, 1000, "left",{1-boolToInt(self.replay_inputs[self.frames]['rotate_left']),1,1-boolToInt(self.replay_inputs[self.frames]['rotate_left']),1}) drawText("R", left+ 50, top+ 8, 1000, "left",{1-boolToInt(self.replay_inputs[self.frames]['rotate_right']),1,1-boolToInt(self.replay_inputs[self.frames]['rotate_right']),1}) drawText("L", left+ 65, top+ 8, 1000, "left",{1-boolToInt(self.replay_inputs[self.frames]['rotate_left2']),1,1-boolToInt(self.replay_inputs[self.frames]['rotate_left2']),1}) drawText("R", left+ 80, top+ 8, 1000, "left",{1-boolToInt(self.replay_inputs[self.frames]['rotate_right2']),1,1-boolToInt(self.replay_inputs[self.frames]['rotate_right2']),1}) else drawText("•" , left+7.5, top+ 8, 1000, "left") drawText(CHAR.key.down , left+ 5, top+18, 1000) drawText(CHAR.key.left , left- 5, top+ 8, 1000) drawText(CHAR.key.right, left+ 15, top+ 8, 1000) drawText("L" , left+ 35, top+ 8, 1000, "left") drawText("R" , left+ 50, top+ 8, 1000, "left") drawText("L" , left+ 65, top+ 8, 1000, "left") drawText("R" , left+ 80, top+ 8, 1000, "left") end end function GameMode:drawSpeedStats(left, top) love.graphics.setColor(0,0,0,0.5) love.graphics.rectangle("fill", left+3, top+3, 190, 145, 10, 10) love.graphics.setColor(0.05,0.05,0.05,1) love.graphics.rectangle("fill", left, top, 190, 145, 10, 10) drawText("Efficiency Bonus: ", left+15, top+5, 1000, "left") local lines = self.total_lines if lines == 0 then lines = 1 end if self.move_count == 0 then drawText(string.format(" %4d Num. of Moves", self.target), left+15, top+20, 1000, "left") else drawText(string.format(" %4d Num. of Moves", self.move_count), left+15, top+20, 1000, "left") end drawText(string.format("/ %4d Active Frames", self.last_active), left+15, top+35, 1000, "left") drawText(string.format("= %1.2f\n (0 added for hole)\n %1.2f %dpc Average\nx %s x Lines\n+ %4d", self.last_percent, self.total_speed_loss, #self.speed_table, self.bonus_components['speed'], self.last_speed), left+15, top+50, 1000, "left") end function GameMode:drawLinesStats(left, top) love.graphics.setColor(0,0,0,0.5) love.graphics.rectangle("fill", left+3, top+3, 190, 90, 10, 10) love.graphics.setColor(0.05,0.05,0.05,1) love.graphics.rectangle("fill", left, top, 190, 90, 10, 10) local lines = self.total_lines if lines == 0 then lines = 1 end drawText("Lines Bonus: ", left+15, top+10, 1000, "left") -- drawText(string.format("+ %4d %3d%%", self.lineClearPoints[self.lines_cleared], (self.score_totals['lines']/(self.lineClearPoints[4]*math.ceil(lines/4)))*100), left+15, top+25, 1000, "left") drawText(string.format("2 x Lines = %d\n3 x Lines = %d\n4 x Lines = %d", self.lineClearPoints[2], self.lineClearPoints[3], self.lineClearPoints[4]), left+15, top+25, 1000, "left") end function GameMode:drawScoringInfo() -- Name & Grade love.graphics.setColor(0,0,0,0.5) love.graphics.rectangle("fill", 98, 83, 110, 180, 10, 10) love.graphics.setColor(0.05,0.05,0.05,1) love.graphics.rectangle("fill", 95, 80, 110, 180, 10, 10) if not PENTO_MODE then drawText("Grade:", 100, 128, 1000, "left") end -- Line & Level if self.input_playback or SETTINGS["lines"] then love.graphics.setColor(0,0,0,0.5) love.graphics.rectangle("fill", 241, 407, 116, 40, 5, 5) --lines love.graphics.setColor(0.05,0.05,0.05,1) love.graphics.rectangle("fill", 239, 405, 116, 40, 5, 5) --lines drawText(string.format("Level: %2d\nLines: %3d/300", self:getBackground(), self.total_lines), 249, 408, 1000, "left") end -- REPLAY if self.input_playback then love.graphics.setColor(0,0,0,0.5) love.graphics.rectangle("fill", 68, 270, 140, 190, 10, 10) love.graphics.setColor(0.05,0.05,0.05,1) love.graphics.rectangle("fill", 65, 267, 140, 190, 10, 10) drawText(string.format("REPLAY IN PROGRESS\n\n\n\n\n\n\n\n\n%s", formatTime(self.frames)), 70, 275, 1000, "left") drawBigText(string.format("%s", self.grade), 100, 143, 1000, "left") self:drawInputDisplay(103,185) elseif not PENTO_MODE then if math.mod(self.grade_change_flash, 5) ~= 0 then drawBigText(string.format("%s", self.gradeNames[self.grade]), 100, 143, 1000, "left", self.grade_change_color) else drawBigText(string.format("%s", self.gradeNames[self.grade]), 100, 143, 1000, "left") end local points_text = nil if self.grade == 1 and self.grade_history[2] == 2 then points_text = " |.." elseif self.grade == 1 and self.grade_history[2] == 3 then points_text = " .|." elseif self.grade == 1 and self.grade_history[2] == 4 then points_text = " ..|" elseif self.grade_history[2] == 0 then points_text = "|...." elseif self.grade_history[2] == 1 then points_text = ".|..." elseif self.grade_history[2] == 2 then points_text = "..|.." elseif self.grade_history[2] == 3 then points_text = "...|." elseif self.grade_history[2] == 4 then points_text = "....|" end if self.grade > 1 then points_text = '-'..points_text else points_text = ' '..points_text end if self.grade < 28 then points_text = points_text..'+' end drawText("Promotion\nMeter:", 100, 174, 1000, "left") if self.point_flash > 0 then drawBigText(points_text, 100, 208, 1000, "left", self.point_flash_color) else drawBigText(points_text, 100, 208, 1000, "left") end end if (self.game_over or self.completed) and self.game_over_frames <= 50 and not self.input_playback and not self.training and not PENTO_MODE then drawText("SAVING, PLEASE WAIT", 232, 460, 1000, "left") end drawText("Name:", 100, 83, 1000, "left") drawBigText(self.player_name:upper(), 100, 98, 1000, "left") drawText("Ver. 2", 550, 435, 1000, "left") if self.input_playback then self:drawSpeedStats(385, 99) self:drawLinesStats(385, 251) love.graphics.setColor(0,0,0,0.5) love.graphics.rectangle("fill", 385+3, 348+3, 190, 50, 10, 10) love.graphics.setColor(0.05,0.05,0.05,1) love.graphics.rectangle("fill", 385, 348, 190, 50, 10, 10) drawText(string.format("Added: %d\nScore: %d",self.score_to_add, self.grade_score), 392, 356, 1000, "left") end if self.clear or self.game_over then SOUNDS['bgm_firsthalf']:stop() SOUNDS['bgm_secondhalf']:stop() end end function GameMode:drawBackground() local bg = self:getBackground() local brightness = 0.9 local limit = 4 if self.training then bg = 0 end --if self.training then bg = math.mod(math.floor(((self.frames+1)/1200)), 10) end if bg == 0 or bg == 6 or bg == 9 then limit = 5 end if bg == 5 then limit = 4.76 end if not BACKGROUNDS[bg]:isPlaying() then BACKGROUNDS[bg]:play() end if BACKGROUNDS[bg]:tell() >= limit then BACKGROUNDS[bg]:rewind() end if bg == 0 or bg == 8 or bg == 9 or bg == 3 then brightness = 0.7 end love.graphics.setColor(brightness, brightness, brightness, 1) love.graphics.draw(BACKGROUNDS[bg]) end function GameMode:drawFrame() love.graphics.setColor(1, 1, 1, 1) love.graphics.setLineWidth(1) love.graphics.line(216,80,216,80+(16*self.grid.height)) love.graphics.line(216+(16*self.grid.width),80,216+(16*self.grid.width),80+(16*self.grid.height)) love.graphics.line(216,80+(16*self.grid.height),216+(16*self.grid.width),80+(16*self.grid.height)) love.graphics.line(216,80,216+(16*self.grid.width),80) love.graphics.setColor(0, 0, 0, 1) love.graphics.rectangle( "fill", 216, 80, 16 * self.grid.width, 16 * (self.grid.height) ) 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 drawBigText("Ready...", 246, 240 - 14, 1000) elseif self.ready_frames <= 50 and self.ready_frames > 2 then drawBigText("Go!", 276, 240 - 14, 1000) end end function GameMode:drawCustom() end function GameMode:draw(paused) self:drawBackground() self:drawFrame() self:drawGrid() self:drawPiece() self:drawNextQueue(self.ruleset) if not self.training then self:drawScoringInfo() end self:drawReadyGo() self:drawCustom() if self:canDrawLCA() then self:drawLineClearAnimation() end if self.completed then self:onGameComplete() elseif self.game_over then self:onGameOver() end end return GameMode