1987 lines
43 KiB
Lua
1987 lines
43 KiB
Lua
------------------------------------------------------
|
||
--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 ct=coroutine
|
||
|
||
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
|
||
|
||
--------------------------<FX>--------------------------
|
||
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.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.sc[2])-30+15+150)*P.size,P.y+(600-30*(P.curY+P.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
|
||
|
||
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=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,wid,r,g,b,a*(SETTING.atkFX+2)*.0626)
|
||
end
|
||
--------------------------</FX>--------------------------
|
||
|
||
--------------------------<Method>--------------------------
|
||
function Player.RND(P,a,b)
|
||
local R=P.randGen
|
||
return R:random(a,b)
|
||
end
|
||
function Player.newTask(P,code,...)
|
||
local thread=ct.create(code)
|
||
ct.resume(thread,P,...)
|
||
if ct.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
|
||
coroutine.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.set20G(P,if20g,init)--Only set init=true when initialize CC, do not use it
|
||
P._20G=if20g
|
||
P.keyAvailable[7]=not if20g
|
||
if P.type=="human"then
|
||
virtualkey[7].ava=not if20g
|
||
end
|
||
if init and 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
|
||
if P.type=="human"then
|
||
virtualkey[8].ava=count>0
|
||
end
|
||
if count==0 then
|
||
P.drawHold=NULL
|
||
while P.holdQueue[1]do rem(P.holdQueue)end
|
||
elseif count==1 then
|
||
P.drawHold=PLY.draw.drawHold_norm
|
||
else
|
||
P.drawHold=PLY.draw.drawHold_multi
|
||
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.getHolePos(P)--Get a good garbage-line hole position
|
||
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)--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.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)--Release n-lines garbage to field
|
||
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.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.imgY=P.imgY+l
|
||
P:freshBlock(false,false)
|
||
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,...)
|
||
if SETTING.atkFX>0 then
|
||
P:createBeam(R,send,time,...)
|
||
end
|
||
R.lastRecv=P
|
||
if R.atkBuffer.sum<26 then
|
||
local B=R.atkBuffer
|
||
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]={
|
||
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.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=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<d0 and P.freshTime>0 then
|
||
if not system then
|
||
P.freshTime=P.freshTime-1
|
||
end
|
||
P.lockDelay=d0
|
||
P.dropDelay=ENV.drop
|
||
end
|
||
if P.curY<P.minY then
|
||
P.minY=P.curY
|
||
P.dropDelay=ENV.drop
|
||
P.lockDelay=ENV.lock
|
||
end
|
||
else
|
||
if P.curY<P.minY then
|
||
P.minY=P.curY
|
||
if P.lockDelay<ENV.lock and P.freshTime>0 then
|
||
P.freshTime=P.freshTime-1
|
||
P.dropDelay=ENV.drop
|
||
P.lockDelay=ENV.lock
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
function Player.lock(P)
|
||
local dest=P.AI_dest
|
||
local has_dest=dest~=nil
|
||
for i=1,P.r 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,P.c do
|
||
if P.cur.bk[i][j]then
|
||
P.field[y][P.curX+j-1]=P.cur.color
|
||
P.visTime[y][P.curX+j-1]=P.showTime
|
||
local x=P.curX+j-1
|
||
if dest then
|
||
local original_length=#dest
|
||
for k=1,original_length do
|
||
if x==dest[k][1]and y==dest[k][2]then
|
||
rem(dest, k)
|
||
break
|
||
end
|
||
end
|
||
if #dest~=original_length-1 then
|
||
dest=nil
|
||
end
|
||
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)
|
||
local C=P.cur
|
||
local id=C.id
|
||
local face=P.gameEnv.face[id]
|
||
local sc=scs[id][face]
|
||
P.sc=sc --Spin center
|
||
P.dir=face --Block direction
|
||
P.r,P.c=#C.bk,#C.bk[1] --Row/column
|
||
P.curX=int(6-P.c*.5)
|
||
local y=21+ceil(P.fieldBeneath/30)
|
||
P.curY=y
|
||
P.minY=y+sc[2]
|
||
|
||
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(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
|
||
|
||
--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 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>=0 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
|
||
elseif iki then
|
||
iki(P,d)
|
||
end
|
||
end
|
||
function Player.hold(P,ifpre)
|
||
if P.holdTime>0 and(ifpre or P.waiting==-1)then
|
||
if #P.holdQueue<P.gameEnv.holdCount and P.nextQueue[1]then--Skip
|
||
local C=P.cur
|
||
C.bk=BLOCKS[C.id][P.gameEnv.face[C.id]]
|
||
ins(P.holdQueue,C)
|
||
|
||
local t=P.holdTime
|
||
P:popNext(true)
|
||
P.holdTime=t
|
||
else--Hold
|
||
local C,H=P.cur,P.holdQueue[1]
|
||
|
||
--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
|
||
|
||
if C then
|
||
C.bk=BLOCKS[C.id][P.gameEnv.face[C.id]]
|
||
ins(P.holdQueue,C)
|
||
end
|
||
P.cur=rem(P.holdQueue,1)
|
||
|
||
P:resetBlock()
|
||
P:freshBlock(false,true)
|
||
P.dropDelay=P.gameEnv.drop
|
||
P.lockDelay=P.gameEnv.lock
|
||
if P:ifoverlap(P.cur.bk,P.curX,P.curY)then
|
||
P:lock()
|
||
P:lose()
|
||
end
|
||
end
|
||
|
||
P.freshTime=int(min(P.freshTime+P.gameEnv.freshLimit*.25,P.gameEnv.freshLimit*((P.holdTime+1)/P.gameEnv.holdCount)))
|
||
if not P.gameEnv.infHold then
|
||
P.holdTime=P.holdTime-1
|
||
end
|
||
|
||
if P.sound then
|
||
SFX.play(ifpre and"prehold"or"hold")
|
||
end
|
||
|
||
if P.AI_mode=="CC"then
|
||
local next=P.nextQueue[P.AIdata.nextCount]
|
||
if next then
|
||
CC.addNext(P.AI_bot,next.id)
|
||
end
|
||
end
|
||
|
||
P.stat.hold=P.stat.hold+1
|
||
end
|
||
end
|
||
|
||
function Player.getNext(P,n)--Push a block(id=n) to nextQueue
|
||
local E=P.gameEnv
|
||
ins(P.nextQueue,{bk=BLOCKS[n][E.face[n]],id=n,color=E.bone and 17 or E.skin[n],name=n})
|
||
end
|
||
function Player.popNext(P,ifhold)--Pop nextQueue to hand
|
||
if not ifhold then
|
||
P.holdTime=min(P.holdTime+1,P.gameEnv.holdCount)
|
||
end
|
||
P.spinLast=false
|
||
P.spinSeq=0
|
||
P.ctrlCount=0
|
||
|
||
P.cur=rem(P.nextQueue,1)
|
||
P:newNext()
|
||
if P.cur then
|
||
P.pieceCount=P.pieceCount+1
|
||
if P.AI_mode=="CC"then
|
||
local next=P.nextQueue[P.AIdata.next]
|
||
if next then
|
||
CC.addNext(P.AI_bot,next.id)
|
||
end
|
||
end
|
||
|
||
local _=P.keyPressing
|
||
|
||
--IHS
|
||
if not ifhold and _[8]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=P.gameEnv.freshLimit
|
||
|
||
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]and not ifhold then
|
||
P.act_hardDrop(P)
|
||
_[6]=false
|
||
end
|
||
else
|
||
P:hold()
|
||
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(P)--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,800,1000,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,2,1,0,1,2,2,1},
|
||
{2,2,2,1,1,2,3,2,2},
|
||
1,2
|
||
},--Z
|
||
1,--S
|
||
{
|
||
{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},
|
||
},--J
|
||
3,--L
|
||
3,--T
|
||
{
|
||
{1,2,2,1,0,1,2,2,1},
|
||
1,1,1
|
||
},--O
|
||
{
|
||
{1,2,1,0,1,2,1},
|
||
{2,2,2,2,1,1,2,2,2,2},
|
||
1,2
|
||
},--I
|
||
{
|
||
{1,2,1,0,1,2,2,1},
|
||
{2,3,2,1,2,3,3,2},
|
||
1,2
|
||
},--Z5
|
||
8,--S5
|
||
3,--P
|
||
3,--Q
|
||
{
|
||
{1,2,1,0,1,2,2,1},
|
||
{2,3,2,1,2,3,3,2},
|
||
{3,4,3,2,3,4,4,3},
|
||
2
|
||
},--F
|
||
12,--E
|
||
12,--T5
|
||
3,--U
|
||
{
|
||
{1,2,1,0,1,2,2,1},
|
||
{2,3,3,2,1,2,3,2},
|
||
{3,4,4,3,2,3,4,3},
|
||
{2,3,2,1,2,3,3,2},
|
||
},--V
|
||
12,--W
|
||
{
|
||
{1,2,1,0,1,2,2,1},
|
||
1,1,1
|
||
},--X
|
||
{
|
||
{1,2,1,0,1,2,1},
|
||
{2,2,3,2,1,2,3,2,2},
|
||
{3,4,3,2,3,4,3},
|
||
2,
|
||
},--J5
|
||
19,--L5
|
||
19,--R
|
||
19,--Y
|
||
19,--N
|
||
19,--H
|
||
{
|
||
{1,1,0,1,2,1},
|
||
{2,3,2,2,1,2,3,2,3,2},
|
||
1,2
|
||
},--I5
|
||
}
|
||
for k,v in next,finesseList do
|
||
if type(v)=="table"then
|
||
for d,l in next,v do
|
||
if type(l)=="number"then
|
||
v[d]=v[l]
|
||
end
|
||
end
|
||
else
|
||
finesseList[k]=finesseList[v]
|
||
end
|
||
end
|
||
|
||
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 finish
|
||
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
|
||
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=CB.id<6 and cc<P.r
|
||
end
|
||
end
|
||
else
|
||
dospin=false
|
||
end
|
||
|
||
--Finesse: roof check
|
||
local finesse
|
||
if 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[_].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==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
|
||
|
||
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)
|
||
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>800 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>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
|
||
|
||
--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=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
|
||
|
||
--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+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>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,CB.color)
|
||
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,CB.color)
|
||
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[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>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.sc[2]-5.5)*30,(10-P.curY-P.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=nil
|
||
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=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
|
||
|
||
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
|
||
P.AI_mode=data.type
|
||
P.AI_stage=1
|
||
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 not CC then
|
||
P.AI_mode="9S"
|
||
P.AI_delay0=int(P.AI_delay0*.3)
|
||
end
|
||
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
|
||
end
|
||
--------------------------</Methods>--------------------------
|
||
|
||
--------------------------<Ticks>--------------------------
|
||
local function tick_throwBadge(ifAI,sender,time)
|
||
while true do
|
||
coroutine.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
|
||
coroutine.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
|
||
coroutine.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
|
||
coroutine.yield()
|
||
time=time+1
|
||
if SCN.cur~="play"or GAME.frame<180 then
|
||
return
|
||
elseif time==120 then
|
||
pauseGame()
|
||
return
|
||
end
|
||
end
|
||
end
|
||
--------------------------</Ticks>--------------------------
|
||
|
||
--------------------------<Events>--------------------------
|
||
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)
|
||
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
|
||
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)
|
||
--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 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)
|
||
TASK.new(tick_autoPause)
|
||
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>--------------------------
|
||
|
||
--------------------------<Actions>--------------------------
|
||
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 then
|
||
resetGameData(false,true)
|
||
elseif GAME.result then
|
||
resetGameData(false,false)
|
||
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
|
||
}
|
||
--------------------------</Actions>--------------------------
|
||
return Player |