import {Logger, logSetup} from "./logger";
import {AttachedModule, gateway_connect, GatewayClient} from "./gateway";
import {Player} from "./protocol/player";
import {Planet, PlanetType} from "./protocol/planet";
import {Module, ModuleType} from "./protocol/module";
import {
MessageC2SAuthenticateAndBeamOut,
MessageC2SAuthenticateAndBeamOut_packetInfo,
MessageC2SInput,
MessageC2SInput_packetInfo,
MessageC2SModuleDetach,
MessageC2SModuleDetach_packetInfo,
MessageC2SModuleGrabBegin,
MessageC2SModuleGrabBegin_packetInfo,
MessageC2SModuleGrabEnd,
MessageC2SModuleGrabEnd_packetInfo,
MessageC2SMouseInput,
MessageC2SMouseInput_packetInfo
} from "./protocol/message_c2s";
import {encode} from "./serde";
import {InputType} from "./protocol/input";
import {createParticle, tickAndDrawParticles} from "./particle";
logSetup();
const logger = new Logger("client");
export interface GlobalData {
client: GatewayClient | null,
players: Player[],
planets: Planet[],
modules: Module[],
me: Player | null,
canvas: HTMLCanvasElement,
spritesheet_img: HTMLImageElement | null,
spritesheet: object | null,
context: CanvasRenderingContext2D,
keys: Keys,
velocity: number,
x_vel: number,
y_vel: number
can_beam_out: boolean,
direction_radians: number,
mouse_x: number,
mouse_y: number,
tree: Map<number, AttachedModule>,
clicked: number | null,
}
export interface Keys {
up: boolean,
left: boolean,
down: boolean,
right: boolean
}
export const global: GlobalData = {
client: null,
players: [],
planets: [],
modules: [],
me: null,
canvas: <HTMLCanvasElement>document.getElementById("canvas"),
spritesheet_img: null,
spritesheet: null,
context: <CanvasRenderingContext2D>(<HTMLCanvasElement>document.getElementById("canvas")).getContext("2d"),
keys: {
up: false,
down: false,
left: false,
right: false
},
velocity: 0,
x_vel: 0,
y_vel: 0,
can_beam_out: false,
direction_radians: 0,
mouse_x: 0,
mouse_y: 0,
tree: new Map<number, AttachedModule>(),
clicked: null,
}
async function client_main(server: string, username: string, texture_quality: string) {
logger.info("StarKingdoms client - starting");
if (window.localStorage.getItem("token") !== null && window.localStorage.getItem("user") !== null) {
global.can_beam_out = true;
document.getElementById("beamout")!.style.setProperty("display", "block");
document.getElementById("beamout")!.addEventListener("click", () => {
let msg = MessageC2SAuthenticateAndBeamOut.encode({
userId: window.localStorage.getItem("user")!,
token: window.localStorage.getItem("token")!
}).finish();
global.client?.socket.send(encode(MessageC2SAuthenticateAndBeamOut_packetInfo.type, msg));
})
}
logger.info("Loading textures");
let spritesheet_url = `/assets/dist/spritesheet-${texture_quality}.png`;
let spritesheet_data_url = `/assets/dist/spritesheet-${texture_quality}.json`;
let load_textures = new Promise<void>(async (resolve) => {
const image_promise: Promise<HTMLImageElement> = new Promise((resolve, reject) => {
const image = document.createElement("img");
image.src = spritesheet_url;
image.onload = () => { resolve(image); }
image.onerror = err => reject(err);
});
const dat_promise: Promise<Object> = fetch(spritesheet_data_url).then(res => res.json());
let image = await image_promise;
let data = await dat_promise;
global.spritesheet_img = image;
global.spritesheet = data;
resolve();
});
await load_textures;
logger.info("Starting the renderer");
//let sprite = PIXI.Sprite.from(global.spritesheet?.textures["hearty.png"]);
global.client = await gateway_connect(server, username);
global.canvas.width = window.innerWidth;
global.canvas.height = window.innerHeight;
window.onresize = () => {
global.canvas.width = window.innerWidth;
global.canvas.height = window.innerHeight;
}
global.canvas.style.setProperty("background-image", `url("/assets/final/${texture_quality}/starfield.png")`);
let canvas = document.getElementById("canvas")!;
let thruster_counter = 0;
document.onmousedown = (e) => {
// convert screenspace to worldspace
if (global.me !== null) {
let worldX = e.clientX - window.innerWidth / 2 + global.me?.x;
let worldY = e.clientY - window.innerHeight / 2 + global.me?.y;
let button: InputType;
if (e.button == 0) {
button = InputType.Left;
} else if (e.button == 1) {
button = InputType.Middle;
} else if (e.button == 2) {
button = InputType.Right;
}
for (let i = 0; i < global.modules.length; i++) {
if (global.modules[i].moduleType == ModuleType.LandingThrusterSuspension) {
console.log("hi");
continue;
}
let relativeX = global.modules[i].x - worldX;
let relativeY = global.modules[i].y - worldY;
let rot = -global.modules[i].rotation;
relativeX = relativeX*Math.cos(rot) - relativeY*Math.sin(rot);
relativeY = relativeX*Math.sin(rot) + relativeY*Math.cos(rot);
let bound = [-25, 25, -25, 25];
if (global.modules[i].moduleType == ModuleType.Cargo) {
bound = [-18.75, 18.75, -25, 21.875];
}
if (bound[0] < relativeX && relativeX < bound[1]) {
if (bound[2] < relativeY && relativeY < bound[3]) {
let msg = MessageC2SModuleGrabBegin.encode({
moduleId: global.modules[i].id,
worldposX: worldX,
worldposY: worldY,
}).finish();
global.clicked = global.modules[i].id;
global.client?.socket.send(encode(MessageC2SModuleGrabBegin_packetInfo.type, msg));
}
}
}
global.tree.forEach((value: AttachedModule, key: number) => {
if (value.module_type == ModuleType.LandingThrusterSuspension) {
console.log("hi");
return;
}
let relativeX = value.x - worldX;
let relativeY = value.y - worldY;
let rot = -value.rotation;
let adjustedX = relativeX*Math.cos(rot) - relativeY*Math.sin(rot);
let adjustedY = relativeX*Math.sin(rot) + relativeY*Math.cos(rot);
let bound = [-25, 25, -25, 25];
if (value.module_type == ModuleType.Cargo) {
bound = [-18.75, 18.75, -25, 21.875];
}
if (bound[0] < adjustedX && adjustedX < bound[1]) {
if (bound[2] < adjustedY && adjustedY < bound[3]) {
let msg = MessageC2SModuleDetach.encode({
moduleId: key,
}).finish();
global.client?.socket.send(encode(MessageC2SModuleDetach_packetInfo.type, msg));
}
}
});
let msg = MessageC2SMouseInput.encode({
worldposX: worldX,
worldposY: worldY,
released: false,
button: button!
}).finish();
global.client?.socket.send(encode(MessageC2SMouseInput_packetInfo.type, msg))
}
}
document.onmouseup = (e) => {
// convert screenspace to worldspace
if (global.me !== null) {
let worldX = e.clientX - window.innerWidth / 2 + global.me?.x;
let worldY = e.clientY - window.innerHeight / 2 + global.me?.y;
let button: InputType;
if (e.button == 0) {
button = InputType.Left;
} else if (e.button == 1) {
button = InputType.Middle;
} else if (e.button == 2) {
button = InputType.Right;
}
let msg = MessageC2SMouseInput.encode({
worldposX: worldX,
worldposY: worldY,
released: true,
button: button!
}).finish();
for (let i = 0; i < global.modules.length; i++) {
if(global.clicked === global.modules[i].id) {
let msg = MessageC2SModuleGrabEnd.encode({
moduleId: global.modules[i].id,
worldposX: worldX,
worldposY: worldY,
}).finish();
global.client?.socket.send(encode(MessageC2SModuleGrabEnd_packetInfo.type, msg))
}
}
global.clicked = null;
global.client?.socket.send(encode(MessageC2SMouseInput_packetInfo.type, msg))
}
}
document.onmousemove = (e) => {
let canvasLeft = canvas.offsetLeft + canvas.clientLeft;
let canvasTop = canvas.offsetTop + canvas.clientTop;
global.mouse_x = e.pageX - canvasLeft;
global.mouse_y = e.pageY - canvasTop;
}
document.onkeydown = (e) => {
if (e.code == "ArrowLeft" || e.code == "KeyA") { // arrow-left
global.keys.left = true;
} else if (e.code == "ArrowRight" || e.code == "KeyD") { // arrow-right
global.keys.right = true;
} else if (e.code == "ArrowUp" || e.code == "KeyW") { // arrow-up
global.keys.up = true;
} else if (e.code == "ArrowDown" || e.code == "KeyS") { // arrow-down
global.keys.down = true;
}
let msg = MessageC2SInput.encode({
upPressed: global.keys.up,
downPressed: global.keys.down,
leftPressed: global.keys.left,
rightPressed: global.keys.right
}).finish();
global.client?.socket.send(encode(MessageC2SInput_packetInfo.type, msg));
}
document.onkeyup = (e) => {
if (e.code == "ArrowLeft" || e.code == "KeyA") { // arrow-left
global.keys.left = false;
} else if (e.code == "ArrowRight" || e.code == "KeyD") { // arrow-right
global.keys.right = false;
} else if (e.code == "ArrowUp" || e.code == "KeyW") { // arrow-up
global.keys.up = false;
} else if (e.code == "ArrowDown" || e.code == "KeyS") { // arrow-down
global.keys.down = false;
}
let msg = MessageC2SInput.encode({
upPressed: global.keys.up,
downPressed: global.keys.down,
leftPressed: global.keys.left,
rightPressed: global.keys.right
}).finish();
global.client?.socket.send(encode(MessageC2SInput_packetInfo.type, msg));
}
let t = performance.now();
let delta = 0.0;
let render = (newT: DOMHighResTimeStamp) => {
delta = newT - t;
t = newT;
let viewer_size_x = global.canvas.width;
let viewer_size_y = global.canvas.height;
global.canvas.style.setProperty("background-position", `${-global.me?.x!/5}px ${-global.me?.y!/5}px`);
global.context.setTransform(1, 0, 0, 1, 0, 0);
global.context.clearRect(0, 0, viewer_size_x, viewer_size_y);
// *dont* translate the camera. we're movign everything else around us. cameracentrism.
// only translation will be to center our core module.
global.context.translate(viewer_size_x / 2, viewer_size_y / 2);
global.context.save();
global.context.rotate(global.direction_radians + Math.PI);
// @ts-ignore
let indic_tex = global.spritesheet!["frames"]["trackindicator.png"];
global.context.drawImage(global.spritesheet_img!, indic_tex.frame.x,
indic_tex.frame.y+1,
indic_tex.frame.w-1,
indic_tex.frame.h-2,
-indic_tex.frame.w/2,
-indic_tex.frame.h/2,
indic_tex.frame.w,
indic_tex.frame.h
);
global.context.restore();
if (global.me !== null) {
document.getElementById("pos")!.innerText = `Position: ${Math.trunc(global.me.x)}, ${Math.trunc(global.me.y)}`;
}
document.getElementById("vel")!.innerText = `Velocity: ${Math.trunc(global.velocity)}`;
document.getElementById("vel-dir")!.innerText = `Track Angle: ${global.direction_radians}`
for (let i = 0; i < global.planets.length; i++) {
let planet = global.planets[i];
// @ts-ignore
let tex = global.spritesheet!["frames"][planet_type_to_tex_id(planet.planetType)];
global.context.drawImage(global.spritesheet_img!,
tex.frame.x, // sx
tex.frame.y, // sy
tex.frame.w-1, // sw
tex.frame.h-1, // sh
(planet.x - planet.radius - global.me?.x!), // dx
(planet.y - planet.radius - global.me?.y!), // dy
planet.radius * 2, // dw
planet.radius * 2); // dh
if (planet.planetType == PlanetType.Moon) {
if (global.me !== null) {
global.context.beginPath();
global.context.strokeStyle = "gray";
global.context.lineWidth = 5;
global.context.moveTo(global.me!.x - global.me!.x, global.me!.y - global.me!.y);
global.context.lineTo(planet.x - global.me!.x, planet.y - global.me!.y);
global.context.stroke();
document.getElementById("pos-moon")!.innerText = `Relative to Moon: ${Math.trunc(global.me!.x - planet.x)}, ${Math.trunc(global.me!.y - planet.y)}`
}
} else if (planet.planetType == PlanetType.Earth) {
if (global.me !== null) {
global.context.beginPath();
global.context.strokeStyle = "limegreen";
global.context.lineWidth = 5;
global.context.moveTo(global.me!.x - global.me!.x, global.me!.y - global.me!.y);
global.context.lineTo(planet.x - global.me!.x, planet.y - global.me!.y);
global.context.stroke();
}
} else if (planet.planetType == PlanetType.Mars) {
if (global.me !== null) {
global.context.beginPath();
global.context.strokeStyle = "orange";
global.context.lineWidth = 5;
global.context.moveTo(global.me!.x - global.me!.x, global.me!.y - global.me!.y);
global.context.lineTo(planet.x - global.me!.x, planet.y - global.me!.y);
global.context.stroke();
}
}
}
for (let i = 0; i < global.modules.length; i++) {
if (global.me !== null) {
let module = global.modules[i];
// @ts-ignore
let tex = global.spritesheet!["frames"][module_type_to_tex_id(module.moduleType, (module.flags & 1) != 0)];
global.context.save();
// x_{screen} = x_{world} - player_{x_{world}}
// x_{world} = x_{screen} + player_{x_{world}}
global.context.translate(module.x - global.me!.x, module.y - global.me!.y);
global.context.rotate(module.rotation);
global.context.drawImage(global.spritesheet_img!,
tex.frame.x,
tex.frame.y,
tex.frame.w-1,
tex.frame.h-1,
-25,
-25, 50, 50);
global.context.restore();
if (global.clicked == module.id) {
global.context.save();
global.context.translate(global.mouse_x - window.innerWidth/2,
global.mouse_y - window.innerHeight/2);
global.context.rotate(module.rotation);
global.context.globalAlpha = 0.6;
global.context.drawImage(global.spritesheet_img!,
tex.frame.x,
tex.frame.y,
tex.frame.w-1,
tex.frame.h-1,
-25,
-25, 50, 50);
global.context.globalAlpha = 1.0;
global.context.restore();
}
}
}
global.tree.forEach((value: AttachedModule, _key: number) => {
if (global.me !== null) {
// @ts-ignore
let tex = global.spritesheet!["frames"][module_type_to_tex_id(value.module_type, true)];
global.context.save();
global.context.translate(value.x - global.me!.x, value.y - global.me!.y);
global.context.rotate(value.rotation);
global.context.drawImage(global.spritesheet_img!,
tex.frame.x,
tex.frame.y,
tex.frame.w-1,
tex.frame.h-1,
-25,
-25, 50, 50);
global.context.restore();
}
});
function isStarkingdomsBirthday() {
let inputDate = new Date("3/20/2022");
let todaysDate = new Date();
return inputDate.getMonth() == todaysDate.getMonth() && inputDate.getDate() == todaysDate.getDate();
}
function isRustBirthday() {
let inputDate = new Date("5/15/2015");
let todaysDate = new Date();
return inputDate.getMonth() == todaysDate.getMonth() && inputDate.getDate() == todaysDate.getDate();
}
for (let i = 0; i < global.players.length; i++) {
if (global.me !== null) {
let player = global.players[i];
// @ts-ignore
//let tex = global.spritesheet!["frames"]["hearty.png"];
//let tex = global.spritesheet!["frames"]["hearty_party.png"];
//let tex = global.spritesheet!["frames"]["hearty_ferris.png"];
let tex;
if (isStarkingdomsBirthday()) {
// @ts-ignore
tex = global.spritesheet!["frames"]["hearty_party.png"];
} else if (isRustBirthday()) {
// @ts-ignore
tex = global.spritesheet!["frames"]["hearty_ferris.png"];
} else {
// @ts-ignore
tex = global.spritesheet!["frames"]["hearty.png"];
}
global.context.save();
global.context.translate(player.x - global.me!.x, player.y - global.me!.y);
global.context.textAlign = "center";
global.context.font = "30px Segoe UI";
global.context.fillStyle = "white";
global.context.fillText(player.username, 0, -35);
global.context.rotate(player.rotation);
global.context.drawImage(global.spritesheet_img!,
tex.frame.x, // sx
tex.frame.y, // sy
tex.frame.w-1, // sw
tex.frame.h-1, // sh
-25, -25, 50, 50); // dh
global.context.restore();
}
}
function calculateRotated(x: number, y: number, rotation: number): [number, number] {
let x2 = x * Math.cos(rotation) - y * Math.sin(rotation);
let y2 = x * Math.sin(rotation) + y * Math.cos(rotation);
return [x2, y2];
}
const thruster_r = 52;
const thruster_g = 189;
const thruster_b = 235;
const thruster_start_a = 80;
const thruster_end_a = 0;
const thruster_start_size = 4;
const thruster_end_size = 50;
const thruster_final_rotation = 90;
function createThrusterParticle(x: number, y: number, vel_x: number, vel_y: number) {
createParticle({
x: global.me!.x + calculateRotated(x, y, global.me!.rotation)[0],
y: global.me!.y + calculateRotated(x, y, global.me!.rotation)[1],
lifetime: 500,
timer: 0,
startSize: thruster_start_size,
finalSize: thruster_end_size,
startRotation: 0,
finalRotation: thruster_final_rotation,
startOpacity: thruster_start_a,
endOpacity: thruster_end_a,
startR: thruster_r,
startG: thruster_g,
startB: thruster_b,
endR: thruster_r,
endG: thruster_g,
endB: thruster_b,
velocity_x: calculateRotated(vel_x, vel_y, global.me!.rotation)[0],
velocity_y: calculateRotated(vel_x, vel_y, global.me!.rotation)[1]
});
}
if (global.me !== null) {
thruster_counter += 1;
//thruster_counter = 1; // uncomment this line to disable particle limits
if (thruster_counter > 3) {
thruster_counter = 0;
} else if (thruster_counter == 1) {
if (global.keys.up) {
// two backward thrusters
// this one is blue
createThrusterParticle(-25, 25, 0, 0.5);
createThrusterParticle(25, 25, 0, 0.5);
}
if (global.keys.down) {
// two backward thrusters
// this one is blue
createThrusterParticle(25, -25, 0, -0.5);
createThrusterParticle(-25, -25, 0, -0.5);
}
if (global.keys.left) {
// two backward thrusters
// this one is blue
createThrusterParticle(25, 25, 0, 0.5);
createThrusterParticle(-25, -25, 0, -0.5);
}
if (global.keys.right) {
// two backward thrusters
// this one is blue
createThrusterParticle(-25, 25, 0,0.5);
createThrusterParticle(25, -25, 0, -0.5);
}
}
tickAndDrawParticles(delta);
}
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
let query = new URLSearchParams(window.location.search);
if (!(query.has("server") || query.has("username") || query.has("textures"))) {
window.location.href = "/index.html";
}
client_main(query.get("server")!, query.get("username")!, query.get("textures")!).then(() => {});
function planet_type_to_tex_id(ty: PlanetType): string {
if (ty == PlanetType.Earth) {
return "earth.png"
} else if (ty == PlanetType.Moon) {
return "moon.png"
} else if (ty == PlanetType.Mars) {
return "mars.png"
}
return "unknown.png"
}
function module_type_to_tex_id(ty: ModuleType, is_on: boolean): string {
if (!is_on) {
if (ty == ModuleType.Cargo) {
return "cargo_off.png"
} else if (ty == ModuleType.LandingThruster) {
return "landingthruster_off.png"
} else if (ty == ModuleType.LandingThrusterSuspension) {
return "landingleg.png"
} else if (ty == ModuleType.Hub) {
return "hub_off.png"
}
} else {
if (ty == ModuleType.Cargo) {
return "cargo_on.png"
} else if (ty == ModuleType.LandingThruster) {
return "landingthruster_on.png"
} else if (ty == ModuleType.LandingThrusterSuspension) {
return "landingleg.png"
} else if (ty == ModuleType.Hub) {
return "hub_on.png"
}
}
return "unknown.png"
}