diff --git a/README.md b/README.md index 662e115..40306ed 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ## роадмап -- пакет на отправку позиции от клиента +- античит - пакет на отправку текста на экран от сервера ## протокол diff --git a/client/dest/script.min.js b/client/dest/script.min.js index 4cd673b..e69de29 100644 --- a/client/dest/script.min.js +++ b/client/dest/script.min.js @@ -1 +0,0 @@ -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