~starkingdoms/starkingdoms

8cc119056530ee53c22b368d52ce3270f0ae7243 — core 2 years ago e310e31
packet log
M starkingdoms-client/index.html => starkingdoms-client/index.html +14 -1
@@ 6,7 6,7 @@
    <title>StarKingdoms</title>
</head>
<body class="bg-grid">
<div class="popup" id="server_selector">
<div class="popup popup-center popup-w19" id="server_selector">

    <h1>StarKingdoms</h1>
    <h2>Join Game</h2>


@@ 39,6 39,19 @@ Here be dragons! You have a <b>prerelease server</b> selected. Expect bugs, and 
    </form>
</div>

<div class="popup popup-wmin log-hidden" id="packet_log">
    <h1>Packet Log</h1>
    <table class="log">
        <tbody id="log_body">

        </tbody>
    </table>
    <hr>
    <h1>Packet Explorer</h1>
    <p id="explorer_selected" class="mono">Selected: --</p>
    <table class="mono json" id="explorer_json"></table>
</div>

<div id="gamewindow" class="game">
    <!-- Canvas gets added here by the game script -->
</div>

M starkingdoms-client/src/css/globals.css => starkingdoms-client/src/css/globals.css +5 -2
@@ 5,7 5,6 @@ html {
    box-sizing: inherit;
}


body {
    background-color: var(--bg);
    color: var(--body);


@@ 15,6 14,10 @@ body {
    line-height: 1.5rem;
}

.mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}

h1 {
    font-size: 1.25rem;
    line-height: 1.75rem;


@@ 22,4 25,4 @@ h1 {
h2 {
    font-size: 1.125rem;
    line-height: 1.75rem;
}
\ No newline at end of file
}

A starkingdoms-client/src/css/json.css => starkingdoms-client/src/css/json.css +21 -0
@@ 0,0 1,21 @@
:root {
    --ident: #75bfff;
    --string: #ff7de9;
    --literal: #86de74;
    --indent-spacing: 16px;
}

.json-ident {
    color: var(--ident);
}
.json-string {
    color: var(--string);
}
.json-literal {
    color: var(--literal);
}
.json {
    max-height: 200px;
    display: block;
    overflow: auto;
}

A starkingdoms-client/src/css/log.css => starkingdoms-client/src/css/log.css +35 -0
@@ 0,0 1,35 @@
.log-icon {
    width: 16px
}
.log-lalign {
    text-align: left;
}
.log-item {
    cursor: pointer;
}
.log-item:hover {
    background-color: #4a4a4f;
}
.log {
    border: none;
    border-collapse: collapse;
    display: block;
    max-height: 200px;
    overflow: auto;
}
.log-selected {
    background-color: #204e8a;
    cursor: default;
}
.log-leaving {
    color: var(--error);
}
.log-arriving {
    color: var(--success);
}
.log > tbody > tr > td {
    padding: 2px;
}
.log-hidden {
    display: none;
}

M starkingdoms-client/src/css/popup.css => starkingdoms-client/src/css/popup.css +15 -8
@@ 1,17 1,24 @@
.popup {
    padding: 2vh 1vw;

    background-color: var(--bg-secondary-1);

    height: min-content;
    border-radius: 5px;
}
.popup-w19 {
    width: 19%;
}
.popup-wmin {
    width: min-content;
}
.popup-center {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;

    padding: 2vh 1vw;

    background-color: var(--bg-secondary-1);
    width: 19%;
    height: min-content;
    border-radius: 5px;
}
.popup > h1 {
    margin: 0;


@@ 19,4 26,4 @@
.popup > h2 {
    margin: 0 0 1vh;
    color: var(--sub-headline);
}
\ No newline at end of file
}

M starkingdoms-client/src/css/style.css => starkingdoms-client/src/css/style.css +3 -1
@@ 2,4 2,6 @@
@import "grid.css";
@import "popup.css";
@import "footer.css";
@import "form.css";
\ No newline at end of file
@import "form.css";
@import "json.css";
@import "log.css";

M starkingdoms-client/src/hub.ts => starkingdoms-client/src/hub.ts +31 -3
@@ 1,5 1,13 @@
import createDebug from "debug";
import {Packet, PacketType} from "./protocol.ts";
import {
    Packet,
    PacketType,
    PartPositionsPacket,
    PlanetPositionsPacket, PlayerLeavePacket,
    PlayerListPacket,
    SpawnPlayerPacket
} from "./protocol.ts";
import {appendPacket} from "./packet_ui.ts";

const logger = createDebug("hub");



@@ 7,6 15,11 @@ 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) {
    logger("connecting to client hub at " + url)



@@ 25,11 38,26 @@ export async function hub_connect(url: string, username: string) {
            jwt: null
        }
    };
    ws.send(JSON.stringify(packet));
    sendPacket(client, packet);

    ws.onmessage = (e) => {
        let packet: Packet = JSON.parse(e.data);
        logger(packet);

        appendPacket(packet);

        if (packet.t == PacketType.SpawnPlayer) {
            let p = <SpawnPlayerPacket> packet.c;
        } else if (packet.t == PacketType.PlayerList) {
            let p = <PlayerListPacket> packet.c;
        } else if (packet.t == PacketType.PlanetPositions) {
            let p = <PlanetPositionsPacket> packet.c;
        } else if (packet.t == PacketType.PartPositions) {
            let p = <PartPositionsPacket> packet.c;
        } else if (packet.t == PacketType.PlayerLeave) {
            let p = <PlayerLeavePacket> packet.c;
        } else {
            logger(`unrecognized packet type ${packet.t}`);
        }
    }

    return client;

M starkingdoms-client/src/main.ts => starkingdoms-client/src/main.ts +5 -1
@@ 1,5 1,5 @@
import createDebug from "debug";
import {hub_connect, ClientHub} from "./hub.ts";
import {ClientHub, hub_connect} from "./hub.ts";
import {ConfigServer, loadConfig} from "./config.ts";
import "./css/style.css";
import "./css/themes/catppuccin-mocha/colors.css";


@@ 9,6 9,10 @@ let config = await loadConfig();
const logger = createDebug("main");
logger(`Hello, world! StarKingdoms ${APP_VERSION} (${COMMIT_HASH}) at your service`);

if (window.localStorage.getItem("stk-packet-mode") === "debug") {
    document.getElementById("packet_log")!.classList.remove("log-hidden");
}

export interface GlobalData {
    client: ClientHub | null
}

A starkingdoms-client/src/packet_ui.ts => starkingdoms-client/src/packet_ui.ts +170 -0
@@ 0,0 1,170 @@
import {Direction, Packet, type_direction} from "./protocol.ts";
import createDebug from "debug";

const logger = createDebug("jsonview");

let selected = document.getElementById("explorer_selected")!;
let table = document.getElementById("explorer_json")!;

export function show_packet(maybe_packet: Packet | null, sequence_number: number) {
    logger(`selected packet ${sequence_number}`);
    if (maybe_packet === null) {
        selected.textContent = "Selected: --";
        table.innerHTML = "";
        return;
    }
    let packet = maybe_packet!;
    let direction = type_direction(packet.t);

    selected.textContent = `Selected: #${sequence_number} ${direction} ${packet.t}`;

    // iterate over everything and calculate a max depth
    let max_depth = depthOf(packet.c);
    logger(`indent depth ${max_depth}`);

    // generate a tree
    let tree = generateTree(packet.c, 0);

    logger(`generating tree of ${tree.length} items`);

    let table_html = "";

    for (let i = 0; i < tree.length; i++) {
        let [indent, key, value, is_text] = tree[i];
        table_html += `
        <tr>
            <td style="padding-left: calc(var(--indent-spacing) * ${indent})" class="json-ident">${key}</td>
            <td style="padding-left: calc(var(--indent-spacing) * ${max_depth + 1})" class="${is_text ? "json-string" : "json-literal"}">${value}</td>
        </tr>
        `
    }

    table.innerHTML = `
<!-- Rendered tree of ${tree.length} items -->
    <thead>
    <tr></tr>
    <tr></tr>
    </thead>
    <tbody>
    ${table_html}
</tbody>
    `;
}

function depthOf(object: any) {
    let level = 0;
    for(let key in object) {
        if (!object.hasOwnProperty(key)) continue;

        if(typeof object[key] == 'object'){
            let depth = depthOf(object[key]) + 1;
            level = Math.max(depth, level);
        }
    }
    return level;
}


function generateTree(object: any, level: number): [indent: number, key: string, value: string, value_string: boolean][] {
    let items: [indent: number, key: string, value: string, value_string: boolean][] = [];

    for (let key in object) {
        if (!object.hasOwnProperty(key)) continue;

        if (object[key] === null) {
            items.push([level, key, "null", false]);
        } else if (object[key] === undefined) {
            items.push([level, key, "undefined", false]);
        } else if (typeof object[key] === 'object') {
            if (Array.isArray(object[key])) {
                if (object[key].length == 0) {
                    items.push([level, key + ":", "[]", false]);
                } else {
                    items.push([level, key + ":", "", false]);
                }
                for (let i = 0; i < object[key].length; i++) {
                    items.push([level+1, i.toString() + ":", "", false]);
                    items = items.concat(generateTree(object[key][i], level+2));
                }
                continue;
            }
            items.push([level, key + ":", "", false]);
            items = items.concat(generateTree(object[key], level + 1));
        } else if (Array.isArray(object[key])) {
            for (let i = 0; i < object[key].length; i++) {
                items.push([level, i.toString() + ":", "", false]);
                items = items.concat(generateTree(object[key][i], level));
            }
        } else if (typeof object[key] === 'string') {
            items.push([level, key + ":", "\"" + object[key] + "\"", true]);
        } else {
            items.push([level, key + ":", object[key].toString(), false]);
        }
    }

    return items;
}

export let packets: Packet[] = [];
export let selected_packet: number | null = null;
let last_packet: Date | null = null;
const log_body = document.getElementById("log_body")!;

const up_arrow = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="log-icon log-leaving">
                <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 10.5L12 3m0 0l7.5 7.5M12 3v18" />
            </svg>`;
const down_arrow = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="log-icon log-arriving">
                <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 13.5L12 21m0 0l-7.5-7.5M12 21V3" />
            </svg>`;

export function selectPacket(id: number) {
    console.log("selecting packet " + id);
    if (selected_packet !== null) {
        document.getElementById("packet-" + selected_packet)!.classList.remove("log-selected");
        document.getElementById("packet-" + selected_packet)!.classList.add("log-item");
    }
    document.getElementById("packet-" + id)!.classList.add("log-selected");
    document.getElementById("packet-" + id)!.classList.remove("log-item");
    selected_packet = id;
    show_packet(packets[id], id);
}

export function appendPacket(packet: Packet) {
    packets.push(packet);

    let duration = "--";
    if (last_packet !== null) {
        duration = (new Date().getTime() - last_packet!.getTime()).toString() + "ms";
    }
    last_packet = new Date();

    let index_deepcopy = packets.length + 1;
    let index = index_deepcopy - 2;

    let tr = document.createElement("tr");
    tr.classList.add("log-item");
    tr.id = "packet-" + index;

    let td_idx = document.createElement("td");
    td_idx.innerHTML = index.toString();
    tr.appendChild(td_idx);

    let td_direction = document.createElement("td");
    td_direction.innerHTML = type_direction(packet.t) == Direction.Clientbound ? down_arrow : up_arrow;
    tr.appendChild(td_direction);

    let td_type = document.createElement("td");
    td_type.innerHTML = packet.t;
    tr.appendChild(td_type);

    let td_ts = document.createElement("td");
    td_ts.innerHTML = duration;
    td_ts.classList.add(type_direction(packet.t) == Direction.Clientbound ? "log-arriving" : "log-leaving")
    tr.appendChild(td_ts);

    tr.addEventListener("click", () => {
        selectPacket(index);
    });

    log_body.appendChild(tr);
}

M starkingdoms-client/src/protocol.ts => starkingdoms-client/src/protocol.ts +32 -4
@@ 23,8 23,7 @@ export interface ClientLoginPacket {
}
export interface SpawnPlayerPacket {
    id: number,
    username: string,
    position: ProtoTransform
    username: string
}
export interface PlanetPositionsPacket {
    planets: [number, Planet][]


@@ 32,15 31,44 @@ export interface PlanetPositionsPacket {
export interface PartPositionsPacket {
    parts: [number, Part][]
}
export interface PlayerListPacket {
    players: [number, String][]
}
export interface PlayerLeavePacket {
    id: number
}

export enum PacketType {
    // serverbound
    ClientLogin = "ClientLogin",
    // clientbound
    SpawnPlayer = "SpawnPlayer",
    PlayerList = "PlayerList",
    PlanetPositions = "PlanetPositions",
    PartPositions = "PartPositions"
    PartPositions = "PartPositions",
    PlayerLeave = "PlayerLeave"
}

export interface Packet {
    t: PacketType,
    c: ClientLoginPacket | SpawnPlayerPacket | PlanetPositionsPacket | PartPositionsPacket
    c: ClientLoginPacket | SpawnPlayerPacket | PlayerListPacket | PlanetPositionsPacket | PartPositionsPacket | PlayerLeavePacket
}

export const SERVERBOUND = [PacketType.ClientLogin];
export const CLIENTBOUND = [PacketType.SpawnPlayer, PacketType.PlayerList, PacketType.PlanetPositions, PacketType.PartPositions, PacketType.PlayerLeave];

export enum Direction {
    Serverbound = "Serverbound",
    Clientbound = "Clientbound",
    InvalidType = "InvalidType"
}

export function type_direction(type: PacketType): Direction {
    if (SERVERBOUND.includes(type)) {
        return Direction.Serverbound;
    } else if (CLIENTBOUND.includes(type)) {
        return Direction.Clientbound;
    } else {
        return Direction.InvalidType;
    }
}