360 lines
9.0 KiB
Lua
360 lines
9.0 KiB
Lua
--[[
|
|
______ __ _
|
|
/_ __/___ _____ / /_ ____ ___ (_)____ ____
|
|
/ / / _ \ / ___// __ \ / __ `__ \ / // __ \ / __ \
|
|
/ / / __// /__ / / / // / / / / // // / / // /_/ /
|
|
/_/ \___/ \___//_/ /_//_/ /_/ /_//_//_/ /_/ \____/
|
|
Techmino is my first "huge project"
|
|
optimization is welcomed if you also love tetromino stacking game
|
|
|
|
Instructions:
|
|
1. I made a framework called Zframework, most code in Zframework are not directly relevant to game;
|
|
2. "xxx" are texts for reading, 'xxx' are string values just in program;
|
|
3. Some goto statement are used for better performance. All goto-labes have detailed names so don't afraid;
|
|
4. Except "gcinfo" function of lua itself, other "gc" are short for "graphics";
|
|
]]--
|
|
|
|
|
|
--Var leak check
|
|
-- setmetatable(_G,{__newindex=function(self,k,v)print('>>'..k)print(debug.traceback():match("\n.-\n\t(.-): "))rawset(self,k,v)end})
|
|
|
|
--Declaration
|
|
goto REM love=require"love"::REM::--Just tell IDE to load love-api, no actual usage
|
|
local fs=love.filesystem
|
|
TIME=love.timer.getTime
|
|
YIELD=coroutine.yield
|
|
SYSTEM=love.system.getOS()
|
|
MOBILE=SYSTEM=='Android'or SYSTEM=='iOS'
|
|
SAVEDIR=fs.getSaveDirectory()
|
|
|
|
--Global Vars & Settings
|
|
DAILYLAUNCH=false
|
|
|
|
--System setting
|
|
math.randomseed(os.time()*626)
|
|
love.setDeprecationOutput(false)
|
|
love.keyboard.setKeyRepeat(true)
|
|
love.keyboard.setTextInput(false)
|
|
love.mouse.setVisible(false)
|
|
|
|
--Load modules
|
|
require'Zframework'
|
|
SCR.setSize(1280,720)--Initialize Screen size
|
|
|
|
--Delete all naked files (from too old version)
|
|
FILE.clear('')
|
|
|
|
--Create directories
|
|
for _,v in next,{'conf','record','replay','cache','lib'}do
|
|
local info=fs.getInfo(v)
|
|
if not info then
|
|
fs.createDirectory(v)
|
|
elseif info.type~='directory'then
|
|
fs.remove(v)
|
|
fs.createDirectory(v)
|
|
end
|
|
end
|
|
|
|
--Load shader files from SOURCE ONLY
|
|
SHADER={}
|
|
for _,v in next,fs.getDirectoryItems('parts/shaders')do
|
|
if fs.getRealDirectory('parts/shaders/'..v)~=SAVEDIR then
|
|
local name=v:sub(1,-6)
|
|
SHADER[name]=love.graphics.newShader('parts/shaders/'..name..'.glsl')
|
|
end
|
|
end
|
|
|
|
require'parts.list'
|
|
require'parts.globalTables'
|
|
require'parts.gametoolfunc'
|
|
|
|
FREEROW= require'parts.freeRow'
|
|
DATA= require'parts.data'
|
|
|
|
TEXTURE= require'parts.texture'
|
|
SKIN= require'parts.skin'
|
|
USERS= require'parts.users'
|
|
NET= require'parts.net'
|
|
VK= require'parts.virtualKey'
|
|
AIFUNC= require'parts.ai'
|
|
AIBUILDER= require'parts.AITemplate'
|
|
PLY= require'parts.player'
|
|
netPLY= require'parts.netPlayer'
|
|
MODES= require'parts.modes'
|
|
|
|
--Initialize field[1]
|
|
FIELD[1]=DATA.newBoard()
|
|
|
|
--First start for phones
|
|
if not fs.getInfo('conf/settings')and MOBILE then
|
|
SETTING.VKSwitch=true
|
|
SETTING.swap=false
|
|
SETTING.powerInfo=true
|
|
SETTING.cleanCanvas=true
|
|
end
|
|
if SETTING.fullscreen then love.window.setFullscreen(true)end
|
|
|
|
--Initialize image libs
|
|
IMG.init{
|
|
lock='mess/lock.png',
|
|
dialCircle='mess/dialCircle.png',
|
|
dialNeedle='mess/dialNeedle.png',
|
|
lifeIcon='mess/life.png',
|
|
badgeIcon='mess/badge.png',
|
|
ctrlSpeedLimit='mess/ctrlSpeedLimit.png',
|
|
speedLimit='mess/speedLimit.png',--Not used, for future C2-mode
|
|
pay1='mess/pay1.png',
|
|
pay2='mess/pay2.png',
|
|
|
|
nakiCH='characters/nakiharu.png',
|
|
miyaCH='characters/miya.png',
|
|
miyaF1='characters/miya_f1.png',
|
|
miyaF2='characters/miya_f2.png',
|
|
miyaF3='characters/miya_f3.png',
|
|
miyaF4='characters/miya_f4.png',
|
|
electric='characters/electric.png',
|
|
hbm='characters/hbm.png',
|
|
|
|
lanterns={
|
|
'lanterns/1.png',
|
|
'lanterns/2.png',
|
|
'lanterns/3.png',
|
|
'lanterns/4.png',
|
|
'lanterns/5.png',
|
|
'lanterns/6.png',
|
|
},
|
|
}
|
|
SKIN.init{
|
|
'crystal_scf',
|
|
'matte_mrz',
|
|
'contrast_mrz',
|
|
'polkadots_scf',
|
|
'toy_scf',
|
|
'smooth_mrz',
|
|
'simple_scf',
|
|
'glass_scf',
|
|
'penta_scf',
|
|
'bubble_scf',
|
|
'minoes_scf',
|
|
'pure_mrz',
|
|
'bright_scf',
|
|
'glow_mrz',
|
|
'plastic_mrz',
|
|
'paper_mrz',
|
|
'yinyang_scf',
|
|
'cartooncup_earety',
|
|
'jelly_miya',
|
|
'brick_notypey',
|
|
'gem_notypey',
|
|
'classic',
|
|
'ball_shaw',
|
|
'retro_notypey',
|
|
'textbone_mrz',
|
|
'coloredbone_mrz',
|
|
'wtf',
|
|
}
|
|
|
|
--Initialize sound libs
|
|
SFX.init((function()
|
|
local L={}
|
|
for _,v in next,fs.getDirectoryItems('media/SFX')do
|
|
if fs.getRealDirectory('media/SFX/'..v)~=SAVEDIR then
|
|
table.insert(L,v:sub(1,-5))
|
|
else
|
|
MES.new('warn',"Dangerous file : %SAVE%/media/SFX/"..v)
|
|
end
|
|
end
|
|
return L
|
|
end)())
|
|
BGM.init((function()
|
|
local L={}
|
|
for _,v in next,fs.getDirectoryItems('media/BGM')do
|
|
if fs.getRealDirectory('media/BGM/'..v)~=SAVEDIR then
|
|
table.insert(L,v:sub(1,-5))
|
|
else
|
|
MES.new('warn',"Dangerous file : %SAVE%/media/BGM/"..v)
|
|
end
|
|
end
|
|
return L
|
|
end)())
|
|
VOC.init{
|
|
'zspin','sspin','lspin','jspin','tspin','ospin','ispin',
|
|
'single','double','triple','techrash',
|
|
'mini','b2b','b3b',
|
|
'perfect_clear','half_clear',
|
|
'win','lose','bye',
|
|
'test','happy','doubt','sad','egg',
|
|
'welcome_voc',
|
|
}
|
|
|
|
--Initialize language lib
|
|
LANG.init(
|
|
{
|
|
require'parts.language.lang_zh',
|
|
require'parts.language.lang_zh2',
|
|
require'parts.language.lang_yygq',
|
|
require'parts.language.lang_en',
|
|
require'parts.language.lang_fr',
|
|
require'parts.language.lang_es',
|
|
require'parts.language.lang_pt',
|
|
require'parts.language.lang_symbol',
|
|
--1. Add language file to LANG folder;
|
|
--2. Require it;
|
|
--3. Add a button in parts/scenes/setting_lang.lua;
|
|
},
|
|
{
|
|
block={
|
|
"Z","S","J","L","T","O","I",
|
|
"Z5","S5","Q","P","F","E",
|
|
"T5","U","V","W","X",
|
|
"J5","L5","R","Y","N","H","I5",
|
|
"I3","C","I2","O1"
|
|
},
|
|
}
|
|
)
|
|
--Load background files from SOURCE ONLY
|
|
for _,v in next,fs.getDirectoryItems('parts/backgrounds')do
|
|
if fs.getRealDirectory('parts/backgrounds/'..v)~=SAVEDIR then
|
|
if v:sub(-3)=='lua'then
|
|
local name=v:sub(1,-5)
|
|
BG.add(name,require('parts.backgrounds.'..name))
|
|
end
|
|
end
|
|
end
|
|
--Load scene files from SOURCE ONLY
|
|
for _,v in next,fs.getDirectoryItems('parts/scenes')do
|
|
if fs.getRealDirectory('parts/scenes/'..v)~=SAVEDIR then
|
|
local sceneName=v:sub(1,-5)
|
|
SCN.add(sceneName,require('parts.scenes.'..sceneName))
|
|
LANG.addScene(sceneName)
|
|
end
|
|
end
|
|
--Load mode files
|
|
for i=1,#MODES do
|
|
local m=MODES[i]--Mode template
|
|
local M=require('parts.modes.'..m.name)--Mode file
|
|
for k,v in next,m do M[k]=v end
|
|
MODES[m.name],MODES[i]=M
|
|
end
|
|
|
|
--Update data
|
|
do
|
|
local needSave
|
|
|
|
if type(STAT.version)~='number'then
|
|
STAT.version=0
|
|
needSave=true
|
|
end
|
|
if STAT.version<1302 then
|
|
FILE.clear_s('')
|
|
end
|
|
if STAT.version<1405 then
|
|
fs.remove('conf/user')
|
|
fs.remove('conf/key')
|
|
end
|
|
if STAT.version<1505 then
|
|
fs.remove('record/bigbang.rec')
|
|
fs.remove('conf/replay')
|
|
end
|
|
if STAT.version~=VERSION.code then
|
|
STAT.version=VERSION.code
|
|
needSave=true
|
|
love.event.quit('restart')
|
|
end
|
|
if SETTING.ghostType=='greyCell'then
|
|
SETTING.ghostType='grayCell'
|
|
needSave=true
|
|
end
|
|
if not SETTING.VKSkin then SETTING.VKSkin=1 end
|
|
if not TABLE.find({8,10,13,17,22,29,37,47,62,80,100},SETTING.frameMul)then
|
|
SETTING.frameMul=100
|
|
end
|
|
SETTING.appLock=nil
|
|
SETTING.dataSaving=nil
|
|
for _,v in next,VK_org do v.color=nil end
|
|
if RANKS.infinite then RANKS.infinite=0 end
|
|
if RANKS.infinite_dig then RANKS.infinite_dig=0 end
|
|
for k in next,RANKS do
|
|
if type(k)=='number'then
|
|
RANKS[k]=nil
|
|
needSave=true
|
|
end
|
|
end
|
|
for k,v in next,oldModeNameTable do
|
|
if RANKS[k]then
|
|
RANKS[v]=RANKS[k]
|
|
RANKS[k]=nil
|
|
end
|
|
k='record/'..k
|
|
if fs.getInfo(k..'.dat')then
|
|
fs.write('record/'..v..'.rec',fs.read(k..'.dat'))
|
|
fs.remove(k..'.dat')
|
|
end
|
|
if fs.getInfo(k..'.rec')then
|
|
fs.write('record/'..v..'.rec',fs.read(k..'.rec'))
|
|
fs.remove(k..'.rec')
|
|
end
|
|
end
|
|
if not RANKS.sprint_10l then
|
|
RANKS.sprint_10l=0
|
|
needSave=true
|
|
end
|
|
|
|
if needSave then
|
|
FILE.save(SETTING,'conf/settings')
|
|
FILE.save(RANKS,'conf/unlock')
|
|
FILE.save(STAT,'conf/data')
|
|
end
|
|
end
|
|
|
|
--Apply system setting
|
|
LANG.set(SETTING.lang)
|
|
VK.setShape(SETTING.VKSkin)
|
|
applyBlockSatur(SETTING.blockSatur)
|
|
applyFieldSatur(SETTING.fieldSatur)
|
|
|
|
--Load replays
|
|
for _,fileName in next,fs.getDirectoryItems('replay')do
|
|
if fileName:sub(12,12):match("[a-zA-Z]")then
|
|
local date,mode,version,player,seed,setting,mod
|
|
local fileData=fs.read('replay/'..fileName)
|
|
date, fileData=STRING.readLine(fileData)date=date:gsub("[a-zA-Z]","")
|
|
mode, fileData=STRING.readLine(fileData)mode=oldModeNameTable[mode]or mode
|
|
version,fileData=STRING.readLine(fileData)
|
|
player, fileData=STRING.readLine(fileData)if player=="Local Player"then player="Stacker"end
|
|
local success
|
|
success,fileData=pcall(love.data.decompress,'string','zlib',fileData)
|
|
if not success then goto BREAK_cannotParse end
|
|
seed, fileData=STRING.readLine(fileData)
|
|
setting,fileData=STRING.readLine(fileData)setting=JSON.decode(setting)
|
|
mod, fileData=STRING.readLine(fileData)mod=JSON.decode(mod)
|
|
if
|
|
not setting or
|
|
not mod or
|
|
not mode or
|
|
#mode==0
|
|
then goto BREAK_cannotParse end
|
|
|
|
fs.remove('replay/'..fileName)
|
|
local newName=fileName:sub(1,10)..fileName:sub(15)
|
|
fs.write('replay/'..newName,
|
|
love.data.compress('string','zlib',
|
|
JSON.encode{
|
|
date=date,
|
|
mode=mode,
|
|
version=version,
|
|
player=player,
|
|
seed=seed,
|
|
setting=setting,
|
|
mod=mod,
|
|
}.."\n"..
|
|
fileData
|
|
)
|
|
)
|
|
fileName=newName
|
|
end
|
|
::BREAK_cannotParse::
|
|
local rep=DATA.parseReplay('replay/'..fileName)
|
|
table.insert(REPLAY,rep)
|
|
end
|
|
table.sort(REPLAY,function(a,b)return a.fileName>b.fileName end) |