联网对战测试推进22

This commit is contained in:
MrZ626
2021-02-06 13:23:52 +08:00
parent 38d3e4477c
commit eec05e7eb6
18 changed files with 350 additions and 105 deletions

View File

@@ -539,6 +539,28 @@ function initPlayerPosition(sudden)--Set initial position for every player
error("TOO MANY PLAYERS!")
end
end
do--function dumpBasicConfig()
local gameSetting={
--Tuning
"das","arr","dascut","sddas","sdarr",
"ihs","irs","ims","RS","swap",
--System
"skin","face",
--Graphic
"block","ghost","center","smooth","grid","bagLine",
"lockFX","dropFX","moveFX","clearFX","splashFX","shakeFX","atkFX",
"text","score","warn","highCam","nextPos",
}
function dumpBasicConfig()
local S={}
for _,key in next,gameSetting do
S[key]=SETTING[key]
end
return data.encode("string","base64",json.encode(S))
end
end
do--function resetGameData(args)
local function tick_showMods()
local time=0
@@ -579,7 +601,7 @@ do--function resetGameData(args)
end
return S
end
function resetGameData(args,playerData)
function resetGameData(args,playerData,seed)
if not args then args=""end
if PLAYERS[1]and not GAME.replaying and(GAME.frame>400 or GAME.result)then
mergeStat(STAT,PLAYERS[1].stat)
@@ -595,7 +617,7 @@ do--function resetGameData(args)
GAME.replaying=1
else
GAME.frame=args:find("n")and 0 or 150-SETTING.reTime*15
GAME.seed=rnd(1046101471,2662622626)
GAME.seed=seed or rnd(1046101471,2662622626)
GAME.pauseTime=0
GAME.pauseCount=0
GAME.saved=false
@@ -650,28 +672,33 @@ function gameStart()--Call when countdown finish (GAME.frame==180)
end
--[[
Table data format:
{frame,event, frame,event, ...}
Byte data format: (1 byte each period)
KeyID, dt, KeyID, dt, ......
KeyID range from 1 to 20, negative when release key
dt, event, dt, event, ...
event range from 1 to 20, negative when release key
dt from 0 to infinity, 0~254 when 0~254, read next byte as dt(if there is an 255, add next byte to dt as well)
Example:
1,6, -1,20, 2,0, -2,255,0, 4,255,255,255,62, ......
6,1, 20,-1, 0,2, 255,0,-2, 255,255,255,62,4 ...
Translate:
(6,1)(20,-1)(0,2)(255,0,-2)(255,255,255,62,4) ...
This means:
Press key1 at 6f
Release key1 at 26f (6+20)
Press key2 at the same time(26+0)
Release key 2 after 255+0 frame
Press key 4 after 255+255+255+62 frame
......
Press key2 at the same time (26+0)
Release key 2 after 255+0 frame (26+0+255+0)
Press key 4 after 255+255+255+62 frame (26+0+255+0+255+255+255+62)
...
]]
function dumpRecording(list,ptr)
local out=""
local buffer=""
local prevFrm=0
ptr=ptr or 1
if not ptr then ptr=1 end
local prevFrm=list[ptr-2]or 0
while list[ptr]do
--Check buffer size
--Flush buffer
if #buffer>10 then
out=out..buffer
buffer=""
@@ -686,14 +713,18 @@ function dumpRecording(list,ptr)
end
buffer=buffer..char(t)
--Encode key
--Encode event
t=list[ptr+1]
buffer=buffer..char(t>0 and t or 256+t)
while t>=255 do
buffer=buffer.."\255"
t=t-255
end
buffer=buffer..char(t)
--Step
ptr=ptr+2
end
return out..buffer
return out..buffer,ptr
end
function pumpRecording(str,L)
local len=#str
@@ -702,22 +733,27 @@ function pumpRecording(str,L)
local curFrm=L[#L-1]or 0
while p<=len do
--Read delta time
::nextByte::
::nextByte1::
local b=byte(str,p)
if b==255 then
curFrm=curFrm+255
p=p+1
goto nextByte
goto nextByte1
end
curFrm=curFrm+b
L[#L+1]=curFrm
p=p+1
::nextByte2::
b=byte(str,p)
if b>127 then
b=b-256
local event=0
if b==255 then
event=event+255
p=p+1
goto nextByte2
end
L[#L+1]=b
event=event+b
L[#L+1]=event
p=p+1
end
end
@@ -840,6 +876,34 @@ function TICK_httpREQ_getAccessToken(task)
end
end
end
function TICK_httpREQ_getUserInfo(task)
local time=0
while true do
coroutine.yield()
local response,request_error=client.poll(task)
if response then
local res=json.decode(response.body)
if response.code==200 and res.message=="OK"then
-- LOG.print(text.accessSuccessed)
USER.username=res.username
USER.motto=res.motto
USER.avatar=res.avatar
FILE.save(USER,"conf/user")
else
-- LOG.print(text.loginFailed..": "..text.httpCode..response.code.."-"..res.message,"warn")
end
return
elseif request_error then
LOG.print(text.loginFailed..": "..request_error,"warn")
return
end
time=time+1
if time>360 then
LOG.print(text.loginFailed..": "..text.httpTimeout,"message")
return
end
end
end
function TICK_wsRead()
while true do
coroutine.yield()

View File

@@ -245,16 +245,15 @@ GAME={--Global game data
RANKS=FILE.load("conf/unlock")or{sprint_10l=0}--Ranks of modes
USER=FILE.load("conf/user")or{--User infomation
--Network infos
name=false,
username=false,
id=false,
email=false,
motto=false,
avatar=false,
auth_token=false,
access_token=false,
--Local data
username=false,
motto=false,
avatar=false,
xp=0,lv=1,
}
SETTING={--Settings

View File

@@ -94,8 +94,8 @@ return{
wsError="WebSocket error: ",
waitNetTask="Connecting, please wait",
chatJoin="joined the room.",
chatLeave="left the room.",
joinRoom="joined the room.",
leaveRoom="left the room.",
chatRemain="Online: ",
chatStart="------Beginning of log------",
chatHistory="------New messages below------",

View File

@@ -96,8 +96,8 @@ return{
-- wsError="WebSocket error: ",
-- waitNetTask="Connecting, please wait",
-- chatJoin="joined the room.",
-- chatLeave="left the room.",
-- joinRoom="joined the room.",
-- leaveRoom="left the room.",
-- chatRemain="Online: ",
-- chatStart="------Beginning of log------",
-- chatHistory="------New messages below------",
@@ -182,7 +182,7 @@ return{
simple-love-lights [dylhunn]
]],
support="Aider le créateur",
group="Groupe QQ officiel (si non piraté) : 1127702001",
group="Groupe QQ officiel (si non piraté) : 913154753",
WidgetText={
main={
-- offline="Single",

View File

@@ -94,8 +94,8 @@ return{
wsError="WebSocket error: ",
waitNetTask="Conectando, aguarde",
chatJoin="Entrou a sala.",
chatLeave="Saiu da sala.",
joinRoom="Entrou a sala.",
leaveRoom="Saiu da sala.",
chatRemain="Online: ",
chatStart="------Começo do log------",
chatHistory="------Novas mensagens abaixo------",

View File

@@ -97,8 +97,8 @@ return{
-- wsError="WebSocket error: ",
-- waitNetTask="Connecting, please wait",
-- chatJoin="joined the room.",
-- chatLeave="left the room.",
-- joinRoom="joined the room.",
-- leaveRoom="left the room.",
-- chatRemain="Online: ",
-- chatStart="------Beginning of log------",
-- chatHistory="------New messages below------",
@@ -184,7 +184,7 @@ return{
simple-love-lights [dylhunn]
]],
support="Apoyen al Autor",
group="Grupo Oficial de QQ (si no lo hackean):1127702001",
group="Grupo Oficial de QQ (si no lo hackean) : 913154753",
WidgetText={
main={
-- offline="Single",

View File

@@ -86,8 +86,8 @@ return{
registerFailed="注册失败",
loginSuccessed="登录成功",
loginFailed="登录失败",
accessSuccessed="授权成功",
accessFailed="授权失败",
accessSuccessed="身份验证成功",
accessFailed="身份验证失败",
wsSuccessed="WS连接成功",
wsFailed="WS连接失败",
wsDisconnected="WS连接断开",
@@ -95,8 +95,8 @@ return{
wsError="WS错误: ",
waitNetTask="正在连接,请稍候",
chatJoin="进入房间",
chatLeave="离开房间",
joinRoom="进入房间",
leaveRoom="离开房间",
chatRemain="人数:",
chatStart="------消息的开头------",
chatHistory="------以上是历史消息------",
@@ -205,7 +205,7 @@ return{
simple-love-lights [dylhunn]
]],
support="支持作者",
group="官方QQ群(如果没有被暗改的话就是这个):1127702001",
group="官方QQ群(如果没有被暗改的话就是这个):913154753",
WidgetText={
main={
offline="单机游戏",

View File

@@ -7,10 +7,8 @@ return{
PLY.newPlayer(1)
local N=2
for i=1,#playerData do
if playerData[i].id~=USER.id then
PLY.newRemotePlayer(N,false,playerData[i])
N=N+1
end
PLY.newRemotePlayer(N,false,playerData[i])
N=N+1
end
end,
}

View File

@@ -349,6 +349,11 @@ function draw.norm(P)
--Field-related things
gc_push("transform")
gc_translate(150,0)
if P.userName then
setFont(30)
gc_setColor(1,1,1)
mStr(P.userName,150,-60)
end
--Things shake with field
gc_push("transform")

View File

@@ -50,8 +50,9 @@ local function releaseKey(P,keyID)
end
local function pressKey_Rec(P,keyID)
if P.keyAvailable[keyID]and P.alive then
ins(GAME.rep,GAME.frame+1)
ins(GAME.rep,keyID)
local L=GAME.rep
ins(L,GAME.frame+1)
ins(L,keyID)
P.keyPressing[keyID]=true
P.actList[keyID](P)
if P.control then
@@ -64,8 +65,9 @@ local function pressKey_Rec(P,keyID)
end
end
local function releaseKey_Rec(P,keyID)
ins(GAME.rep,GAME.frame+1)
ins(GAME.rep,-keyID)
local L=GAME.rep
ins(L,GAME.frame+1)
ins(L,32+keyID)
P.keyPressing[keyID]=false
end
local function newEmptyPlayer(id,mini)
@@ -186,8 +188,7 @@ end
local function loadGameEnv(P)--Load gameEnv
P.gameEnv={}--Current game setting environment
local ENV=P.gameEnv
local GAME=GAME
local SETTING=SETTING
local GAME,SETTING=GAME,SETTING
--Load game settings
for k,v in next,gameEnv0 do
if GAME.modeEnv[k]~=nil then
@@ -214,6 +215,26 @@ local function loadGameEnv(P)--Load gameEnv
end
end
end
local function loadRemoteEnv(P,conf)--Load gameEnv
P.gameEnv={}--Current game setting environment
local ENV=P.gameEnv
local GAME,SETTING=GAME,SETTING
--Load game settings
for k,v in next,gameEnv0 do
if GAME.modeEnv[k]~=nil then
v=GAME.modeEnv[k] --Mode setting
elseif conf[k]~=nil then
v=conf[k] --Game setting
elseif SETTING[k]~=nil then
v=SETTING[k] --Global setting
end
if type(v)~="table"then--Default setting
ENV[k]=v
else
ENV[k]=copyTable(v)
end
end
end
local function applyGameEnv(P)--Finish gameEnv processing
local ENV=P.gameEnv
@@ -332,16 +353,19 @@ function PLY.newDemoPlayer(id)
}
P:popNext()
end
function PLY.newRemotePlayer(id,mini,userInfo)
function PLY.newRemotePlayer(id,mini,playerData)
local P=newEmptyPlayer(id,mini)
P.type="remote"
P.update=PLY.update.remote_alive
P.userName=userInfo.name
P.userID=userInfo.id
P.stream={}
P.streamProgress=1
loadGameEnv(P)
playerData.p=P
P.userName=playerData.name
P.userID=playerData.id
loadRemoteEnv(P,playerData.conf or{})
applyGameEnv(P)
prepareSequence(P)
end

View File

@@ -224,6 +224,14 @@ function Player.setRS(P,RSname)
P.RS=kickList[RSname]
end
function Player.setConf(P,conf)
for k,v in next,conf do
if not GAME.modeEnv[k]then
P.gameEnv[k]=v
end
end
end
function Player.getHolePos(P)--Get a good garbage-line hole position
if P.garbageBeneath==0 then
return P:RND(10)
@@ -332,9 +340,21 @@ 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 GAME.net then
if P.type=="human"then
--TODO
end
if R.type=="human"then
--TODO
end
else
R:receive(P,send,time)
end
end
function Player.receive(P,A,send,time)
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
@@ -342,7 +362,7 @@ function Player.attack(P,R,send,time,...)
B[i+1]=B[i]
end
B[k]={
pos=P:RND(10),
pos=A:RND(10),
amount=send,
countdown=time,
cd0=time,
@@ -351,8 +371,8 @@ function Player.attack(P,R,send,time,...)
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
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

View File

@@ -1,4 +1,5 @@
local int,max,min,abs=math.floor,math.max,math.min,math.abs
local max,min=math.max,math.min
local int,abs,rnd=math.floor,math.abs,math.random
local rem=table.remove
local function updateLine(P)--Attacks, line pushing, cam moving
@@ -108,7 +109,9 @@ local updateTasks do--updateTasks(P)
end
end
local function update_alive(P,dt)
local update={
}
function update.alive(P,dt)
local ENV=P.gameEnv
if P.timing then
local S=P.stat
@@ -336,10 +339,6 @@ local function update_alive(P,dt)
updateFXs(P,dt)
updateTasks(P)
end
local update={
alive=update_alive,
}
function update.dead(P,dt)
if P.keyRec then
local S=P.stat
@@ -369,23 +368,41 @@ function update.dead(P,dt)
updateTasks(P)
end
function update.remote_alive(P,dt)
local frmStep=GAME.frame-P.stat.frame
frmStep=
frmStep<60 and 1 or
frmStep<120 and rnd(2)or
frmStep<240 and 2 or
frmStep<480 and rnd(2,3) or
3
::readNext::
local pos=P.streamProgress
local tar=P.stream[pos]
if tar then
if P.stat.frame==tar then
local eventTime=P.stream[pos]
if eventTime then
if P.stat.frame==eventTime then
local key=P.stream[pos+1]
if key>0 then--Press key
if key==0 then--Just wait
elseif key<=32 then--Press key
P:pressKey(key)
elseif key<0 then--Release key
P:releaseKey(-key)
else--Receiving garbage
--TODO:
elseif key<=64 then--Release key
P:releaseKey(key-32)
elseif key>1023 then--Receiving garbage
local line=key%1024
local amount=int(key/1024)%256
local color=int(key/262144)%256
local sid=int(key/67108864)%256
local time=int(key/17179869184)%256
P:receive()
--TODO
end
P.streamProgress=pos+2
goto readNext
end
update_alive(P,dt)
update.alive(P,dt)
end
if frmStep>1 then
frmStep=frmStep-1
goto readNext
end
end
return update

View File

@@ -42,6 +42,16 @@ local function tick_httpREQ_autoLogin(task)
if response.code==200 and res.message=="OK"then
LOGIN=true
LOG.print(text.loginSuccessed)
httpRequest(
TICK_httpREQ_getUserInfo,
PATH.api..PATH.users,
"GET",
{["Content-Type"]="application/json"},
json.encode{
email=USER.email,
auth_token=USER.auth_token,
}
)
else
LOGIN=false
LOG.print(text.loginFailed..": "..text.httpCode..response.code.."-"..res.message,"warn")

View File

@@ -9,13 +9,21 @@ local function tick_httpREQ_newLogin(task)
LOGIN=true
USER.email=res.email
USER.auth_token=res.auth_token
USER.name=res.name
USER.id=res.id
USER.motto=res.motto
USER.avatar=res.avatar
FILE.save(USER,"conf/user","q")
LOG.print(text.loginSuccessed)
httpRequest(
TICK_httpREQ_getUserInfo,
PATH.api..PATH.users,
"GET",
{["Content-Type"]="application/json"},
json.encode{
email=USER.email,
auth_token=USER.auth_token,
}
)
httpRequest(
TICK_httpREQ_getAccessToken,
PATH.api..PATH.access,

View File

@@ -68,7 +68,7 @@ function scene.socketRead(mes)
textBox:push{
COLOR.lR,args[1],
COLOR.dY,args[2].." ",
COLOR.Y,text[cmd=="J"and"chatJoin"or"chatLeave"]
COLOR.Y,text[cmd=="J"and"joinRoom"or"leaveRoom"]
}
remain=tonumber(args[3])
elseif cmd=="T"then

View File

@@ -1,3 +1,4 @@
local data=love.data
local gc=love.graphics
local gc_setColor,gc_circle=gc.setColor,gc.circle
local tc=love.touch
@@ -26,8 +27,15 @@ end
local hideChatBox
local textBox=WIDGET.newTextBox{name="texts",x=340,y=80,w=600,h=550,hide=function()return hideChatBox end}
local function switchChat()
hideChatBox=not hideChatBox
end
local playing
local heartBeatTimer
local lastUpstreamTime
local upstreamProgress
local lastBackTime=0
local noTouch,noKey=false,false
local touchMoveLastFrame=false
@@ -38,16 +46,22 @@ function scene.sceneBack()
wsWrite("Q")
WSCONN=false
LOG.print(text.wsDisconnected,"warn")
love.keyboard.setKeyRepeat(true)
end
function scene.sceneInit()
love.keyboard.setKeyRepeat(false)
wsWrite("C"..dumpBasicConfig())
TASK.new(TICK_wsRead)
hideChatBox=true
textBox:clear()
playerData={}
resetGameData("n",playerData)
noTouch=not SETTING.VKSwitch
playing=false
lastUpstreamTime=0
upstreamProgress=1
heartBeatTimer=0
end
function scene.touchDown(_,x,y)
@@ -120,6 +134,10 @@ function scene.keyDown(key)
lastBackTime=TIME()
LOG.print(text.sureQuit,COLOR.orange)
end
elseif key=="b"then
wsWrite("B")
elseif key=="\\"then
switchChat()
else
if noKey then return end
local k=keyMap.keyboard[key]
@@ -170,17 +188,18 @@ end
function scene.socketRead(mes)
local cmd=mes:sub(1,1)
local args=splitStr(mes:sub(2),":")
LOG.print(cmd,table.concat(args, " ; "))-------DEBUG PRINT
print(cmd.." "..table.concat(args, " ; "))-------DEBUG PRINT
if cmd=="J"or cmd=="L"then
textBox:push{
COLOR.lR,args[1],
COLOR.dY,args[2].." ",
COLOR.Y,text[cmd=="J"and"chatJoin"or"chatLeave"]
COLOR.Y,text[cmd=="J"and"joinRoom"or"leaveRoom"]
}
if cmd=="J"then
ins(playerData,{name=args[1],id=args[2],conf=false})
if not playing then
resetGameData("n",playerData)
if tostring(USER.id)~=args[2]then
wsWrite("C"..dumpBasicConfig())
ins(playerData,{name=args[1],id=args[2]})
resetGameData("qn",playerData)
end
else
for i=1,#playerData do
@@ -197,8 +216,9 @@ function scene.socketRead(mes)
end
for i=1,#PLAYERS.alive do
if PLAYERS.alive[i].userID==args[2]then
rem(PLAYERS,i)
break
rem(PLAYERS.alive,i)
initPlayerPosition(true)
return
end
end
end
@@ -209,23 +229,36 @@ function scene.socketRead(mes)
COLOR.sky,args[3]
}
elseif cmd=="C"then
for i=1,#playerData do
if playerData[i].id==args[1]then
playerData[i].conf=args[2]
return
if tostring(USER.id)~=args[2]then
local ENV=json.decode(data.decode("string","base64",args[3]))
for i=1,#playerData do
if playerData[i].id==args[2]then
playerData[i].conf=ENV
playerData[i].p:setConf(ENV)
return
end
end
ins(playerData,{name=args[1],id=args[2],conf=ENV})
resetGameData("qn",playerData)
end
elseif cmd=="S"then
for _,P in next,PLAYERS do
if P.userID==args[1]then
pumpRecording(args[2],P.stream)
if args[1]~=tostring(USER.id)then
for _,P in next,PLAYERS do
if P.userID==args[1]then
pumpRecording(data.decode("string","base64",args[2]),P.stream)
end
end
end
elseif cmd=="B"then
playing=true
resetGameData("n",playerData)
if not playing then
playing=true
resetGameData("n",playerData,tonumber(args[1]))
else
LOG.print("Redundant signal: B(begin)",30,COLOR.green)
end
elseif cmd=="F"then
playing=false
LOG.print(text.gameover,30,COLOR.green)
else
LOG.print("Illegal message: ["..mes.."]",30,COLOR.green)
end
@@ -248,10 +281,27 @@ function scene.update(dt)
end
end
if not playing then return end
if not playing then
heartBeatTimer=heartBeatTimer+dt
if heartBeatTimer>42 then
heartBeatTimer=0
wsWrite("P")
end
return
end
GAME.frame=GAME.frame+1
if GAME.frame-lastUpstreamTime>10 then
local stream
stream,upstreamProgress=dumpRecording(GAME.rep,upstreamProgress)
if #stream>0 then
wsWrite("S"..data.encode("string","base64",stream))
end
lastUpstreamTime=PLAYERS[1].alive and GAME.frame or 1e99
end
--Counting,include pre-das,directy RETURN,or restart counting
GAME.frame=GAME.frame+1
if GAME.frame<180 then
if GAME.frame==179 then
gameStart()
@@ -303,6 +353,9 @@ function scene.update(dt)
elseif GAME.warnLVL>0 then
GAME.warnLVL=max(GAME.warnLVL-.026,0)
end
if GAME.warnLVL>1.126 and GAME.frame%30==0 then
SFX.fplay("warning",SETTING.sfx_warn)
end
end
function scene.draw()
@@ -378,7 +431,7 @@ function scene.draw()
end
scene.widgetList={
textBox,
WIDGET.newKey{name="hideChat",fText="...",x=410,y=40,w=60,font=35,code=function()hideChatBox=not hideChatBox end},
WIDGET.newKey{name="hideChat",fText="...",x=410,y=40,w=60,font=35,code=switchChat},
WIDGET.newKey{name="quit",fText="X",x=870,y=40,w=60,font=40,code=pressKey"escape"},
}

View File

@@ -29,6 +29,30 @@ local function task_fetchRooms(task)
end
end
end
local function task_createRooms(task)
local time=0
while true do
coroutine.yield()
local response,request_error=client.poll(task)
if response then
local res=json.decode(response.body)
if response.code==200 and res.message=="OK"then
LOG.print("OK")
else
LOG.print(text.httpCode..response.code..": "..res.message,"warn")
end
return
elseif request_error then
LOG.print(text.roomsCreateFailed..": "..request_error,"warn")
return
end
time=time+1
if time>210 then
LOG.print(text.roomsCreateFailed..": "..text.httpTimeout,"warn")
return
end
end
end
local function task_enterRoom(task)
local time=0
while true do
@@ -82,6 +106,19 @@ function scene.keyDown(k)
if TIME()-lastfreshTime>1 then
fresh()
end
elseif k=="n"then
httpRequest(
task_createRooms,
PATH.api..PATH.rooms.."/classic",
"POST",
{["Content-Type"]="application/json"},
json.encode{
email=USER.email,
access_token=USER.access_token,
room_name="Test Room "..math.random(26,626),
room_password=nil,
}
)
elseif k=="escape"then
SCN.back()
elseif rooms and #rooms>0 then

View File

@@ -2,6 +2,7 @@ local gc=love.graphics
local gc_setColor,gc_circle=gc.setColor,gc.circle
local tc=love.touch
local int=math.floor
local max,sin=math.max,math.sin
local SCR=SCR
@@ -181,14 +182,23 @@ function scene.update(dt)
_=GAME.replaying
local L=GAME.rep
while GAME.frame==L[_]do
local k=L[_+1]
if k>0 then
P1:pressKey(k)
VK[k].isDown=true
VK[k].pressTime=10
else
VK[-k].isDown=false
P1:releaseKey(-k)
local key=L[_+1]
if key==0 then--Just wait
elseif key<=32 then--Press key
P1:pressKey(key)
VK[key].isDown=true
VK[key].pressTime=10
elseif key<=64 then--Release key
VK[key-32].isDown=false
P1:releaseKey(key-32)
elseif key>1023 then--Receiving garbage
local sid=key%256
local amount=int(key/256)%256
local time=int(key/4194304)%16384
local line=int(key/274877906944)%65536
local color=int(key/70368744177664)%256
P1:receive(sid,amount,time,line,color)
--TODO
end
_=_+2
end