import createDebug from "debug"; import type { DespawnPartPacket, MessagePacket, Packet, PartPositionsPacket, PlanetPositionsPacket, PlayerLeavePacket, PlayerListPacket, SaveDataPacket, SaveEligibilityPacket, SpawnPartPacket, SpawnPlayerPacket, } from "./protocol.ts"; import { MessageType, PacketType } from "./protocol.ts"; import { appendPacket } from "./packet_ui.ts"; import { global } from "./globals.ts"; import { startRender } from "./rendering.ts"; import { ButtonType } from "./protocol.ts"; import type Chatbox from "./components/Chatbox.svelte"; import { part_texture_url } from "./textures.js"; import * as PIXI from "pixi.js"; const logger = createDebug("hub"); let hud = { x: 0, y: 0, next_poll: 0, }; export interface ClientHub { socket: WebSocket; } export function sendPacket(client: ClientHub, packet: Packet) { client.socket.send(JSON.stringify(packet)); appendPacket(packet); } export async function hub_connect( url: string, username: string, chatbox: Chatbox, velocity: HTMLSpanElement, x_pos: HTMLSpanElement, y_pos: HTMLSpanElement, ): Promise { logger("connecting to client hub at " + url); let ws = new WebSocket(url); ws.onerror = (e) => { console.error(e); throw e; }; ws.onopen = () => { logger("connected to client hub, sending username and auth details"); let client: ClientHub = { socket: ws, }; global.client = client; let packet: Packet = { t: PacketType.ClientLogin, c: { username, save: window.localStorage.getItem("save"), }, }; sendPacket(client, packet); // input document.onkeydown = (e) => { // currently, input packet is sent on any key down. fix that if (e.key == "ArrowUp" || e.key == "w") { global.up = true; } if (e.key == "ArrowDown" || e.key == "s") { global.down = true; } if (e.key == "ArrowLeft" || e.key == "a") { global.left = true; } if (e.key == "ArrowRight" || e.key == "d") { global.right = true; } let input_packet: Packet = { t: PacketType.PlayerInput, c: { up: global.up, down: global.down, left: global.left, right: global.right, }, }; sendPacket(client, input_packet); }; document.onkeyup = (e) => { if (e.key == "ArrowUp" || e.key == "w") { global.up = false; } if (e.key == "ArrowDown" || e.key == "s") { global.down = false; } if (e.key == "ArrowLeft" || e.key == "a") { global.left = false; } if (e.key == "ArrowRight" || e.key == "d") { global.right = false; } let input_packet: Packet = { t: PacketType.PlayerInput, c: { up: global.up, down: global.down, left: global.left, right: global.right, }, }; sendPacket(client, input_packet); }; document.onmousedown = (e) => { if (global.me !== null) { let me_transform = global.parts_map.get(global.me?.part_id)!.transform; let x = e.clientX - window.innerWidth / 2 + me_transform.x; let y = e.clientY - window.innerHeight / 2 + me_transform.y; let button: ButtonType; if (e.button == 0) { button = ButtonType.Left; } else if (e.button == 1) { button = ButtonType.Middle; } else if (e.button == 2) { button = ButtonType.Right; } let packet: Packet = { t: PacketType.PlayerMouseInput, c: { x: x, y: y, button: button!, released: false, }, }; sendPacket(client, packet); } }; document.onmouseup = (e) => { if (global.me !== null) { let me_transform = global.parts_map.get(global.me?.part_id)!.transform; let x = e.clientX - window.innerWidth / 2 + me_transform.x; let y = e.clientY - window.innerHeight / 2 + me_transform.y; let button: ButtonType; if (e.button == 0) { button = ButtonType.Left; } else if (e.button == 1) { button = ButtonType.Middle; } else if (e.button == 2) { button = ButtonType.Right; } let packet: Packet = { t: PacketType.PlayerMouseInput, c: { x: x, y: y, button: button!, released: true, }, }; sendPacket(client, packet); } }; ws.onmessage = (e) => { let packet: Packet = JSON.parse(e.data); appendPacket(packet); if (packet.t == PacketType.SpawnPlayer) { let p = packet.c; if (p.username === username) { global.me = { username: p.username, part_id: p.id, }; logger(`client spawned (username=${p.username} part_id=${p.id})`); startRender(); } else { global.players_map.set(p.id, p.username); global.inverse_players_map.set(p.username, p.id); logger(`player joined (username=${p.username} part_id=${p.id})`); } } else if (packet.t == PacketType.PlayerList) { let p = packet.c; for (let i = 0; i < p.players.length; i++) { global.players_map.set(p.players[i][0], p.players[i][1]); global.inverse_players_map.set(p.players[i][1], p.players[i][0]); } logger(`added ${p.players.length} existing players to player list`); } else if (packet.t == PacketType.PlanetPositions) { let p = packet.c; for (let i = 0; i < p.planets.length; i++) { global.planets_map.set(p.planets[i][0], p.planets[i][1]); } } else if (packet.t == PacketType.PartPositions) { let p = packet.c; for (let i = 0; i < p.parts.length; i++) { let id = p.parts[i][0]; let new_part = p.parts[i][1]; let old_part = global.parts_map.get(id); if ( old_part !== undefined && (old_part.part_type !== new_part.part_type || old_part.flags !== new_part.flags) ) { global.rendering?.part_sprites_need_texture_change.push(id); } global.parts_map.set(id, new_part); if (id === global.me?.part_id) { if (hud.next_poll <= 0) { velocity!.innerText = Math.round( Math.sqrt( Math.pow(Math.abs(new_part.transform.x - hud.x), 2) + Math.pow(Math.abs(new_part.transform.y - hud.y), 2), ), ).toString(); hud.next_poll = 30; hud.x = new_part.transform.x; hud.y = new_part.transform.y; x_pos!.innerText = Math.round(new_part.transform.x).toString(); y_pos!.innerText = Math.round(new_part.transform.y).toString(); } hud.next_poll--; } } } else if (packet.t == PacketType.SpawnPart) { let p = packet.c; let id = p.id; let part = p.part; global.parts_map.set(id, part); logger(`spawn part`); let part_sprite = PIXI.Sprite.from( part_texture_url(part.part_type, p.part.flags.attached), ); global.rendering!.part_sprite_map.set(id, part_sprite); global.rendering!.app.stage.addChild(part_sprite); } else if (packet.t == PacketType.DespawnPart) { let p = packet.c; let id = p.id; let part_sprite = global.rendering!.part_sprite_map.get(id)!; global.rendering!.app.stage.removeChild(part_sprite); global.rendering!.part_sprite_map.delete(id); global.parts_map.delete(id); } else if (packet.t == PacketType.PlayerLeave) { let p = packet.c; let username = global.players_map.get(p.id)!; global.inverse_players_map.delete(username); global.players_map.delete(p.id); logger(`player removed (id=${p.id})`); } else if (packet.t == PacketType.Message) { let p = packet.c; logger( `message type=${p.message_type} actor=${p.actor} content=${p.content}`, ); if (p.message_type == MessageType.Server) { chatbox.addMessage("server-message", `[SERVER] ${p.content}`); } else if (p.message_type == MessageType.Chat) { chatbox.addMessage("global-message", `${p.actor}: ${p.content}`); } else if (p.message_type == MessageType.Direct) { // actor is who sent the message. destination is not included in this packet if (p.actor === global.me!.username) { // skip (shown above) } else { chatbox.addMessage( "direct-message", `${p.actor} -> you: ${p.content}`, ); } } else { chatbox.addMessage("server-error", `${p.content}`); } } else if (packet.t == PacketType.SaveEligibility) { let p = packet.c; chatbox.addMessage( "server-message", p.eligible ? "Eligible to save. Run the .save command to save your progress!" : "No longer eligible to save.", ); global.saveEligible = p.eligible; } else if (packet.t == PacketType.SaveData) { let p = packet.c; // request save // receive save data from server // upload save file to storage // done chatbox.addMessage( "server-message", "[Save][2/3] Received save data from server!", ); logger(`save file received save=${p.payload}`); chatbox.addMessage( "server-message", "[Save][3/3] Uploading save file to storage...", ); window.localStorage.setItem("save", p.payload); chatbox.addMessage("server-message", "[Save] Game saved successfully!"); } else { logger(`unrecognized packet type ${packet.t}`); } }; return client; }; return null; }