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();