------------------------------------------------- --Var P in other files represent Player object!-- ------------------------------------------------- local Player={}--Player class local int,ceil,rnd=math.floor,math.ceil,math.random local max,min,modf=math.max,math.min,math.modf local ins,rem=table.insert,table.remove local resume,yield,status=coroutine.resume,coroutine.yield,coroutine.status local SFX,BGM,VOC,VIB,SYSFX=SFX,BGM,VOC,VIB,SYSFX local FREEROW,TABLE,TEXT,NET,TASK=FREEROW,TABLE,TEXT,NET,TASK local PLAYERS,PLY_ALIVE,GAME=PLAYERS,PLY_ALIVE,GAME local ply_draw=require"parts.player.draw" local ply_update=require"parts.player.update" ---------------------------------------------------- function Player:showText(text,dx,dy,font,style,spd,stop) if self.gameEnv.text then ins(self.bonus,TEXT.getText(text,150+dx,300+dy,font,style,spd,stop)) end end function Player:showTextF(text,dx,dy,font,style,spd,stop) ins(self.bonus,TEXT.getText(text,150+dx,300+dy,font,style,spd,stop)) end function Player:popScore(score,x,y) self:showText( score,x,y, 40-600/(score+20), 'score',2 ) end function Player:createLockFX() local CB=self.cur.bk local t=12-self.gameEnv.lockFX*2 for i=1,#CB do local y=self.curY+i-1 local L=self.clearedRow for j=1,#L do if L[j]==y then goto CONTINUE_skip end 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}) end end ::CONTINUE_skip:: end end function Player:createDropFX(x,y,w,h) ins(self.dropFX,{x,y,w,h,0,13-2*self.gameEnv.dropFX}) end function Player:createMoveFX(dir) local T=10-1.5*self.gameEnv.moveFX local C=self.cur.color local CB=self.cur.bk local x=self.curX-1 local y=self.gameEnv.smooth and self.curY+self.dropDelay/self.gameEnv.drop-2 or self.curY-1 if dir=='left'then for i=1,#CB do for j=#CB[1],1,-1 do if self.cur.bk[i][j]then ins(self.moveFX,{C,x+j,y+i,0,T}) break end end end elseif dir=='right'then for i=1,#CB do for j=1,#CB[1]do if self.cur.bk[i][j]then ins(self.moveFX,{C,x+j,y+i,0,T}) break end end end elseif dir=='down'then for j=1,#CB[1]do for i=#CB,1,-1 do if self.cur.bk[i][j]then ins(self.moveFX,{C,x+j,y+i,0,T}) break end end end else for i=1,#CB do for j=1,#CB[1]do if self.cur.bk[i][j]then ins(self.moveFX,{C,x+j,y+i,0,T}) end end end end end function Player:createSplashFX(h) local L=self.field[h] local size=self.size local y=self.fieldY+size*(self.fieldOff.y+self.fieldBeneath+self.fieldUp+615) for x=1,10 do local c=L[x] if c>0 then SYSFX.newCell( 2.5-self.gameEnv.splashFX*.4, self.skinLib[c], size, self.fieldX+(30*x-15)*size,y-30*h*size, rnd()*5-2.5,rnd()*-1, 0,.6 ) end end end function Player:createClearingFX(y,spd) ins(self.clearFX,{y,0,spd}) end function Player:createBeam(R,send,power,color) local x1,y1,x2,y2 if self.miniMode then x1,y1=self.centerX,self.centerY else x1,y1=self.x+(30*(self.curX+self.cur.sc[2])-30+15+150)*self.size,self.y+(600-30*(self.curY+self.cur.sc[1])+15)*self.size end if R.small then x2,y2=R.centerX,R.centerY else x2,y2=R.x+308*R.size,R.y+450*R.size end local c=minoColor[color] local r,g,b=c[1]*2,c[2]*2,c[3]*2 local a=GAME.modeEnv.royaleMode and not(self.type=='human'or R.type=='human')and .2 or 1 SYSFX.newAttack(1-power*.1,x1,y1,x2,y2,int(send^.7*(4+power)),r,g,b,a*(power+2)*.0626) end ---------------------------------------------------- ---------------------------------------------------- function Player:newTask(code,...) local thread=coroutine.create(code) resume(thread,self,...) if status(thread)~='dead'then ins(self.tasks,{ thread=thread, code=code, args={...}, }) end end function Player:setPosition(x,y,size) size=size or 1 self.x,self.y,self.size=x,y,size if self.miniMode or self.demo then self.fieldX,self.fieldY=x,y self.centerX,self.centerY=x+300*size,y+600*size else self.fieldX,self.fieldY=x+150*size,y self.centerX,self.centerY=x+300*size,y+370*size self.absFieldX,self.absFieldY=x+150*size,y-10*size end end do--function Player:movePosition(x,y,size) local function task_movePosition(self,x,y,size) local x1,y1,size1=self.x,self.y,self.size while true do yield() if (x1-x)^2+(y1-y)^2<1 then self:setPosition(x,y,size) return true else x1=x1+(x-x1)*.126 y1=y1+(y-y1)*.126 size1=size1+(size-size1)*.126 self:setPosition(x1,y1,size1) end end end local function checkPlayer(obj,Ptar) return obj.args[1]==Ptar end function Player:movePosition(x,y,size) TASK.removeTask_iterate(checkPlayer,self) TASK.new(task_movePosition,self,x,y,size or self.size) end end local frameColorList={[0]=COLOR.Z,COLOR.lG,COLOR.lB,COLOR.lV,COLOR.lO} function Player:setFrameColor(c) self.frameColor=frameColorList[c] end function Player:switchKey(id,on) self.keyAvailable[id]=on if not on then self:releaseKey(id) end if self.type=='human'then VK.switchKey(id,on) end end function Player:set20G(if20g) self._20G=if20g self:switchKey(13,not if20g) self:switchKey(14,not if20g) self:switchKey(15,not if20g) self:switchKey(16,not if20g) if if20g and self.AI_mode=='CC'then CC.switch20G(self)end end function Player:setHold(count)--Set hold count (false/true as 0/1) if not count then count=0 elseif count==true then count=1 end self:switchKey(8,count>0) self.gameEnv.holdCount=count self.holdTime=count while self.holdQueue[count+1]do rem(self.holdQueue)end end function Player:setNext(next,hidden)--Set next count (use hidden=true if set env.nextStartPos>1) self.gameEnv.nextCount=next if next==0 then self.drawNext=NULL elseif not hidden then self.drawNext=ply_draw.drawNext_norm else self.drawNext=ply_draw.drawNext_hidden end end function Player:setInvisible(time)--Time in frames if time<0 then self.keepVisible=true self.showTime=1e99 else self.keepVisible=false self.showTime=time end end function Player:setRS(RSname) self.RS=RSlist[RSname]or RSlist.TRS end function Player:getHolePos()--Get a good garbage-line hole position if self.garbageBeneath==0 then return generateLine(self.holeRND:random(10)) else local p=self.holeRND:random(10) if self.field[1][p]<=0 then return generateLine(self.holeRND:random(10)) end return generateLine(p) end end function Player:garbageRelease()--Check garbage buffer and try to release them local n,flag=1 while true do local A=self.atkBuffer[n] if A and A.countdown<=0 and not A.sent then self:garbageRise(19+A.lv,A.amount,A.line) self.atkBufferSum=self.atkBufferSum-A.amount A.sent,A.time=true,0 self.stat.pend=self.stat.pend+A.amount n=n+1 flag=true else break end end if flag and self.AI_mode=='CC'and self.AI_bot then CC.updateField(self)end end function Player:garbageRise(color,amount,line)--Release n-lines garbage to field local _ local t=self.showTime*2 for _=1,amount do ins(self.field,1,FREEROW.get(0,true)) ins(self.visTime,1,FREEROW.get(t)) for i=1,10 do self.field[1][i]=bit.rshift(line,i-1)%2==1 and color or 0 end end self.fieldBeneath=self.fieldBeneath+amount*30 if self.cur then self.curY=self.curY+amount self.ghoY=self.ghoY+amount end self.garbageBeneath=self.garbageBeneath+amount for i=1,#self.clearingRow do self.clearingRow[i]=self.clearingRow[i]+amount end self:freshBlock('push') for i=1,#self.lockFX do _=self.lockFX[i] _[2]=_[2]-30*amount--Shift 30px per line cleared end for i=1,#self.dropFX do _=self.dropFX[i] _[3],_[5]=_[3]+amount,_[5]+amount end if #self.field>self.gameEnv.heightLimit then self:lose()end end local invList={2,1,4,3,5,6,7} function Player:pushLineList(L,mir)--Push some lines to field local l=#L local S=self.gameEnv.skin for i=1,l do local r=FREEROW.get(0) if not mir then for j=1,10 do r[j]=S[L[i][j]]or 0 end else for j=1,10 do r[j]=S[invList[L[i][11-j]]]or 0 end end ins(self.field,1,r) ins(self.visTime,1,FREEROW.get(20)) end self.fieldBeneath=self.fieldBeneath+30*l self.curY=self.curY+l self.ghoY=self.ghoY+l self:freshBlock('push') end function Player:pushNextList(L,mir)--Push some nexts to nextQueue for i=1,#L do self:getNext(mir and invList[L[i]]or L[i]) end end function Player:getCenterX() return self.curX+self.cur.sc[2]-5.5 end function Player:getCenterY() return self.curY-self.cur.sc[1] end function Player:solid(x,y) if x<1 or x>10 or y<1 then return true end if y>#self.field then return false end return self.field[y] [x]>0--to catch bug (nil[*]) end function Player:ifoverlap(bk,x,y) local C=#bk[1] if x<1 or x+C>11 or y<1 then return true end if y>#self.field then return end for i=1,#bk do if self.field[y+i-1]then for j=1,C do if bk[i][j]and self.field[y+i-1][x+j-1]>0 then return true end end end end end function Player:attack(R,send,time,line,fromStream) local atkFX=self.gameEnv.atkFX if GAME.net then if self.type=='human'then--Local player attack others ins(GAME.rep,self.frameRun) ins(GAME.rep, R.sid+ send*0x100+ time*0x10000+ line*0x100000000+ 0x2000000000000 ) if atkFX then self:createBeam(R,send,atkFX,self.cur.color) end end if fromStream and R.type=='human'then--Local player receiving lines ins(GAME.rep,R.frameRun) ins(GAME.rep, self.sid+ send*0x100+ time*0x10000+ line*0x100000000+ 0x1000000000000 ) R:receive(self,send,time,line) end else R:receive(self,send,time,line) if atkFX then self:createBeam(R,send,atkFX,self.cur.color) end end end function Player:receive(A,send,time,line) self.lastRecv=A local B=self.atkBuffer if send+self.atkBufferSum>self.gameEnv.bufferLimit then send=self.gameEnv.bufferLimit-self.atkBufferSum end if send>0 then local m,k=#B,1 while k<=m and time>B[k].countdown do k=k+1 end ins(B,k,{ line=line, amount=send, countdown=time, cd0=time, time=0, sent=false, lv=min(int(send^.69),5), })--Sorted insert(by time) self.atkBufferSum=self.atkBufferSum+send self.stat.recv=self.stat.recv+send if self.sound then SFX.play(send<4 and'blip_1'or'blip_2',min(send+1,5)*.1) end end end function Player:clearAttackBuffer() for i=1,#self.atkBuffer do local A=self.atkBuffer[i] if not A.sent then A.sent=true A.time=0 end end self.atkBufferSum=0 end function Player:freshTarget() if self.atkMode==1 then if not self.atking or not self.atking.alive or rnd()<.1 then self:changeAtk(randomTarget(self)) end elseif self.atkMode==2 then self:changeAtk(self~=GAME.mostBadge and GAME.mostBadge or GAME.secBadge or randomTarget(self)) elseif self.atkMode==3 then self:changeAtk(self~=GAME.mostDangerous and GAME.mostDangerous or GAME.secDangerous or randomTarget(self)) elseif self.atkMode==4 then for i=1,#self.atker do if not self.atker[i].alive then rem(self.atker,i) return end end end end function Player:changeAtkMode(m) if self.atkMode==m then return end self.atkMode=m if m==1 then self:changeAtk(randomTarget(self)) elseif m==2 or m==3 then self:freshTarget() elseif m==4 then self:changeAtk() end end function Player:changeAtk(R) -- if self.type~='human'then R=PLAYERS[1]end--1vALL mode? if self.atking then local K=self.atking.atker local i=TABLE.find(K,self) if i then rem(K,i)end end if R then self.atking=R ins(R.atker,self) else self.atking=false end end function Player:freshBlock(mode)--string mode: push/move/fresh/newBlock local ENV=self.gameEnv --Fresh ghost if(mode=='move'or mode=='newBlock'or mode=='push')and self.cur then local CB=self.cur.bk self.ghoY=min(#self.field+1,self.curY) if self._20G or ENV.sdarr==0 and self.keyPressing[7]and self.downing>ENV.sddas then local _=self.ghoY --Move ghost to bottom while not self:ifoverlap(CB,self.curX,self.ghoY-1)do self.ghoY=self.ghoY-1 end --Cancel spinLast if _~=self.ghoY then self.spinLast=false end --Create FX if dropped if self.curY>self.ghoY then if ENV.dropFX and ENV.block and self.curY-self.ghoY-#CB>-1 then self:createDropFX(self.curX,self.curY-1,#CB[1],self.curY-self.ghoY-#CB+1) end if ENV.shakeFX then self.fieldOff.vy=ENV.shakeFX*.5 end self.curY=self.ghoY end else while not self:ifoverlap(CB,self.curX,self.ghoY-1)do self.ghoY=self.ghoY-1 end end end --Fresh delays if mode=='move'or mode=='newBlock'or mode=='fresh'then local d0,l0=ENV.drop,ENV.lock if ENV.easyFresh then if self.lockDelay0 then if mode~='newBlock'then self.freshTime=self.freshTime-1 end self.lockDelay=l0 self.dropDelay=d0 end if self.curY+self.cur.sc[1]0 then self.freshTime=self.freshTime-1 self.dropDelay=d0 self.lockDelay=l0 end end end end end function Player:lock() local dest=self.AI_dest local has_dest=dest~=nil local CB=self.cur.bk for i=1,#CB do local y=self.curY+i-1 if not self.field[y]then self.field[y],self.visTime[y]=FREEROW.get(0),FREEROW.get(0)end for j=1,#CB[1]do if CB[i][j]then self.field[y][self.curX+j-1]=self.cur.color self.visTime[y][self.curX+j-1]=self.showTime if dest then local x=self.curX+j-1 for k=1,#dest,2 do if x==dest[k]+1 and y==dest[k+1]+1 then rem(dest,k)rem(dest,k) goto BREAK_success end end dest=nil ::BREAK_success:: end end end end if has_dest and not dest and self.AI_mode=='CC'and self.AI_bot then CC.updateField(self) end end function Player:checkClear(field,start,height,CB,CX) local cc=0 if self.gameEnv.fillClear then for i=1,height do local h=start+i-2 --Bomb trigger (optional, must with CB) if CB and h>0 and field[h]and self.clearedRow[cc]~=h then for x=1,#CB[1]do if CB[i][x]and field[h][CX+x-1]==19 then cc=cc+1 self.clearingRow[cc]=h-cc+1 self.clearedRow[cc]=h break end end end h=h+1 --Row filled for x=1,10 do if field[h][x]<=0 then goto CONTINUE_notFull end end cc=cc+1 ins(self.clearingRow,h-cc+1) ins(self.clearedRow,h) ::CONTINUE_notFull:: end end return cc end function Player:roofCheck() local CB=self.cur.bk for x=1,#CB[1]do local y=#CB --Find the highest y of blocks' x-th column while not CB[y][x]do y=y-1 end local testX=self.curX+x-1--Optimize --Test the whole column of field to find roof for testY=self.curY+y,#self.field do if self:solid(testX,testY)then return true end end end return false end function Player:removeTopClearingFX() for i=#self.clearingRow,1,-1 do if self.clearingRow[i]>#self.field then rem(self.clearingRow) else return end end end function Player:checkMission(piece,mission) if mission<5 then return piece.row==mission and not piece.spin elseif mission<9 then return piece.row==mission-4 and piece.spin elseif mission==9 then return piece.pc elseif mission<90 then return piece.row==mission%10 and piece.name==int(mission/10)and piece.spin end return false end local spawnSFX_name={}for i=1,7 do spawnSFX_name[i]='spawn_'..i end function Player:resetBlock()--Reset Block's position and execute I*S local B=self.cur.bk self.curX=int(6-#B[1]*.5) local y=int(self.gameEnv.fieldH+1-modf(self.cur.sc[1]))+ceil(self.fieldBeneath/30) self.curY=y self.minY=y+self.cur.sc[1] local pressing=self.keyPressing --IMS if self.gameEnv.ims and(pressing[1]and self.movDir==-1 or pressing[2]and self.movDir==1)and self.moving>=self.gameEnv.das then local x=self.curX+self.movDir if not self:ifoverlap(B,x,y)then self.curX=x end end --IRS if self.gameEnv.irs then if pressing[5]then self:spin(2,true) elseif pressing[3]then if pressing[4]then self:spin(2,true) else self:spin(1,true) end elseif pressing[4]then self:spin(3,true) end pressing[3],pressing[4],pressing[5]=false,false,false end --DAS cut if self.gameEnv.dascut>0 then self.moving=self.moving-(self.moving>0 and 1 or -1)*self.gameEnv.dascut end --Spawn SFX if self.sound and self.cur.id<8 then SFX.fplay(spawnSFX_name[self.cur.id],SETTING.sfx_spawn) end end function Player:spin(d,ifpre) local cur=self.cur local kickData=self.RS.kickTable[cur.id] if type(kickData)=='table'then local idir=(cur.dir+d)%4 kickData=kickData[cur.dir*10+idir] if not kickData then self:freshBlock('move') SFX.play(ifpre and'prerotate'or'rotate',nil,self:getCenterX()*.15) return end local icb=BLOCKS[cur.id][idir] local isc=self.RS.centerPos[cur.id][idir] local baseX,baseY=self.curX+cur.sc[2]-isc[2],self.curY+cur.sc[1]-isc[1] for test=1,#kickData do local ix,iy=baseX+kickData[test][1],baseY+kickData[test][2] if (self.freshTime>0 or kickData[test][2]<=0)and not self:ifoverlap(icb,ix,iy)then --Create moveFX at the original position if self.gameEnv.moveFX and self.gameEnv.block then self:createMoveFX()end --Change block position cur.sc,cur.bk,cur.dir=isc,icb,idir self.curX,self.curY=ix,iy self.spinLast=test==2 and 0 or 1 --Fresh ghost and freshTime local t=self.freshTime if not ifpre then self:freshBlock('move')end if kickData[test][2]>0 and self.freshTime==t and self.curY~=self.imgY then self.freshTime=self.freshTime-1 end --Sound & Field shaking if self.sound then local sfx if ifpre then sfx='prerotate' elseif self:ifoverlap(icb,ix,iy+1)and self:ifoverlap(icb,ix-1,iy)and self:ifoverlap(icb,ix+1,iy)then sfx='rotatekick' if self.gameEnv.shakeFX then if d==1 or d==3 then self.fieldOff.va=self.fieldOff.va+(2-d)*self.gameEnv.shakeFX*6e-3 else self.fieldOff.va=self.fieldOff.va+self:getCenterX()*self.gameEnv.shakeFX*3e-3 end end else sfx='rotate' end SFX.play(sfx,nil,self:getCenterX()*.15) end self.stat.rotate=self.stat.rotate+1 return end end elseif kickData then kickData(self,d) else self:freshBlock('move') SFX.play(ifpre and'prerotate'or'rotate',nil,self:getCenterX()*.15) end end local phyHoldKickX={ [true]={0,-1,1},--X==?.0 tests [false]={-.5,.5},--X==?.5 tests } function Player:hold(ifpre) local ENV=self.gameEnv if self.holdTime>0 and(ifpre or self.waiting==-1)then if #self.holdQueue All test failed, interrupt with sound SFX.play('finesseError') do return end -- ::BREAK_success:: self.spinLast=false self.spinSeq=0 local hb=self:getBlock(C.id) hb.name=C.name hb.color=C.color ins(self.holdQueue,hb) self.cur=rem(self.holdQueue,1) self.curX,self.curY=x,y else--Normal hold self.spinLast=false self.spinSeq=0 if C then local hb=self:getBlock(C.id) hb.color=C.color hb.name=C.name ins(self.holdQueue,hb) end self.cur=rem(self.holdQueue,1) self:resetBlock() end self:freshBlock('move') self.dropDelay=ENV.drop self.lockDelay=ENV.lock if self:ifoverlap(self.cur.bk,self.curX,self.curY)then self:lock() self:lose() end end self.freshTime=int(min(self.freshTime+ENV.freshLimit*.25,ENV.freshLimit*((self.holdTime+1)/ENV.holdCount),ENV.freshLimit)) if not ENV.infHold then self.holdTime=self.holdTime-1 end if self.sound then SFX.play(ifpre and'prehold'or'hold') end if self.AI_mode=='CC'then local next=self.nextQueue[self.AIdata.nextCount] if next then CC.addNext(self.AI_bot,next.id) end end self.stat.hold=self.stat.hold+1 end end function Player:getBlock(id,name,color)--Get a block(id=n) object local E=self.gameEnv local dir=E.face[id] return{ id=id, dir=dir, bk=BLOCKS[id][dir], sc=self.RS.centerPos[id][dir], name=name or id, color=E.bone and 17 or color or E.skin[id], } end function Player:getNext(n)--Push a block(id=n) to nextQueue local E=self.gameEnv local dir=E.face[n] ins(self.nextQueue,{ id=n, bk=BLOCKS[n][dir], sc=self.RS.centerPos[n][dir], dir=dir, name=n, color=E.bone and 17 or E.skin[n], }) end function Player:popNext(ifhold)--Pop nextQueue to hand if not ifhold then self.holdTime=min(self.holdTime+1,self.gameEnv.holdCount) end self.spinLast=false self.spinSeq=0 self.ctrlCount=0 self.cur=rem(self.nextQueue,1) self.newNext() if self.cur then self.pieceCount=self.pieceCount+1 if self.AI_mode=='CC'then local next=self.nextQueue[self.AIdata.next] if next then CC.addNext(self.AI_bot,next.id) end end local pressing=self.keyPressing --IHS if not ifhold and pressing[8]and self.gameEnv.ihs and self.holdTime>0 then self:hold(true) pressing[8]=false else self:resetBlock() end self.dropDelay=self.gameEnv.drop self.lockDelay=self.gameEnv.lock self.freshTime=self.gameEnv.freshLimit if self.cur then if self:ifoverlap(self.cur.bk,self.curX,self.curY)then self:lock() self:lose() end self:freshBlock('newBlock') end --IHdS if pressing[6]and not ifhold then self:act_hardDrop() pressing[6]=false end else self:hold() end end function Player:cancel(N)--Cancel Garbage local off=0--Lines offseted local bf=self.atkBuffer for i=1,#bf do if self.atkBufferSum==0 or N==0 then break end local A=bf[i] if not A.sent then local O=min(A.amount,N)--Cur Offset if N2 then dospin=dospin+2 end end end end --Immovable spin check if self:ifoverlap(CB,CX,CY+1)and self:ifoverlap(CB,CX-1,CY)and self:ifoverlap(CB,CX+1,CY)then dospin=dospin+2 end --Lock block to field self:lock() --Clear list of cleared-rows if self.clearedRow[1]then TABLE.cut(self.clearedRow)end --Check line clear cc=cc+self:checkClear(self.field,CY,#CB,CB,CX) --Create clearing FX for i=1,cc do local y=self.clearedRow[i] if ENV.clearFX then self:createClearingFX(y,7-ENV.clearFX)end if ENV.splashFX then self:createSplashFX(y)end end --Create locking FX if ENV.lockFX then if cc==0 then self:createLockFX() else _=#self.lockFX if _>0 then for _=1,_ do rem(self.lockFX) end end end end --Final spin check if dospin>0 then if cc>0 then dospin=dospin+(self.spinLast or 0) if dospin<3 then mini=C.id<6 and cc<#CB end end else dospin=false end --Finesse: roof check local finesse=CY>ENV.fieldH-2 or self:roofCheck() --Remove rows need to be cleared if cc>0 then for i=cc,1,-1 do _=self.clearedRow[i] if self.field[_].garbage then self.garbageBeneath=self.garbageBeneath-1 gbcc=gbcc+1 end FREEROW.discard(rem(self.field,_)) FREEROW.discard(rem(self.visTime,_)) end end --Cancel top clearing FX self:removeTopClearingFX() if self.clearingRow[1]then self.falling=ENV.fall else clear=true end --Finesse check (control) local finePts if not finesse then if dospin then self.ctrlCount=self.ctrlCount-2 end--Allow 2 more step for roof-less spin local id=C.id local d=self.ctrlCount-finesseList[id][C.dir+1][CX] finePts=d<=0 and 5 or max(3-d,0) else finePts=5 end piece.finePts=finePts Stat.finesseRate=Stat.finesseRate+finePts if finePts<5 then Stat.extraPiece=Stat.extraPiece+1 if ENV.fineKill then finish=true end if self.sound then if ENV.fineKill then SFX.play('finesseError_long',.6) elseif ENV.fine then SFX.play('finesseError',.8) else SFX.play('lock',nil,self:getCenterX()*.15) end end elseif self.sound then SFX.play('lock',nil,self:getCenterX()*.15) end if finePts<=1 then self.finesseCombo=0 else self.finesseCombo=self.finesseCombo+1 if self.finesseCombo>2 then self.finesseComboTime=12 end end piece.spin,piece.mini=dospin,false piece.pc,piece.hpc=false,false piece.special=false if cc>0 then--If lines cleared,about 200 lines of codes below cmb=cmb+1 if dospin then cscore=(spinSCR[C.name]or spinSCR[8])[cc] if self.b2b>800 then self:showText(text.b3b..text.block[C.name]..text.spin.." "..text.clear[cc],0,-30,35,'stretch') atk=b2bATK[cc]+cc*.5 exblock=exblock+1 cscore=cscore*2 Stat.b3b=Stat.b3b+1 if self.sound then VOC.play('b3b',CHN) end elseif self.b2b>=50 then self:showText(text.b2b..text.block[C.name]..text.spin.." "..text.clear[cc],0,-30,35,'spin') atk=b2bATK[cc] cscore=cscore*1.2 Stat.b2b=Stat.b2b+1 if self.sound then VOC.play('b2b',CHN) end else self:showText(text.block[C.name]..text.spin.." "..text.clear[cc],0,-30,45,'spin') atk=2*cc end sendTime=20+atk*20 if mini then self:showText(text.mini,0,-80,35,'appear') atk=atk*.25 sendTime=sendTime+60 cscore=cscore*.5 self.b2b=self.b2b+b2bPoint[cc]*.5 if self.sound then VOC.play('mini',CHN) end else self.b2b=self.b2b+b2bPoint[cc] end piece.mini=mini piece.special=true if self.sound then SFX.play(spinSFX[cc]or'spin_3') VOC.play(spinVoice[C.name],CHN) end elseif cc>=4 then cscore=cc==4 and 1000 or cc==5 and 1500 or 2000 if self.b2b>800 then self:showText(text.b3b..text.clear[cc],0,-30,50,'fly') atk=4*cc-10 sendTime=100 exblock=exblock+1 cscore=cscore*1.8 Stat.b3b=Stat.b3b+1 if self.sound then VOC.play('b3b',CHN) end elseif self.b2b>=50 then self:showText(text.b2b..text.clear[cc],0,-30,50,'drive') sendTime=80 atk=3*cc-7 cscore=cscore*1.3 Stat.b2b=Stat.b2b+1 if self.sound then VOC.play('b2b',CHN) end else self:showText(text.clear[cc],0,-30,70,'stretch') sendTime=60 atk=2*cc-4 end self.b2b=self.b2b+cc*50-50 piece.special=true else piece.special=false end if self.sound then VOC.play(clearVoice[cc],CHN) end --Normal clear,reduce B2B point if not piece.special then self.b2b=max(self.b2b-250,0) self:showText(text.clear[cc],0,-30,35,'appear',(8-cc)*.3) atk=cc-.5 sendTime=20+int(atk*20) cscore=cscore+clearSCR[cc] end --Combo bonus sendTime=sendTime+25*cmb if cmb>1 then atk=atk*(1+(cc==1 and .15 or .25)*min(cmb-1,12)) if cmb>=3 then atk=atk+1 end self:showText(text.cmb[min(cmb,21)],0,25,15+min(cmb,15)*5,cmb<10 and'appear'or'flicker') cscore=cscore+min(50*cmb,500)*(2*cc-1) end --PC/HPC if clear and cc>=#C.bk then if CY==1 then self:showText(text.PC,0,-80,50,'flicker') atk=max(atk,min(8+Stat.pc*2,16)) exblock=exblock+2 sendTime=sendTime+120 if Stat.row+cc>4 then self.b2b=self.b2b+800 cscore=cscore+300*min(6+Stat.pc,10) else cscore=cscore+626 end Stat.pc=Stat.pc+1 if self.sound then SFX.play('clear') VOC.play('perfect_clear',CHN) end piece.pc=true piece.special=true elseif cc>1 or #self.field==self.garbageBeneath then self:showText(text.HPC,0,-80,50,'fly') atk=atk+4 exblock=exblock+2 sendTime=sendTime+60 self.b2b=self.b2b+100 cscore=cscore+626 Stat.hpc=Stat.hpc+1 if self.sound then SFX.play('clear') VOC.play('half_clear',CHN) end piece.hpc=true piece.special=true end end if self.b2b>1000 then self.b2b=1000 elseif self.b2b==0 and ENV.b2bKill then finish=true end --Bonus atk/def when focused if GAME.modeEnv.royaleMode then local i=min(#self.atker,9) if i>1 then atk=atk+reAtk[i] exblock=exblock+reDef[i] end end --Send Lines atk=int(atk*(1+self.strength*.25))--Badge Buff send=atk if exblock>0 then exblock=int(exblock*(1+self.strength*.25))--Badge Buff self:showText("+"..exblock,0,53,20,'fly') off=off+self:cancel(exblock) end if send>=1 then self:showText(send,0,80,35,'zoomout') _=self:cancel(send) send=send-_ off=off+_ if send>0 then local T if GAME.modeEnv.royaleMode then if self.atkMode==4 then local M=#self.atker if M>0 then for i=1,M do self:attack(self.atker[i],send,sendTime,generateLine(self.atkRND:random(10))) end else T=randomTarget(self) end else T=self.atking self:freshTarget() end elseif #PLY_ALIVE>1 then T=randomTarget(self) end if T then self:attack(T,send,sendTime,generateLine(self.atkRND:random(10))) end end if self.sound and send>3 then SFX.play('emit',min(send,7)*.1)end end --SFX & Vibrate if self.sound then SFX.play(clearSFX[cc]or'clear_4') SFX.play(renSFX[min(cmb,11)]) if cmb>14 then SFX.play('ren_mega',(cmb-10)*.1)end VIB(cc+1) end else--No lines clear cmb=0 --Spin bonus if dospin then self:showText(text.block[C.name]..text.spin,0,-30,45,'appear') self.b2b=self.b2b+20 if self.sound then SFX.play('spin_0') VOC.play(spinVoice[C.name],CHN) end cscore=30 end if self.b2b>800 then self.b2b=max(self.b2b-40,800) end self:garbageRelease() end self.combo=cmb --Spike if atk>0 then self.spike=self.spikeTime==0 and atk or self.spike+atk self.spikeTime=min(self.spikeTime+atk*20,100) self.spikeText:set(self.spike) end --DropSpeed bonus if self._20G then cscore=cscore*2 elseif ENV.drop<1 then cscore=cscore*1.5 elseif ENV.drop<3 then cscore=cscore*1.2 end --Speed bonus if self.dropSpeed>60 then cscore=cscore*(.9+self.dropSpeed/600)end cscore=int(cscore) if ENV.score then self:popScore( cscore, self:getCenterX()*30, (10-self:getCenterY())*30+self.fieldBeneath+self.fieldUp ) end piece.row,piece.dig=cc,gbcc piece.score=cscore piece.atk,piece.exblock=atk,exblock piece.off,piece.send=off,send --Check clearing task if cc>0 and self.curMission then if self:checkMission(piece,ENV.mission[self.curMission])then self.curMission=self.curMission+1 SFX.play('reach') if self.curMission>#ENV.mission then self.curMission=false if not finish then finish='finish'end end elseif ENV.missionKill then self:showText(text.missionFailed,0,140,40,'flicker',.5) SFX.play('finesseError_long',.6) finish=true end end --Check height limit if cc==0 and #self.field>ENV.heightLimit then self:lose()end --Update stat Stat.piece=Stat.piece+1 Stat.row=Stat.row+cc Stat.maxFinesseCombo=max(Stat.maxFinesseCombo,self.finesseCombo) Stat.maxCombo=max(Stat.maxCombo,self.combo) Stat.score=Stat.score+cscore if atk>0 then Stat.atk=Stat.atk+atk if send>0 then Stat.send=Stat.send+int(send) end if off>0 then Stat.off=Stat.off+off end end if gbcc>0 then Stat.dig=Stat.dig+gbcc if atk>0 then Stat.digatk=Stat.digatk+atk*gbcc/cc end end local n=C.name if dospin then _=Stat.spin[n] _[cc+1]=_[cc+1]+1--Spin[1~25][0~4] _=Stat.spins _[cc+1]=_[cc+1]+1--Spin[0~4] elseif cc>0 then _=Stat.clear[n] _[cc]=_[cc]+1--Clear[1~25][1~5] _=Stat.clears _[cc]=_[cc]+1--Clear[1~5] end if finish then if finish==true then self:lose()return end _=ENV.dropPiece if _ then _(self)end if finish then self:win(finish)end else _=ENV.dropPiece if _ then _(self)end end end end function Player:loadAI(data)--Load AI params if not CC then data.type='9S' data.delta=int(data.delta*.3) end self.AI_mode=data.type self.AI_keys={} self.AI_delay=min(int(self.gameEnv.drop*.8),data.delta*rnd()*4) self.AI_delay0=data.delta self.AIdata={ type=data.type, delay=data.delay, delta=data.delta, next=data.next, hold=data.hold, _20G=self._20G, bag=data.bag, node=data.node, } if self.AI_mode=='CC'then self:setRS('SRS') local opt,wei=CC.getConf() CC.fastWeights(wei) CC.setHold(opt,self.AIdata.hold) CC.set20G(opt,self.AIdata._20G) CC.setBag(opt,self.AIdata.bag=='bag') CC.setNode(opt,self.AIdata.node) self.AI_bot=CC.new(opt,wei) CC.free(opt)CC.free(wei) for i=1,self.AIdata.next do CC.addNext(self.AI_bot,self.nextQueue[i].id) end if self.gameEnv.holdCount and self.gameEnv.holdCount>1 then self:setHold(1) end else self:setRS('TRS') end self.AI_thread=coroutine.wrap(AIFUNC[data.type]) self:AI_thread(self.AI_keys) end ---------------------------------------------------- ---------------------------------------------------- local function tick_throwBadge(ifAI,sender,time) while true do yield() time=time-1 if time%4==0 then local S,R=sender,sender.lastRecv local x1,y1,x2,y2 if S.small then x1,y1=S.centerX,S.centerY else x1,y1=S.x+308*S.size,S.y+450*S.size end if R.small then x2,y2=R.centerX,R.centerY else x2,y2=R.x+66*R.size,R.y+344*R.size end --Generate badge object SYSFX.newBadge(x1,y1,x2,y2) if not ifAI and time%8==0 then SFX.play('collect') end end if time<=0 then return end end end local function tick_finish(self) while true do yield() self.endCounter=self.endCounter+1 if self.endCounter<40 then --Make field visible for j=1,#self.field do for i=1,10 do if self.visTime[j][i]<20 then self.visTime[j][i]=self.visTime[j][i]+.5 end end end elseif self.endCounter==60 then return end end end local function tick_lose(self) while true do yield() self.endCounter=self.endCounter+1 if self.endCounter<40 then --Make field visible for j=1,#self.field do for i=1,10 do if self.visTime[j][i]<20 then self.visTime[j][i]=self.visTime[j][i]+.5 end end end elseif self.endCounter>80 then for i=1,#self.field do for j=1,10 do if self.visTime[i][j]>0 then self.visTime[i][j]=self.visTime[i][j]-1 end end end if self.endCounter==120 then for _=#self.field,1,-1 do FREEROW.discard(self.field[_]) FREEROW.discard(self.visTime[_]) self.field[_],self.visTime[_]=nil end return end end if not GAME.modeEnv.royaleMode and #PLAYERS>1 then self.y=self.y+self.endCounter*.26 self.absFieldY=self.absFieldY+self.endCounter*.26 end end end local function tick_autoPause() local time=0 while true do yield() time=time+1 if SCN.cur~='game'or PLAYERS[1].frameRun<180 then return elseif time==120 then pauseGame() return end end end ---------------------------------------------------- ---------------------------------------------------- function Player:die()--Called both when win/lose! self.alive=false self.timing=false self.control=false self.update=ply_update.dead self.waiting=1e99 self.b2b=0 self.tasks={} self:clearAttackBuffer() for i=1,#self.field do for j=1,10 do self.visTime[i][j]=min(self.visTime[i][j],20) end end if GAME.net then if self.id==1 then ins(GAME.rep,self.frameRun) ins(GAME.rep,0) else if self.lastRecv and self.lastRecv.id==1 then SFX.play('collect') end end end end function Player:revive() self.waiting=62 local h=#self.field for _=h,1,-1 do FREEROW.discard(self.field[_]) FREEROW.discard(self.visTime[_]) self.field[_],self.visTime[_]=nil end self.garbageBeneath=0 if self.AI_mode=='CC'then CC.destroy(self.AI_bot) TABLE.cut(self.holdQueue) self:loadAI(self.AIdata) end self:clearAttackBuffer() self.life=self.life-1 self.fieldBeneath=0 self.b2b=0 for i=1,h do self:createClearingFX(i,1.5) end SYSFX.newShade(1.4,self.fieldX,self.fieldY,300*self.size,610*self.size) SYSFX.newRectRipple(2,self.fieldX,self.fieldY,300*self.size,610*self.size) SYSFX.newRipple(2,self.x+(475+25*(self.life<3 and self.life or 0)+12)*self.size,self.y+(665+12)*self.size,20) SFX.play('clear_3') SFX.play('emit') end function Player:win(result) if self.result then return end self:die() self.result='win' if GAME.modeEnv.royaleMode then self.modeData.place=1 self:changeAtk() end if self.type=='human'then GAME.result=result or'gamewin' SFX.play('win') VOC.play('win') if GAME.modeEnv.royaleMode then BGM.play('8-bit happiness') end end if GAME.curMode.name=='custom_puzzle'then self:showTextF(text.win,0,0,90,'beat',.4) else self:showTextF(text.win,0,0,90,'beat',.5,.2) end if self.type=='human'then gameOver() TASK.new(tick_autoPause) end self:newTask(tick_finish) end function Player:lose(force) if self.result then return end if not force then if self.life>0 then self:revive()return end if self.type=='remote'then self.waiting=1e99 return end end self:die() local p=TABLE.find(PLY_ALIVE,self)if p then rem(PLY_ALIVE,p)end self.result='lose' if GAME.modeEnv.royaleMode then self:changeAtk() self.modeData.place=#PLY_ALIVE+1 self.strength=0 if self.lastRecv then local A,depth=self,0 repeat A,depth=A.lastRecv,depth+1 until not A or A.alive or A==self or depth==3 if A and A.alive then if self.id==1 or A.id==1 then self.killMark=A.id==1 end A.modeData.ko,A.badge=A.modeData.ko+1,A.badge+self.badge+1 for i=A.strength+1,4 do if A.badge>=ROYALEDATA.powerUp[i]then A.strength=i A:setFrameColor(i) end end self.lastRecv=A if self.id==1 or A.id==1 then TASK.new(tick_throwBadge,not A.type=='human',self,max(3,self.badge)*4) end end else self.badge=-1 end freshMostBadge() freshMostDangerous() if #PLY_ALIVE==ROYALEDATA.stage[GAME.stage]then royaleLevelup() end self:showTextF(self.modeData.place,0,120,60,'appear',.26,.9) end self.gameEnv.keepVisible=self.gameEnv.visible~='show' self:showTextF(text.lose,0,0,90,'appear',.26,.9) if self.type=='human'then GAME.result='gameover' SFX.play('fail') VOC.play('lose') if GAME.modeEnv.royaleMode then BGM.play('end') end gameOver() self:newTask(#PLAYERS>1 and tick_lose or tick_finish) if GAME.net and not NET.spectate then NET.signal_die() else TASK.new(tick_autoPause) end else self:newTask(tick_lose) end if #PLY_ALIVE==1 then PLY_ALIVE[1]:win() end end --------------------------<\Events>-------------------------- ---------------------------------------------------- function Player:act_moveLeft(auto) if not auto then self.ctrlCount=self.ctrlCount+1 end self.movDir=-1 if self.keyPressing[9]then if self.gameEnv.swap then self:changeAtkMode(1) self.keyPressing[1]=false end elseif self.control and self.waiting==-1 then if self.cur and not self:ifoverlap(self.cur.bk,self.curX-1,self.curY)then if self.gameEnv.moveFX and self.gameEnv.block then self:createMoveFX('left') end self.curX=self.curX-1 self:freshBlock('move') if self.sound and self.curY==self.ghoY then SFX.play('move')end if not auto then self.moving=0 end self.spinLast=false else self.moving=self.gameEnv.das end else self.moving=0 end end function Player:act_moveRight(auto) if not auto then self.ctrlCount=self.ctrlCount+1 end self.movDir=1 if self.keyPressing[9]then if self.gameEnv.swap then self:changeAtkMode(2) self.keyPressing[2]=false end elseif self.control and self.waiting==-1 then if self.cur and not self:ifoverlap(self.cur.bk,self.curX+1,self.curY)then if self.gameEnv.moveFX and self.gameEnv.block then self:createMoveFX('right') end self.curX=self.curX+1 self:freshBlock('move') if self.sound and self.curY==self.ghoY then SFX.play('move')end if not auto then self.moving=0 end self.spinLast=false else self.moving=self.gameEnv.das end else self.moving=0 end end function Player:act_rotRight() if self.control and self.waiting==-1 and self.cur then self.ctrlCount=self.ctrlCount+1 self:spin(1) self.keyPressing[3]=false end end function Player:act_rotLeft() if self.control and self.waiting==-1 and self.cur then self.ctrlCount=self.ctrlCount+1 self:spin(3) self.keyPressing[4]=false end end function Player:act_rot180() if self.control and self.waiting==-1 and self.cur then self.ctrlCount=self.ctrlCount+2 self:spin(2) self.keyPressing[5]=false end end function Player:act_hardDrop() local ENV=self.gameEnv if self.keyPressing[9]then if ENV.swap then self:changeAtkMode(3) end self.keyPressing[6]=false elseif self.control and self.waiting==-1 and self.cur then if self.lastPiece.autoLock and self.frameRun-self.lastPiece.frameself.ghoY then local CB=self.cur.bk if ENV.dropFX and ENV.block and self.curY-self.ghoY-#CB>-1 then self:createDropFX(self.curX,self.curY-1,#CB[1],self.curY-self.ghoY-#CB+1) end self.curY=self.ghoY self.spinLast=false if self.sound then SFX.play('drop',nil,self:getCenterX()*.15) VIB(1) end end if ENV.shakeFX then self.fieldOff.vy=ENV.shakeFX*.6 self.fieldOff.va=self.fieldOff.va+self:getCenterX()*ENV.shakeFX*6e-4 end self.lockDelay=-1 self:drop() self.keyPressing[6]=false end end end function Player:act_softDrop() local ENV=self.gameEnv if self.keyPressing[9]then if ENV.swap then self:changeAtkMode(4) end else self.downing=1 if self.control and self.waiting==-1 and self.cur then if self.curY>self.ghoY then self.curY=self.curY-1 self:freshBlock('fresh') self.spinLast=false elseif ENV.deepDrop then local CB=self.cur.bk local y=self.curY-1 while self:ifoverlap(CB,self.curX,y)and y>0 do y=y-1 end if y>0 then if ENV.dropFX and ENV.block and self.curY-y-#CB>-1 then self:createDropFX(self.curX,self.curY-1,#CB[1],self.curY-y-#CB+1) end self.curY=y self:freshBlock('move') SFX.play('swipe') end end end end end function Player:act_hold() if self.control then if self.waiting==-1 then self:hold() self.keyPressing[8]=false end end end function Player:act_func1() self.gameEnv.fkey1(self) end function Player:act_func2() self.gameEnv.fkey2(self) end function Player:act_insLeft(auto) if not self.cur then return end local x0=self.curX while not self:ifoverlap(self.cur.bk,self.curX-1,self.curY)do if self.gameEnv.moveFX and self.gameEnv.block then self:createMoveFX('left') end self.curX=self.curX-1 self:freshBlock('move') end if self.curX~=x0 then self.spinLast=false end if self.gameEnv.shakeFX then self.fieldOff.vx=-self.gameEnv.shakeFX*.5 end if auto then if self.ctrlCount==0 then self.ctrlCount=1 end else self.ctrlCount=self.ctrlCount+1 end end function Player:act_insRight(auto) if not self.cur then return end local x0=self.curX while not self:ifoverlap(self.cur.bk,self.curX+1,self.curY)do if self.gameEnv.moveFX and self.gameEnv.block then self:createMoveFX('right') end self.curX=self.curX+1 self:freshBlock('move') end if self.curX~=x0 then self.spinLast=false end if self.gameEnv.shakeFX then self.fieldOff.vx=self.gameEnv.shakeFX*.5 end if auto then if self.ctrlCount==0 then self.ctrlCount=1 end else self.ctrlCount=self.ctrlCount+1 end end function Player:act_insDown() if self.cur and self.curY>self.ghoY then local ENV=self.gameEnv local CB=self.cur.bk if ENV.dropFX and ENV.block and self.curY-self.ghoY-#CB>-1 then self:createDropFX(self.curX,self.curY-1,#CB[1],self.curY-self.ghoY-#CB+1) end if ENV.shakeFX then self.fieldOff.vy=ENV.shakeFX*.5 end self.curY=self.ghoY self.lockDelay=ENV.lock self.spinLast=false self:freshBlock('fresh') end end function Player:act_down1() if self.cur and self.curY>self.ghoY then if self.gameEnv.moveFX and self.gameEnv.block then self:createMoveFX('down') end self.curY=self.curY-1 self:freshBlock('fresh') self.spinLast=false end end function Player:act_down4() if self.cur and self.curY>self.ghoY then local y=max(self.curY-4,self.ghoY) local CB=self.cur.bk if self.gameEnv.dropFX and self.gameEnv.block and self.curY-y-#CB>-1 then self:createDropFX(self.curX,self.curY-1,#CB[1],self.curY-y-#CB+1) end self.curY=y self:freshBlock('fresh') self.spinLast=false end end function Player:act_down10() if self.cur and self.curY>self.ghoY then local y=max(self.curY-10,self.ghoY) local CB=self.cur.bk if self.gameEnv.dropFX and self.gameEnv.block and self.curY-y-#CB>-1 then self:createDropFX(self.curX,self.curY-1,#CB[1],self.curY-y-#CB+1) end self.curY=y self:freshBlock('fresh') self.spinLast=false end end function Player:act_dropLeft() if not self.cur then return end self:act_insLeft() self:act_hardDrop() end function Player:act_dropRight() if not self.cur then return end self:act_insRight() self:act_hardDrop() end function Player:act_zangiLeft() if not self.cur then return end self:act_insLeft() self:act_insDown() self:act_insRight() self:act_hardDrop() end function Player:act_zangiRight() if not self.cur then return end self:act_insRight() self:act_insDown() self:act_insLeft() self:act_hardDrop() end Player.actList={ Player.act_moveLeft, --1 Player.act_moveRight, --2 Player.act_rotRight, --3 Player.act_rotLeft, --4 Player.act_rot180, --5 Player.act_hardDrop, --6 Player.act_softDrop, --7 Player.act_hold, --8 Player.act_func1, --9 Player.act_func2, --10 Player.act_insLeft, --11 Player.act_insRight, --12 Player.act_insDown, --13 Player.act_down1, --14 Player.act_down4, --15 Player.act_down10, --16 Player.act_dropLeft, --17 Player.act_dropRight, --18 Player.act_zangiLeft, --19 Player.act_zangiRight, --20 } ---------------------------------------------------- return Player