diff --git a/.gitignore b/.gitignore index 2a491ce..d5050da 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ server/__pycache__/ -server/src/__pycache__/ \ No newline at end of file +server/src/__pycache__/ +client/dest \ No newline at end of file diff --git a/client/build.sh b/client/build.sh index f614120..446d588 100755 --- a/client/build.sh +++ b/client/build.sh @@ -1,13 +1,13 @@ #!/bin/sh tsc +mkdir -p dest cat dest/main.js > dest/script.js cat dest/block.js >> dest/script.js cat dest/network.js >> dest/script.js cat dest/player.js >> dest/script.js cat dest/core.js >> dest/script.js terser dest/script.js -o dest/script.min.js --compress --mangle -rm dest/script.js rm dest/main.js rm dest/block.js rm dest/network.js diff --git a/client/dest/script.min.js b/client/dest/script.min.js index e07b7a0..4cd673b 100644 --- a/client/dest/script.min.js +++ b/client/dest/script.min.js @@ -1 +1 @@ -const canvas=document.getElementById("game"),ctx=canvas.getContext("2d"),width=640,height=480,server_ip=document.getElementById("server-ip"),server_nick=document.getElementById("server-nick"),connect_server=document.getElementById("connect-server"),server_error=document.getElementById("server-error");function wrapText(e,t,s){const c=[];let l="";for(let o=0;os&&l?(c.push(l),l=n):l=i}return l&&c.push(l),c}function lerp(e,t,s){return e+s*(t-e)}function setServerError(e){server_error.innerText=e}connect_server.onclick=()=>{let e=server_ip.value,t=server_nick.value;return setServerError(""),0==e.length?setServerError("введите айпи пж"):0==t.length?setServerError("введите ник пж"):(e.includes(":")||(e+=":8000"),void connectServer(e,t))},setInterval(tick,50),setInterval(renderTick,1e3/60);let renderTimer=()=>{render(),requestAnimationFrame(renderTimer)};requestAnimationFrame(renderTimer);class Block{constructor(e,t,s,c,l){this.x=e,this.y=t,this.color=s,this.collides=c,this.type=l}render(){let e=this.translate_to_camera();this.is_need_render(e)&&(ctx.fillStyle=this.color,ctx.fillRect(...e))}is_need_render(e){return e[0]+e[2]>0||e[1]+e[3]>0||e[0]<640||e[1]<480}translate_to_camera(){let e=16*camera.size;return[this.x*e-e/2+(320-camera.x*e),480-(this.y+1)*e+e/2-(240-camera.y*e),e,e]}tick(){}renderTick(){}on_collide(e,t,s){0!=t&&(e.velocity_x=this.x+t-e.x),0!=s&&(e.velocity_y=this.y+s-e.y)}renderText(){}}class Player extends Block{constructor(e,t,s,c,l,o){super(e,t,c,!0,null),this.velocity_x=l,this.velocity_y=o,this.name=s}reset(){this.on_ground=!1}on_collide(e,t,s){super.on_collide(e,t,s)}tick(e=!0){this.x=Math.round(100*this.x)/100,this.y=Math.round(100*this.y)/100,this.velocity_x=Math.round(100*this.velocity_x)/100,this.velocity_y=Math.round(100*this.velocity_y)/100,e&&this.collide()}collide(){this.on_ground=!1;for(const e of blocks){if(!e.collides)continue;let t=0,s=0;this.x>e.x-1&&this.xe.y&&this.y+this.velocity_y-1e.y-1&&(s=-1)),this.y>e.y-1&&this.ye.x&&this.x+this.velocity_x-1e.x-1&&(t=-1)),e.on_collide(this,t,s)}}renderTick(){this.velocity_x*=.5,this.velocity_y*=.5,this.x+=this.velocity_x,this.y+=this.velocity_y}renderText(){super.renderText();let e=this.translate_to_camera();if(this.is_need_render(e)){ctx.fillStyle="#ddd",ctx.font="15px monospace";let t=ctx.measureText(this.name).width;ctx.fillText(this.name,e[0]+e[2]/2-t/2,e[1]-5)}}teleport(e,t){this.velocity_x=e-this.x,this.velocity_y=t-this.y}forceTeleport(e,t){this.x=e,this.y=t,this.velocity_x=0,this.velocity_y=0}}class Packet{constructor(e,t){this.id=e,this.data=t}getId(){return this.id}getData(){return this.data}static fromString(e){return new Packet(e[0],e.slice(1).split("\n"))}}class JoinPacket extends Packet{constructor(e){super("J",[e])}}class MessagePacket extends Packet{constructor(e){super("M",[e])}}class KeyPacket extends Packet{constructor(e,t){super("K",[e,t?"1":"0"])}}class PlaceBlockPacket extends Packet{constructor(e,t,s){super("P",[e.toString(),t.toString(),s])}}class DestroyBlockPacket extends Packet{constructor(e,t){super("D",[e.toString(),t.toString()])}}class PositionPacket extends Packet{constructor(e,t){super("X",[e.toString(),t.toString()])}}class VelocityPacket extends Packet{constructor(e,t){super("V",[e.toString(),t.toString()])}}class Connection{constructor(e,t,s){this.socket=new WebSocket("ws://"+e,"cubic"),this.on_packet=t,this.on_close=s,this.socket.onmessage=this._on_message,this.socket.onclose=this._on_close,this.socket.onerror=this._on_error}_on_message(e){this.on_packet(Packet.fromString(e.data))}_on_close(e){this.on_close(null)}_on_error(e){this.on_close(e.toString())}close(){this.socket.close()}send(e){this.socket.send(e.getId()+e.getData())}}class MainPlayer extends Player{constructor(){super(0,0,"unnamed player","#5e6",0,0),this.reset(),this.conn=null}reset(){super.reset(),this.walk_speed=1,this.jump_speed=2,this.gravity_speed=.5,this.controls_x=0,this.controls_jump=!1,this.block_type=null,this.all_block_types=["normal","normal","normal","normal","normal","normal","normal","normal","normal","normal"]}onConnect(e){this.x=0,this.y=0,this.velocity_x=0,this.velocity_y=0,camera.x=0,camera.y=0,chatOpened=!1,chatMessages=[],this.name=e,this.color="#5e6",blocks=[],this.reset()}register(){document.addEventListener("keydown",(e=>{let t=e.code;if(chatOpened){if("Backspace"==t)chatTyping=chatTyping.slice(0,chatTyping.length-1);else if("Enter"==t){if(""==chatTyping)return void(chatOpened=!1);this.sendPacket(new MessagePacket(chatTyping)),chatTyping="",chatOpened=!1}else if("Escape"==t)chatOpened=!1;else if(1==e.key.length)return chatTyping+=e.key,e.preventDefault(),!1}else{if("KeyD"==t)this.controls_x=1;else if("KeyA"==t)this.controls_x=-1;else{if("Space"==t)return this.controls_jump=!0,e.preventDefault(),!1;"KeyR"==t?null==this.conn&&this.forceTeleport(0,1):"KeyT"==t&&(chatOpened=!0)}"0"==e.key&&(this.block_type=null),"123456789".includes(e.key)&&(this.block_type=this.all_block_types[parseInt(e.key)-1]),allowed_key_to_send.includes(t)&&this.sendPacket(new KeyPacket(t,!0)),"Escape"==t&&this.closeConnection()}if("F3"==t)return debugMode=!debugMode,e.preventDefault(),!1})),document.addEventListener("keyup",(e=>{let t=e.code;"KeyD"==t&&1==this.controls_x||"KeyA"==t&&-1==this.controls_x?this.controls_x=0:"Space"==t&&this.controls_jump&&(this.controls_jump=!1),allowed_key_to_send.includes(t)&&this.sendPacket(new KeyPacket(t,!1))})),canvas.addEventListener("wheel",(e=>(e.deltaY>0?camera.size*=e.deltaY/114*.5:camera.size*=e.deltaY/-114*2,e.preventDefault(),!1))),canvas.addEventListener("mousedown",(e=>{let t=canvas.getBoundingClientRect(),s=16*camera.size,c=Math.round((e.clientX-t.x)/s-640/s/2+camera.x),l=Math.round((480-(e.clientY-t.y))/s-480/s/2+camera.y);2==e.buttons&&null!=this.block_type?(null==this.conn&&placeBlock(new Block(c,l,"#555",!0,this.block_type)),this.sendPacket(new PlaceBlockPacket(c,l,this.block_type))):1==e.buttons&&(null==this.conn&&removeBlock(c,l),this.sendPacket(new DestroyBlockPacket(c,l)))}))}sendPacket(e){null!=this.conn&&this.conn.send(e)}closeConnection(){null!=this.conn&&this.conn.close(),this.conn=null}onPacket(e){let t=e.getId(),s=e.getData();if("K"==t&&(setServerError(s[0]),this.closeConnection()),"N"==t&&(this.name=s[0]),"C"==t&&(this.color=s[0]),"M"==t&&chatMessages.unshift(...s),"R"==t&&(this.velocity_x=parseFloat(s[0])-this.x+parseFloat(s[2]),this.velocity_y=parseFloat(s[1])-this.y+parseFloat(s[3])),"P"==t){let e=parseFloat(s[0]),t=parseFloat(s[1]);this.x=e,this.y=t}if("V"==t){let e=parseFloat(s[0]),t=parseFloat(s[1]);this.velocity_x=e,this.velocity_y=t}if("S"==t){let e=s[0],t=parseFloat(s[1]);"W"==e?this.walk_speed=t:"J"==e?this.jump_speed=t:"G"==e&&(this.gravity_speed=t)}if("W"==t)for(const e of s){let t=e[0],s="1"==e[1],c=e.slice(2).split(",");if("B"==t){let e=parseFloat(c[0]),t=parseFloat(c[1]);if(s){let s="1"==c[2],l=c[3],o=c[4],n=getBlock(e,t);null!=n?(n.x=e,n.y=t,n.color=o,n.collides=s,n.type=l):placeBlock(new Block(e,t,o,s,l))}else removeBlock(e,t)}else if("P"==t){let e=c[0];if(s){let t=parseFloat(c[1]),s=parseFloat(c[2]),l=parseFloat(c[3]),o=parseFloat(c[4]),n=c[5],i=getPlayer(e);null!=i?(i.x=t,i.y=s,i.color=n,i.velocity_x=l,i.velocity_y=o):placeBlock(new Player(t,s,e,n,l,o))}else removePlayer(e)}}"B"==t&&(this.all_block_types=s.slice(0,9),this.block_type=null)}tick(){super.tick(!1);let e=this.velocity_x,t=this.velocity_y;this.velocity_x+=this.controls_x*this.walk_speed,this.controls_jump&&this.on_ground?(this.velocity_y+=this.jump_speed,this.on_ground=!1):this.velocity_y-=this.gravity_speed,this.collide(),ticksAlive++,this.sendPacket(new VelocityPacket(this.velocity_x-e,this.velocity_y-t)),this.sendPacket(new PositionPacket(this.x,this.y))}render(){super.render(),camera.x=Math.round(1e3*lerp(camera.x,this.x,.075))/1e3,camera.y=Math.round(1e3*lerp(camera.y,this.y,.075))/1e3}renderText(){if(super.renderText(),null!=this.block_type&&(ctx.fillStyle="#76d",ctx.font="15px monospace",ctx.fillText("selected: "+this.block_type,0,15)),debugMode&&(ctx.fillStyle="#de7",ctx.font="20px monospace",ctx.fillText("x: "+this.x,0,20),ctx.fillText("y: "+this.y,0,40),ctx.fillText("velocity_x: "+this.velocity_x,0,60),ctx.fillText("velocity_y: "+this.velocity_y,0,80),ctx.fillText("camera.x: "+camera.x,0,100),ctx.fillText("camera.y: "+camera.y,0,120)),chatOpened){if(ctx.fillStyle="#22222288",ctx.fillRect(5,445,630,30),ctx.save(),ctx.beginPath(),ctx.moveTo(5,445),ctx.lineTo(635,445),ctx.lineTo(635,475),ctx.lineTo(5,475),ctx.lineTo(5,445),ctx.closePath(),ctx.clip(),ctx.font="15px monospace",0==chatTyping.length)ctx.fillStyle="#aaaaaa88",ctx.fillText("Напишите сообщение...",15,465);else{ctx.fillStyle="#ffffff";let e=ctx.measureText(chatTyping).width;ctx.fillText(chatTyping,Math.min(10,640-e-10),465)}ctx.restore()}else ctx.fillStyle="#22222288",ctx.fillRect(5,445,256,30),ctx.fillStyle="#aaaaaa88",ctx.font="15px monospace",ctx.fillText("Нажмите T для чата",15,465);if(chatMessages.length>0){let e=e=>{let c=wrapText(ctx,e,t-20),l=20*c.length+5+5,o=s-l;ctx.fillStyle="#22222288",ctx.fillRect(5,o,t,l);let n=5;for(let e of c)ctx.fillStyle="#ffffff",ctx.fillText(e,15,o+n+15),n+=25;s-=l+5},t=256,s=440;chatOpened?chatMessages.forEach(e):e(chatMessages[0])}}}var ticksAlive=0,debugMode=!1,camera={x:0,y:0,size:1.5},chatOpened=!1,chatMessages=[],chatTyping="",player=new MainPlayer;player.register();var blocks=[];const allowed_key_to_send=["KeyR","KeyW","KeyE","KeyQ","KeyS","Numpad1","Numpad2","Numpad3","Numpad4","Numpad5","Numpad6","Numpad7","Numpad8","Numpad9","Numpad0","ShiftLeft","ControlLeft","Enter","F1","F2","KeyZ","KeyX","KeyC"];function connectServer(e,t){player.closeConnection(),player.onConnect(t);try{new Connection(e,player.onPacket,(e=>{player.conn=null,setServerError(null==e?"Connection closed due to error":e),resetWorld()})).send(new JoinPacket(t))}catch(e){setServerError(e)}}function resetWorld(){player.onConnect("unnamed player"),(blocks=[]).push(new Block(-1,-1,"#555",!0,"normal")),blocks.push(new Block(0,-1,"#a67",!0,"spawn")),blocks.push(new Block(1,-1,"#555",!0,"normal"))}function getBlock(e,t){let s=blocks.find((s=>!(s instanceof Player)&&s.x==e&&s.y==t));return void 0===s?null:s}function placeBlock(e){blocks.push(e)}function removeBlock(e,t){blocks=blocks.filter((s=>s instanceof Player||s.x!=e||s.y!=t))}function getPlayer(e){let t=blocks.find((t=>t instanceof Player&&t.name==e));return void 0===t?null:t}function removePlayer(e){blocks=blocks.filter((t=>!(t instanceof Player)||t.name!=e))}function render(){ctx.fillStyle="#333",ctx.fillRect(0,0,640,480);for(const e of blocks)e.render();for(const e of blocks)e.renderText();player.render(),player.renderText()}function tick(){for(const e of blocks)e.tick();player.tick()}function renderTick(){for(const e of blocks)e.renderTick();player.renderTick()}resetWorld(); \ No newline at end of file +const canvas=document.getElementById("game"),ctx=canvas.getContext("2d"),width=640,height=480,server_ip=document.getElementById("server-ip"),server_nick=document.getElementById("server-nick"),connect_server=document.getElementById("connect-server"),server_error=document.getElementById("server-error");function wrapText(e,t,s){const c=[];let n="";for(let l=0;ls&&n?(c.push(n),n=o):n=i}return n&&c.push(n),c}function lerp(e,t,s){return e+s*(t-e)}function setServerError(e){server_error.innerText=e}connect_server.onclick=()=>{let e=server_ip.value,t=server_nick.value;return setServerError(""),0==e.length?setServerError("введите айпи пж"):0==t.length?setServerError("введите ник пж"):(e.includes(":")||(e+=":8000"),void connectServer(e,t))},setInterval(tick,50),setInterval(renderTick,1e3/60);let renderTimer=()=>{render(),requestAnimationFrame(renderTimer)};requestAnimationFrame(renderTimer);class Block{constructor(e,t,s,c,n){this.x=e,this.y=t,this.color=s,this.collides=c,this.type=n}render(){let e=this.translate_to_camera();this.is_need_render(e)&&(ctx.fillStyle=this.color,ctx.fillRect(...e))}is_need_render(e){return e[0]+e[2]>0||e[1]+e[3]>0||e[0]<640||e[1]<480}translate_to_camera(){let e=16*camera.size;return[this.x*e-e/2+(320-camera.x*e),480-(this.y+1)*e+e/2-(240-camera.y*e),e,e]}tick(){}renderTick(){}on_collide(e,t,s){0!=t&&(e.velocity_x=this.x+t-e.x),0!=s&&(e.velocity_y=this.y+s-e.y)}renderText(){}}class Player extends Block{constructor(e,t,s,c,n,l){super(e,t,c,!0,null),this.velocity_x=n,this.velocity_y=l,this.name=s}reset(){this.on_ground=!1}on_collide(e,t,s){super.on_collide(e,t,s)}tick(e=!0){this.x=Math.round(100*this.x)/100,this.y=Math.round(100*this.y)/100,this.velocity_x=Math.round(100*this.velocity_x)/100,this.velocity_y=Math.round(100*this.velocity_y)/100,e&&this.collide()}collide(){this.on_ground=!1;for(const e of blocks){if(!e.collides)continue;let t=0,s=0;this.x>e.x-1&&this.xe.y&&this.y+this.velocity_y-1e.y-1&&(s=-1)),this.y>e.y-1&&this.ye.x&&this.x+this.velocity_x-1e.x-1&&(t=-1)),e.on_collide(this,t,s)}}renderTick(){this.velocity_x*=.5,this.velocity_y*=.5,this.x+=this.velocity_x,this.y+=this.velocity_y}renderText(){super.renderText();let e=this.translate_to_camera();if(this.is_need_render(e)){ctx.fillStyle="#ddd",ctx.font="15px monospace";let t=ctx.measureText(this.name).width;ctx.fillText(this.name,e[0]+e[2]/2-t/2,e[1]-5)}}teleport(e,t){this.velocity_x=e-this.x,this.velocity_y=t-this.y}forceTeleport(e,t){this.x=e,this.y=t,this.velocity_x=0,this.velocity_y=0}}class Packet{constructor(e,t){this.id=e,this.data=t}getId(){return this.id}getData(){return this.data}static fromString(e){return new Packet(e[0],e.slice(1).split("\n"))}}class JoinPacket extends Packet{constructor(e){super("J",[e])}}class MessagePacket extends Packet{constructor(e){super("M",[e])}}class KeyPacket extends Packet{constructor(e,t){super("K",[e,t?"1":"0"])}}class PlaceBlockPacket extends Packet{constructor(e,t,s){super("P",[e.toString(),t.toString(),s])}}class DestroyBlockPacket extends Packet{constructor(e,t){super("D",[e.toString(),t.toString()])}}class PositionPacket extends Packet{constructor(e,t){super("X",[e.toString(),t.toString()])}}class VelocityPacket extends Packet{constructor(e,t){super("V",[e.toString(),t.toString()])}}class Connection{static createSocket(e,t,s){return new Promise(((c,n)=>{let l=new WebSocket("ws://"+e,"cubic");l.onmessage=e=>t(Packet.fromString(e.data)),l.onclose=e=>s(null),l.onerror=e=>s(e.toString()),l.onopen=()=>c(l)}))}constructor(e){this.socket=e}close(){this.socket.close()}send(e){this.socket.send(e.getId()+e.getData().join("\n"))}}class MainPlayer extends Player{constructor(){super(0,0,"unnamed player","#5e6",0,0),this.reset(),this.conn=null}reset(){super.reset(),this.walk_speed=1,this.jump_speed=2,this.gravity_speed=.5,this.controls_x=0,this.controls_jump=!1,this.block_type=null,this.all_block_types=["normal","normal","normal","normal","normal","normal","normal","normal","normal","normal"]}onConnect(e){this.x=0,this.y=0,this.velocity_x=0,this.velocity_y=0,camera.x=0,camera.y=0,chatOpened=!1,chatMessages=[],this.name=e,this.color="#5e6",blocks=[],this.reset()}register(){document.addEventListener("keydown",(e=>{let t=e.code;if(chatOpened){if("Backspace"==t)chatTyping=chatTyping.slice(0,chatTyping.length-1);else if("Enter"==t){if(""==chatTyping)return void(chatOpened=!1);this.sendPacket(new MessagePacket(chatTyping)),chatTyping="",chatOpened=!1}else if("Escape"==t)chatOpened=!1;else if(1==e.key.length)return chatTyping+=e.key,e.preventDefault(),!1}else{if("KeyD"==t)this.controls_x=1;else if("KeyA"==t)this.controls_x=-1;else{if("Space"==t)return this.controls_jump=!0,e.preventDefault(),!1;"KeyR"==t?null==this.conn&&this.forceTeleport(0,1):"KeyT"==t&&(chatOpened=!0)}"0"==e.key&&(this.block_type=null),"123456789".includes(e.key)&&(this.block_type=this.all_block_types[parseInt(e.key)-1]),allowed_key_to_send.includes(t)&&this.sendPacket(new KeyPacket(t,!0)),"Escape"==t&&this.closeConnection()}if("F3"==t)return debugMode=!debugMode,e.preventDefault(),!1})),document.addEventListener("keyup",(e=>{let t=e.code;"KeyD"==t&&1==this.controls_x||"KeyA"==t&&-1==this.controls_x?this.controls_x=0:"Space"==t&&this.controls_jump&&(this.controls_jump=!1),allowed_key_to_send.includes(t)&&this.sendPacket(new KeyPacket(t,!1))})),canvas.addEventListener("wheel",(e=>(e.deltaY>0?camera.size*=e.deltaY/114*.5:camera.size*=e.deltaY/-114*2,e.preventDefault(),!1))),canvas.addEventListener("mousedown",(e=>{let t=canvas.getBoundingClientRect(),s=16*camera.size,c=Math.round((e.clientX-t.x)/s-640/s/2+camera.x),n=Math.round((480-(e.clientY-t.y))/s-480/s/2+camera.y);2==e.buttons&&null!=this.block_type?(null==this.conn&&placeBlock(new Block(c,n,"#555",!0,this.block_type)),this.sendPacket(new PlaceBlockPacket(c,n,this.block_type))):1==e.buttons&&(null==this.conn&&removeBlock(c,n),this.sendPacket(new DestroyBlockPacket(c,n)))}))}sendPacket(e){null!=this.conn&&this.conn.send(e)}closeConnection(){null!=this.conn&&this.conn.close(),this.conn=null}onPacket(e){let t=e.getId(),s=e.getData();if("K"==t&&(setServerError(s[0]),this.closeConnection()),"N"==t&&(this.name=s[0]),"C"==t&&(this.color=s[0]),"M"==t&&chatMessages.unshift(...s),"R"==t&&(this.velocity_x=parseFloat(s[0])-this.x+parseFloat(s[2]),this.velocity_y=parseFloat(s[1])-this.y+parseFloat(s[3])),"P"==t){let e=parseFloat(s[0]),t=parseFloat(s[1]);this.x=e,this.y=t}if("V"==t){let e=parseFloat(s[0]),t=parseFloat(s[1]);this.velocity_x=e,this.velocity_y=t}if("S"==t){let e=s[0],t=parseFloat(s[1]);"W"==e?this.walk_speed=t:"J"==e?this.jump_speed=t:"G"==e&&(this.gravity_speed=t)}if("W"==t)for(const e of s){let t=e[0],s="1"==e[1],c=e.slice(2).split(",");if("B"==t){let e=parseFloat(c[0]),t=parseFloat(c[1]);if(s){let s="1"==c[2],n=c[3],l=c[4],o=getBlock(e,t);null!=o?(o.x=e,o.y=t,o.color=l,o.collides=s,o.type=n):placeBlock(new Block(e,t,l,s,n))}else removeBlock(e,t)}else if("P"==t){let e=c[0];if(s){let t=parseFloat(c[1]),s=parseFloat(c[2]),n=parseFloat(c[3]),l=parseFloat(c[4]),o=c[5],i=getPlayer(e);null!=i?(i.x=t,i.y=s,i.color=o,i.velocity_x=n,i.velocity_y=l):placeBlock(new Player(t,s,e,o,n,l))}else removePlayer(e)}}"B"==t&&(this.all_block_types=s.slice(0,9),this.block_type=null)}tick(){super.tick(!1);let e=this.velocity_x,t=this.velocity_y;this.velocity_x+=this.controls_x*this.walk_speed,this.controls_jump&&this.on_ground?(this.velocity_y+=this.jump_speed,this.on_ground=!1):this.velocity_y-=this.gravity_speed,this.collide(),ticksAlive++,this.sendPacket(new VelocityPacket(this.velocity_x-e,this.velocity_y-t)),this.sendPacket(new PositionPacket(this.x,this.y))}render(){super.render(),camera.x=Math.round(1e3*lerp(camera.x,this.x,.075))/1e3,camera.y=Math.round(1e3*lerp(camera.y,this.y,.075))/1e3}renderText(){if(super.renderText(),null!=this.block_type&&(ctx.fillStyle="#76d",ctx.font="15px monospace",ctx.fillText("selected: "+this.block_type,0,15)),debugMode&&(ctx.fillStyle="#de7",ctx.font="20px monospace",ctx.fillText("x: "+this.x,0,20),ctx.fillText("y: "+this.y,0,40),ctx.fillText("velocity_x: "+this.velocity_x,0,60),ctx.fillText("velocity_y: "+this.velocity_y,0,80),ctx.fillText("camera.x: "+camera.x,0,100),ctx.fillText("camera.y: "+camera.y,0,120)),chatOpened){if(ctx.fillStyle="#22222288",ctx.fillRect(5,445,630,30),ctx.save(),ctx.beginPath(),ctx.moveTo(5,445),ctx.lineTo(635,445),ctx.lineTo(635,475),ctx.lineTo(5,475),ctx.lineTo(5,445),ctx.closePath(),ctx.clip(),ctx.font="15px monospace",0==chatTyping.length)ctx.fillStyle="#aaaaaa88",ctx.fillText("Напишите сообщение...",15,465);else{ctx.fillStyle="#ffffff";let e=ctx.measureText(chatTyping).width;ctx.fillText(chatTyping,Math.min(10,640-e-10),465)}ctx.restore()}else ctx.fillStyle="#22222288",ctx.fillRect(5,445,256,30),ctx.fillStyle="#aaaaaa88",ctx.font="15px monospace",ctx.fillText("Нажмите T для чата",15,465);if(chatMessages.length>0){let e=e=>{let c=wrapText(ctx,e,t-20),n=20*c.length+5+5,l=s-n;ctx.fillStyle="#22222288",ctx.fillRect(5,l,t,n);let o=5;for(let e of c)ctx.fillStyle="#ffffff",ctx.fillText(e,15,l+o+15),o+=25;s-=n+5},t=256,s=440;chatOpened?chatMessages.forEach(e):e(chatMessages[0])}}}var __awaiter=this&&this.__awaiter||function(e,t,s,c){return new(s||(s=Promise))((function(n,l){function o(e){try{r(c.next(e))}catch(e){l(e)}}function i(e){try{r(c.throw(e))}catch(e){l(e)}}function r(e){var t;e.done?n(e.value):(t=e.value,t instanceof s?t:new s((function(e){e(t)}))).then(o,i)}r((c=c.apply(e,t||[])).next())}))},ticksAlive=0,debugMode=!1,camera={x:0,y:0,size:1.5},chatOpened=!1,chatMessages=[],chatTyping="",player=new MainPlayer;player.register();var blocks=[];const allowed_key_to_send=["KeyR","KeyW","KeyE","KeyQ","KeyS","Numpad1","Numpad2","Numpad3","Numpad4","Numpad5","Numpad6","Numpad7","Numpad8","Numpad9","Numpad0","ShiftLeft","ControlLeft","Enter","F1","F2","KeyZ","KeyX","KeyC"];function connectServer(e,t){return __awaiter(this,void 0,void 0,(function*(){player.closeConnection(),player.onConnect(t);try{player.conn=new Connection(yield Connection.createSocket(e,(e=>player.onPacket(e)),(e=>{player.conn=null,setServerError(null==e?"Connection closed due to error":e),resetWorld()}))),player.conn.send(new JoinPacket(t))}catch(e){setServerError(e),console.log(e)}}))}function resetWorld(){player.onConnect("unnamed player"),(blocks=[]).push(new Block(-1,-1,"#555",!0,"normal")),blocks.push(new Block(0,-1,"#a67",!0,"spawn")),blocks.push(new Block(1,-1,"#555",!0,"normal"))}function getBlock(e,t){let s=blocks.find((s=>!(s instanceof Player)&&s.x==e&&s.y==t));return void 0===s?null:s}function placeBlock(e){blocks.push(e)}function removeBlock(e,t){blocks=blocks.filter((s=>s instanceof Player||s.x!=e||s.y!=t))}function getPlayer(e){let t=blocks.find((t=>t instanceof Player&&t.name==e));return void 0===t?null:t}function removePlayer(e){blocks=blocks.filter((t=>!(t instanceof Player)||t.name!=e))}function render(){ctx.fillStyle="#333",ctx.fillRect(0,0,640,480);for(const e of blocks)e.render();for(const e of blocks)e.renderText();player.render(),player.renderText()}function tick(){for(const e of blocks)e.tick();player.tick()}function renderTick(){for(const e of blocks)e.renderTick();player.renderTick()}resetWorld(); \ No newline at end of file diff --git a/client/index.html b/client/index.html index 833e8b7..731859a 100644 --- a/client/index.html +++ b/client/index.html @@ -75,6 +75,6 @@ репозиторий гитхаб. там же протокол сервер и готовая реализация сервера на Python
- + \ No newline at end of file diff --git a/client/src/core.ts b/client/src/core.ts index fb5038a..19f0115 100644 --- a/client/src/core.ts +++ b/client/src/core.ts @@ -27,19 +27,26 @@ const allowed_key_to_send = [ "F1", "F2", "KeyZ", "KeyX", "KeyC" ] -function connectServer(address: string, name: string) { +async function connectServer(address: string, name: string) { player.closeConnection() player.onConnect(name) try { - let conn = new Connection(address, player.onPacket, (e) => { - player.conn = null - setServerError(e == null ? "Connection closed due to error" : e) - resetWorld() - }) - conn.send(new JoinPacket(name)) + player.conn = new Connection( + await Connection.createSocket( + address, + (p) => player.onPacket(p), + (e) => { + player.conn = null + setServerError(e == null ? "Connection closed due to error" : e) + resetWorld() + } + ) + ) + player.conn.send(new JoinPacket(name)) } catch (exception) { setServerError(exception) + console.log(exception) } } diff --git a/client/src/network.ts b/client/src/network.ts index 64dc6f0..b0fa926 100644 --- a/client/src/network.ts +++ b/client/src/network.ts @@ -59,36 +59,27 @@ class VelocityPacket extends Packet { class Connection { private socket: WebSocket - private on_packet: (packet: Packet) => void - private on_close: (error: string | null) => void - constructor( - address: string, + static createSocket(address: string, on_packet: (packet: Packet) => void, on_close: (error: string | null) => void + ): Promise { + return new Promise((resolve, _) => { + let socket = new WebSocket( + "ws://"+address, + "cubic", + ) + socket.onmessage = (e) => on_packet(Packet.fromString(e.data)) + socket.onclose = (_) => on_close(null) + socket.onerror = (e) => on_close(e.toString()) + socket.onopen = () => resolve(socket) + }) + } + + constructor( + socket: WebSocket ) { - this.socket = new WebSocket( - "ws://"+address, - "cubic", - ) - this.on_packet = on_packet - this.on_close = on_close - - this.socket.onmessage = this._on_message - this.socket.onclose = this._on_close - this.socket.onerror = this._on_error - } - - private _on_message(event: MessageEvent) { - this.on_packet(Packet.fromString(event.data)) - } - - private _on_close(event: CloseEvent) { - this.on_close(null) - } - - private _on_error(event: Event) { - this.on_close(event.toString()) + this.socket = socket } close() { @@ -96,6 +87,6 @@ class Connection { } send(packet: Packet) { - this.socket.send(packet.getId()+packet.getData()) + this.socket.send(packet.getId()+packet.getData().join("\n")) } } \ No newline at end of file diff --git a/server/src/block.py b/server/src/block.py index d49c593..066a1d6 100644 --- a/server/src/block.py +++ b/server/src/block.py @@ -18,18 +18,17 @@ class Block: if x != 0: player.vel_x = self.x + x - player.x if y != 0: player.vel_y = self.y + y - player.y - if x != 0 or y != 0: # special blocks - if self.type == "jump_boost": - player.setGravitySpeed(1.25) - player.setJumpSpeed(5) - player.on_ground = True - elif player.jump_speed != 2: - player.setGravitySpeed(0.5) - player.setJumpSpeed(2) + if self.type == "jump_boost": + await player.setGravitySpeed(1.25) + await player.setJumpSpeed(5) + player.on_ground = True + elif player.jump_speed != 2: + await player.setGravitySpeed(0.5) + await player.setJumpSpeed(2) - if self.type == "killer": - await player.setPos(*SPAWN) - await player.sendToPlayers() + if self.type == "killer": + await player.setPos(*SPAWN) + await player.sendToPlayers() def toStatement(self, add=True): return f"B1{self.x},{self.y},{int(self.collides)},{self.type},{self.color}" if add else f"B0{self.x},{self.y}" \ No newline at end of file diff --git a/server/src/network.py b/server/src/network.py index 2dc3843..039cd2f 100644 --- a/server/src/network.py +++ b/server/src/network.py @@ -5,6 +5,7 @@ from world import * from player import Player from packet import * import random +import traceback async def startServer(host, port): async with serve(handler, host, port): @@ -43,8 +44,10 @@ async def handler(websocket: ServerConnection): while True: packet_id, packet_data = await readPacket(websocket) - player.onPacket(packet_id, packet_data) + await player.onPacket(packet_id, packet_data) except Exception as exc: + traceback.print_exc() + WORLD.remove(player) for p in getPlayers(): diff --git a/server/src/player.py b/server/src/player.py index ec49b87..aba4632 100644 --- a/server/src/player.py +++ b/server/src/player.py @@ -1,12 +1,13 @@ from block import Block from packet import writePacket from config import SPAWN, REACH_DISTANCE, BLOCK_TYPES -from world import * +import world import time class Player(Block): - def __init__(self, websocket, x=None, y=None, name=None, color=None, vel_x=None, vel_y=None): + def __init__(self, websocket, x=0, y=0, name=None, color=None, vel_x=None, vel_y=None): super().__init__(x, y, None, color, True) + self.x = x self.y = y @@ -27,7 +28,7 @@ class Player(Block): self.lsd_time = time.time() async def sendPacket(self, packet_id, packet_data): - await writePacket(self.websocket, packet_id, packet_data) + await self.sendPacket(packet_id, packet_data) async def onPacket(self, packet_id, packet_data): if packet_id == "V": @@ -77,7 +78,7 @@ class Player(Block): message = packet_data[0] message = f"{self.name} > {message}" - for p in getPlayers(): + for p in world.getPlayers(): await p.sendMessage(message) print(message) @@ -87,7 +88,7 @@ class Player(Block): x,y = int(x),int(y) block = None - for i in WORLD: + for i in world.WORLD: if type(i) == Player: continue if i.x == x and i.y == y: @@ -100,9 +101,9 @@ class Player(Block): if abs(x - self.x) ** 2 + abs(y - self.y) ** 2 > REACH_DISTANCE ** 2: return - WORLD.remove(block) + world.WORLD.remove(block) - for p in getPlayers(): + for p in world.getPlayers(): await p.sendWorld([block.toStatement(False)]) if packet_id == "P": @@ -116,7 +117,7 @@ class Player(Block): return found_block = False - for i in WORLD: + for i in world.WORLD: if type(i) == Player: continue if i.x == x and i.y == y: @@ -126,25 +127,25 @@ class Player(Block): block = Block(x,y,block_type,BLOCK_TYPES[block_type],True) - WORLD.append(block) + world.WORLD.append(block) - for p in getPlayers(): + for p in world.getPlayers(): await p.sendWorld([block.toStatement()]) async def setWalkSpeed(self, speed): - await writePacket(self.websocket, "S", ["W", str(speed)]) + await self.sendPacket("S", ["W", str(speed)]) self.walk_speed = speed async def setGravitySpeed(self, speed): - await writePacket(self.websocket, "S", ["G", str(speed)]) + await self.sendPacket("S", ["G", str(speed)]) self.gravity_speed = speed async def setJumpSpeed(self, speed): - await writePacket(self.websocket, "S", ["J", str(speed)]) + await self.sendPacket("S", ["J", str(speed)]) self.jump_speed = speed async def sendName(self, name): - await writePacket(self.websocket, "N", [name]) + await self.sendPacket("N", [name]) async def setName(self, name): self.name = name @@ -152,7 +153,7 @@ class Player(Block): async def setColor(self, color): self.color = color - await writePacket(self.websocket, "C", [color]) + await self.sendPacket("C", [color]) async def setVel(self, x, y): if x == self.vel_x and y == self.vel_y: return @@ -171,23 +172,23 @@ class Player(Block): await self.sendPos(x, y) async def sendVel(self, x, y): - await writePacket(self.websocket, "V", [str(x), str(y)]) + await self.sendPacket("V", [str(x), str(y)]) async def sendPos(self, x, y): - await writePacket(self.websocket, "P", [str(x), str(y)]) + await self.sendPacket("P", [str(x), str(y)]) async def sendMessage(self, message): - await writePacket(self.websocket, "M", message.split("\n")) + await self.sendPacket("M", message.split("\n")) async def sendWorld(self, statements): if len(statements) == 0: return - await writePacket(self.websocket, "W", statements) + await self.sendPacket("W", statements) async def sendBlockTypes(self, types): - await writePacket(self.websocket, "B", types) + await self.sendPacket("B", types) async def sendToPlayers(self): - for p in getPlayers(): + for p in world.getPlayers(): if p != self: await p.sendWorld([self.toStatement()]) @@ -203,11 +204,9 @@ class Player(Block): await self.collide() async def collide(self): - global WORLD - self.on_ground = False - for block in WORLD: + for block in world.WORLD: if not block.collides: continue if block == self: continue @@ -227,7 +226,8 @@ class Player(Block): if self.x < block.x and self.x + self.vel_x > block.x - 1: collide_x = -1 - await block.onCollide(self, collide_x, collide_y) + if collide_x != 0 or collide_y != 0: + await block.onCollide(self, collide_x, collide_y) async def onCollide(self, player, x, y): await super().onCollide(player, x, y) @@ -249,7 +249,7 @@ class Player(Block): return self.vel_x != 0 or self.vel_y != 0 async def keepAlive(self): - await writePacket(self.websocket, "R", [str(self.x), str(self.y), str(self.vel_x), str(self.vel_y)]) + await self.sendPacket("R", [str(self.x), str(self.y), str(self.vel_x), str(self.vel_y)]) def toStatement(self, add=True): return f"P1{self.name},{self.x},{self.y},{self.vel_x},{self.vel_y},{self.color}" if add else f"P0{self.name}"