diff --git a/parts/player/init.lua b/parts/player/init.lua index 8d262ca7..c77dbfb3 100644 --- a/parts/player/init.lua +++ b/parts/player/init.lua @@ -1,26 +1,19 @@ +local Player=require("parts/player/player") +local prepareSequence=require("parts/player/prepareSequence") + local mt=love.math -local int,ceil,rnd=math.floor,math.ceil,math.random -local max,min=math.max,math.min -local ins,rem=table.insert,table.remove +local rnd,max,min=math.random,math.max,math.min +local ins=table.insert + +local gameEnv0=require("parts/player/gameEnv0") + local PLY={ update=require("parts/player/update"), draw=require("parts/player/draw"), } -local Player={}--Player class - -local gameEnv0=require("parts/player/gameEnv0") -local prepareSequence=require("parts/player/prepareSequence") -local kickList=require("parts/kickList") -local scs=spinCenters ---------------------------------------------------- -local function without(L,e) - for i=1,#L do - if L[i]==e then return end - end - return true -end local function getNewStatTable() local T={ time=0,score=0, @@ -174,44 +167,6 @@ local function applyGameEnv(P)--Finish gameEnv processing if ENV.ghost==0 then ENV.ghost=nil end if ENV.center==0 then ENV.center=nil end end - -local function loadAI(P,AIdata)--Load AI params - local ENV=P.gameEnv - P.AI_mode=AIdata.type - P.AI_stage=1 - P.AI_keys={} - P.AI_delay=AIdata.delay or min(int(ENV.drop*.8),AIdata.delta*rnd()*4) - P.AI_delay0=AIdata.delta - P.AIdata={ - type=AIdata.type, - delay=AIdata.delay, - delta=AIdata.delta, - - next=AIdata.next, - hold=AIdata.hold, - _20G=P._20G, - bag=AIdata.bag, - node=AIdata.node, - } - if not CC then - P.AI_mode="9S" - P.AI_delay0=int(P.AI_delay0*.26) - end - if P.AI_mode=="CC"then - P.RS=kickList.AIRS - local opt,wei=CC.getConf() - CC.fastWeights(wei) - CC.setHold(opt,P.AIdata.hold) - CC.set20G(opt,P.AIdata._20G) - CC.setBag(opt,P.AIdata.bag=="bag") - CC.setNode(opt,P.AIdata.node) - P.AI_bot=CC.new(opt,wei) - CC.free(opt)CC.free(wei) - for i=1,AIdata.next do - CC.addNext(P.AI_bot,P.next[i].id) - end - end -end local function newEmptyPlayer(id,x,y,size) local P={id=id} PLAYERS[id]=P @@ -305,7 +260,7 @@ local function newEmptyPlayer(id,x,y,size) P.human=false P.sound=false - P.RS=kickList.TRS + P:setRS("TRS") -- P.newNext=nil--Call prepareSequence()to get a function to get new next @@ -332,1674 +287,6 @@ local function newEmptyPlayer(id,x,y,size) end ---------------------------------------------------- ----------------------------------------------------- -function Player.showText(P,text,dx,dy,font,style,spd,stop) - if P.gameEnv.text then - ins(P.bonus,TEXT.getText(text,150+dx,300+dy,font*P.size,style,spd,stop)) - end -end -function Player.showTextF(P,text,dx,dy,font,style,spd,stop) - ins(P.bonus,TEXT.getText(text,150+dx,300+dy,font*P.size,style,spd,stop)) -end -function Player.createLockFX(P) - local BK=P.cur.bk - local t=12-P.gameEnv.lockFX*2 - - for i=1,P.r do - local y=P.curY+i-1 - if without(P.clearedRow,y)then - y=-30*y - for j=1,P.c do - if BK[i][j]then - ins(P.lockFX,{30*(P.curX+j-2),y,0,t}) - end - end - end - end -end -function Player.createDropFX(P,x,y,w,h) - ins(P.dropFX,{x,y,w,h,0,13-2*P.gameEnv.dropFX}) -end -function Player.createMoveFX(P,dir) - local T=10-1.5*P.gameEnv.moveFX - local C=P.cur.color - local x=P.curX-1 - local y=P.gameEnv.smooth and P.curY+P.dropDelay/P.gameEnv.drop-2 or P.curY-1 - if dir=="left"then - for i=1,P.r do for j=P.c,1,-1 do - if P.cur.bk[i][j]then - ins(P.moveFX,{C,x+j,y+i,0,T}) - break - end - end end - elseif dir=="right"then - for i=1,P.r do for j=1,P.c do - if P.cur.bk[i][j]then - ins(P.moveFX,{C,x+j,y+i,0,T}) - break - end - end end - elseif dir=="down"then - for j=1,P.c do for i=P.r,1,-1 do - if P.cur.bk[i][j]then - ins(P.moveFX,{C,x+j,y+i,0,T}) - break - end - end end - else - for i=1,P.r do for j=1,P.c do - if P.cur.bk[i][j]then - ins(P.moveFX,{C,x+j,y+i,0,T}) - end - end end - end -end -function Player.createClearingFX(P,y,spd) - ins(P.clearFX,{y,0,spd}) -end -function Player.createBeam(P,R,send,color) - local x1,y1,x2,y2 - if P.small then x1,y1=P.centerX,P.centerY - else x1,y1=P.x+(30*(P.curX+P.sc[2])-30+15+150)*P.size,P.y+(600-30*(P.curY+P.sc[1])+15+70)*P.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 - - wid=int(send^.7*(4+SETTING.atkFX)) - local r,g,b=unpack(SKIN.libColor[color]) - r,g,b=r*2,g*2,b*2 - - local a=modeEnv.royaleMode and not(P.human or R.human)and .2 or 1 - sysFX.newAttack(1-SETTING.atkFX*.1,x1,y1,x2,y2,wid,r,g,b,a*(SETTING.atkFX+2)*.0626) -end ----------------------------------------------------- - ----------------------------------------------------- -function Player.RND(P,a,b) - local R=P.randGen - return R:random(a,b) -end - -function Player.set20G(P,if20g,init) - P._20G=if20g - P.keyAvailable[7]=not if20g - virtualkey[7].ava=not if20g - if init and if20g and P.AI_mode=="CC"then CC.switch20G(P)end -end -function Player.setHold(P,ifhold) - P.gameEnv.hold=ifhold - P.keyAvailable[8]=not ifhold - virtualkey[8].ava=not ifhold - if not ifhold then - P.hd=nil - end -end -function Player.setNext(P,next) - P.gameEnv.next=next -end -function Player.setInvisible(P,time) - if time<0 then - P.keepVisible=true - else - P.keepVisible=false - P.showTime=time - end -end - -function Player.newTask(P,code,data) - local L=P.tasks - ins(L,{ - code=code, - data=data, - }) -end - -function Player.solid(P,x,y) - if x<1 or x>10 or y<1 then return true end - if y>#P.field then return false end - return P.field[y] - [x]>0--to catch bug (nil[*]) -end -function Player.ifoverlap(P,bk,x,y) - local C=#bk[1] - if x<1 or x+C>11 or y<1 then return true end - if y>#P.field then return end - for i=1,#bk do - if P.field[y+i-1]then - for j=1,C do - if bk[i][j]and P.field[y+i-1][x+j-1]>0 then return true end - end - end - end -end -function Player.attack(P,R,send,time,...) - if SETTING.atkFX>0 then - P:createBeam(R,send,time,...) - end - R.lastRecv=P - if R.atkBuffer.sum<20 then - local B=R.atkBuffer - if send>20-B.sum then send=20-B.sum end--No more then 20 - local m,k=#B,1 - while k<=m and time>B[k].countdown do k=k+1 end - for i=m,k,-1 do - B[i+1]=B[i] - end - B[k]={ - pos=P:RND(10), - amount=send, - countdown=time, - cd0=time, - time=0, - sent=false, - lv=min(int(send^.69),5), - }--Sorted insert(by time) - B.sum=B.sum+send - R.stat.recv=R.stat.recv+send - if R.sound then - SFX.play(send<4 and"blip_1"or"blip_2",min(send+1,5)*.1) - end - end -end - -function Player.getHolePos(P) - if P.garbageBeneath==0 then - return P:RND(10) - else - local p=P:RND(10) - if P.field[1][p]<=0 then - return P:RND(10) - end - return p - end -end -function Player.garbageRelease(P) - local n,flag=1 - while true do - local A=P.atkBuffer[n] - if A and A.countdown<=0 and not A.sent then - P:garbageRise(19+A.lv,A.amount,A.pos) - P.atkBuffer.sum=P.atkBuffer.sum-A.amount - A.sent,A.time=true,0 - P.stat.pend=P.stat.pend+A.amount - n=n+1 - flag=true - else - break - end - end - if flag and P.AI_mode=="CC"then CC.updateField(P)end -end -function Player.garbageRise(P,color,amount,pos) - local _ - local t=P.showTime*2 - for _=1,amount do - ins(P.field,1,FREEROW.get(color,true)) - ins(P.visTime,1,FREEROW.get(t)) - P.field[1][pos]=0 - end - P.fieldBeneath=P.fieldBeneath+amount*30 - if P.cur then - P.curY=P.curY+amount - P.imgY=P.imgY+amount - end - P.garbageBeneath=P.garbageBeneath+amount - for i=1,#P.clearingRow do - P.clearingRow[i]=P.clearingRow[i]+amount - end - P:freshBlock(false,false) - for i=1,#P.lockFX do - _=P.lockFX[i] - _[2]=_[2]-30*amount--Shift 30px per line cleared - end - for i=1,#P.dropFX do - _=P.dropFX[i] - _[3],_[5]=_[3]+amount,_[5]+amount - end - if #P.field>42 then P:lose()end -end - -local invList={2,1,4,3,5,6,7} -function Player.pushLine(P,L,mir) - local l=#L - local S=P.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(P.field,1,r) - ins(P.visTime,1,FREEROW.get(20)) - end - P.fieldBeneath=P.fieldBeneath+30*l - P.curY=P.curY+l - P.imgY=P.imgY+l - P:freshBlock(false,false) -end -function Player.pushNext(P,L,mir) - for i=1,#L do - P:getNext(mir and invList[L[i]]or L[i]) - end -end - -function Player.freshTarget(P) - if P.atkMode==1 then - if not P.atking or not P.atking.alive or rnd()<.1 then - P:changeAtk(randomTarget(P)) - end - elseif P.atkMode==2 then - P:changeAtk(P~=GAME.mostBadge and GAME.mostBadge or GAME.secBadge or randomTarget(P)) - elseif P.atkMode==3 then - P:changeAtk(P~=GAME.mostDangerous and GAME.mostDangerous or GAME.secDangerous or randomTarget(P)) - elseif P.atkMode==4 then - for i=1,#P.atker do - if not P.atker[i].alive then - rem(P.atker,i) - return - end - end - end -end -function Player.changeAtkMode(P,m) - if P.atkMode==m then return end - P.atkMode=m - if m==1 then - P:changeAtk(randomTarget(P)) - elseif m==2 or m==3 then - P:freshTarget() - elseif m==4 then - P:changeAtk() - end -end -function Player.changeAtk(P,R) - -- if not P.human then R=PLAYERS[1]end--1vALL mode? - if P.atking then - local K=P.atking.atker - for i=1,#K do - if K[i]==P then - rem(K,i) - break - end - end - end - if R then - P.atking=R - ins(R.atker,P) - else - P.atking=nil - end -end -function Player.freshBlock(P,keepGhost,control,system) - local ENV=P.gameEnv - if not keepGhost and P.cur then - P.imgY=min(#P.field+1,P.curY) - if P._20G or P.keyPressing[7]and ENV.sdarr==0 then - local _=P.imgY - - --Move ghost to bottom - while not P:ifoverlap(P.cur.bk,P.curX,P.imgY-1)do - P.imgY=P.imgY-1 - end - - --Cancel spinLast - if _~=P.imgY then - P.spinLast=false - end - - --Create FX if dropped - if P.curY>P.imgY then - if ENV.dropFX and ENV.block and P.curY-P.imgY-P.r>-1 then - P:createDropFX(P.curX,P.curY-1,P.c,P.curY-P.imgY-P.r+1) - end - if ENV.shakeFX then - P.fieldOff.vy=ENV.shakeFX*.5 - end - P.curY=P.imgY - end - else - while not P:ifoverlap(P.cur.bk,P.curX,P.imgY-1)do - P.imgY=P.imgY-1 - end - end - end - - if control then - if ENV.easyFresh then - local d0=ENV.lock - if P.lockDelay=P.gameEnv.das then - local x=P.curX+P.movDir - if not P:ifoverlap(C.bk,x,y)then - P.curX=x - end - end - - --IRS - if P.gameEnv.irs then - if _[5]then - P:spin(2,true) - else - if _[3]then - if _[4]then - P:spin(2,true) - else - P:spin(1,true) - end - elseif _[4]then - P:spin(3,true) - end - end - end - - --Spawn SFX - if P.sound and id<8 then - SFX.fplay(spawnSFX_name[id],SETTING.spawn) - end -end - -function Player.spin(P,d,ifpre) - local iki=P.RS[P.cur.id] - if type(iki)=="table"then - local idir=(P.dir+d)%4 - local icb=BLOCKS[P.cur.id][idir] - local isc=scs[P.cur.id][idir] - local ir,ic=#icb,#icb[1] - local ix,iy=P.curX+P.sc[2]-isc[2],P.curY+P.sc[1]-isc[1] - iki=iki[P.dir*10+idir] - if not iki then - if P.gameEnv.easyFresh then - P:freshBlock(false,true) - end - SFX.fieldPlay(ifpre and"prerotate"or"rotate",nil,P) - return - end - for test=1,#iki do - local x,y=ix+iki[test][1],iy+iki[test][2] - if not P:ifoverlap(icb,x,y)and(P.freshTime<=P.gameEnv.freshLimit or iki[test][2]<0)then - ix,iy=x,y - if P.gameEnv.moveFX and P.gameEnv.block then - P:createMoveFX() - end - P.curX,P.curY,P.dir=ix,iy,idir - P.sc,P.cur.bk=scs[P.cur.id][idir],icb - P.r,P.c=ir,ic - P.spinLast=test==2 and 0 or 1 - if not ifpre then - P:freshBlock(false,true) - end - if iki[test][2]>0 and not P.gameEnv.easyFresh then - P.freshTime=P.freshTime+1 - end - - if P.sound then - SFX.fieldPlay(ifpre and"prerotate"or P:ifoverlap(P.cur.bk,P.curX,P.curY+1)and P:ifoverlap(P.cur.bk,P.curX-1,P.curY)and P:ifoverlap(P.cur.bk,P.curX+1,P.curY)and"rotatekick"or"rotate",nil,P) - end - P.stat.rotate=P.stat.rotate+1 - return - end - end - else - iki(P,d) - end -end -function Player.hold(P,ifpre) - if not P.holded and (ifpre or P.waiting==-1) and P.gameEnv.hold then - local H,C=P.hd,P.cur - if not(H or C)then return end - - --Finesse check - if H and C and H.id==C.id and H.name==C.name then - P.ctrlCount=P.ctrlCount+1 - elseif P.ctrlCount<=1 then - P.ctrlCount=0 - end - - P.spinLast=false - P.spinSeq=0 - - P.cur,P.hd=H,C--Swap hold - H,C=P.hd,P.cur - - if P.next[1]or C then--Make hold available in fixed sequence - P.holded=P.gameEnv.oncehold - end - - if H then - local hid=P.hd.id - P.hd.bk=BLOCKS[hid][P.gameEnv.face[hid]] - end - if not C then - C=rem(P.next,1) - P:newNext() - if C then - P.cur=C - P.pieceCount=P.pieceCount+1 - if P.AI_mode=="CC"then - local next=P.next[P.AIdata.next] - if next then - CC.addNext(P.AI_bot,next.id) - end - end - else - P.holded=false - end - end - if C then - P:resetBlock() - P:freshBlock(false,true) - P.dropDelay=P.gameEnv.drop - P.lockDelay=P.gameEnv.lock - P.freshTime=max(P.freshTime-5,0) - if P:ifoverlap(P.cur.bk,P.curX,P.curY)then P:lock()P:lose()end - end - - if P.sound then - SFX.play(ifpre and"prehold"or"hold") - end - P.stat.hold=P.stat.hold+1 - end -end - -function Player.getNext(P,n) - local E=P.gameEnv - ins(P.next,{bk=BLOCKS[n][E.face[n]],id=n,color=E.bone and 17 or E.skin[n],name=n}) -end -function Player.popNext(P)--Pop next queue to hand - P.holded=false - P.spinLast=false - P.spinSeq=0 - P.ctrlCount=0 - - P.cur=rem(P.next,1) - P:newNext() - if P.cur then - P.pieceCount=P.pieceCount+1 - if P.AI_mode=="CC"then - local next=P.next[P.AIdata.next] - if next then - CC.addNext(P.AI_bot,next.id) - end - end - - local _=P.keyPressing - --IHS - if _[8]and P.gameEnv.hold and P.gameEnv.ihs then - P:hold(true) - _[8]=false - else - P:resetBlock() - end - - P.dropDelay=P.gameEnv.drop - P.lockDelay=P.gameEnv.lock - P.freshTime=0 - - if P.cur then - if P:ifoverlap(P.cur.bk,P.curX,P.curY)then - P:lock() - P:lose() - end - P:freshBlock(false,true,true) - end - - --IHdS - if _[6]then - P.act_hardDrop(P) - _[6]=false - end - end -end - -function Player.cancel(P,N)--Cancel Garbage - local k=0 --Pointer, attack bar selected - local off=0 --Lines offseted - local bf=P.atkBuffer - ::R:: - if bf.sum>0 then - local A - repeat - k=k+1 - A=bf[k] - if not A then return off end - until not A.sent - if N>=A.amount then - local O=A.amount--Cur Offset - N=N-O - off=off+O - bf.sum=bf.sum-O - A.sent,A.time=true,0 - if N>0 then goto R end - else - off=off+N - A.amount=A.amount-N - bf.sum=bf.sum-N - end - end - return off -end -do--player:drop()--Place piece - local clearSCR={80,200,400}--Techrash:1K; B2Bmul:1.3/1.8 - local spinSCR={ - {200,750,1300,2000},--Z - {200,750,1300,2000},--S - {220,700,1300,2000},--L - {220,700,1300,2000},--J - {250,800,1400,2000},--T - {260,900,1600,4500,7000},--O - {300,1200,1700,4000,6000},--I - {220,800,2000,3000,8000,26000},--Else - }--B2Bmul:1.2/2.0; Mini*=.6 - local b2bPoint={50,100,180,1000,1200,9999} - - local b2bATK={3,5,8,12,18} - local reAtk={0,0,1,1,1,2,2,3,3} - local reDef={0,1,1,2,3,3,4,4,5} - - local spinVoice={"zspin","sspin","jspin","lspin","tspin","ospin","ispin","zspin","sspin","pspin","qspin","fspin","espin","tspin","uspin","vspin","wspin","xspin","jspin","lspin","rspin","yspin","hspin","nspin","ispin"} - local clearVoice={"single","double","triple","techrash","pentcrash","hexcrash"} - local spinSFX={[0]="spin_0","spin_1","spin_2"} - local clearSFX={"clear_1","clear_2","clear_3"} - local renSFX={}for i=1,11 do renSFX[i]="ren_"..i end - local finesseList={ - [1]={ - {1,2,1,0,1,2,2,1}, - {2,2,2,1,1,2,3,2,2}, - },--Z - [3]={ - {1,2,1,0,1,2,2,1}, - {2,2,3,2,1,2,3,3,2}, - {3,4,3,2,3,4,4,3}, - {2,3,2,1,2,3,3,2,2}, - },--L - [6]={ - {1,2,2,1,0,1,2,2,1}, - },--O - [7]={ - {1,2,1,0,1,2,1}, - {2,2,2,2,1,1,2,2,2,2}, - },--I - } - finesseList[1][3],finesseList[1][4],finesseList[7][3],finesseList[7][4]=finesseList[1][1],finesseList[1][2],finesseList[7][1],finesseList[7][2]--"2-phase" SZI - finesseList[2]=finesseList[1]--S=Z - finesseList[4],finesseList[5]=finesseList[3],finesseList[3]--J=L=T - function Player.drop(P) - local _ - local CHN=VOC.getFreeChannel() - P.dropTime[11]=ins(P.dropTime,1,GAME.frame)--Update speed dial - local ENV=P.gameEnv - local STAT=P.stat - local piece=P.lastPiece - - local cmb=P.combo - local CB,CX,CY=P.cur,P.curX,P.curY - local clear--If clear with no line fall - local cc,gbcc=0,0--Row/garbage-row cleared,full-part - local atk,exblock=0,0--Attack & extra defense - local send,off=0,0--Sending lines remain & offset - local cscore,sendTime=10,0--Score & send Time - local dospin,mini=0 - - piece.id,piece.name=CB.id,CB.name - P.waiting=ENV.wait - - --Tri-corner spin check - if P.spinLast then - if CB.id<6 then - local x,y=CX+P.sc[2],CY+P.sc[1] - local c=0 - if P:solid(x-1,y+1)then c=c+1 end - if P:solid(x+1,y+1)then c=c+1 end - if c==0 then goto NTC end - if P:solid(x-1,y-1)then c=c+1 end - if P:solid(x+1,y-1)then c=c+1 end - if c>2 then dospin=dospin+2 end - end - ::NTC:: - end - --Immovable spin check - if P:ifoverlap(CB.bk,CX,CY+1)and P:ifoverlap(CB.bk,CX-1,CY)and P:ifoverlap(CB.bk,CX+1,CY)then - dospin=dospin+2 - end - - --Lock block to field - P:lock() - - --Clear list of cleared-rows - if P.clearedRow[1]then P.clearedRow={}end - - --Check line clear - for i=1,P.r do - local h=CY+i-2 - - --Bomb trigger - if h>0 and P.field[h]and P.clearedRow[cc]~=h then - for x=1,P.c do - if CB.bk[i][x]and P.field[h][CX+x-1]==19 then - cc=cc+1 - P.clearingRow[cc]=h-cc+1 - P.clearedRow[cc]=h - break - end - end - end - - h=h+1 - --Row filled - for x=1,10 do - if P.field[h][x]<=0 then - goto notFull - end - end - cc=cc+1 - P.clearingRow[cc]=h-cc+1 - P.clearedRow[cc]=h - ::notFull:: - end - - --Create clearing FX - if cc>0 and ENV.clearFX then - local t=7-ENV.clearFX*1 - for i=1,cc do - P:createClearingFX(P.clearedRow[i],t) - end - end - - --Create locking FX - if ENV.lockFX then - if cc==0 then - P:createLockFX() - else - _=#P.lockFX - if _>0 then - for _=1,_ do - rem(P.lockFX) - end - end - end - end - - --Final spin check - if dospin>0 then - if cc>0 then - dospin=dospin+(P.spinLast or 0) - if dospin<3 then - mini=CB.id<6 and cc7 then - finesse=true - elseif CY<=18 then - local y0=CY - local c=P.c - local B=CB.bk - for x=1,c do - local y - for i=#B,1,-1 do - if B[i][x]then - y=i - goto L1 - end - end - goto L2 - ::L1:: - if y then - x=CX+x-1 - for y1=y0+y,#P.field do - --Roof=finesse - if P:solid(x,y1)then - finesse=true - goto L2 - end - end - end - end - else - finesse=true - end - ::L2:: - - --Remove rows need to be cleared - if cc>0 then - for i=cc,1,-1 do - _=P.clearedRow[i] - if P.field[_][11]then - P.garbageBeneath=P.garbageBeneath-1 - gbcc=gbcc+1 - end - FREEROW.discard(rem(P.field,_)) - FREEROW.discard(rem(P.visTime,_)) - end - end - - --Cancel no-sense clearing FX - _=#P.clearingRow - while _>0 and P.clearingRow[_]>#P.field do - P.clearingRow[_]=nil - _=_-1 - end - if P.clearingRow[1]then - P.falling=ENV.fall - elseif cc==P.r then - clear=true - end - - --Finesse check (control) - local finePts - if not finesse then - if dospin then P.ctrlCount=P.ctrlCount-2 end--Allow 2 more step for roof-less spin - local id=CB.id - local d=P.ctrlCount-finesseList[id][P.dir+1][CX] - finePts=d<=0 and 5 or max(3-d,0) - else - finePts=5 - end - piece.finePts=finePts - - P.stat.finesseRate=P.stat.finesseRate+finePts - if finePts<5 then - P.stat.extraPiece=P.stat.extraPiece+1 - if ENV.fineKill then - P:lose() - end - if P.sound then - if ENV.fineKill then - SFX.play("finesseError_long",.6) - elseif ENV.fine then - SFX.play("finesseError",.8) - end - end - end - if finePts<=1 then - P.finesseCombo=0 - else - P.finesseCombo=P.finesseCombo+1 - if P.finesseCombo>2 then - P.finesseComboTime=12 - end - if P.sound then SFX.fieldPlay("lock",nil,P)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 below - cmb=cmb+1 - if dospin then - cscore=(spinSCR[CB.name]or spinSCR[8])[cc] - if P.b2b>1000 then - P:showText(text.b3b..text.block[CB.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 P.sound then - VOC.play("b3b",CHN) - end - elseif P.b2b>=50 then - P:showText(text.b2b..text.block[CB.name]..text.spin.." "..text.clear[cc],0,-30,35,"spin") - atk=b2bATK[cc] - cscore=cscore*1.2 - STAT.b2b=STAT.b2b+1 - if P.sound then - VOC.play("b2b",CHN) - end - else - P:showText(text.block[CB.name]..text.spin.." "..text.clear[cc],0,-30,45,"spin") - atk=2*cc - end - sendTime=20+atk*20 - if mini then - P:showText(text.mini,0,-80,35,"appear") - atk=atk*.25 - sendTime=sendTime+60 - cscore=cscore*.5 - P.b2b=P.b2b+b2bPoint[cc]*.5 - if P.sound then - VOC.play("mini",CHN) - end - else - P.b2b=P.b2b+b2bPoint[cc] - end - piece.mini=mini - piece.special=true - if P.sound then - SFX.play(spinSFX[cc]or"spin_3") - VOC.play(spinVoice[CB.name],CHN) - end - elseif cc>=4 then - cscore=cc==4 and 1000 or cc==5 and 1500 or 2000 - if P.b2b>1000 then - P: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 P.sound then - VOC.play("b3b",CHN) - end - elseif P.b2b>=50 then - P: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 P.sound then - VOC.play("b2b",CHN) - end - else - P:showText(text.clear[cc],0,-30,70,"stretch") - sendTime=60 - atk=2*cc-4 - end - P.b2b=P.b2b+cc*100-300 - piece.special=true - else - piece.special=false - end - if P.sound then - VOC.play(clearVoice[cc],CHN) - end - - --PC/HPC bonus - if clear and #P.field==0 then - P:showText(text.PC,0,-80,50,"flicker") - atk=atk*.5+min(8+STAT.pc*2,20) - exblock=exblock+2 - sendTime=sendTime+120 - if STAT.row+cc>4 then - P.b2b=1200 - cscore=cscore+300*min(6+STAT.pc,10) - else - cscore=cscore+626 - end - STAT.pc=STAT.pc+1 - if P.sound then - SFX.play("clear") - VOC.play("perfect_clear",CHN) - end - piece.pc=true - piece.special=true - elseif clear and(cc>1 or #P.field==P.garbageBeneath)then - P:showText(text.HPC,0,-80,50,"fly") - atk=atk+2 - exblock=exblock+2 - sendTime=sendTime+60 - cscore=cscore+626 - STAT.hpc=STAT.hpc+1 - if P.sound then - SFX.play("clear") - VOC.play("half_clear",CHN) - end - piece.hpc=true - piece.special=true - end - - --Normal clear, reduce B2B point - if not piece.special then - P.b2b=max(P.b2b-250,0) - P:showText(text.clear[cc],0,-30,35,"appear",(8-cc)*.3) - atk=cc-.5 - sendTime=20+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 - P: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 - - if P.b2b>1200 then P.b2b=1200 end - - --Bonus atk/def when focused - if modeEnv.royaleMode then - local i=min(#P.atker,9) - if i>1 then - atk=atk+reAtk[i] - exblock=exblock+reDef[i] - end - end - - --Send Lines - send=atk - if send>0 then - if exblock>0 then - exblock=int(exblock*(1+P.strength*.25))--Badge Buff - P:showText("+"..exblock,0,53,20,"fly") - off=off+P:cancel(exblock) - end - - send=int(send*(1+P.strength*.25))--Badge Buff - if send>0 then - P:showText(send,0,80,35,"zoomout") - _=P:cancel(send) - send=send-_ - off=off+_ - if send>0 then - local T - if modeEnv.royaleMode then - if P.atkMode==4 then - local M=#P.atker - if M>0 then - for i=1,M do - P:attack(P.atker[i],send,CB.color) - end - else - T=randomTarget(P) - end - else - P:freshTarget() - T=P.atking - end - elseif #PLAYERS.alive>1 then - T=randomTarget(P) - end - if T then - P:attack(T,send,CB.color) - end - end - if P.sound and send>3 then SFX.play("emit",min(send,7)*.1)end - end - end - - --SFX & Vibrate - if P.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 - P:showText(text.block[CB.name]..text.spin,0,-30,45,"appear") - P.b2b=P.b2b+20 - if P.sound then - SFX.play("spin_0") - VOC.play(spinVoice[CB.name],CHN) - end - cscore=30 - end - - if P.b2b>1000 then - P.b2b=max(P.b2b-40,1000) - end - P:garbageRelease() - end - - P.combo=cmb - - --DropSpeed bonus - if P._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 P.dropSpeed>60 then - cscore=cscore*(.9+P.dropSpeed/600) - end - - cscore=int(cscore) - if ENV.score then - P:showText(cscore,(P.curX+P.sc[2]-5.5)*30,(10-P.curY-P.sc[1])*30+P.fieldBeneath+P.fieldUp,int(8-120/(cscore+20))*5,"score",2) - 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 P.curMission then - local t=ENV.mission[P.curMission] - local success - if t<5 then - if piece.row==t and not piece.spin then - success=true - end - elseif t<9 then - if piece.row==t-4 and piece.spin then - success=true - end - elseif t==9 then - if piece.pc then - success=true - end - elseif t<90 then - if piece.row==t%10 and piece.name==int(t/10)and piece.spin then - success=true - end - end - if success then - P.curMission=P.curMission+1 - SFX.play("reach") - if P.curMission>#ENV.mission then - P.curMission=nil - P:win("finish") - end - elseif ENV.missionKill then - P:showText(text.missionFailed,0,140,40,"flicker",.5) - SFX.play("finesseError_long",.6) - P:lose(true) - end - end - - --Update stat - STAT.score=STAT.score+cscore - STAT.piece=STAT.piece+1 - STAT.row=STAT.row+cc - STAT.maxFinesseCombo=max(STAT.maxFinesseCombo,P.finesseCombo) - STAT.maxCombo=max(STAT.maxCombo,P.combo) - 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 - STAT.digatk=STAT.digatk+atk*gbcc/cc - end - local n=CB.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 - - --Drop event - _=ENV.dropPiece - if _ then _(P)end - end -end ----------------------------------------------------- - ----------------------------------------------------- -local function gameOver()--Save record - if GAME.replaying then return end - FILE.saveData() - local M=CURMODE - local R=M.getRank - if R then - local P=PLAYERS[1] - R=R(P)--New rank - if R then - local r=RANKS[M.name]--Old rank - local needSave - if R>r then - RANKS[M.name]=R - needSave=true - end - if R>0 then - GAME.rank=R - if M.unlock then - for i=1,#M.unlock do - local m=M.unlock[i] - local n=MODES[m].name - if not RANKS[n]then - RANKS[n]=MODES[m].score and 0 or 6 - needSave=true - end - end - end - end - if needSave then - FILE.saveUnlock() - end - local D=M.score(P) - local L=M.records - local p=#L--Rank-1 - if p>0 then - while M.comp(D,L[p])do--If higher rank - p=p-1 - if p==0 then break end - end - end - if p<10 then - if p==0 then - P:showTextF(text.newRecord,0,-100,100,"beat",.5) - end - D.date=os.date("%Y/%m/%d %H:%M") - ins(L,p+1,D) - if L[11]then L[11]=nil end - FILE.saveRecord(M.name,L) - end - end - end -end - -function Player.die(P)--Called both when win/lose! - P.alive=false - P.timing=false - P.control=false - P.update=PLY.update.dead - P.waiting=1e99 - P.b2b=0 - P.tasks={} - for i=1,#P.atkBuffer do - P.atkBuffer[i].sent=true - P.atkBuffer[i].time=0 - end - for i=1,#P.field do - for j=1,10 do - P.visTime[i][j]=min(P.visTime[i][j],20) - end - end -end -function Player.win(P,result) - if P.result then return end - P:die() - P.result="WIN" - if modeEnv.royaleMode then - P.modeData.event=1 - P:changeAtk() - end - if P.human then - GAME.result=result or"win" - SFX.play("win") - VOC.play("win") - if modeEnv.royaleMode then - BGM.play("8-bit happiness") - end - end - if CURMODE.id=="custom_puzzle"then - P:showTextF(text.win,0,0,90,"beat",.4) - else - P:showTextF(text.win,0,0,90,"beat",.5,.2) - end - if P.human then - gameOver() - TASK.new(TICK.autoPause,{0}) - if MARKING then - P:showTextF(text.marking,0,-226,25,"appear",.4,.0626) - end - end - P:newTask(TICK.finish) -end -function Player.lose(P,force) - if P.result then return end - if P.life>0 and not force then - P.waiting=62 - for _=#P.field,1,-1 do - FREEROW.discard(P.field[_]) - FREEROW.discard(P.visTime[_]) - P.field[_],P.visTime[_]=nil - end - P.garbageBeneath=0 - - if P.AI_mode=="CC"then - CC.destroy(P.AI_bot) - P.hd=nil - loadAI(P,P.AIdata) - end - - P.life=P.life-1 - P.fieldBeneath=0 - P.b2b=0 - for i=1,#P.atkBuffer do - local A=P.atkBuffer[i] - if not A.sent then - A.sent=true - A.time=0 - end - end - P.atkBuffer.sum=0 - - for i=1,21 do - P:createClearingFX(i,1.5) - end - sysFX.newShade(.7,1,1,1,P.x+150*P.size,P.y+60*P.size,300*P.size,610*P.size) - sysFX.newRectRipple(.5,P.x+150*P.size,P.y+60*P.size,300*P.size,610*P.size) - sysFX.newRipple(.5,P.x+(475+25*(P.life<3 and P.life or 0)+12)*P.size,P.y+(665+12)*P.size,20) - --300+25*i,595 - SFX.play("clear_3") - SFX.play("emit") - - return - end - P:die() - for i=1,#PLAYERS.alive do - if PLAYERS.alive[i]==P then - rem(PLAYERS.alive,i) - break - end - end - P.result="K.O." - if modeEnv.royaleMode then - P:changeAtk() - P.modeData.event=#PLAYERS.alive+1 - P.strength=0 - if P.lastRecv then - local A,i=P,0 - repeat - A,i=A.lastRecv,i+1 - until not A or A.alive or A==P or i==3 - if A and A.alive then - if P.id==1 or A.id==1 then - P.killMark=A.id==1 - end - A.modeData.point,A.badge=A.modeData.point+1,A.badge+P.badge+1 - for j=A.strength+1,4 do - if A.badge>=royaleData.powerUp[j]then - A.strength=j - A.frameColor=A.strength - end - end - P.lastRecv=A - if P.id==1 or A.id==1 then - TASK.new(TICK.throwBadge,{A.ai,P,max(3,P.badge)*4}) - end - end - else - P.badge=-1 - end - - freshMostBadge() - freshMostDangerous() - if #PLAYERS.alive==royaleData.stage[GAME.stage]then - royaleLevelup() - end - P:showTextF(P.modeData.event,0,120,60,"appear",.26,.9) - end - P.gameEnv.keepVisible=P.gameEnv.visible~="show" - P:showTextF(text.gameover,0,0,60,"appear",.26,.9) - if P.human then - GAME.result="gameover" - SFX.play("fail") - VOC.play("lose") - if modeEnv.royaleMode then - if P.modeData.event==2 then - BGM.play("hay what kind of feeling") - else - BGM.play("end") - end - end - gameOver() - P:newTask(#PLAYERS>1 and TICK.lose or TICK.finish) - TASK.new(TICK.autoPause,{0}) - if MARKING then - P:showTextF(text.marking,0,-226,25,"appear",.4,.0626) - end - else - P:newTask(TICK.lose) - end - if #PLAYERS.alive==1 then - PLAYERS.alive[1]:win() - end -end ---------------------------<\Events>-------------------------- - ----------------------------------------------------- -function Player.act_moveLeft(P,auto) - if not auto then - P.ctrlCount=P.ctrlCount+1 - end - P.movDir=-1 - if P.keyPressing[9]then - if P.gameEnv.swap then - P:changeAtkMode(1) - P.keyPressing[1]=false - end - elseif P.control and P.waiting==-1 then - if P.cur and not P:ifoverlap(P.cur.bk,P.curX-1,P.curY)then - if P.gameEnv.moveFX and P.gameEnv.block then - P:createMoveFX("left") - end - P.curX=P.curX-1 - P:freshBlock(false,true) - if P.sound and P.curY==P.imgY then SFX.play("move")end - if not auto then P.moving=0 end - P.spinLast=false - else - P.moving=P.gameEnv.das - end - else - P.moving=0 - end -end -function Player.act_moveRight(P,auto) - if not auto then - P.ctrlCount=P.ctrlCount+1 - end - P.movDir=1 - if P.keyPressing[9]then - if P.gameEnv.swap then - P:changeAtkMode(2) - P.keyPressing[2]=false - end - elseif P.control and P.waiting==-1 then - if P.cur and not P:ifoverlap(P.cur.bk,P.curX+1,P.curY)then - if P.gameEnv.moveFX and P.gameEnv.block then - P:createMoveFX("right") - end - P.curX=P.curX+1 - P:freshBlock(false,true) - if P.sound and P.curY==P.imgY then SFX.play("move")end - if not auto then P.moving=0 end - P.spinLast=false - else - P.moving=P.gameEnv.das - end - else - P.moving=0 - end -end -function Player.act_rotRight(P) - if P.control and P.waiting==-1 and P.cur then - P.ctrlCount=P.ctrlCount+1 - P:spin(1) - P.keyPressing[3]=false - end -end -function Player.act_rotLeft(P) - if P.control and P.waiting==-1 and P.cur then - P.ctrlCount=P.ctrlCount+1 - P:spin(3) - P.keyPressing[4]=false - end -end -function Player.act_rot180(P) - if P.control and P.waiting==-1 and P.cur then - P.ctrlCount=P.ctrlCount+2 - P:spin(2) - P.keyPressing[5]=false - end -end -function Player.act_hardDrop(P) - if P.keyPressing[9]then - if P.gameEnv.swap then - P:changeAtkMode(3) - end - P.keyPressing[6]=false - elseif P.control and P.waiting==-1 and P.cur then - if P.curY>P.imgY then - if P.gameEnv.dropFX and P.gameEnv.block and P.curY-P.imgY-P.r>-1 then - P:createDropFX(P.curX,P.curY-1,P.c,P.curY-P.imgY-P.r+1) - end - P.curY=P.imgY - P.spinLast=false - if P.gameEnv.shakeFX then - P.fieldOff.vy=P.gameEnv.shakeFX*.6 - end - if P.sound then - SFX.fieldPlay("drop",nil,P) - VIB(1) - end - end - P.lockDelay=-1 - P:drop() - P.keyPressing[6]=false - end -end -function Player.act_softDrop(P) - if P.keyPressing[9]then - if P.gameEnv.swap then - P:changeAtkMode(4) - end - else - P.downing=1 - if P.control and P.waiting==-1 and P.cur then - if P.curY>P.imgY then - P.curY=P.curY-1 - P:freshBlock(true,true) - P.spinLast=false - end - end - end -end -function Player.act_hold(P) - if P.control and P.waiting==-1 then - P:hold() - end -end -function Player.act_func(P) - P.gameEnv.Fkey(P) -end -function Player.act_restart() - if GAME.frame<240 or GAME.result then - resetPartGameData() - else - LOG.print(text.holdR,20,COLOR.orange) - end -end -function Player.act_insLeft(P,auto) - if not P.cur then return end - local x0=P.curX - while not P:ifoverlap(P.cur.bk,P.curX-1,P.curY)do - if P.gameEnv.moveFX and P.gameEnv.block then - P:createMoveFX("left") - end - P.curX=P.curX-1 - P:freshBlock(false,true) - end - if P.curX~=x0 then - P.spinLast=false - end - if P.gameEnv.shakeFX then - P.fieldOff.vx=-P.gameEnv.shakeFX*.5 - end - if auto then - if P.ctrlCount==0 then P.ctrlCount=1 end - else - P.ctrlCount=P.ctrlCount+1 - end -end -function Player.act_insRight(P,auto) - if not P.cur then return end - local x0=P.curX - while not P:ifoverlap(P.cur.bk,P.curX+1,P.curY)do - if P.gameEnv.moveFX and P.gameEnv.block then - P:createMoveFX("right") - end - P.curX=P.curX+1 - P:freshBlock(false,true) - end - if P.curX~=x0 then - P.spinLast=false - end - if P.gameEnv.shakeFX then - P.fieldOff.vx=P.gameEnv.shakeFX*.5 - end - if auto then - if P.ctrlCount==0 then P.ctrlCount=1 end - else - P.ctrlCount=P.ctrlCount+1 - end -end -function Player.act_insDown(P) - if P.cur and P.curY>P.imgY then - if P.gameEnv.dropFX and P.gameEnv.block and P.curY-P.imgY-P.r>-1 then - P:createDropFX(P.curX,P.curY-1,P.c,P.curY-P.imgY-P.r+1) - end - if P.gameEnv.shakeFX then - P.fieldOff.vy=P.gameEnv.shakeFX*.5 - end - P.curY=P.imgY - P.lockDelay=P.gameEnv.lock - P.spinLast=false - P:freshBlock(true,true) -end - end -function Player.act_down1(P) - if P.cur and P.curY>P.imgY then - if P.gameEnv.moveFX and P.gameEnv.block then - P:createMoveFX("down") - end - P.curY=P.curY-1 - P:freshBlock(true,true) - P.spinLast=false - end -end -function Player.act_down4(P) - if P.cur and P.curY>P.imgY then - local y=max(P.curY-4,P.imgY) - if P.gameEnv.dropFX and P.gameEnv.block and P.curY-y-P.r>-1 then - P:createDropFX(P.curX,P.curY-1,P.c,P.curY-y-P.r+1) - end - P.curY=y - P:freshBlock(true,true) - P.spinLast=false - end -end -function Player.act_down10(P) - if P.cur and P.curY>P.imgY then - local y=max(P.curY-10,P.imgY) - if P.gameEnv.dropFX and P.gameEnv.block and P.curY-y-P.r>-1 then - P:createDropFX(P.curX,P.curY-1,P.c,P.curY-y-P.r+1) - end - P.curY=y - P:freshBlock(true,true) - P.spinLast=false - end -end -function Player.act_dropLeft(P) - if not P.cur then return end - P:act_insLeft() - P:act_hardDrop() -end -function Player.act_dropRight(P) - if not P.cur then return end - P:act_insRight() - P:act_hardDrop() -end -function Player.act_zangiLeft(P) - if not P.cur then return end - P:act_insLeft() - P:act_insDown() - P:act_insRight() - P:act_hardDrop() -end -function Player.act_zangiRight(P) - if not P.cur then return end - P:act_insRight() - P:act_insDown() - P:act_insLeft() - P: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_func, --9 - Player.act_restart, --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 -} ----------------------------------------------------- - ---------------------------------------------------- function PLY.check_lineReach(P) if P.stat.row>=P.gameEnv.target then @@ -2063,7 +350,7 @@ function PLY.newDemoPlayer(id,x,y,size) } applyGameEnv(P) prepareSequence(P) - loadAI(P,{ + P:loadAI({ type="CC", next=5, hold=true, @@ -2101,7 +388,7 @@ function PLY.newAIPlayer(id,x,y,size,AIdata) end applyGameEnv(P) prepareSequence(P) - loadAI(P,AIdata) + P:loadAI(AIdata) end function PLY.newPlayer(id,x,y,size) local P=newEmptyPlayer(id,x,y,size) diff --git a/parts/player/player.lua b/parts/player/player.lua new file mode 100644 index 00000000..0ac33524 --- /dev/null +++ b/parts/player/player.lua @@ -0,0 +1,1728 @@ +------------------------------------------------------ +--Notice: anything in this file or in any other file, +--var P stands for Player object. Don't forget that. +------------------------------------------------------ +local Player={}--Player class + +local int,ceil,rnd=math.floor,math.ceil,math.random +local max,min=math.max,math.min +local ins,rem=table.insert,table.remove + +local kickList=require("parts/kickList") +local scs=spinCenters + +local function without(L,e) + for i=1,#L do + if L[i]==e then return end + end + return true +end + +---------------------------------------------------- +function Player.showText(P,text,dx,dy,font,style,spd,stop) + if P.gameEnv.text then + ins(P.bonus,TEXT.getText(text,150+dx,300+dy,font*P.size,style,spd,stop)) + end +end +function Player.showTextF(P,text,dx,dy,font,style,spd,stop) + ins(P.bonus,TEXT.getText(text,150+dx,300+dy,font*P.size,style,spd,stop)) +end +function Player.createLockFX(P) + local BK=P.cur.bk + local t=12-P.gameEnv.lockFX*2 + + for i=1,P.r do + local y=P.curY+i-1 + if without(P.clearedRow,y)then + y=-30*y + for j=1,P.c do + if BK[i][j]then + ins(P.lockFX,{30*(P.curX+j-2),y,0,t}) + end + end + end + end +end +function Player.createDropFX(P,x,y,w,h) + ins(P.dropFX,{x,y,w,h,0,13-2*P.gameEnv.dropFX}) +end +function Player.createMoveFX(P,dir) + local T=10-1.5*P.gameEnv.moveFX + local C=P.cur.color + local x=P.curX-1 + local y=P.gameEnv.smooth and P.curY+P.dropDelay/P.gameEnv.drop-2 or P.curY-1 + if dir=="left"then + for i=1,P.r do for j=P.c,1,-1 do + if P.cur.bk[i][j]then + ins(P.moveFX,{C,x+j,y+i,0,T}) + break + end + end end + elseif dir=="right"then + for i=1,P.r do for j=1,P.c do + if P.cur.bk[i][j]then + ins(P.moveFX,{C,x+j,y+i,0,T}) + break + end + end end + elseif dir=="down"then + for j=1,P.c do for i=P.r,1,-1 do + if P.cur.bk[i][j]then + ins(P.moveFX,{C,x+j,y+i,0,T}) + break + end + end end + else + for i=1,P.r do for j=1,P.c do + if P.cur.bk[i][j]then + ins(P.moveFX,{C,x+j,y+i,0,T}) + end + end end + end +end +function Player.createClearingFX(P,y,spd) + ins(P.clearFX,{y,0,spd}) +end +function Player.createBeam(P,R,send,color) + local x1,y1,x2,y2 + if P.small then x1,y1=P.centerX,P.centerY + else x1,y1=P.x+(30*(P.curX+P.sc[2])-30+15+150)*P.size,P.y+(600-30*(P.curY+P.sc[1])+15+70)*P.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 + + wid=int(send^.7*(4+SETTING.atkFX)) + local r,g,b=unpack(SKIN.libColor[color]) + r,g,b=r*2,g*2,b*2 + + local a=modeEnv.royaleMode and not(P.human or R.human)and .2 or 1 + sysFX.newAttack(1-SETTING.atkFX*.1,x1,y1,x2,y2,wid,r,g,b,a*(SETTING.atkFX+2)*.0626) +end +---------------------------------------------------- + +---------------------------------------------------- +function Player.RND(P,a,b) + local R=P.randGen + return R:random(a,b) +end + +function Player.set20G(P,if20g,init) + P._20G=if20g + P.keyAvailable[7]=not if20g + virtualkey[7].ava=not if20g + if init and if20g and P.AI_mode=="CC"then CC.switch20G(P)end +end +function Player.setHold(P,ifhold) + P.gameEnv.hold=ifhold + P.keyAvailable[8]=not ifhold + virtualkey[8].ava=not ifhold + if not ifhold then + P.hd=nil + end +end +function Player.setNext(P,next) + P.gameEnv.next=next +end +function Player.setInvisible(P,time) + if time<0 then + P.keepVisible=true + else + P.keepVisible=false + P.showTime=time + end +end +function Player.setRS(P,RSname) + P.RS=kickList[RSname] +end + +function Player.newTask(P,code,data) + local L=P.tasks + ins(L,{ + code=code, + data=data, + }) +end + +function Player.solid(P,x,y) + if x<1 or x>10 or y<1 then return true end + if y>#P.field then return false end + return P.field[y] + [x]>0--to catch bug (nil[*]) +end +function Player.ifoverlap(P,bk,x,y) + local C=#bk[1] + if x<1 or x+C>11 or y<1 then return true end + if y>#P.field then return end + for i=1,#bk do + if P.field[y+i-1]then + for j=1,C do + if bk[i][j]and P.field[y+i-1][x+j-1]>0 then return true end + end + end + end +end +function Player.attack(P,R,send,time,...) + if SETTING.atkFX>0 then + P:createBeam(R,send,time,...) + end + R.lastRecv=P + if R.atkBuffer.sum<20 then + local B=R.atkBuffer + if send>20-B.sum then send=20-B.sum end--No more then 20 + local m,k=#B,1 + while k<=m and time>B[k].countdown do k=k+1 end + for i=m,k,-1 do + B[i+1]=B[i] + end + B[k]={ + pos=P:RND(10), + amount=send, + countdown=time, + cd0=time, + time=0, + sent=false, + lv=min(int(send^.69),5), + }--Sorted insert(by time) + B.sum=B.sum+send + R.stat.recv=R.stat.recv+send + if R.sound then + SFX.play(send<4 and"blip_1"or"blip_2",min(send+1,5)*.1) + end + end +end + +function Player.getHolePos(P) + if P.garbageBeneath==0 then + return P:RND(10) + else + local p=P:RND(10) + if P.field[1][p]<=0 then + return P:RND(10) + end + return p + end +end +function Player.garbageRelease(P) + local n,flag=1 + while true do + local A=P.atkBuffer[n] + if A and A.countdown<=0 and not A.sent then + P:garbageRise(19+A.lv,A.amount,A.pos) + P.atkBuffer.sum=P.atkBuffer.sum-A.amount + A.sent,A.time=true,0 + P.stat.pend=P.stat.pend+A.amount + n=n+1 + flag=true + else + break + end + end + if flag and P.AI_mode=="CC"then CC.updateField(P)end +end +function Player.garbageRise(P,color,amount,pos) + local _ + local t=P.showTime*2 + for _=1,amount do + ins(P.field,1,FREEROW.get(color,true)) + ins(P.visTime,1,FREEROW.get(t)) + P.field[1][pos]=0 + end + P.fieldBeneath=P.fieldBeneath+amount*30 + if P.cur then + P.curY=P.curY+amount + P.imgY=P.imgY+amount + end + P.garbageBeneath=P.garbageBeneath+amount + for i=1,#P.clearingRow do + P.clearingRow[i]=P.clearingRow[i]+amount + end + P:freshBlock(false,false) + for i=1,#P.lockFX do + _=P.lockFX[i] + _[2]=_[2]-30*amount--Shift 30px per line cleared + end + for i=1,#P.dropFX do + _=P.dropFX[i] + _[3],_[5]=_[3]+amount,_[5]+amount + end + if #P.field>42 then P:lose()end +end + +local invList={2,1,4,3,5,6,7} +function Player.pushLine(P,L,mir) + local l=#L + local S=P.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(P.field,1,r) + ins(P.visTime,1,FREEROW.get(20)) + end + P.fieldBeneath=P.fieldBeneath+30*l + P.curY=P.curY+l + P.imgY=P.imgY+l + P:freshBlock(false,false) +end +function Player.pushNext(P,L,mir) + for i=1,#L do + P:getNext(mir and invList[L[i]]or L[i]) + end +end + +function Player.freshTarget(P) + if P.atkMode==1 then + if not P.atking or not P.atking.alive or rnd()<.1 then + P:changeAtk(randomTarget(P)) + end + elseif P.atkMode==2 then + P:changeAtk(P~=GAME.mostBadge and GAME.mostBadge or GAME.secBadge or randomTarget(P)) + elseif P.atkMode==3 then + P:changeAtk(P~=GAME.mostDangerous and GAME.mostDangerous or GAME.secDangerous or randomTarget(P)) + elseif P.atkMode==4 then + for i=1,#P.atker do + if not P.atker[i].alive then + rem(P.atker,i) + return + end + end + end +end +function Player.changeAtkMode(P,m) + if P.atkMode==m then return end + P.atkMode=m + if m==1 then + P:changeAtk(randomTarget(P)) + elseif m==2 or m==3 then + P:freshTarget() + elseif m==4 then + P:changeAtk() + end +end +function Player.changeAtk(P,R) + -- if not P.human then R=PLAYERS[1]end--1vALL mode? + if P.atking then + local K=P.atking.atker + for i=1,#K do + if K[i]==P then + rem(K,i) + break + end + end + end + if R then + P.atking=R + ins(R.atker,P) + else + P.atking=nil + end +end +function Player.freshBlock(P,keepGhost,control,system) + local ENV=P.gameEnv + if not keepGhost and P.cur then + P.imgY=min(#P.field+1,P.curY) + if P._20G or P.keyPressing[7]and ENV.sdarr==0 then + local _=P.imgY + + --Move ghost to bottom + while not P:ifoverlap(P.cur.bk,P.curX,P.imgY-1)do + P.imgY=P.imgY-1 + end + + --Cancel spinLast + if _~=P.imgY then + P.spinLast=false + end + + --Create FX if dropped + if P.curY>P.imgY then + if ENV.dropFX and ENV.block and P.curY-P.imgY-P.r>-1 then + P:createDropFX(P.curX,P.curY-1,P.c,P.curY-P.imgY-P.r+1) + end + if ENV.shakeFX then + P.fieldOff.vy=ENV.shakeFX*.5 + end + P.curY=P.imgY + end + else + while not P:ifoverlap(P.cur.bk,P.curX,P.imgY-1)do + P.imgY=P.imgY-1 + end + end + end + + if control then + if ENV.easyFresh then + local d0=ENV.lock + if P.lockDelay=P.gameEnv.das then + local x=P.curX+P.movDir + if not P:ifoverlap(C.bk,x,y)then + P.curX=x + end + end + + --IRS + if P.gameEnv.irs then + if _[5]then + P:spin(2,true) + else + if _[3]then + if _[4]then + P:spin(2,true) + else + P:spin(1,true) + end + elseif _[4]then + P:spin(3,true) + end + end + end + + --Spawn SFX + if P.sound and id<8 then + SFX.fplay(spawnSFX_name[id],SETTING.spawn) + end +end + +function Player.spin(P,d,ifpre) + local iki=P.RS[P.cur.id] + if type(iki)=="table"then + local idir=(P.dir+d)%4 + local icb=BLOCKS[P.cur.id][idir] + local isc=scs[P.cur.id][idir] + local ir,ic=#icb,#icb[1] + local ix,iy=P.curX+P.sc[2]-isc[2],P.curY+P.sc[1]-isc[1] + iki=iki[P.dir*10+idir] + if not iki then + if P.gameEnv.easyFresh then + P:freshBlock(false,true) + end + SFX.fieldPlay(ifpre and"prerotate"or"rotate",nil,P) + return + end + for test=1,#iki do + local x,y=ix+iki[test][1],iy+iki[test][2] + if not P:ifoverlap(icb,x,y)and(P.freshTime<=P.gameEnv.freshLimit or iki[test][2]<0)then + ix,iy=x,y + if P.gameEnv.moveFX and P.gameEnv.block then + P:createMoveFX() + end + P.curX,P.curY,P.dir=ix,iy,idir + P.sc,P.cur.bk=scs[P.cur.id][idir],icb + P.r,P.c=ir,ic + P.spinLast=test==2 and 0 or 1 + if not ifpre then + P:freshBlock(false,true) + end + if iki[test][2]>0 and not P.gameEnv.easyFresh then + P.freshTime=P.freshTime+1 + end + + if P.sound then + SFX.fieldPlay(ifpre and"prerotate"or P:ifoverlap(P.cur.bk,P.curX,P.curY+1)and P:ifoverlap(P.cur.bk,P.curX-1,P.curY)and P:ifoverlap(P.cur.bk,P.curX+1,P.curY)and"rotatekick"or"rotate",nil,P) + end + P.stat.rotate=P.stat.rotate+1 + return + end + end + else + iki(P,d) + end +end +function Player.hold(P,ifpre) + if not P.holded and (ifpre or P.waiting==-1) and P.gameEnv.hold then + local H,C=P.hd,P.cur + if not(H or C)then return end + + --Finesse check + if H and C and H.id==C.id and H.name==C.name then + P.ctrlCount=P.ctrlCount+1 + elseif P.ctrlCount<=1 then + P.ctrlCount=0 + end + + P.spinLast=false + P.spinSeq=0 + + P.cur,P.hd=H,C--Swap hold + H,C=P.hd,P.cur + + if P.next[1]or C then--Make hold available in fixed sequence + P.holded=P.gameEnv.oncehold + end + + if H then + local hid=P.hd.id + P.hd.bk=BLOCKS[hid][P.gameEnv.face[hid]] + end + if not C then + C=rem(P.next,1) + P:newNext() + if C then + P.cur=C + P.pieceCount=P.pieceCount+1 + if P.AI_mode=="CC"then + local next=P.next[P.AIdata.next] + if next then + CC.addNext(P.AI_bot,next.id) + end + end + else + P.holded=false + end + end + if C then + P:resetBlock() + P:freshBlock(false,true) + P.dropDelay=P.gameEnv.drop + P.lockDelay=P.gameEnv.lock + P.freshTime=max(P.freshTime-5,0) + if P:ifoverlap(P.cur.bk,P.curX,P.curY)then P:lock()P:lose()end + end + + if P.sound then + SFX.play(ifpre and"prehold"or"hold") + end + P.stat.hold=P.stat.hold+1 + end +end + +function Player.getNext(P,n) + local E=P.gameEnv + ins(P.next,{bk=BLOCKS[n][E.face[n]],id=n,color=E.bone and 17 or E.skin[n],name=n}) +end +function Player.popNext(P)--Pop next queue to hand + P.holded=false + P.spinLast=false + P.spinSeq=0 + P.ctrlCount=0 + + P.cur=rem(P.next,1) + P:newNext() + if P.cur then + P.pieceCount=P.pieceCount+1 + if P.AI_mode=="CC"then + local next=P.next[P.AIdata.next] + if next then + CC.addNext(P.AI_bot,next.id) + end + end + + local _=P.keyPressing + --IHS + if _[8]and P.gameEnv.hold and P.gameEnv.ihs then + P:hold(true) + _[8]=false + else + P:resetBlock() + end + + P.dropDelay=P.gameEnv.drop + P.lockDelay=P.gameEnv.lock + P.freshTime=0 + + if P.cur then + if P:ifoverlap(P.cur.bk,P.curX,P.curY)then + P:lock() + P:lose() + end + P:freshBlock(false,true,true) + end + + --IHdS + if _[6]then + P.act_hardDrop(P) + _[6]=false + end + end +end + +function Player.cancel(P,N)--Cancel Garbage + local k=0 --Pointer, attack bar selected + local off=0 --Lines offseted + local bf=P.atkBuffer + ::R:: + if bf.sum>0 then + local A + repeat + k=k+1 + A=bf[k] + if not A then return off end + until not A.sent + if N>=A.amount then + local O=A.amount--Cur Offset + N=N-O + off=off+O + bf.sum=bf.sum-O + A.sent,A.time=true,0 + if N>0 then goto R end + else + off=off+N + A.amount=A.amount-N + bf.sum=bf.sum-N + end + end + return off +end +do--player:drop()--Place piece + local clearSCR={80,200,400}--Techrash:1K; B2Bmul:1.3/1.8 + local spinSCR={ + {200,750,1300,2000},--Z + {200,750,1300,2000},--S + {220,700,1300,2000},--L + {220,700,1300,2000},--J + {250,800,1400,2000},--T + {260,900,1600,4500,7000},--O + {300,1200,1700,4000,6000},--I + {220,800,2000,3000,8000,26000},--Else + }--B2Bmul:1.2/2.0; Mini*=.6 + local b2bPoint={50,100,180,1000,1200,9999} + + local b2bATK={3,5,8,12,18} + local reAtk={0,0,1,1,1,2,2,3,3} + local reDef={0,1,1,2,3,3,4,4,5} + + local spinVoice={"zspin","sspin","jspin","lspin","tspin","ospin","ispin","zspin","sspin","pspin","qspin","fspin","espin","tspin","uspin","vspin","wspin","xspin","jspin","lspin","rspin","yspin","hspin","nspin","ispin"} + local clearVoice={"single","double","triple","techrash","pentcrash","hexcrash"} + local spinSFX={[0]="spin_0","spin_1","spin_2"} + local clearSFX={"clear_1","clear_2","clear_3"} + local renSFX={}for i=1,11 do renSFX[i]="ren_"..i end + local finesseList={ + [1]={ + {1,2,1,0,1,2,2,1}, + {2,2,2,1,1,2,3,2,2}, + },--Z + [3]={ + {1,2,1,0,1,2,2,1}, + {2,2,3,2,1,2,3,3,2}, + {3,4,3,2,3,4,4,3}, + {2,3,2,1,2,3,3,2,2}, + },--L + [6]={ + {1,2,2,1,0,1,2,2,1}, + },--O + [7]={ + {1,2,1,0,1,2,1}, + {2,2,2,2,1,1,2,2,2,2}, + },--I + } + finesseList[1][3],finesseList[1][4],finesseList[7][3],finesseList[7][4]=finesseList[1][1],finesseList[1][2],finesseList[7][1],finesseList[7][2]--"2-phase" SZI + finesseList[2]=finesseList[1]--S=Z + finesseList[4],finesseList[5]=finesseList[3],finesseList[3]--J=L=T + function Player.drop(P) + local _ + local CHN=VOC.getFreeChannel() + P.dropTime[11]=ins(P.dropTime,1,GAME.frame)--Update speed dial + local ENV=P.gameEnv + local STAT=P.stat + local piece=P.lastPiece + + local cmb=P.combo + local CB,CX,CY=P.cur,P.curX,P.curY + local clear--If clear with no line fall + local cc,gbcc=0,0--Row/garbage-row cleared,full-part + local atk,exblock=0,0--Attack & extra defense + local send,off=0,0--Sending lines remain & offset + local cscore,sendTime=10,0--Score & send Time + local dospin,mini=0 + + piece.id,piece.name=CB.id,CB.name + P.waiting=ENV.wait + + --Tri-corner spin check + if P.spinLast then + if CB.id<6 then + local x,y=CX+P.sc[2],CY+P.sc[1] + local c=0 + if P:solid(x-1,y+1)then c=c+1 end + if P:solid(x+1,y+1)then c=c+1 end + if c==0 then goto NTC end + if P:solid(x-1,y-1)then c=c+1 end + if P:solid(x+1,y-1)then c=c+1 end + if c>2 then dospin=dospin+2 end + end + ::NTC:: + end + --Immovable spin check + if P:ifoverlap(CB.bk,CX,CY+1)and P:ifoverlap(CB.bk,CX-1,CY)and P:ifoverlap(CB.bk,CX+1,CY)then + dospin=dospin+2 + end + + --Lock block to field + P:lock() + + --Clear list of cleared-rows + if P.clearedRow[1]then P.clearedRow={}end + + --Check line clear + for i=1,P.r do + local h=CY+i-2 + + --Bomb trigger + if h>0 and P.field[h]and P.clearedRow[cc]~=h then + for x=1,P.c do + if CB.bk[i][x]and P.field[h][CX+x-1]==19 then + cc=cc+1 + P.clearingRow[cc]=h-cc+1 + P.clearedRow[cc]=h + break + end + end + end + + h=h+1 + --Row filled + for x=1,10 do + if P.field[h][x]<=0 then + goto notFull + end + end + cc=cc+1 + P.clearingRow[cc]=h-cc+1 + P.clearedRow[cc]=h + ::notFull:: + end + + --Create clearing FX + if cc>0 and ENV.clearFX then + local t=7-ENV.clearFX*1 + for i=1,cc do + P:createClearingFX(P.clearedRow[i],t) + end + end + + --Create locking FX + if ENV.lockFX then + if cc==0 then + P:createLockFX() + else + _=#P.lockFX + if _>0 then + for _=1,_ do + rem(P.lockFX) + end + end + end + end + + --Final spin check + if dospin>0 then + if cc>0 then + dospin=dospin+(P.spinLast or 0) + if dospin<3 then + mini=CB.id<6 and cc7 then + finesse=true + elseif CY<=18 then + local y0=CY + local c=P.c + local B=CB.bk + for x=1,c do + local y + for i=#B,1,-1 do + if B[i][x]then + y=i + goto L1 + end + end + goto L2 + ::L1:: + if y then + x=CX+x-1 + for y1=y0+y,#P.field do + --Roof=finesse + if P:solid(x,y1)then + finesse=true + goto L2 + end + end + end + end + else + finesse=true + end + ::L2:: + + --Remove rows need to be cleared + if cc>0 then + for i=cc,1,-1 do + _=P.clearedRow[i] + if P.field[_][11]then + P.garbageBeneath=P.garbageBeneath-1 + gbcc=gbcc+1 + end + FREEROW.discard(rem(P.field,_)) + FREEROW.discard(rem(P.visTime,_)) + end + end + + --Cancel no-sense clearing FX + _=#P.clearingRow + while _>0 and P.clearingRow[_]>#P.field do + P.clearingRow[_]=nil + _=_-1 + end + if P.clearingRow[1]then + P.falling=ENV.fall + elseif cc==P.r then + clear=true + end + + --Finesse check (control) + local finePts + if not finesse then + if dospin then P.ctrlCount=P.ctrlCount-2 end--Allow 2 more step for roof-less spin + local id=CB.id + local d=P.ctrlCount-finesseList[id][P.dir+1][CX] + finePts=d<=0 and 5 or max(3-d,0) + else + finePts=5 + end + piece.finePts=finePts + + P.stat.finesseRate=P.stat.finesseRate+finePts + if finePts<5 then + P.stat.extraPiece=P.stat.extraPiece+1 + if ENV.fineKill then + P:lose() + end + if P.sound then + if ENV.fineKill then + SFX.play("finesseError_long",.6) + elseif ENV.fine then + SFX.play("finesseError",.8) + end + end + end + if finePts<=1 then + P.finesseCombo=0 + else + P.finesseCombo=P.finesseCombo+1 + if P.finesseCombo>2 then + P.finesseComboTime=12 + end + if P.sound then SFX.fieldPlay("lock",nil,P)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 below + cmb=cmb+1 + if dospin then + cscore=(spinSCR[CB.name]or spinSCR[8])[cc] + if P.b2b>1000 then + P:showText(text.b3b..text.block[CB.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 P.sound then + VOC.play("b3b",CHN) + end + elseif P.b2b>=50 then + P:showText(text.b2b..text.block[CB.name]..text.spin.." "..text.clear[cc],0,-30,35,"spin") + atk=b2bATK[cc] + cscore=cscore*1.2 + STAT.b2b=STAT.b2b+1 + if P.sound then + VOC.play("b2b",CHN) + end + else + P:showText(text.block[CB.name]..text.spin.." "..text.clear[cc],0,-30,45,"spin") + atk=2*cc + end + sendTime=20+atk*20 + if mini then + P:showText(text.mini,0,-80,35,"appear") + atk=atk*.25 + sendTime=sendTime+60 + cscore=cscore*.5 + P.b2b=P.b2b+b2bPoint[cc]*.5 + if P.sound then + VOC.play("mini",CHN) + end + else + P.b2b=P.b2b+b2bPoint[cc] + end + piece.mini=mini + piece.special=true + if P.sound then + SFX.play(spinSFX[cc]or"spin_3") + VOC.play(spinVoice[CB.name],CHN) + end + elseif cc>=4 then + cscore=cc==4 and 1000 or cc==5 and 1500 or 2000 + if P.b2b>1000 then + P: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 P.sound then + VOC.play("b3b",CHN) + end + elseif P.b2b>=50 then + P: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 P.sound then + VOC.play("b2b",CHN) + end + else + P:showText(text.clear[cc],0,-30,70,"stretch") + sendTime=60 + atk=2*cc-4 + end + P.b2b=P.b2b+cc*100-300 + piece.special=true + else + piece.special=false + end + if P.sound then + VOC.play(clearVoice[cc],CHN) + end + + --PC/HPC bonus + if clear and #P.field==0 then + P:showText(text.PC,0,-80,50,"flicker") + atk=atk*.5+min(8+STAT.pc*2,20) + exblock=exblock+2 + sendTime=sendTime+120 + if STAT.row+cc>4 then + P.b2b=1200 + cscore=cscore+300*min(6+STAT.pc,10) + else + cscore=cscore+626 + end + STAT.pc=STAT.pc+1 + if P.sound then + SFX.play("clear") + VOC.play("perfect_clear",CHN) + end + piece.pc=true + piece.special=true + elseif clear and(cc>1 or #P.field==P.garbageBeneath)then + P:showText(text.HPC,0,-80,50,"fly") + atk=atk+2 + exblock=exblock+2 + sendTime=sendTime+60 + cscore=cscore+626 + STAT.hpc=STAT.hpc+1 + if P.sound then + SFX.play("clear") + VOC.play("half_clear",CHN) + end + piece.hpc=true + piece.special=true + end + + --Normal clear, reduce B2B point + if not piece.special then + P.b2b=max(P.b2b-250,0) + P:showText(text.clear[cc],0,-30,35,"appear",(8-cc)*.3) + atk=cc-.5 + sendTime=20+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 + P: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 + + if P.b2b>1200 then P.b2b=1200 end + + --Bonus atk/def when focused + if modeEnv.royaleMode then + local i=min(#P.atker,9) + if i>1 then + atk=atk+reAtk[i] + exblock=exblock+reDef[i] + end + end + + --Send Lines + send=atk + if send>0 then + if exblock>0 then + exblock=int(exblock*(1+P.strength*.25))--Badge Buff + P:showText("+"..exblock,0,53,20,"fly") + off=off+P:cancel(exblock) + end + + send=int(send*(1+P.strength*.25))--Badge Buff + if send>0 then + P:showText(send,0,80,35,"zoomout") + _=P:cancel(send) + send=send-_ + off=off+_ + if send>0 then + local T + if modeEnv.royaleMode then + if P.atkMode==4 then + local M=#P.atker + if M>0 then + for i=1,M do + P:attack(P.atker[i],send,CB.color) + end + else + T=randomTarget(P) + end + else + P:freshTarget() + T=P.atking + end + elseif #PLAYERS.alive>1 then + T=randomTarget(P) + end + if T then + P:attack(T,send,CB.color) + end + end + if P.sound and send>3 then SFX.play("emit",min(send,7)*.1)end + end + end + + --SFX & Vibrate + if P.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 + P:showText(text.block[CB.name]..text.spin,0,-30,45,"appear") + P.b2b=P.b2b+20 + if P.sound then + SFX.play("spin_0") + VOC.play(spinVoice[CB.name],CHN) + end + cscore=30 + end + + if P.b2b>1000 then + P.b2b=max(P.b2b-40,1000) + end + P:garbageRelease() + end + + P.combo=cmb + + --DropSpeed bonus + if P._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 P.dropSpeed>60 then + cscore=cscore*(.9+P.dropSpeed/600) + end + + cscore=int(cscore) + if ENV.score then + P:showText(cscore,(P.curX+P.sc[2]-5.5)*30,(10-P.curY-P.sc[1])*30+P.fieldBeneath+P.fieldUp,int(8-120/(cscore+20))*5,"score",2) + 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 P.curMission then + local t=ENV.mission[P.curMission] + local success + if t<5 then + if piece.row==t and not piece.spin then + success=true + end + elseif t<9 then + if piece.row==t-4 and piece.spin then + success=true + end + elseif t==9 then + if piece.pc then + success=true + end + elseif t<90 then + if piece.row==t%10 and piece.name==int(t/10)and piece.spin then + success=true + end + end + if success then + P.curMission=P.curMission+1 + SFX.play("reach") + if P.curMission>#ENV.mission then + P.curMission=nil + P:win("finish") + end + elseif ENV.missionKill then + P:showText(text.missionFailed,0,140,40,"flicker",.5) + SFX.play("finesseError_long",.6) + P:lose(true) + end + end + + --Update stat + STAT.score=STAT.score+cscore + STAT.piece=STAT.piece+1 + STAT.row=STAT.row+cc + STAT.maxFinesseCombo=max(STAT.maxFinesseCombo,P.finesseCombo) + STAT.maxCombo=max(STAT.maxCombo,P.combo) + 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 + STAT.digatk=STAT.digatk+atk*gbcc/cc + end + local n=CB.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 + + --Drop event + _=ENV.dropPiece + if _ then _(P)end + end +end +function Player.loadAI(P,AIdata)--Load AI params + local ENV=P.gameEnv + P.AI_mode=AIdata.type + P.AI_stage=1 + P.AI_keys={} + P.AI_delay=AIdata.delay or min(int(ENV.drop*.8),AIdata.delta*rnd()*4) + P.AI_delay0=AIdata.delta + P.AIdata={ + type=AIdata.type, + delay=AIdata.delay, + delta=AIdata.delta, + + next=AIdata.next, + hold=AIdata.hold, + _20G=P._20G, + bag=AIdata.bag, + node=AIdata.node, + } + if not CC then + P.AI_mode="9S" + P.AI_delay0=int(P.AI_delay0*.26) + end + if P.AI_mode=="CC"then + P:setRS("AIRS") + local opt,wei=CC.getConf() + CC.fastWeights(wei) + CC.setHold(opt,P.AIdata.hold) + CC.set20G(opt,P.AIdata._20G) + CC.setBag(opt,P.AIdata.bag=="bag") + CC.setNode(opt,P.AIdata.node) + P.AI_bot=CC.new(opt,wei) + CC.free(opt)CC.free(wei) + for i=1,AIdata.next do + CC.addNext(P.AI_bot,P.next[i].id) + end + end +end +---------------------------------------------------- + +---------------------------------------------------- +local function gameOver()--Save record + if GAME.replaying then return end + FILE.saveData() + local M=CURMODE + local R=M.getRank + if R then + local P=PLAYERS[1] + R=R(P)--New rank + if R then + local r=RANKS[M.name]--Old rank + local needSave + if R>r then + RANKS[M.name]=R + needSave=true + end + if R>0 then + GAME.rank=R + if M.unlock then + for i=1,#M.unlock do + local m=M.unlock[i] + local n=MODES[m].name + if not RANKS[n]then + RANKS[n]=MODES[m].score and 0 or 6 + needSave=true + end + end + end + end + if needSave then + FILE.saveUnlock() + end + local D=M.score(P) + local L=M.records + local p=#L--Rank-1 + if p>0 then + while M.comp(D,L[p])do--If higher rank + p=p-1 + if p==0 then break end + end + end + if p<10 then + if p==0 then + P:showTextF(text.newRecord,0,-100,100,"beat",.5) + end + D.date=os.date("%Y/%m/%d %H:%M") + ins(L,p+1,D) + if L[11]then L[11]=nil end + FILE.saveRecord(M.name,L) + end + end + end +end + +function Player.die(P)--Called both when win/lose! + P.alive=false + P.timing=false + P.control=false + P.update=PLY.update.dead + P.waiting=1e99 + P.b2b=0 + P.tasks={} + for i=1,#P.atkBuffer do + P.atkBuffer[i].sent=true + P.atkBuffer[i].time=0 + end + for i=1,#P.field do + for j=1,10 do + P.visTime[i][j]=min(P.visTime[i][j],20) + end + end +end +function Player.win(P,result) + if P.result then return end + P:die() + P.result="WIN" + if modeEnv.royaleMode then + P.modeData.event=1 + P:changeAtk() + end + if P.human then + GAME.result=result or"win" + SFX.play("win") + VOC.play("win") + if modeEnv.royaleMode then + BGM.play("8-bit happiness") + end + end + if CURMODE.id=="custom_puzzle"then + P:showTextF(text.win,0,0,90,"beat",.4) + else + P:showTextF(text.win,0,0,90,"beat",.5,.2) + end + if P.human then + gameOver() + TASK.new(TICK.autoPause,{0}) + if MARKING then + P:showTextF(text.marking,0,-226,25,"appear",.4,.0626) + end + end + P:newTask(TICK.finish) +end +function Player.lose(P,force) + if P.result then return end + if P.life>0 and not force then + P.waiting=62 + for _=#P.field,1,-1 do + FREEROW.discard(P.field[_]) + FREEROW.discard(P.visTime[_]) + P.field[_],P.visTime[_]=nil + end + P.garbageBeneath=0 + + if P.AI_mode=="CC"then + CC.destroy(P.AI_bot) + P.hd=nil + P:loadAI(P.AIdata) + end + + P.life=P.life-1 + P.fieldBeneath=0 + P.b2b=0 + for i=1,#P.atkBuffer do + local A=P.atkBuffer[i] + if not A.sent then + A.sent=true + A.time=0 + end + end + P.atkBuffer.sum=0 + + for i=1,21 do + P:createClearingFX(i,1.5) + end + sysFX.newShade(.7,1,1,1,P.x+150*P.size,P.y+60*P.size,300*P.size,610*P.size) + sysFX.newRectRipple(.5,P.x+150*P.size,P.y+60*P.size,300*P.size,610*P.size) + sysFX.newRipple(.5,P.x+(475+25*(P.life<3 and P.life or 0)+12)*P.size,P.y+(665+12)*P.size,20) + --300+25*i,595 + SFX.play("clear_3") + SFX.play("emit") + + return + end + P:die() + for i=1,#PLAYERS.alive do + if PLAYERS.alive[i]==P then + rem(PLAYERS.alive,i) + break + end + end + P.result="K.O." + if modeEnv.royaleMode then + P:changeAtk() + P.modeData.event=#PLAYERS.alive+1 + P.strength=0 + if P.lastRecv then + local A,i=P,0 + repeat + A,i=A.lastRecv,i+1 + until not A or A.alive or A==P or i==3 + if A and A.alive then + if P.id==1 or A.id==1 then + P.killMark=A.id==1 + end + A.modeData.point,A.badge=A.modeData.point+1,A.badge+P.badge+1 + for j=A.strength+1,4 do + if A.badge>=royaleData.powerUp[j]then + A.strength=j + A.frameColor=A.strength + end + end + P.lastRecv=A + if P.id==1 or A.id==1 then + TASK.new(TICK.throwBadge,{A.ai,P,max(3,P.badge)*4}) + end + end + else + P.badge=-1 + end + + freshMostBadge() + freshMostDangerous() + if #PLAYERS.alive==royaleData.stage[GAME.stage]then + royaleLevelup() + end + P:showTextF(P.modeData.event,0,120,60,"appear",.26,.9) + end + P.gameEnv.keepVisible=P.gameEnv.visible~="show" + P:showTextF(text.gameover,0,0,60,"appear",.26,.9) + if P.human then + GAME.result="gameover" + SFX.play("fail") + VOC.play("lose") + if modeEnv.royaleMode then + if P.modeData.event==2 then + BGM.play("hay what kind of feeling") + else + BGM.play("end") + end + end + gameOver() + P:newTask(#PLAYERS>1 and TICK.lose or TICK.finish) + TASK.new(TICK.autoPause,{0}) + if MARKING then + P:showTextF(text.marking,0,-226,25,"appear",.4,.0626) + end + else + P:newTask(TICK.lose) + end + if #PLAYERS.alive==1 then + PLAYERS.alive[1]:win() + end +end +--------------------------<\Events>-------------------------- + +---------------------------------------------------- +function Player.act_moveLeft(P,auto) + if not auto then + P.ctrlCount=P.ctrlCount+1 + end + P.movDir=-1 + if P.keyPressing[9]then + if P.gameEnv.swap then + P:changeAtkMode(1) + P.keyPressing[1]=false + end + elseif P.control and P.waiting==-1 then + if P.cur and not P:ifoverlap(P.cur.bk,P.curX-1,P.curY)then + if P.gameEnv.moveFX and P.gameEnv.block then + P:createMoveFX("left") + end + P.curX=P.curX-1 + P:freshBlock(false,true) + if P.sound and P.curY==P.imgY then SFX.play("move")end + if not auto then P.moving=0 end + P.spinLast=false + else + P.moving=P.gameEnv.das + end + else + P.moving=0 + end +end +function Player.act_moveRight(P,auto) + if not auto then + P.ctrlCount=P.ctrlCount+1 + end + P.movDir=1 + if P.keyPressing[9]then + if P.gameEnv.swap then + P:changeAtkMode(2) + P.keyPressing[2]=false + end + elseif P.control and P.waiting==-1 then + if P.cur and not P:ifoverlap(P.cur.bk,P.curX+1,P.curY)then + if P.gameEnv.moveFX and P.gameEnv.block then + P:createMoveFX("right") + end + P.curX=P.curX+1 + P:freshBlock(false,true) + if P.sound and P.curY==P.imgY then SFX.play("move")end + if not auto then P.moving=0 end + P.spinLast=false + else + P.moving=P.gameEnv.das + end + else + P.moving=0 + end +end +function Player.act_rotRight(P) + if P.control and P.waiting==-1 and P.cur then + P.ctrlCount=P.ctrlCount+1 + P:spin(1) + P.keyPressing[3]=false + end +end +function Player.act_rotLeft(P) + if P.control and P.waiting==-1 and P.cur then + P.ctrlCount=P.ctrlCount+1 + P:spin(3) + P.keyPressing[4]=false + end +end +function Player.act_rot180(P) + if P.control and P.waiting==-1 and P.cur then + P.ctrlCount=P.ctrlCount+2 + P:spin(2) + P.keyPressing[5]=false + end +end +function Player.act_hardDrop(P) + if P.keyPressing[9]then + if P.gameEnv.swap then + P:changeAtkMode(3) + end + P.keyPressing[6]=false + elseif P.control and P.waiting==-1 and P.cur then + if P.curY>P.imgY then + if P.gameEnv.dropFX and P.gameEnv.block and P.curY-P.imgY-P.r>-1 then + P:createDropFX(P.curX,P.curY-1,P.c,P.curY-P.imgY-P.r+1) + end + P.curY=P.imgY + P.spinLast=false + if P.gameEnv.shakeFX then + P.fieldOff.vy=P.gameEnv.shakeFX*.6 + end + if P.sound then + SFX.fieldPlay("drop",nil,P) + VIB(1) + end + end + P.lockDelay=-1 + P:drop() + P.keyPressing[6]=false + end +end +function Player.act_softDrop(P) + if P.keyPressing[9]then + if P.gameEnv.swap then + P:changeAtkMode(4) + end + else + P.downing=1 + if P.control and P.waiting==-1 and P.cur then + if P.curY>P.imgY then + P.curY=P.curY-1 + P:freshBlock(true,true) + P.spinLast=false + end + end + end +end +function Player.act_hold(P) + if P.control and P.waiting==-1 then + P:hold() + end +end +function Player.act_func(P) + P.gameEnv.Fkey(P) +end +function Player.act_restart() + if GAME.frame<240 or GAME.result then + resetPartGameData() + else + LOG.print(text.holdR,20,COLOR.orange) + end +end +function Player.act_insLeft(P,auto) + if not P.cur then return end + local x0=P.curX + while not P:ifoverlap(P.cur.bk,P.curX-1,P.curY)do + if P.gameEnv.moveFX and P.gameEnv.block then + P:createMoveFX("left") + end + P.curX=P.curX-1 + P:freshBlock(false,true) + end + if P.curX~=x0 then + P.spinLast=false + end + if P.gameEnv.shakeFX then + P.fieldOff.vx=-P.gameEnv.shakeFX*.5 + end + if auto then + if P.ctrlCount==0 then P.ctrlCount=1 end + else + P.ctrlCount=P.ctrlCount+1 + end +end +function Player.act_insRight(P,auto) + if not P.cur then return end + local x0=P.curX + while not P:ifoverlap(P.cur.bk,P.curX+1,P.curY)do + if P.gameEnv.moveFX and P.gameEnv.block then + P:createMoveFX("right") + end + P.curX=P.curX+1 + P:freshBlock(false,true) + end + if P.curX~=x0 then + P.spinLast=false + end + if P.gameEnv.shakeFX then + P.fieldOff.vx=P.gameEnv.shakeFX*.5 + end + if auto then + if P.ctrlCount==0 then P.ctrlCount=1 end + else + P.ctrlCount=P.ctrlCount+1 + end +end +function Player.act_insDown(P) + if P.cur and P.curY>P.imgY then + if P.gameEnv.dropFX and P.gameEnv.block and P.curY-P.imgY-P.r>-1 then + P:createDropFX(P.curX,P.curY-1,P.c,P.curY-P.imgY-P.r+1) + end + if P.gameEnv.shakeFX then + P.fieldOff.vy=P.gameEnv.shakeFX*.5 + end + P.curY=P.imgY + P.lockDelay=P.gameEnv.lock + P.spinLast=false + P:freshBlock(true,true) +end + end +function Player.act_down1(P) + if P.cur and P.curY>P.imgY then + if P.gameEnv.moveFX and P.gameEnv.block then + P:createMoveFX("down") + end + P.curY=P.curY-1 + P:freshBlock(true,true) + P.spinLast=false + end +end +function Player.act_down4(P) + if P.cur and P.curY>P.imgY then + local y=max(P.curY-4,P.imgY) + if P.gameEnv.dropFX and P.gameEnv.block and P.curY-y-P.r>-1 then + P:createDropFX(P.curX,P.curY-1,P.c,P.curY-y-P.r+1) + end + P.curY=y + P:freshBlock(true,true) + P.spinLast=false + end +end +function Player.act_down10(P) + if P.cur and P.curY>P.imgY then + local y=max(P.curY-10,P.imgY) + if P.gameEnv.dropFX and P.gameEnv.block and P.curY-y-P.r>-1 then + P:createDropFX(P.curX,P.curY-1,P.c,P.curY-y-P.r+1) + end + P.curY=y + P:freshBlock(true,true) + P.spinLast=false + end +end +function Player.act_dropLeft(P) + if not P.cur then return end + P:act_insLeft() + P:act_hardDrop() +end +function Player.act_dropRight(P) + if not P.cur then return end + P:act_insRight() + P:act_hardDrop() +end +function Player.act_zangiLeft(P) + if not P.cur then return end + P:act_insLeft() + P:act_insDown() + P:act_insRight() + P:act_hardDrop() +end +function Player.act_zangiRight(P) + if not P.cur then return end + P:act_insRight() + P:act_insDown() + P:act_insLeft() + P: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_func, --9 + Player.act_restart, --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 \ No newline at end of file