Files
tromi_mobile/game/gamemode.lua
Squishy (C6H12O6+NaCl+H2O) 3343d8711b V0 version
Add ``.gitignore``

Update ``.vscode\settings.json``

Main file changed a bit

Replace every single ``io.open`` into ``fs.read()``

Add ``input.waiting2trigger`` as buffer for too quick inputs

Replace ``binser`` with ``bitser``

Add the missing buffer logical code in training mode

Add a debug connector

Not a big update

Update VirtualControl.lua

Update in vctrl system

Trimming some unnecessary empty lines in classic library

Update virtual control stuff

Replace ``table.getn`` with ``#`` and ``scene`` with ``SCENE``

Renaming and moving some modules

Removing unnecessary ``local mino = {...}``

Add loading screen

Update loading screen

Apply replay patch

Not showing virtual control on computer

Adding touch screen configuration scene (placeholder)

Fix loading screen

update virtual control texture

Do some preparation for touch config screen

Quick patch

Compress background

Not important uodates

Small changes on how virtual key call action

Add ``SCENE:onInputMove``

Apply V2.2 patch

Clean up unnecessary imports

Test

.

Remove a redudant global variable

Small change

Split up alpha number

Sorting code

Update storeInput function

Optimize replay storing, saving and reading

Add VCTRL.export (for saving feature)

Remove unnecessary imports

Redesign loading screen

Replace loading screen

Make a simple BUTTON module

Update BUTTON module

Update button module

Add new callback

Add new callback

TEST

Update simple-button module

Update simple button module

Set default draw function for button

Add scene type notation

TEST

Not important updates

Design a error screen

Small update

Remove error key

Update

TEST

TEST

Test

TEST

TEST

Update button module

TEST

TEST

TEST

TEST

TEST

TEST

TEST

TEST

test

TEST

TEST

TEST

test

TEST

test

Fix a bug in VCTRL module that affect to SCENE:onInputRelease

Moving VCTRL related calls and adding buttons for name entry screen

Add type notation

Update modules

Final update for touch configuration scene

Fix 2 buttons can be highlighted at the same time in simple-button module

Narrow the safe border

Remove id = b (it was there for test)

Update of touch configuration scene

Add touch gesture for replay and input configuration scene

Add buttons for Replay, add MENU to go out after finishing game or in 20G Training mode

TEST

Fix some bugs (TEST)

Fix lỗi giữa đêm

Fix bug again

It should work imo

TEST

Fix SCENE:onInputMove{type="touch"} is not working

Fix bug once again (DONE!)

Temproraily allowing save

Fix settings module

Fix VCTRL.exportAll()

Fix VCTRL.exportAll returns userdata

Reverse a change

Fix forgetting to import virtual control settings

Fix grid drawing

Fix bugs related to the first time launching game

Add README file

Add README file

Update README and add LICENSE files

Update README

Add TV remote code

Disable debug code

Fix Android code

Small fix

Rename LICENSE to COPYING

Moving scene.lua to modules folder

Add new libraries

Make a new FILE API and add a simple error screen in case most thing went down

Change special code, add a way to skip keys

Update icon + README file

Rename screenshot file

Update README

Updating README file

Replace loading screen

Update README

Update virtual control texture

Fix virtual button method

Update README

Add icon font

Add importing and exporting replays

Update touch control

Update conf.lua

Replacing font, to avoid license issue

convert indents to spaces

Update font related stuff

Replace font

Updating README file

Update virtual control texture
2024-06-05 18:54:11 +07:00

1121 lines
42 KiB
Lua

local Object = require 'libs.classic'
local bit = require("bit")
local lualzw = require 'libs.lualzw'
local bitser = require 'libs.bitser'
local playedReadySE = false
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)
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.training 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)
drawBoldText(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.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