From f3c592b6d89b8090aa0987d4161ff66febbcf243 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Sat, 21 Dec 2024 23:42:19 +0300 Subject: [PATCH] client update --- client/src/block.ts | 168 ++++++++++ client/src/core.ts | 108 +++++++ client/src/network.ts | 80 ++++- client/src/player.ts | 401 +++++++++++++++++++++++ client/src/render.ts | 0 client/src/script.ts | 715 +----------------------------------------- 6 files changed, 756 insertions(+), 716 deletions(-) create mode 100644 client/src/core.ts delete mode 100644 client/src/render.ts diff --git a/client/src/block.ts b/client/src/block.ts index e69de29..33b9b91 100644 --- a/client/src/block.ts +++ b/client/src/block.ts @@ -0,0 +1,168 @@ + +class Block { + public x: number + public y: number + public color: string + public collides: boolean + public type: string + + constructor( + x: number, + y: number, + color: string, + collides: boolean, + type: string, + ) { + this.x = x + this.y = y + this.color = color + this.collides = collides + this.type = type + } + + render() { + let rect = this.translate_to_camera() + if (this.is_need_render(rect)) { + ctx.fillStyle = this.color + ctx.fillRect(...rect) + } + } + + is_need_render(rect: Rect): boolean { + return rect[0] + rect[2] > 0 || rect[1] + rect[3] > 0 || rect[0] < width || rect[1] < height + } + + translate_to_camera(): Rect { + let size = camera.size * 16 + + return [ + this.x * size - size / 2 + (width / 2 - camera.x * size), + height - (this.y + 1) * size + size / 2 - (height / 2 - camera.y * size), + size, + size + ] + } + + tick() { + + } + + renderTick() { + + } + + on_collide(player: Player, x: number, y: number) { + if (x != 0) player.velocity_x = this.x + x - player.x + if (y != 0) player.velocity_y = this.y + y - player.y + } + + renderText() { + + } +} + +class Player extends Block { + public velocity_x: number + public velocity_y: number + public name: string + public on_ground: boolean + + constructor( + x: number, + y: number, + name: string, + color: string, + velocity_x: number, + velocity_y: number, + ) { + super(x, y, color, true, null) + + this.velocity_x = velocity_x + this.velocity_y = velocity_y + + this.name = name + } + + reset() { + this.on_ground = false + } + + on_collide(player: Player, x: number, y: number) { + super.on_collide(player, x, y) + // if (x != 0) { + // player.vel_x *= 0.5 + // this.vel_x = player.vel_x + // } + // if (y != 0) { + // player.vel_y *= 0.5 + // this.vel_y = player.vel_y + // } + } + + tick(collide=true) { + this.x = Math.round(this.x * 100) / 100 + this.y = Math.round(this.y * 100) / 100 + this.velocity_x = Math.round(this.velocity_x * 100) / 100 + this.velocity_y = Math.round(this.velocity_y * 100) / 100 + + if (collide) this.collide() + } + + collide() { + this.on_ground = false + + for (const block of blocks) { + if (!block.collides) continue + + let collide_x = 0 + let collide_y = 0 + + if (this.x > block.x-1 && this.x < block.x+1) { + if (this.y > block.y && this.y + this.velocity_y - 1 < block.y) { + this.on_ground = true + collide_y = 1 + } + if (this.y < block.y && this.y + this.velocity_y > block.y - 1) collide_y = -1 + } + + if (this.y > block.y-1 && this.y < block.y+1) { + if (this.x > block.x && this.x + this.velocity_x - 1 < block.x) collide_x = 1 + if (this.x < block.x && this.x + this.velocity_x > block.x - 1) collide_x = -1 + } + + block.on_collide(this, collide_x, collide_y) + } + } + + renderTick() { + this.velocity_x *= 0.5 + this.velocity_y *= 0.5 + this.x += this.velocity_x + this.y += this.velocity_y + } + + renderText() { + super.renderText() + + let rect = this.translate_to_camera() + + if (this.is_need_render(rect)) { + ctx.fillStyle = "#ddd" + ctx.font = "15px monospace"; + let width = ctx.measureText(this.name).width + ctx.fillText(this.name,rect[0]+rect[2]/2-width/2, rect[1]-5); + } + } + + teleport(x: number, y: number) { + this.velocity_x = x - this.x + this.velocity_y = y - this.y + } + + forceTeleport(x: number, y: number) { + this.x = x + this.y = y + this.velocity_x = 0 + this.velocity_y = 0 + } +} \ No newline at end of file diff --git a/client/src/core.ts b/client/src/core.ts new file mode 100644 index 0000000..546cd92 --- /dev/null +++ b/client/src/core.ts @@ -0,0 +1,108 @@ +type Rect = [number, number, number, number] + +var ticksAlive = 0 + +var debugMode = false + +var camera = { + x: 0.0, + y: 0.0, + size: 1.5 +} + +var chatOpened = false +var chatMessages = [] +var chatTyping = "" + +var player = new MainPlayer() +player.register() + +var blocks: Block[] = [] + +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(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)) + } catch (exception) { + setServerError(exception) + } +} + +function resetWorld() { + player.onConnect("unnamed player") + blocks = [] + blocks.push(new Block(-1, -1, "#555", true, "normal")); + blocks.push(new Block(0, -1, "#a67", true, "spawn")); + blocks.push(new Block(1, -1, "#555", true, "normal")); +} + +function getBlock(x: number, y: number): Block { + let value = blocks.find(o => !(o instanceof Player) && o.x == x && o.y == y) + if (typeof value === "undefined") { + return null + } else { + return value + } +} + +function placeBlock(block: Block) { + blocks.push(block); +} + +function removeBlock(x: number, y: number) { + blocks = blocks.filter(o => o instanceof Player || o.x != x || o.y != y) +} + +function getPlayer(name: string): Player | null { + let value = blocks.find(o => o instanceof Player && o.name == name) as Player + if (typeof value === "undefined") { + return null + } else { + return value + } +} + +function removePlayer(name: string) { + blocks = blocks.filter(o => !(o instanceof Player) || o.name != name) +} + +function render() { + ctx.fillStyle = "#333"; + ctx.fillRect(0, 0, width, height); + + for (const block of blocks) + block.render() + for (const block of blocks) + block.renderText() + player.render() + player.renderText() +} + +function tick() { + for (const block of blocks) + block.tick() + player.tick() +} + +function renderTick() { + for (const block of blocks) + block.renderTick() + player.renderTick() +} + +resetWorld() \ No newline at end of file diff --git a/client/src/network.ts b/client/src/network.ts index 38417a7..64dc6f0 100644 --- a/client/src/network.ts +++ b/client/src/network.ts @@ -1,30 +1,76 @@ -interface Packet { - getId(): string - getData(): string +class Packet { + private id: string + private data: string[] + + constructor(id: string, data: string[]) { + this.id = id + this.data = data + } + + getId(): string { return this.id } + getData(): string[] { return this.data } + + static fromString(data: string): Packet { + return new Packet(data[0], data.slice(1).split("\n")) + } } -function createPacket(id: string, ...params: string[]): Packet { - return { - getId: () => id, - getData: () => params.join("\n"), - } +class JoinPacket extends Packet { + constructor(name: string) { + super("J", [name]) + } } -function fromPacket(data: string): Packet { - return createPacket(data[0], ...data.slice(1).split("\n")) +class MessagePacket extends Packet { + constructor(message: string) { + super("M", [message]) + } +} + +class KeyPacket extends Packet { + constructor(key: string, pressed: boolean) { + super("K", [key, pressed ? "1" : "0"]) + } +} + +class PlaceBlockPacket extends Packet { + constructor(x: number, y: number, type: string) { + super("P", [x.toString(), y.toString(), type]) + } +} + +class DestroyBlockPacket extends Packet { + constructor(x: number, y: number) { + super("D", [x.toString(), y.toString()]) + } +} + +class PositionPacket extends Packet { + constructor(x: number, y: number) { + super("X", [x.toString(), y.toString()]) + } +} + +class VelocityPacket extends Packet { + constructor(x: number, y: number) { + super("V", [x.toString(), y.toString()]) + } } class Connection { private socket: WebSocket - private on_packet: (packet: Packet) => {} - private on_close: (error: string | null) => {} + private on_packet: (packet: Packet) => void + private on_close: (error: string | null) => void constructor( - socket: WebSocket, - on_packet: (packet: Packet) => {}, - on_close: (error: string | null) => {} + address: string, + on_packet: (packet: Packet) => void, + on_close: (error: string | null) => void ) { - this.socket = socket + this.socket = new WebSocket( + "ws://"+address, + "cubic", + ) this.on_packet = on_packet this.on_close = on_close @@ -34,7 +80,7 @@ class Connection { } private _on_message(event: MessageEvent) { - this.on_packet(fromPacket(event.data)) + this.on_packet(Packet.fromString(event.data)) } private _on_close(event: CloseEvent) { diff --git a/client/src/player.ts b/client/src/player.ts index e69de29..574b92f 100644 --- a/client/src/player.ts +++ b/client/src/player.ts @@ -0,0 +1,401 @@ +class MainPlayer extends Player { + public conn: Connection | null + public walk_speed: number + public jump_speed: number + public gravity_speed: number + public controls_x: number + public controls_jump: boolean + public block_type: string | null + public all_block_types: string[] + + constructor() { + super(0.0, 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 = 0.5 + + this.controls_x = 0 + this.controls_jump = false + + this.block_type = null + this.all_block_types = [ + "normal", "normal", "normal", "normal", "normal", + "normal", "normal", "normal", "normal", "normal" + ] + } + + onConnect(name) { + this.x = 0.0 + this.y = 0.0 + this.velocity_x = 0.0 + this.velocity_y = 0.0 + + camera.x = 0.0 + camera.y = 0.0 + + chatOpened = false + chatMessages = [] + + this.name = name + this.color = "#5e6" + + blocks = [] + + this.reset() + } + + register() { + document.addEventListener("keydown", (e) => { + let key = e.code + + if (chatOpened) { + if (key == "Backspace") { + chatTyping = chatTyping.slice(0, chatTyping.length-1) + } else if (key == "Enter") { + if (chatTyping == "") { + chatOpened = false + return + } + this.sendPacket(new MessagePacket(chatTyping)) + chatTyping = "" + chatOpened = false + } else if (key == "Escape") { + chatOpened = false + } else if (e.key.length == 1) { + chatTyping += e.key + e.preventDefault() + return false + } + } else { + if (key == "KeyD") { + this.controls_x = 1 + } else if (key == "KeyA") { + this.controls_x = -1 + } else if (key == "Space") { + this.controls_jump = true + e.preventDefault() + return false + } else if (key == "KeyR") { + if (this.conn == null) { + this.forceTeleport(0, 1) + } + } else if (key == "KeyT") { + chatOpened = true + } + + if (e.key == "0") this.block_type = null + if ("123456789".includes(e.key)) this.block_type = this.all_block_types[parseInt(e.key)-1] + + if (allowed_key_to_send.includes(key)) { + this.sendPacket(new KeyPacket(key, true)) + } + + if (key == "Escape") { + this.closeConnection() + } + } + + if (key == "F3") { + debugMode = !debugMode + e.preventDefault() + return false + } + }) + + document.addEventListener("keyup", (e) => { + let key = e.code + + if ((key == "KeyD" && this.controls_x == 1) + || (key == "KeyA" && this.controls_x == -1)) { + this.controls_x = 0 + } else if (key == "Space" && this.controls_jump) { + this.controls_jump = false + } + + if (allowed_key_to_send.includes(key)) { + this.sendPacket(new KeyPacket(key, false)) + } + }) + + canvas.addEventListener("wheel", e => { + if (e.deltaY > 0) { + camera.size *= 0.5 * (e.deltaY/114) + } else { + camera.size *= 2 * (e.deltaY/-114) + } + e.preventDefault() + return false + }) + + canvas.addEventListener("mousedown", e => { + let rect = canvas.getBoundingClientRect() + let size = 16 * camera.size + let x = Math.round((e.clientX - rect.x) / size - (width / size / 2) + camera.x) + let y = Math.round((height - (e.clientY - rect.y)) / size - (height / size / 2) + camera.y) + + if (e.buttons == 2 && this.block_type != null) { + if (this.conn == null) { + placeBlock(new Block(x,y,"#555",true,this.block_type)) + } + this.sendPacket(new PlaceBlockPacket(x, y, this.block_type)) + } else if (e.buttons == 1) { + if (this.conn == null) { + removeBlock(x, y) + } + this.sendPacket(new DestroyBlockPacket(x, y)) + } + }) + } + + sendPacket(packet: Packet) { + if (this.conn != null) { + this.conn.send(packet) + } + } + + closeConnection() { + if (this.conn != null) + this.conn.close() + this.conn = null + } + + onPacket(packet: Packet) { + let packet_id = packet.getId() + let packet_data = packet.getData() + + if (packet_id == "K") { + setServerError(packet_data[0]) + this.closeConnection() + } + + if (packet_id == "N") { + this.name = packet_data[0] + } + + if (packet_id == "C") { + this.color = packet_data[0] + } + + if (packet_id == "M") { + chatMessages.unshift(...packet_data) + } + + if (packet_id == "R") { + this.velocity_x = parseFloat(packet_data[0]) - this.x + parseFloat(packet_data[2]) + this.velocity_y = parseFloat(packet_data[1]) - this.y + parseFloat(packet_data[3]) + } + + if (packet_id == "P") { + let x = parseFloat(packet_data[0]) + let y = parseFloat(packet_data[1]) + this.x = x + this.y = y + } + + if (packet_id == "V") { + let x = parseFloat(packet_data[0]) + let y = parseFloat(packet_data[1]) + this.velocity_x = x + this.velocity_y = y + } + + if (packet_id == "S") { + let speed_type = packet_data[0] + let speed = parseFloat(packet_data[1]) + + if (speed_type == "W") { + this.walk_speed = speed + } else if (speed_type == "J") { + this.jump_speed = speed + } else if (speed_type == "G") { + this.gravity_speed = speed + } + } + + if (packet_id == "W") { + for (const data of packet_data) { + let type = data[0] + let create = data[1] == "1" + let params = data.slice(2).split(",") + + if (type == "B") { + let x = parseFloat(params[0]) + let y = parseFloat(params[1]) + + if (create) { + let collides = params[2] == "1" + let type = params[3] + let color = params[4] + + let block = getBlock(x, y) + if (block != null) { + block.x = x + block.y = y + block.color = color + block.collides = collides + block.type = type + } else { + placeBlock(new Block(x,y,color,collides,type)) + } + } else { + removeBlock(x, y) + } + } else if (type == "P") { + let name = params[0] + + if (create) { + let x = parseFloat(params[1]) + let y = parseFloat(params[2]) + let vel_x = parseFloat(params[3]) + let vel_y = parseFloat(params[4]) + let color = params[5] + + let player = getPlayer(name) + if (player != null) { + player.x = x + player.y = y + player.color = color + player.velocity_x = vel_x + player.velocity_y = vel_y + } else { + placeBlock(new Player(x,y,name,color,vel_x,vel_y)) + } + } else { + removePlayer(name) + } + } + } + } + + if (packet_id == "B") { + this.all_block_types = packet_data.slice(0, 9) + this.block_type = null + } + } + + tick() { + super.tick(false) + + let vel_x = this.velocity_x + let vel_y = this.velocity_y + + this.velocity_x += this.controls_x * this.walk_speed + + if (this.controls_jump && this.on_ground) { + this.velocity_y += this.jump_speed + this.on_ground = false + } else { + this.velocity_y -= this.gravity_speed + } + + this.collide() + + ticksAlive++ + + this.sendPacket(new VelocityPacket(this.velocity_x-vel_x, this.velocity_y-vel_y)) + this.sendPacket(new PositionPacket(this.x, this.y)) + } + + render() { + super.render() + + camera.x = Math.round(lerp(camera.x, this.x, 0.075) * 1000) / 1000 + camera.y = Math.round(lerp(camera.y, this.y, 0.075) * 1000) / 1000 + } + + renderText() { + super.renderText() + + if (this.block_type != null) { + ctx.fillStyle = "#76d" + ctx.font = "15px monospace"; + ctx.fillText("selected: "+this.block_type, 0, 15); + } + + if (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); + } + + if (!chatOpened) { + ctx.fillStyle = "#22222288" + ctx.fillRect(5, height - 35, width * 0.4, 30) + + ctx.fillStyle = "#aaaaaa88" + ctx.font = "15px monospace"; + ctx.fillText("Нажмите T для чата", 15, height - 15); + } else { + ctx.fillStyle = "#22222288" + ctx.fillRect(5, height - 35, width -10, 30) + + ctx.save(); + + ctx.beginPath(); + ctx.moveTo(5, height - 35); + ctx.lineTo(width - 5 , height - 35); + ctx.lineTo(width - 5, height - 5); + ctx.lineTo(5, height - 5); + ctx.lineTo(5, height - 35); + ctx.closePath(); + + ctx.clip(); + + ctx.font = "15px monospace" + if (chatTyping.length == 0) { + ctx.fillStyle = "#aaaaaa88" + ctx.fillText("Напишите сообщение...", 15, height - 15); + } else { + ctx.fillStyle = "#ffffff" + let text_width = ctx.measureText(chatTyping).width + ctx.fillText(chatTyping, Math.min(10, width - text_width - 10), height - 15) + } + + ctx.restore(); + } + + if (chatMessages.length > 0) { + let draw_message = (message) => { + let lines = wrapText(ctx, message, message_width - 20) + let height = lines.length * 20 + 5 + 5 + + let top = message_bottom - height + + ctx.fillStyle = "#22222288" + ctx.fillRect(5, top, message_width, height) + + let y = 5 + for (let line of lines) { + ctx.fillStyle = "#ffffff" + ctx.fillText(line, 15, top+y+15); + y += 5 + 20 + } + + message_bottom -= height + 5 + } + + let message_width = width * 0.4 + let message_bottom = height - 35 - 5 + + if (chatOpened) { + chatMessages.forEach(draw_message) + } else { + draw_message(chatMessages[0]) + } + } + } +} \ No newline at end of file diff --git a/client/src/render.ts b/client/src/render.ts deleted file mode 100644 index e69de29..0000000 diff --git a/client/src/script.ts b/client/src/script.ts index 1b40e95..18e991e 100644 --- a/client/src/script.ts +++ b/client/src/script.ts @@ -4,79 +4,12 @@ const ctx = canvas.getContext("2d"); const width = 640 const height = 480 -function lerp(a: number, b: number, alpha: number): number { - return a + alpha * ( b - a ) -} - const server_ip = document.getElementById("server-ip") as HTMLInputElement const server_nick = document.getElementById("server-nick") as HTMLInputElement const connect_server = document.getElementById("connect-server") as HTMLButtonElement const server_error = document.getElementById("server-error") as HTMLSpanElement -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" -] -connect_server.onclick = () => { - let ip = server_ip.value - let nick = server_nick.value - - server_error.innerText = "" - - if (ip.length == 0) { - server_error.innerText = "введите айпи пж" - return - } - - if (nick.length == 0) { - server_error.innerText = "введите ник пж" - return - } - - if (!ip.includes(":")) { - ip += ":8000" - } - - if (typeof player.socket !== "undefined") { - player.socket.close() - } - - player.onConnect(nick) - - blocks = [] - - try { - let socket = new WebSocket( - "ws://"+ip, - "cubic", - ) - - socket.onopen = e => { - socket.send("J"+nick); - player.socket = socket - } - - socket.onmessage = e => { - player.onRecvPacket(e.data[0], e.data.slice(1).split("\n")) - } - - socket.onclose = e => { - player.socket = undefined - resetWorld() - } - - socket.onerror = () => { - server_error.innerText = "Connection closed due to error" - resetWorld() - } - } catch (exception) { - server_error.innerText = exception - } -} function wrapText(ctx: CanvasRenderingContext2D, text: string, maxWidth: number): string[] { const lines = []; @@ -102,655 +35,39 @@ function wrapText(ctx: CanvasRenderingContext2D, text: string, maxWidth: number) return lines; } -var ticksAlive = 0 - -var debugMode = false - -var camera = { - x: 0.0, - y: 0.0, - size: 1.5 +function lerp(a: number, b: number, alpha: number): number { + return a + alpha * ( b - a ) } -var chatOpened = false -var chatMessages = [] -var chatTyping = "" -type Rect = [number, number, number, number] -class Block { - public x: number - public y: number - public color: string - public collides: boolean - public type: string - - constructor( - x: number, - y: number, - color: string, - collides: boolean, - type: string, - ) { - this.x = x - this.y = y - this.color = color - this.collides = collides - this.type = type - } - - render() { - let rect = this.translate_to_camera() - if (this.is_need_render(rect)) { - ctx.fillStyle = this.color - ctx.fillRect(...rect) - } - } - - is_need_render(rect: Rect): boolean { - return rect[0] + rect[2] > 0 || rect[1] + rect[3] > 0 || rect[0] < width || rect[1] < height - } - - translate_to_camera(): Rect { - let size = camera.size * 16 - - return [ - this.x * size - size / 2 + (width / 2 - camera.x * size), - height - (this.y + 1) * size + size / 2 - (height / 2 - camera.y * size), - size, - size - ] - } - - tick() { - - } - - renderTick() { - - } - - on_collide(player: Player, x: number, y: number) { - if (x != 0) player.velocity_x = this.x + x - player.x - if (y != 0) player.velocity_y = this.y + y - player.y - } - - renderText() { - - } +function setServerError(text: string) { + server_error.innerText = text } -class Player extends Block { - public velocity_x: number - public velocity_y: number - public name: string - public on_ground: boolean +connect_server.onclick = () => { + let ip = server_ip.value + let nick = server_nick.value - constructor( - x: number, - y: number, - name: string, - color: string, - velocity_x: number, - velocity_y: number, - ) { - super(x, y, color, true, null) + setServerError("") - this.velocity_x = velocity_x - this.velocity_y = velocity_y + if (ip.length == 0) return setServerError("введите айпи пж") + if (nick.length == 0) return setServerError("введите ник пж") + if (!ip.includes(":")) ip += ":8000" - this.name = name - } - - reset() { - this.on_ground = false - } + player.closeConnection() - on_collide(player: Player, x: number, y: number) { - super.on_collide(player, x, y) - // if (x != 0) { - // player.vel_x *= 0.5 - // this.vel_x = player.vel_x - // } - // if (y != 0) { - // player.vel_y *= 0.5 - // this.vel_y = player.vel_y - // } - } - - tick(collide=true) { - this.x = Math.round(this.x * 100) / 100 - this.y = Math.round(this.y * 100) / 100 - this.velocity_x = Math.round(this.velocity_x * 100) / 100 - this.velocity_y = Math.round(this.velocity_y * 100) / 100 - - if (collide) this.collide() - } - - collide() { - this.on_ground = false - - for (const block of blocks) { - if (!block.collides) continue - - let collide_x = 0 - let collide_y = 0 - - if (this.x > block.x-1 && this.x < block.x+1) { - if (this.y > block.y && this.y + this.velocity_y - 1 < block.y) { - this.on_ground = true - collide_y = 1 - } - if (this.y < block.y && this.y + this.velocity_y > block.y - 1) collide_y = -1 - } - - if (this.y > block.y-1 && this.y < block.y+1) { - if (this.x > block.x && this.x + this.velocity_x - 1 < block.x) collide_x = 1 - if (this.x < block.x && this.x + this.velocity_x > block.x - 1) collide_x = -1 - } - - block.on_collide(this, collide_x, collide_y) - } - } - - renderTick() { - this.velocity_x *= 0.5 - this.velocity_y *= 0.5 - this.x += this.velocity_x - this.y += this.velocity_y - } - - renderText() { - super.renderText() - - let rect = this.translate_to_camera() - - if (this.is_need_render(rect)) { - ctx.fillStyle = "#ddd" - ctx.font = "15px monospace"; - let width = ctx.measureText(this.name).width - ctx.fillText(this.name,rect[0]+rect[2]/2-width/2, rect[1]-5); - } - } - - teleport(x: number, y: number) { - this.velocity_x = x - this.x - this.velocity_y = y - this.y - } - - forceTeleport(x: number, y: number) { - this.x = x - this.y = y - this.velocity_x = 0 - this.velocity_y = 0 - } + connectServer(ip, nick) } -class MainPlayer extends Player { - public socket: WebSocket | undefined - public walk_speed: number - public jump_speed: number - public gravity_speed: number - public controls_x: number - public controls_jump: boolean - public block_type: string | null - public all_block_types: string[] - constructor() { - super(0.0, 0.0, "unnamed player", "#5e6", 0, 0) - this.reset() +setInterval(tick, 1000 / 20) - this.socket = undefined - } - - reset() { - super.reset() - - this.walk_speed = 1 - this.jump_speed = 2 - this.gravity_speed = 0.5 - - this.controls_x = 0 - this.controls_jump = false - - this.block_type = null - this.all_block_types = [ - "normal", "normal", "normal", "normal", "normal", - "normal", "normal", "normal", "normal", "normal" - ] - } - - onConnect(name) { - this.x = 0.0 - this.y = 0.0 - this.velocity_x = 0.0 - this.velocity_y = 0.0 - - camera.x = 0.0 - camera.y = 0.0 - - chatOpened = false - chatMessages = [] - - this.name = name - this.color = "#5e6" - - this.reset() - } - - register() { - document.addEventListener("keydown", (e) => { - let key = e.code - - if (chatOpened) { - if (key == "Backspace") { - chatTyping = chatTyping.slice(0, chatTyping.length-1) - } else if (key == "Enter") { - if (chatTyping == "") { - chatOpened = false - return - } - this.sendPacket("M", chatTyping) - chatTyping = "" - chatOpened = false - } else if (key == "Escape") { - chatOpened = false - } else if (e.key.length == 1) { - chatTyping += e.key - e.preventDefault() - return false - } - } else { - if (key == "KeyD") { - this.controls_x = 1 - } else if (key == "KeyA") { - this.controls_x = -1 - } else if (key == "Space") { - this.controls_jump = true - e.preventDefault() - return false - } else if (key == "KeyR") { - if (typeof this.socket === "undefined") { - this.forceTeleport(0, 1) - } - } else if (key == "KeyT") { - chatOpened = true - } - - if (e.key == "0") this.block_type = null - if ("123456789".includes(e.key)) this.block_type = this.all_block_types[parseInt(e.key)-1] - - if (allowed_key_to_send.includes(key)) { - this.sendPacket("K",key,"1") - } - - if (key == "Escape") { - if (typeof this.socket !== "undefined") { - this.socket.close() - } - } - } - - if (key == "F3") { - debugMode = !debugMode - e.preventDefault() - return false - } - }) - - document.addEventListener("keyup", (e) => { - let key = e.code - - if ((key == "KeyD" && this.controls_x == 1) - || (key == "KeyA" && this.controls_x == -1)) { - this.controls_x = 0 - } else if (key == "Space" && this.controls_jump) { - this.controls_jump = false - } - - if (allowed_key_to_send.includes(key)) { - this.sendPacket("K",key,"0") - } - }) - - canvas.addEventListener("wheel", e => { - if (e.deltaY > 0) { - camera.size *= 0.5 * (e.deltaY/114) - } else { - camera.size *= 2 * (e.deltaY/-114) - } - e.preventDefault() - return false - }) - - canvas.addEventListener("mousedown", e => { - let rect = canvas.getBoundingClientRect() - let size = 16 * camera.size - let x = Math.round((e.clientX - rect.x) / size - (width / size / 2) + camera.x) - let y = Math.round((height - (e.clientY - rect.y)) / size - (height / size / 2) + camera.y) - - if (e.buttons == 2 && this.block_type != null) { - if (typeof this.socket === "undefined") { - placeBlock(new Block(x,y,"#555",true,this.block_type)) - } - this.sendPacket("P", x.toString(), y.toString(), this.block_type) - } else if (e.buttons == 1) { - if (typeof this.socket === "undefined") { - removeBlock(x, y) - } - this.sendPacket("D", x.toString(), y.toString()) - } - }) - } - - onRecvPacket(packet_id: string, packet_data: string[]) { - if (packet_id == "K") { - server_error.innerText = packet_data[0] - this.socket.close() - this.socket = null - } - - if (packet_id == "N") { - this.name = packet_data[0] - } - - if (packet_id == "C") { - this.color = packet_data[0] - } - - if (packet_id == "M") { - chatMessages.unshift(...packet_data) - } - - if (packet_id == "R") { - this.velocity_x = parseFloat(packet_data[0]) - this.x + parseFloat(packet_data[2]) - this.velocity_y = parseFloat(packet_data[1]) - this.y + parseFloat(packet_data[3]) - } - - if (packet_id == "P") { - let x = parseFloat(packet_data[0]) - let y = parseFloat(packet_data[1]) - this.x = x - this.y = y - } - - if (packet_id == "V") { - let x = parseFloat(packet_data[0]) - let y = parseFloat(packet_data[1]) - this.velocity_x = x - this.velocity_y = y - } - - if (packet_id == "S") { - let speed_type = packet_data[0] - let speed = parseFloat(packet_data[1]) - - if (speed_type == "W") { - this.walk_speed = speed - } else if (speed_type == "J") { - this.jump_speed = speed - } else if (speed_type == "G") { - this.gravity_speed = speed - } - } - - if (packet_id == "W") { - for (const data of packet_data) { - let type = data[0] - let create = data[1] == "1" - let params = data.slice(2).split(",") - - if (type == "B") { - let x = parseFloat(params[0]) - let y = parseFloat(params[1]) - - if (create) { - let collides = params[2] == "1" - let type = params[3] - let color = params[4] - - let block = getBlock(x, y) - if (block != null) { - block.x = x - block.y = y - block.color = color - block.collides = collides - block.type = type - } else { - placeBlock(new Block(x,y,color,collides,type)) - } - } else { - removeBlock(x, y) - } - } else if (type == "P") { - let name = params[0] - - if (create) { - let x = parseFloat(params[1]) - let y = parseFloat(params[2]) - let vel_x = parseFloat(params[3]) - let vel_y = parseFloat(params[4]) - let color = params[5] - - let player = getPlayer(name) - if (player != null) { - player.x = x - player.y = y - player.color = color - player.velocity_x = vel_x - player.velocity_y = vel_y - } else { - placeBlock(new Player(x,y,name,color,vel_x,vel_y)) - } - } else { - removePlayer(name) - } - } - } - } - - if (packet_id == "B") { - this.all_block_types = packet_data.slice(0, 9) - this.block_type = null - } - } - - sendPacket(id: string, ...params: string[]) { - if (typeof this.socket === "undefined") return - this.socket.send(id+params.join("\n")) - } - - sendVelPacket(x, y) { - if (x == 0 && y == 0) return - this.sendPacket("V", x, y) - } - - tick() { - super.tick(false) - - let vel_x = this.velocity_x - let vel_y = this.velocity_y - - this.velocity_x += this.controls_x * this.walk_speed - - if (this.controls_jump && this.on_ground) { - this.velocity_y += this.jump_speed - this.on_ground = false - } else { - this.velocity_y -= this.gravity_speed - } - - this.collide() - - ticksAlive++ - - this.sendVelPacket(this.velocity_x-vel_x, this.velocity_y-vel_y) - } - - render() { - super.render() - - camera.x = Math.round(lerp(camera.x, this.x, 0.075) * 1000) / 1000 - camera.y = Math.round(lerp(camera.y, this.y, 0.075) * 1000) / 1000 - } - - renderText() { - super.renderText() - - if (this.block_type != null) { - ctx.fillStyle = "#76d" - ctx.font = "15px monospace"; - ctx.fillText("selected: "+this.block_type, 0, 15); - } - - if (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); - } - - if (!chatOpened) { - ctx.fillStyle = "#22222288" - ctx.fillRect(5, height - 35, width * 0.4, 30) - - ctx.fillStyle = "#aaaaaa88" - ctx.font = "15px monospace"; - ctx.fillText("Нажмите T для чата", 15, height - 15); - } else { - ctx.fillStyle = "#22222288" - ctx.fillRect(5, height - 35, width -10, 30) - - ctx.save(); - - ctx.beginPath(); - ctx.moveTo(5, height - 35); - ctx.lineTo(width - 5 , height - 35); - ctx.lineTo(width - 5, height - 5); - ctx.lineTo(5, height - 5); - ctx.lineTo(5, height - 35); - ctx.closePath(); - - ctx.clip(); - - ctx.font = "15px monospace" - if (chatTyping.length == 0) { - ctx.fillStyle = "#aaaaaa88" - ctx.fillText("Напишите сообщение...", 15, height - 15); - } else { - ctx.fillStyle = "#ffffff" - let text_width = ctx.measureText(chatTyping).width - ctx.fillText(chatTyping, Math.min(10, width - text_width - 10), height - 15) - } - - ctx.restore(); - } - - if (chatMessages.length > 0) { - let draw_message = (message) => { - let lines = wrapText(ctx, message, message_width - 20) - let height = lines.length * 20 + 5 + 5 - - let top = message_bottom - height - - ctx.fillStyle = "#22222288" - ctx.fillRect(5, top, message_width, height) - - let y = 5 - for (let line of lines) { - ctx.fillStyle = "#ffffff" - ctx.fillText(line, 15, top+y+15); - y += 5 + 20 - } - - message_bottom -= height + 5 - } - - let message_width = width * 0.4 - let message_bottom = height - 35 - 5 - - if (chatOpened) { - chatMessages.forEach(draw_message) - } else { - draw_message(chatMessages[0]) - } - } - } -} - -var player = new MainPlayer() -player.register() - -var blocks: Block[] = [] - -function resetWorld() { - player.onConnect("unnamed player") - blocks = [] - blocks.push(new Block(-1, -1, "#555", true, "normal")); - blocks.push(new Block(0, -1, "#a67", true, "spawn")); - blocks.push(new Block(1, -1, "#555", true, "normal")); -} - -resetWorld() - -function getBlock(x: number, y: number): Block { - let value = blocks.find(o => !(o instanceof Player) && o.x == x && o.y == y) - if (typeof value === "undefined") { - return null - } else { - return value - } -} - -function placeBlock(block: Block) { - blocks.push(block); -} - -function removeBlock(x: number, y: number) { - blocks = blocks.filter(o => o instanceof Player || o.x != x || o.y != y) -} - -function getPlayer(name: string): Player | null { - let value = blocks.find(o => o instanceof Player && o.name == name) as Player - if (typeof value === "undefined") { - return null - } else { - return value - } -} - -function removePlayer(name: string) { - blocks = blocks.filter(o => !(o instanceof Player) || o.name != name) -} - -setInterval(() => { - for (const block of blocks) - block.tick() - player.tick() -}, 1000 / 20) - -setInterval(() => { - for (const block of blocks) - block.renderTick() - player.renderTick() -}, 1000 / 60) +setInterval(renderTick, 1000 / 60) let renderTimer = () => { - ctx.fillStyle = "#333"; - ctx.fillRect(0, 0, width, height); - - for (const block of blocks) - block.render() - for (const block of blocks) - block.renderText() - player.render() - player.renderText() - + render() requestAnimationFrame(renderTimer) }