import {Logger} from "./logger";
import {
MessageC2SGoodbye, MessageC2SGoodbye_packetInfo,
MessageC2SHello,
MessageC2SHello_packetInfo, MessageC2SPing,
MessageC2SPing_packetInfo
} from "./protocol/message_c2s";
import {State} from "./protocol/state";
import {decode, encode} from "./serde";
import {
MessageS2CChat,
MessageS2CChat_packetInfo,
MessageS2CGoodbye,
MessageS2CGoodbye_packetInfo,
MessageS2CHello,
MessageS2CHello_packetInfo,
MessageS2CModulesUpdate,
MessageS2CModulesUpdate_packetInfo,
MessageS2CPlanetData, MessageS2CPlanetData_packetInfo,
MessageS2CPlayersUpdate,
MessageS2CPlayersUpdate_packetInfo,
MessageS2CPong_packetInfo
} from "./protocol/message_s2c";
import {GoodbyeReason} from "./protocol/goodbye_reason";
import {global} from "./index";
const logger = new Logger("Gateway");
export interface GatewayClient {
state: State;
socket: WebSocket;
username: string | null;
version: number | null;
// @ts-ignore
ping_timeout: Timeout | null; // i am aware that these types dont exist
// @ts-ignore
ping_timeout_left: Timeout; // its fine
}
export async function gateway_connect(gateway_url: string, username: string): Promise<GatewayClient> {
logger.info("FAST CONNECT - Connecting to gateway socket at " + gateway_url);
let ws = await _websocket_connect(gateway_url);
logger.debug("[fastconnect] connected to gateway, performing handshake with server");
let client: GatewayClient = {
state: State.Handshake,
socket: ws,
username: null,
version: null,
ping_timeout: null,
ping_timeout_left: null
};
let ping_timeout_fn = () => {
logger.error("Server didn't send back pong in time.");
let cya = MessageC2SGoodbye.encode({reason: GoodbyeReason.PingPongTimeout}).finish();
client.socket.send(encode(MessageC2SGoodbye_packetInfo.type, cya));
client.state = State.Handshake;
client.username = null;
client.version = null;
client.socket.close();
}
client.ping_timeout_left = setTimeout(ping_timeout_fn, 10 * 1000);
let ping_fn = () => {
let ping = MessageC2SPing.encode({}).finish();
client.socket.send(encode(MessageC2SPing_packetInfo.type, ping));
setTimeout(ping_fn, 5 * 1000);
}
client.ping_timeout = setTimeout(ping_fn, 5 * 1000);
let handshake_start_msg;
if (global.can_beam_out) {
handshake_start_msg = MessageC2SHello.encode({
version: 4,
requestedUsername: username,
nextState: State.Play,
user: window.localStorage.getItem("user")!,
token: window.localStorage.getItem("token")!
}).finish();
} else {
handshake_start_msg = MessageC2SHello.encode({
version: 4,
requestedUsername: username,
nextState: State.Play,
// @ts-ignore
user: "",//null,
// @ts-ignore
token: ""//null
}).finish();
}
client.socket.send(encode(MessageC2SHello_packetInfo.type, handshake_start_msg));
client.socket.addEventListener('message', async (msg) => {
let pkt_info = decode(new Uint8Array(await msg.data.arrayBuffer()));
let pkt_id = pkt_info[0];
let pkt_data = pkt_info[1];
if (pkt_id == 0) {
// not a real message, skip
return;
}
if (client.state == State.Handshake) {
if (pkt_id == MessageS2CHello_packetInfo.type) {
let pkt = MessageS2CHello.decode(pkt_data);
logger.info(`FAST CONNECT - Handshake finished with server with protocol v${pkt.version}, assigned username ${pkt.givenUsername}, switching to state ${pkt.nextState}`);
client.state = pkt.nextState;
client.username = pkt.givenUsername;
client.version = pkt.version;
} else if (pkt_id == MessageS2CGoodbye_packetInfo.type) {
let pkt = MessageS2CGoodbye.decode(pkt_data);
logger.error(`Disconnected by server. Reason: ${pkt.reason}`);
client.state = State.Handshake;
client.username = null;
client.version = null;
throw "Disconnected by server";
} else {
logger.warn(`server sent unexpected packet ${pkt_id} for state Handshake`);
}
} else if (client.state == State.Play) {
if (pkt_id == MessageS2CGoodbye_packetInfo.type) {
let pkt = MessageS2CGoodbye.decode(pkt_data);
logger.error(`Disconnected by server. Reason: ${pkt.reason}`);
client.state = State.Handshake;
client.username = null;
client.version = null;
throw "Disconnected by server";
} else if (pkt_id == MessageS2CChat_packetInfo.type) {
let pkt = MessageS2CChat.decode(pkt_data);
logger.info(`CHAT: [${pkt.from}] ${pkt.message}`);
} else if (pkt_id == MessageS2CPong_packetInfo.type) {
clearTimeout(client.ping_timeout_left);
client.ping_timeout_left = setTimeout(ping_timeout_fn, 10 * 1000);
} else if (pkt_id == MessageS2CPlayersUpdate_packetInfo.type) {
let pkt = MessageS2CPlayersUpdate.decode(pkt_data);
global.players = pkt.players;
for (let i = 0; i < pkt.players.length; i++) {
if (pkt.players[i].username == client.username) {
if (global.me !== null) {
let x_vel = (global.me.x - pkt.players[i].x) / (1 / 20);
let y_vel = (global.me.y - pkt.players[i].y) / (1 / 20);
let total_vel = Math.sqrt(x_vel * x_vel + y_vel * y_vel);
global.x_vel = x_vel;
global.y_vel = y_vel;
global.velocity = total_vel;
// calc theta
global.direction_radians = Math.atan(global.y_vel / global.x_vel);
}
global.me = pkt.players[i];
}
}
} else if (pkt_id == MessageS2CPlanetData_packetInfo.type) {
let pkt = MessageS2CPlanetData.decode(pkt_data);
global.planets = pkt.planets;
} else if (pkt_id == MessageS2CModulesUpdate_packetInfo.type) {
let pkt = MessageS2CModulesUpdate.decode(pkt_data);
global.modules = pkt.modules;
} else {
logger.warn(`server sent unexpected packet ${pkt_id} for state Play`);
}
}
});
return client;
}
let socket: WebSocket | undefined = undefined;
function _websocket_connect(url: string): Promise<WebSocket> {
if (socket && socket.readyState < 2) {
// reuse socket connection
return Promise.resolve(socket);
}
return new Promise((resolve, reject) => {
socket = new WebSocket(url);
socket.onopen = () => {
// @ts-ignore if here, guaranteed that `socket` != undefined
resolve(socket);
}
socket.onerror = (err) => {
reject(err);
}
});
}