------------------------------------------------------
--Notice: anything in this file or in any other file,
--local var P stands for Player object. Don't forget it.
------------------------------------------------------
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=coroutine.resume,coroutine.yield
local kickList=require"parts.kickList"
----------------------------------------------------
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 CB=P.cur.bk
local t=12-P.gameEnv.lockFX*2
for i=1,#CB do
local y=P.curY+i-1
local L=P.clearedRow
for j=1,#L do
if L[j]==y then goto continue end
end
y=-30*y
for j=1,#CB[1]do
if CB[i][j]then
ins(P.lockFX,{30*(P.curX+j-2),y,0,t})
end
end
::continue::
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 CB=P.cur.bk
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,#CB do for j=#CB[1],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,#CB do for j=1,#CB[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=="down"then
for j=1,#CB[1]do for i=#CB,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,#CB do for j=1,#CB[1]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.createSplashFX(P,h)
local L=P.field[h]
local size=P.size
local y=P.fieldY+size*(P.fieldOff.y+P.fieldBeneath+P.fieldUp+615)
for x=1,10 do
local c=L[x]
if c>0 then
SYSFX.newCell(
2.5-P.gameEnv.splashFX*.4,
SKIN.curText[c],
size,
P.fieldX+(30*x-15)*size,y-30*h*size,
rnd()*5-2.5,rnd()*-1,
0,.6
)
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.mini then x1,y1=P.centerX,P.centerY
else x1,y1=P.x+(30*(P.curX+P.cur.sc[2])-30+15+150)*P.size,P.y+(600-30*(P.curY+P.cur.sc[1])+15)*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
local r,g,b=unpack(minoColor[color])
r,g,b=r*2,g*2,b*2
local a=GAME.modeEnv.royaleMode and not(P.type=="human"or R.type=="human")and .2 or 1
SYSFX.newAttack(1-SETTING.atkFX*.1,x1,y1,x2,y2,int(send^.7*(4+SETTING.atkFX)),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.newTask(P,code,...)
local thread=coroutine.create(code)
coroutine.resume(thread,P,...)
if coroutine.status(thread)~="dead"then
P.tasks[#P.tasks+1]={
thread=thread,
code=code,
args={...},
}
end
end
function Player.setPosition(P,x,y,size)
size=size or 1
P.x,P.y,P.size=x,y,size
if P.mini or P.demo then
P.fieldX,P.fieldY=x,y
P.centerX,P.centerY=x+300*size,y+600*size
else
P.fieldX,P.fieldY=x+150*size,y
P.centerX,P.centerY=x+300*size,y+370*size
P.absFieldX,P.absFieldY=x+150*size,y-10*size
end
end
local function task_movePosition(P,x,y,size)
local x1,y1,size1=P.x,P.y,P.size
while true do
yield()
if (x1-x)^2+(y1-y)^2<1 then
P:setPosition(x,y,size)
return true
else
x1=x1+(x-x1)*.126
y1=y1+(y-y1)*.126
size1=size1+(size-size1)*.126
P:setPosition(x1,y1,size1)
end
end
end
local function checkPlayer(obj,Ptar)
return obj.args[1]==Ptar
end
function Player.movePosition(P,x,y,size)
TASK.removeTask_iterate(checkPlayer,P)
TASK.new(task_movePosition,P,x,y,size or P.size)
end
function Player.switchKey(P,id,on)
P.keyAvailable[id]=on
if not on then
P:releaseKey(id)
end
if P.type=="human"then
virtualkey[id].ava=on
end
end
function Player.set20G(P,if20g)
P._20G=if20g
P:switchKey(7,not if20g)
if if20g and P.AI_mode=="CC"then CC.switch20G(P)end
end
function Player.setHold(P,count)--Set hold count (false/true as 0/1)
if not count then
count=0
elseif count==true then
count=1
end
P.gameEnv.holdCount=count
P.holdTime=count
while P.holdQueue[count+1]do rem(P.holdQueue)end
end
function Player.setNext(P,next,hidden)--Set next count (use hidden=true if set env.nextStartPos>1)
P.gameEnv.nextCount=next
if next==0 then
P.drawNext=NULL
elseif not hidden then
P.drawNext=PLY.draw.drawNext_norm
else
P.drawNext=PLY.draw.drawNext_hidden
end
end
function Player.setInvisible(P,time)--Time in frames
if time<0 then
P.keepVisible=true
P.showTime=1e99
else
P.keepVisible=false
P.showTime=time
end
end
function Player.setRS(P,RSname)
P.RS=kickList[RSname]
end
function Player.setConf(P,confStr)
local _,conf=pcall(love.data.decode,"string","base64",confStr)
if _ then
for k,v in next,conf do
if not GAME.modeEnv[k]then
P.gameEnv[k]=v
end
end
else
LOG.print("Bad conf from "..P.userName.."#"..P.userID)
end
end
function Player.getHolePos(P)--Get a good garbage-line hole position
if P.garbageBeneath==0 then
return generateLine(P:RND(10))
else
local p=P:RND(10)
if P.field[1][p]<=0 then
return generateLine(P:RND(10))
end
return generateLine(p)
end
end
function Player.garbageRelease(P)--Check garbage buffer and try to release them
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.line)
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,line)--Release n-lines garbage to field
local _
local t=P.showTime*2
for _=1,amount do
ins(P.field,1,FREEROW.get(0,true))
ins(P.visTime,1,FREEROW.get(t))
for i=1,10 do
P.field[1][i]=bit.rshift(line,i-1)%2==1 and color or 0
end
end
P.fieldBeneath=P.fieldBeneath+amount*30
if P.cur then
P.curY=P.curY+amount
P.ghoY=P.ghoY+amount
end
P.garbageBeneath=P.garbageBeneath+amount
for i=1,#P.clearingRow do
P.clearingRow[i]=P.clearingRow[i]+amount
end
P:freshBlock("push")
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.pushLineList(P,L,mir)--Push some lines to field
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.ghoY=P.ghoY+l
P:freshBlock("push")
end
function Player.pushNextList(P,L,mir)--Push some nexts to nextQueue
for i=1,#L do
P:getNext(mir and invList[L[i]]or L[i])
end
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,line,fromStream)
if GAME.net then
if P.type=="human"then--Local player attack others
ins(GAME.rep,GAME.frame)
ins(GAME.rep,
R.subID+
send*0x100+
time*0x10000+
line*0x100000000+
0x2000000000000
)
end
if fromStream and R.type=="human"then--Local player receiving lines
ins(GAME.rep,GAME.frame)
ins(GAME.rep,
P.subID+
send*0x100+
time*0x10000+
line*0x100000000+
0x1000000000000
)
R:receive(P,send,time,line)
end
else
R:receive(P,send,time,line)
end
end
function Player.receive(P,A,send,time,line)
P.lastRecv=A
local B=P.atkBuffer
if B.sum<26 then
if send>26-B.sum then send=26-B.sum end
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]={
line=line,
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
P.stat.recv=P.stat.recv+send
if P.sound then
SFX.play(send<4 and"blip_1"or"blip_2",min(send+1,5)*.1)
end
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 P.type~="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=false
end
end
function Player.freshBlock(P,mode)--string mode: push/move/fresh/newBlock
local ENV=P.gameEnv
--Fresh ghost
if(mode=="move"or mode=="newBlock"or mode=="push")and P.cur then
local CB=P.cur.bk
P.ghoY=min(#P.field+1,P.curY)
if P._20G or ENV.sdarr==0 and P.keyPressing[7]and P.downing>ENV.sddas then
local _=P.ghoY
--Move ghost to bottom
while not P:ifoverlap(CB,P.curX,P.ghoY-1)do
P.ghoY=P.ghoY-1
end
--Cancel spinLast
if _~=P.ghoY then
P.spinLast=false
end
--Create FX if dropped
if P.curY>P.ghoY then
if ENV.dropFX and ENV.block and P.curY-P.ghoY-#CB>-1 then
P:createDropFX(P.curX,P.curY-1,#CB[1],P.curY-P.ghoY-#CB+1)
end
if ENV.shakeFX then
P.fieldOff.vy=ENV.shakeFX*.5
end
P.curY=P.ghoY
end
else
while not P:ifoverlap(CB,P.curX,P.ghoY-1)do
P.ghoY=P.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 P.lockDelay0 then
if mode~="newBlock"then
P.freshTime=P.freshTime-1
end
P.lockDelay=l0
P.dropDelay=d0
end
if P.curY+P.cur.sc[1]0 then
P.freshTime=P.freshTime-1
P.dropDelay=d0
P.lockDelay=l0
end
end
end
end
end
function Player.lock(P)
local dest=P.AI_dest
local has_dest=dest~=nil
local CB=P.cur.bk
for i=1,#CB do
local y=P.curY+i-1
if not P.field[y]then P.field[y],P.visTime[y]=FREEROW.get(0),FREEROW.get(0)end
for j=1,#CB[1]do
if CB[i][j]then
P.field[y][P.curX+j-1]=P.cur.color
P.visTime[y][P.curX+j-1]=P.showTime
if dest then
local x=P.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 success
end
end
dest=nil
::success::
end
end
end
end
if has_dest and not dest then
CC.updateField(P)
end
end
local spawnSFX_name={}for i=1,7 do spawnSFX_name[i]="spawn_"..i end
function Player.resetBlock(P)--Reset Block's position and execute I*S
local B=P.cur.bk
P.curX=int(6-#B[1]*.5)
local y=int(P.gameEnv.fieldH+1-modf(P.cur.sc[1]))+ceil(P.fieldBeneath/30)
P.curY=y
P.minY=y+P.cur.sc[1]
local _=P.keyPressing
--IMS
if P.gameEnv.ims and(_[1]and P.movDir==-1 or _[2]and P.movDir==1)and P.moving>=P.gameEnv.das then
local x=P.curX+P.movDir
if not P:ifoverlap(B,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
--DAS cut
if P.gameEnv.dascut>0 then
P.moving=P.moving-(P.moving>0 and 1 or -1)*P.gameEnv.dascut
end
--Spawn SFX
if P.sound and P.cur.id<8 then
SFX.fplay(spawnSFX_name[P.cur.id],SETTING.sfx_spawn)
end
end
function Player.spin(P,d,ifpre)
local kickData=P.RS[P.cur.id]
if type(kickData)=="table"then
local idir=(P.cur.dir+d)%4
kickData=kickData[P.cur.dir*10+idir]
if not kickData then
P:freshBlock("move")
SFX.fieldPlay(ifpre and"prerotate"or"rotate",nil,P)
return
end
local icb=BLOCKS[P.cur.id][idir]
local isc=SCS[P.cur.id][idir]
local ix,iy=P.curX+P.cur.sc[2]-isc[2],P.curY+P.cur.sc[1]-isc[1]
for test=1,#kickData do
local x,y=ix+kickData[test][1],iy+kickData[test][2]
if not P:ifoverlap(icb,x,y)and(P.freshTime>=0 or kickData[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.cur.dir=ix,iy,idir
P.cur.sc,P.cur.bk=isc,icb
P.spinLast=test==2 and 0 or 1
local t=P.freshTime
if not ifpre then
P:freshBlock("move")
end
if kickData[test][2]>0 and P.freshTime~=t and P.curY~=P.imgY 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
elseif kickData then
kickData(P,d)
else
P:freshBlock("move")
SFX.fieldPlay(ifpre and"prerotate"or"rotate",nil,P)
end
end
function Player.hold(P,ifpre)
local ENV=P.gameEnv
if P.holdTime>0 and(ifpre or P.waiting==-1)then
if #P.holdQueue2 then dospin=dospin+2 end
end
end
end
--Immovable spin check
if P:ifoverlap(CB,CX,CY+1)and P:ifoverlap(CB,CX-1,CY)and P:ifoverlap(CB,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,#CB do
local h=CY+i-2
--Bomb trigger
if h>0 and P.field[h]and P.clearedRow[cc]~=h then
for x=1,#CB[1]do
if CB[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
local y=P.clearedRow[i]
P:createClearingFX(y,t)
if ENV.splashFX then
P:createSplashFX(y)
end
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=C.id<6 and cc<#CB
end
end
else
dospin=false
end
--Finesse: roof check
local finesse
if CY>ENV.fieldH-2 then
finesse=true
else
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=CX+x-1--Optimize
--Test the whole column of field to find roof
for testY=CY+y,#P.field do
if P:solid(testX,testY)then
finesse=true
goto BERAK
end
end
end
::BERAK::
end
--Remove rows need to be cleared
if cc>0 then
for i=cc,1,-1 do
_=P.clearedRow[i]
if P.field[_].garbage 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>=#C.bk 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=C.id
local d=P.ctrlCount-finesseList[id][P.cur.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 P.sound then
if ENV.fineKill then
SFX.play("finesseError_long",.6)
elseif ENV.fine then
SFX.play("finesseError",.8)
else
SFX.fieldPlay("lock",nil,P)
end
end
elseif P.sound then
SFX.fieldPlay("lock",nil,P)
end
if finePts<=1 then
P.finesseCombo=0
else
P.finesseCombo=P.finesseCombo+1
if P.finesseCombo>2 then
P.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 below
cmb=cmb+1
if dospin then
cscore=(spinSCR[C.name]or spinSCR[8])[cc]
if P.b2b>800 then
P: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 P.sound then
VOC.play("b3b",CHN)
end
elseif P.b2b>=50 then
P: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 P.sound then
VOC.play("b2b",CHN)
end
else
P: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
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[C.name],CHN)
end
elseif cc>=4 then
cscore=cc==4 and 1000 or cc==5 and 1500 or 2000
if P.b2b>800 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
--Normal clear,reduce B2B point
if not piece.special then
P.b2b=max(P.b2b-250,0)
if P.b2b<50 and ENV.b2bKill then
finish=true
end
P: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
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
--PC/HPC
if clear and #P.field==0 then
P: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
P.b2b=1000
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
if P.b2b>1000 then P.b2b=1000 end
--Bonus atk/def when focused
if GAME.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
atk=int(atk*(1+P.strength*.25))--Badge Buff
send=atk
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
if send>=1 then
P:showText(send,0,80,35,"zoomout")
_=P:cancel(send)
send=send-_
off=off+_
if send>0 then
local T
if GAME.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,sendTime,generateLine(P:RND(10)))
if SETTING.atkFX>0 then
P:createBeam(P.atker[i],send,C.color)
end
end
else
T=randomTarget(P)
end
else
T=P.atking
P:freshTarget()
end
elseif #PLAYERS.alive>1 then
T=randomTarget(P)
end
if T then
P:attack(T,send,sendTime,generateLine(P:RND(10)))
if SETTING.atkFX>0 then
P:createBeam(T,send,C.color)
end
end
end
if P.sound and send>3 then SFX.play("emit",min(send,7)*.1)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[C.name]..text.spin,0,-30,45,"appear")
P.b2b=P.b2b+20
if P.sound then
SFX.play("spin_0")
VOC.play(spinVoice[C.name],CHN)
end
cscore=30
end
if P.b2b>800 then
P.b2b=max(P.b2b-40,800)
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.cur.sc[2]-5.5)*30,(10-P.curY-P.cur.sc[1])*30+P.fieldBeneath+P.fieldUp,40-600/(cscore+20),"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=false
if not finish then finish="finish"end
end
elseif ENV.missionKill then
P:showText(text.missionFailed,0,140,40,"flicker",.5)
SFX.play("finesseError_long",.6)
finish=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
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 P:lose()end
_=ENV.dropPiece if _ then _(P)end
if finish then P:win(finish)end
else
_=ENV.dropPiece if _ then _(P)end
end
end
end
function Player.loadAI(P,data)--Load AI params
if not CC then
data.type="9S"
data.delta=int(data.delta*.3)
end
P.AI_mode=data.type
P.AI_keys={}
P.AI_delay=min(int(P.gameEnv.drop*.8),data.delta*rnd()*4)
P.AI_delay0=data.delta
P.AIdata={
type=data.type,
delay=data.delay,
delta=data.delta,
next=data.next,
hold=data.hold,
_20G=P._20G,
bag=data.bag,
node=data.node,
}
if P.AI_mode=="CC"then
P:setRS("SRS")
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,P.AIdata.next do
CC.addNext(P.AI_bot,P.nextQueue[i].id)
end
if P.gameEnv.holdCount and P.gameEnv.holdCount>1 then
P:setHold(1)
end
else
P:setRS("TRS")
end
P.AI_thread=coroutine.create(AIFUNC[data.type])
coroutine.resume(P.AI_thread,P,P.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(P)
while true do
yield()
P.endCounter=P.endCounter+1
if P.endCounter<40 then
--Make field visible
for j=1,#P.field do for i=1,10 do
if P.visTime[j][i]<20 then P.visTime[j][i]=P.visTime[j][i]+.5 end
end end
elseif P.endCounter==60 then
return
end
end
end
local function tick_lose(P)
while true do
yield()
P.endCounter=P.endCounter+1
if P.endCounter<40 then
--Make field visible
for j=1,#P.field do for i=1,10 do
if P.visTime[j][i]<20 then P.visTime[j][i]=P.visTime[j][i]+.5 end
end end
elseif P.endCounter>80 then
for i=1,#P.field do
for j=1,10 do
if P.visTime[i][j]>0 then
P.visTime[i][j]=P.visTime[i][j]-1
end
end
end
if P.endCounter==120 then
for _=#P.field,1,-1 do
FREEROW.discard(P.field[_])
FREEROW.discard(P.visTime[_])
P.field[_],P.visTime[_]=nil
end
return
end
end
if not GAME.modeEnv.royaleMode and #PLAYERS>1 then
P.y=P.y+P.endCounter*.26
P.absFieldY=P.absFieldY+P.endCounter*.26
end
end
end
function tick_autoPause()
local time=0
while true do
yield()
time=time+1
if SCN.cur~="play"or GAME.frame<180 then
return
elseif time==120 then
pauseGame()
return
end
end
end
----------------------------------------------------
----------------------------------------------------
local function gameOver()--Save record
if GAME.replaying then return end
FILE.save(STAT,"conf/data")
local M=GAME.curMode
local R=M.getRank
if R then
local P=PLAYERS[1]
R=R(P)--New rank
if R then
if R>0 then
GAME.rank=R
end
if scoreValid()and M.score 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
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].getRank and 0 or 6
needSave=true
end
end
end
end
if needSave then
FILE.save(RANKS,"conf/unlock","q")
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.save(L,"record/"..M.name..".rec","lq")
end
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 GAME.modeEnv.royaleMode then
P.modeData.event=1
P:changeAtk()
end
if P.type=="human"then
GAME.result=result or"win"
SFX.play("win")
VOC.play("win")
if GAME.modeEnv.royaleMode then
BGM.play("8-bit happiness")
end
end
if GAME.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.type=="human"then
gameOver()
TASK.new(tick_autoPause)
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
local h=#P.field
for _=h,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)
while P.holdQueue[1]do rem(P.holdQueue)end
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,h do
P:createClearingFX(i,1.5)
end
SYSFX.newShade(1.4,P.fieldX,P.fieldY,300*P.size,610*P.size)
SYSFX.newRectRipple(2,P.fieldX,P.fieldY,300*P.size,610*P.size)
SYSFX.newRipple(2,P.x+(475+25*(P.life<3 and P.life or 0)+12)*P.size,P.y+(665+12)*P.size,20)
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 GAME.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,not A.type=="human",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.type=="human"then
GAME.result="gameover"
SFX.play("fail")
VOC.play("lose")
if GAME.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)
if GAME.net then
WS.send("play","D")
else
TASK.new(tick_autoPause)
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("move")
if P.sound and P.curY==P.ghoY 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("move")
if P.sound and P.curY==P.ghoY 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.ghoY then
local CB=P.cur.bk
if P.gameEnv.dropFX and P.gameEnv.block and P.curY-P.ghoY-#CB>-1 then
P:createDropFX(P.curX,P.curY-1,#CB[1],P.curY-P.ghoY-#CB+1)
end
P.curY=P.ghoY
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)
local ENV=P.gameEnv
if P.keyPressing[9]then
if ENV.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.ghoY then
P.curY=P.curY-1
P:freshBlock("fresh")
P.spinLast=false
elseif ENV.deepDrop then
local CB=P.cur.bk
local y=P.curY-1
while P:ifoverlap(CB,P.curX,y)and y>0 do
y=y-1
end
if y>0 then
if ENV.dropFX and ENV.block and P.curY-y-#CB>-1 then
P:createDropFX(P.curX,P.curY-1,#CB[1],P.curY-y-#CB+1)
end
P.curY=y
P:freshBlock("move")
SFX.play("swipe")
end
end
end
end
end
function Player.act_hold(P)
if P.control and P.waiting==-1 then
P:hold()
end
end
function Player.act_func1(P)
P.gameEnv.fkey1(P)
end
function Player.act_func2(P)
P.gameEnv.fkey2(P)
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("move")
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("move")
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.ghoY then
local ENV=P.gameEnv
local CB=P.cur.bk
if ENV.dropFX and ENV.block and P.curY-P.ghoY-#CB>-1 then
P:createDropFX(P.curX,P.curY-1,#CB[1],P.curY-P.ghoY-#CB+1)
end
if ENV.shakeFX then
P.fieldOff.vy=ENV.shakeFX*.5
end
P.curY=P.ghoY
P.lockDelay=ENV.lock
P.spinLast=false
P:freshBlock("fresh")
end
end
function Player.act_down1(P)
if P.cur and P.curY>P.ghoY then
if P.gameEnv.moveFX and P.gameEnv.block then
P:createMoveFX("down")
end
P.curY=P.curY-1
P:freshBlock("fresh")
P.spinLast=false
end
end
function Player.act_down4(P)
if P.cur and P.curY>P.ghoY then
local y=max(P.curY-4,P.ghoY)
local CB=P.cur.bk
if P.gameEnv.dropFX and P.gameEnv.block and P.curY-y-#CB>-1 then
P:createDropFX(P.curX,P.curY-1,#CB[1],P.curY-y-#CB+1)
end
P.curY=y
P:freshBlock("fresh")
P.spinLast=false
end
end
function Player.act_down10(P)
if P.cur and P.curY>P.ghoY then
local y=max(P.curY-10,P.ghoY)
local CB=P.cur.bk
if P.gameEnv.dropFX and P.gameEnv.block and P.curY-y-#CB>-1 then
P:createDropFX(P.curX,P.curY-1,#CB[1],P.curY-y-#CB+1)
end
P.curY=y
P:freshBlock("fresh")
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_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