diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a96e8aee..2adc6826 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -143,7 +143,8 @@ jobs: build-android: runs-on: ubuntu-latest needs: [get-info, build-core, auto-test] - if: github.event_name != 'pull_request' + # if: github.event_name != 'pull_request' + if: ${{ !always() }} env: OUTPUT_FOLDER: ./build RELEASE_FOLDER: ./release @@ -216,7 +217,8 @@ jobs: build-ios: runs-on: macos-latest needs: [get-info, build-core, auto-test] - if: github.event_name != 'pull_request' + # if: github.event_name != 'pull_request' + if: ${{ !always() }} env: OUTPUT_FOLDER: ./build RELEASE_FOLDER: ./release @@ -297,6 +299,7 @@ jobs: build-linux: runs-on: ubuntu-latest needs: [get-info, build-core, auto-test] + if: ${{ !always() }} env: OUTPUT_FOLDER: ./build RELEASE_FOLDER: ./release @@ -380,7 +383,8 @@ jobs: build-macos-appstore: runs-on: macos-latest needs: [get-info, build-core, auto-test] - if: github.event_name != 'pull_request' + # if: github.event_name != 'pull_request' + if: ${{ !always() }} env: OUTPUT_FOLDER: ./build RELEASE_FOLDER: ./release @@ -464,7 +468,8 @@ jobs: build-macos-portable: runs-on: macos-latest needs: [get-info, build-core, auto-test] - if: github.event_name != 'pull_request' + # if: github.event_name != 'pull_request' + if: ${{ !always() }} env: OUTPUT_FOLDER: ./build RELEASE_FOLDER: ./release @@ -560,6 +565,7 @@ jobs: build-windows: runs-on: windows-latest needs: [get-info, build-core, auto-test] + if: ${{ !always() }} env: OUTPUT_FOLDER: ./build RELEASE_FOLDER: ./release diff --git a/Zframework b/Zframework index 20a5757a..4aa87c61 160000 --- a/Zframework +++ b/Zframework @@ -1 +1 @@ -Subproject commit 20a5757a9c6f90c484ec550d5a048cf04987d9bf +Subproject commit 4aa87c6147c259b6ad2aff918c5b956f531e00e5 diff --git a/conf.lua b/conf.lua index f66d41bf..a62defa6 100644 --- a/conf.lua +++ b/conf.lua @@ -2,6 +2,15 @@ SYSTEM=love._os if SYSTEM=='OS X' then SYSTEM='macOS' end MOBILE=SYSTEM=='Android' or SYSTEM=='iOS' FNNS=SYSTEM:find'\79\83'-- What does FNSF stand for? IDK so don't ask me lol +if SYSTEM=='Web' then + local oldRead=love.filesystem.read + function love.filesystem.read(name,size) + if love.filesystem.getInfo(name) then + return oldRead(name,size) + end + end +end + function love.conf(t) local identity='Techmino' local msaa=0 diff --git a/main.lua b/main.lua index 794b0957..a3915726 100644 --- a/main.lua +++ b/main.lua @@ -10,8 +10,7 @@ Instructions: 1. I made a framework called Zframework, *most* code in Zframework are not directly relevant to game; 2. "xxx" are texts for reading by player, 'xxx' are string values just used in program; - 3. Some goto statement are used for better performance. All goto-labes have detailed names so don't be afraid; - 4. Except "gcinfo" function of lua itself, other "gc" are short for "graphics"; + 3. Except "gcinfo" function of lua itself, other "gc" are short for "graphics"; ]]-- @@ -560,16 +559,15 @@ applySettings() -- Load replays for _,fileName in next,fs.getDirectoryItems('replay') do - if fileName:sub(12,12):match("[a-zA-Z]") then + if fileName:sub(12,12):match("[a-zA-Z]") then repeat local date,mode,version,player,seed,setting,mod - local fileData=fs.read('replay/'..fileName) + local success,fileData=true,fs.read('replay/'..fileName) date, fileData=STRING.readLine(fileData)date=date:gsub("[a-zA-Z]","") mode, fileData=STRING.readLine(fileData)mode=MODE_UPDATE_MAP[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 + if not success then break end seed, fileData=STRING.readLine(fileData) setting,fileData=STRING.readLine(fileData)setting=JSON.decode(setting) mod, fileData=STRING.readLine(fileData)mod=JSON.decode(mod) @@ -578,7 +576,7 @@ for _,fileName in next,fs.getDirectoryItems('replay') do not mod or not mode or #mode==0 - then goto BREAK_cannotParse end + then break end fs.remove('replay/'..fileName) local newName=fileName:sub(1,10)..fileName:sub(15) @@ -597,8 +595,7 @@ for _,fileName in next,fs.getDirectoryItems('replay') do ) ) fileName=newName - end - ::BREAK_cannotParse:: + until true end local rep=DATA.parseReplay('replay/'..fileName) table.insert(REPLAY,rep) end diff --git a/parts/bot/bot_9s.lua b/parts/bot/bot_9s.lua index ac13f8ba..5f91e2cc 100644 --- a/parts/bot/bot_9s.lua +++ b/parts/bot/bot_9s.lua @@ -65,14 +65,19 @@ local function _getScore(field,cb,cy) local hole=0 for i=cy+#cb-1,cy,-1 do + local full=true for j=1,10 do if field[i][j]==0 then - goto CONTINUE_notFull + -- goto CONTINUE_notFull + full=false + break end end - discardRow(rem(field,i)) - clear=clear+1 - ::CONTINUE_notFull:: + if full then + -- ::CONTINUE_notFull:: + discardRow(rem(field,i)) + clear=clear+1 + end end if #field==0 then-- PC return 1e99 diff --git a/parts/data.lua b/parts/data.lua index 9498026b..bd8ff4ce 100644 --- a/parts/data.lua +++ b/parts/data.lua @@ -376,22 +376,26 @@ function DATA.parseReplay(fileName,ifFull) return DATA.parseReplayData(fileName,fileData,ifFull) end function DATA.parseReplayData(fileName,fileData,ifFull) - local success,metaData,rep + local success,metaData + local rep={-- unavailable replay object + fileName=fileName, + available=false, + } - if not (fileData and #fileData>0) then goto BREAK_cannotParse end + if not (fileData and #fileData>0) then return rep end-- goto BREAK_cannotParse -- Decompress file success,fileData=pcall(love.data.decompress,'string','zlib',fileData) - if not success then goto BREAK_cannotParse end + if not success then return rep end-- goto BREAK_cannotParse -- Load metadata metaData,fileData=STRING.readLine(fileData) metaData=JSON.decode(metaData) - if not metaData then goto BREAK_cannotParse end + if not metaData then return rep end-- goto BREAK_cannotParse -- Convert ancient replays metaData.mode=MODE_UPDATE_MAP[metaData.mode] or metaData.mode - if not MODES[metaData.mode] then goto BREAK_cannotParse end + if not MODES[metaData.mode] then return rep end-- goto BREAK_cannotParse -- Create replay object rep={ @@ -409,13 +413,6 @@ function DATA.parseReplayData(fileName,fileData,ifFull) tasUsed=metaData.tasUsed, } if ifFull then rep.data=fileData end - do return rep end - - -- Create unavailable replay object - ::BREAK_cannotParse:: - return { - fileName=fileName, - available=false, - } + return rep end return DATA diff --git a/parts/eventsets/secret_grade.lua b/parts/eventsets/secret_grade.lua index 81b42f1a..c3d17d09 100644 --- a/parts/eventsets/secret_grade.lua +++ b/parts/eventsets/secret_grade.lua @@ -76,15 +76,17 @@ return { D.rankPts=1 for i=1,#P.field do local h=getOpenHole(i) + local flag for j=1,10 do - if P.field[i][j]>0 and h==j then goto post_pts_calc end - if P.field[i][j]==0 and h~=j then goto post_pts_calc end + if P.field[i][j]>0 and h==j then flag=true break end-- goto post_pts_calc + if P.field[i][j]==0 and h~=j then flag=true break end-- goto post_pts_calc end - if i==#P.field then goto post_pts_calc end - if P.field[i+1][h]==0 then goto post_pts_calc end + if flag then break end + if i==#P.field then break end-- goto post_pts_calc + if P.field[i+1][h]==0 then break end-- goto post_pts_calc D.rankPts=D.rankPts+1 end - ::post_pts_calc:: + -- ::post_pts_calc:: generateGuide(D.rankPts+20) end } diff --git a/parts/gameFuncs.lua b/parts/gameFuncs.lua index a79828d1..aefff5f6 100644 --- a/parts/gameFuncs.lua +++ b/parts/gameFuncs.lua @@ -981,7 +981,7 @@ do-- function resetGameData(args) GAME.replaying=true else GAME.frameStart=args:find'n' and 0 or 180-SETTING.reTime*60 - GAME.seed=seed or math.random(1046101471,2662622626) + GAME.seed=seed or math.random(1046101471) GAME.pauseTime=0 GAME.pauseCount=0 GAME.saved=false diff --git a/parts/gameTables.lua b/parts/gameTables.lua index f8000a95..cee2bc22 100644 --- a/parts/gameTables.lua +++ b/parts/gameTables.lua @@ -652,7 +652,7 @@ do-- Userdata tables showSpike=true, highCam=true, nextPos=true, - fullscreen=true, + fullscreen=SYSTEM~='Web', portrait=false, msaa=0, bg='on', diff --git a/parts/player/player.lua b/parts/player/player.lua index 34f70158..40380251 100644 --- a/parts/player/player.lua +++ b/parts/player/player.lua @@ -77,16 +77,19 @@ function Player:createLockFX() for i=1,#CB do local y=self.curY+i-1 local L=self.clearedRow + local skip for j=1,#L do - if L[j]==y then goto CONTINUE_skip end + if L[j]==y then skip=true break end-- goto CONTINUE_skip end - y=-30*y - for j=1,#CB[1] do - if CB[i][j] then - ins(self.lockFX,{30*(self.curX+j-2),y,0,t}) + if not skip then + y=-30*y + for j=1,#CB[1] do + if CB[i][j] then + ins(self.lockFX,{30*(self.curX+j-2),y,0,t}) + end end end - ::CONTINUE_skip:: + -- ::CONTINUE_skip:: end end end @@ -610,6 +613,7 @@ do-- function Player:dropPosition(x,y,size) vy=vy+.0626 self:setPosition(x,y,size) if y>2600 then + table.remove(PLAYERS,TABLE.find(PLAYERS,self)) return true end end @@ -1041,16 +1045,20 @@ function Player:_checkClear(field,start,height,CB,CX) h=h+1 -- Row filled + local full=true for x=1,10 do if field[h][x]<=0 then - goto CONTINUE_notFull + full=false + break-- goto CONTINUE_notFull end end - cc=cc+1 - if field[h].garbage then gbcc=gbcc+1 end - ins(self.clearingRow,h-cc+1) - ins(self.clearedRow,h) - ::CONTINUE_notFull:: + if full then + cc=cc+1 + if field[h].garbage then gbcc=gbcc+1 end + ins(self.clearingRow,h-cc+1) + ins(self.clearedRow,h) + end + -- ::CONTINUE_notFull:: end return cc,gbcc end @@ -1265,20 +1273,22 @@ function Player:hold_norm(ifpre) y=y+(#C.bk-#H.bk)*.5 local iki=phyHoldKickX[x==int(x)] + local success for Y=int(y),ceil(y+.5) do for i=1,#iki do local X=x+iki[i] if not self:ifoverlap(H.bk,X,Y) then x,y=X,Y - goto BREAK_success + success=true + break end end + if success then break end end - -- All test failed, interrupt with sound + if not success then -- All test failed, interrupt with sound SFX.play('drop_cancel') - do return end - -- - ::BREAK_success:: + return + end self.spinLast=false @@ -1332,20 +1342,22 @@ function Player:hold_swap(ifpre) y=y+(#C.bk-#H.bk)*.5 local iki=phyHoldKickX[x==int(x)] - for Y=int(y),ceil(y+.5) do - for i=1,#iki do - local X=x+iki[i] - if not self:ifoverlap(H.bk,X,Y) then - x,y=X,Y - goto BREAK_success + local success + for Y=int(y),ceil(y+.5) do + for i=1,#iki do + local X=x+iki[i] + if not self:ifoverlap(H.bk,X,Y) then + x,y=X,Y + success=true + break + end end + if success then break end end - end - -- All test failed, interrupt with sound + if not success then -- All test failed, interrupt with sound SFX.play('finesseError') - do return end - -- - ::BREAK_success:: + return + end self.spinLast=false @@ -2508,90 +2520,92 @@ local function update_alive(P,dt) local stopAtFalling -- Falling animation - if P.falling>0 then - stopAtFalling=true - P:_updateFalling(P.falling-1) + repeat if P.falling>0 then - goto THROW_stop - end - end - - -- Update block state - if P.control then - -- Try spawn new block - if not P.cur then - if not stopAtFalling and P.waiting>0 then - P.waiting=P.waiting-1 + stopAtFalling=true + P:_updateFalling(P.falling-1) + if P.falling>0 then + break-- goto THROW_stop end - if P.waiting<=0 then - P:popNext() - end - goto THROW_stop end - -- Natural block falling - if P.cur then - if P.curY>P.ghoY then - local D=P.dropDelay - local dist-- Drop distance - if D>1 then - D=D-1 - if P.keyPressing[7] and P.downing>=ENV.sddas then - D=D-ceil(ENV.drop/ENV.sdarr) - end - if D<=0 then - dist=1 - P.dropDelay=(D-1)%ENV.drop+1 - else - P.dropDelay=D - goto THROW_stop - end - elseif D==1 then-- We don't know why dropDelay is 1, so checking ENV.drop>1 is neccessary - if ENV.drop>1 and P.downing>=ENV.sddas and (P.downing-ENV.sddas)%ENV.sdarr==0 then - dist=2 - else - dist=1 - end - -- Reset drop delay - P.dropDelay=ENV.drop - else-- High gravity case (>1G) - -- Add extra 1 if time to auto softdrop - if P.downing>ENV.sddas and (P.downing-ENV.sddas)%ENV.sdarr==0 then - dist=1/D+1 - else - dist=1/D - end + -- Update block state + if P.control then + -- Try spawn new block + if not P.cur then + if not stopAtFalling and P.waiting>0 then + P.waiting=P.waiting-1 end + if P.waiting<=0 then + P:popNext() + end + break-- goto THROW_stop + end - -- Limit dropping to ghost at max - dist=min(dist,P.curY-P.ghoY) - - -- Drop and create FXs - if ENV.moveFX and ENV.block and dist>1 then - for _=1,dist do - P:createMoveFX('down') - P.curY=P.curY-1 + -- Natural block falling + if P.cur then + if P.curY>P.ghoY then + local D=P.dropDelay + local dist-- Drop distance + if D>1 then + D=D-1 + if P.keyPressing[7] and P.downing>=ENV.sddas then + D=D-ceil(ENV.drop/ENV.sdarr) + end + if D<=0 then + dist=1 + P.dropDelay=(D-1)%ENV.drop+1 + else + P.dropDelay=D + break-- goto THROW_stop + end + elseif D==1 then-- We don't know why dropDelay is 1, so checking ENV.drop>1 is neccessary + if ENV.drop>1 and P.downing>=ENV.sddas and (P.downing-ENV.sddas)%ENV.sdarr==0 then + dist=2 + else + dist=1 + end + -- Reset drop delay + P.dropDelay=ENV.drop + else-- High gravity case (>1G) + -- Add extra 1 if time to auto softdrop + if P.downing>ENV.sddas and (P.downing-ENV.sddas)%ENV.sdarr==0 then + dist=1/D+1 + else + dist=1/D + end end + + -- Limit dropping to ghost at max + dist=min(dist,P.curY-P.ghoY) + + -- Drop and create FXs + if ENV.moveFX and ENV.block and dist>1 then + for _=1,dist do + P:createMoveFX('down') + P.curY=P.curY-1 + end + else + P.curY=P.curY-dist + end + + P.spinLast=false + P:freshBlock('fresh') + P:checkTouchSound() else - P.curY=P.curY-dist - end - - P.spinLast=false - P:freshBlock('fresh') - P:checkTouchSound() - else - P.lockDelay=P.lockDelay-1 - if P.lockDelay>=0 then - goto THROW_stop - end - P:drop(true) - if P.bot then - P.bot:lockWrongPlace() + P.lockDelay=P.lockDelay-1 + if P.lockDelay>=0 then + break-- goto THROW_stop + end + P:drop(true) + if P.bot then + P.bot:lockWrongPlace() + end end end end - end - ::THROW_stop:: + until true + -- ::THROW_stop:: -- B2B bar animation if P.b2b1~=P.b2b then @@ -2911,16 +2925,20 @@ function Player:lose(force) self:dropPosition() freshPlayerPosition('update') + local finished=true for i=1,#PLY_ALIVE-1 do if PLY_ALIVE[i].group==0 or PLY_ALIVE[i].group~=PLY_ALIVE[i+1].group then - goto BREAK_notFinished + finished=false + break-- goto BREAK_notFinished end end -- Only 1 people or only 1 team survived, they win - for i=1,#PLY_ALIVE do - PLY_ALIVE[i]:win() + if finished then + for i=1,#PLY_ALIVE do + PLY_ALIVE[i]:win() + end end - ::BREAK_notFinished:: + -- ::BREAK_notFinished:: end end --------------------------<\Event>-------------------------- diff --git a/parts/player/seqGenerators.lua b/parts/player/seqGenerators.lua index 191ba3be..0de68d8f 100644 --- a/parts/player/seqGenerators.lua +++ b/parts/player/seqGenerators.lua @@ -65,13 +65,15 @@ local seqGenerators={ local r for _=1,hisLen do-- Reroll up to [hisLen] times r=rndGen:random(len) + local rollAgain for i=1,hisLen do if r==history[i] then - goto CONTINUE_rollAgain + rollAgain=true + break-- goto CONTINUE_rollAgain end end - do break end - ::CONTINUE_rollAgain:: + if not rollAgain then break end + -- ::CONTINUE_rollAgain:: end if history[1]~=0 then P:getNext(seq0[r]) @@ -128,16 +130,21 @@ local seqGenerators={ -- print"======================" -- Pick a mino from pool local tryTime=0 - ::REPEAT_pickAgain:: - local r=_poolPick()-- Random mino-index in pool - for i=1,len do - if r==history[i] then - tryTime=tryTime+1 - if tryTime1 and not field[ruy-1][x2] do ruy=ruy-1 end while rdy1 and not field[y2][dlx-1] do dlx=dlx-1 end while drx2 then - merged=true - self.board=b1 - b1[y][x]=cur+1 + repeat-- ::REPEAT_merge:: + local repeating + local cur=self.board[y][x] + local b1=TABLE.shift(self.board) + self.mergedTiles={} + local count=self:merge(b1,cur,y,x) + if count>2 then + merged=true + self.board=b1 + b1[y][x]=cur+1 - if cur+1>self.maxTile then - self.maxTile=cur+1 - if self.maxTile>=6 then - ins(self.progress,("%s - %.3fs"):format(self.maxTile,TIME()-player.startTime)) + if cur+1>self.maxTile then + self.maxTile=cur+1 + if self.maxTile>=6 then + ins(self.progress,("%s - %.3fs"):format(self.maxTile,TIME()-player.startTime)) + end + SFX.play('reach') end - SFX.play('reach') - end - local getScore=4^cur*count - self.score=self.score+getScore - TEXT.show(getScore,player.x+self.selectX*100-50,player.y+self.selectY*100-50,40,'score',1.626/math.log(getScore,3)) - for i=1,#self.mergedTiles do - newMergeFX(self.mergedTiles[i][1],self.mergedTiles[i][2],cur+1) + local getScore=4^cur*count + self.score=self.score+getScore + TEXT.show(getScore,player.x+self.selectX*100-50,player.y+self.selectY*100-50,40,'score',1.626/math.log(getScore,3)) + for i=1,#self.mergedTiles do + newMergeFX(self.mergedTiles[i][1],self.mergedTiles[i][2],cur+1) + end + repeating=true-- goto REPEAT_merge end - goto REPEAT_merge - end + until not repeating ins(self.nexts,self:newTile()) diff --git a/parts/scenes/customGame.lua b/parts/scenes/customGame.lua index e928bf84..b134259f 100644 --- a/parts/scenes/customGame.lua +++ b/parts/scenes/customGame.lua @@ -104,20 +104,23 @@ function scene.keyDown(key,isRep) elseif key=='v' and kb.isDown('lctrl','rctrl') or key=='cV' then local str=sys.getClipboardText() local args=str:sub((str:find(":") or 0)+1):split("!") - if #args<4 then goto THROW_fail end - if not ( - DATA.pasteQuestArgs(args[1]) and - DATA.pasteSequence(args[2]) and - DATA.pasteMission(args[3]) - ) then goto THROW_fail end - TABLE.cut(FIELD) - FIELD[1]=DATA.newBoard() - for i=4,#args do - if args[i]:find("%S") and not DATA.pasteBoard(args[i],i-3) and i<#args then goto THROW_fail end - end - MES.new('check',text.importSuccess) - do return end - ::THROW_fail::MES.new('error',text.dataCorrupted) + repeat + if #args<4 then break end-- goto THROW_fail + if not ( + DATA.pasteQuestArgs(args[1]) and + DATA.pasteSequence(args[2]) and + DATA.pasteMission(args[3]) + ) then break end-- goto THROW_fail + TABLE.cut(FIELD) + FIELD[1]=DATA.newBoard() + for i=4,#args do + if args[i]:find("%S") and not DATA.pasteBoard(args[i],i-3) and i<#args then break end-- goto THROW_fail + end + MES.new('check',text.importSuccess) + return + until true + -- ::THROW_fail:: + MES.new('error',text.dataCorrupted) else return true end diff --git a/parts/scenes/custom_field.lua b/parts/scenes/custom_field.lua index 5bac9175..ac7bc33b 100644 --- a/parts/scenes/custom_field.lua +++ b/parts/scenes/custom_field.lua @@ -195,14 +195,17 @@ function scene.keyDown(key) local F=FIELD[page] local cleared=false for i=#F,1,-1 do + local full for j=1,10 do - if F[i][j]<=0 then goto CONTINUE_notFull end + if F[i][j]<=0 then full=false break end-- goto CONTINUE_notFull end - cleared=true - SYSFX.newShade(3,200,660-30*i,300,30) - SYSFX.newRectRipple(3,200,660-30*i,300,30) - rem(F,i) - ::CONTINUE_notFull:: + if full then + cleared=true + SYSFX.newShade(3,200,660-30*i,300,30) + SYSFX.newRectRipple(3,200,660-30*i,300,30) + rem(F,i) + end + -- ::CONTINUE_notFull:: end if cleared then SFX.play('clear_3',.8) diff --git a/parts/scenes/game.lua b/parts/scenes/game.lua index 66937854..a0dad185 100644 --- a/parts/scenes/game.lua +++ b/parts/scenes/game.lua @@ -195,15 +195,19 @@ function scene.touchMove() for n=1,#keys do local B=keys[n] if B.ava then + local nextKey for i=1,#L,2 do if (L[i]-B.x)^2+(L[i+1]-B.y)^2<=B.r^2 then - goto CONTINUE_nextKey + nextKey=true + break-- goto CONTINUE_nextKey end end - PLAYERS[1]:releaseKey(n) - VK.release(n) + if not nextKey then + PLAYERS[1]:releaseKey(n) + VK.release(n) + end + -- ::CONTINUE_nextKey:: end - ::CONTINUE_nextKey:: end end function scene.keyDown(key,isRep) diff --git a/parts/scenes/net_game.lua b/parts/scenes/net_game.lua index 60a57ffc..c3b731f7 100644 --- a/parts/scenes/net_game.lua +++ b/parts/scenes/net_game.lua @@ -109,15 +109,19 @@ function scene.touchMove() for n=1,#keys do local B=keys[n] if B.ava then + local nextKey for i=1,#L,2 do if (L[i]-B.x)^2+(L[i+1]-B.y)^2<=B.r^2 then - goto CONTINUE_nextKey + nextKey=true + break-- goto CONTINUE_nextKey end end - PLAYERS[1]:releaseKey(n) - VK.release(n) + if not nextKey then + PLAYERS[1]:releaseKey(n) + VK.release(n) + end + -- ::CONTINUE_nextKey:: end - ::CONTINUE_nextKey:: end end function scene.keyDown(key,isRep)