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<ClientHub | null> {
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 = <SpawnPlayerPacket>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 = <PlayerListPacket>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 = <PlanetPositionsPacket>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 = <PartPositionsPacket>packet.c;
for (let i = 0; i < p.parts.length; i++) {
let id = p.parts[i][0];
let new_part = p.parts[i][1];
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 = <SpawnPartPacket>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));
global.rendering!.part_sprite_map.set(id, part_sprite);
global.rendering!.app.stage.addChild(part_sprite);
} else if (packet.t == PacketType.DespawnPart) {
let p = <DespawnPartPacket>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 = <PlayerLeavePacket>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 = <MessagePacket>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 = <SaveEligibilityPacket>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 = <SaveDataPacket>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;
}