Greasy Fork is available in English.

Glxy Client

Survev.io multipurpose client

// ==UserScript==
// @name Glxy Client
// @author Chpsterz
// @description Survev.io multipurpose client
// @version 0.0.6
// @match *://survev.io/*
// @match http://66.179.254.36/*
// @run-at document-start
// @grant none
// @license MIT
// @namespace https://greasyfork.org/users/1411859
// ==/UserScript==

class GameMod {
    constructor() {
      this.lastFrameTime = performance.now();
      this.frameCount = 0;
      this.fps = 0;
      this.kills = 0;
      this.isFpsUncapped = this.getFpsUncappedFromLocalStorage();
      this.isFpsVisible = true;
      this.isPingVisible = true;
      this.isKillsVisible = true;
      this.isMenuVisible = true;
  
      this.pingCounter = null;
      this.initPingCounter();
  
      this.setAnimationFrameCallback();
  
      this.initFpsCounter();
      this.initKillsCounter();
      this.initMenu();
      this.loadBackgroundFromLocalStorage();
      this.loadLocalStorage();
      this.startUpdateLoop();
      this.setupWeaponBorderHandler();
      this.setupKeyListeners();
    }
  
    setAnimationFrameCallback() {
      this.animationFrameCallback = this.isFpsUncapped
        ? (callback) => setTimeout(callback, 1)
        : window.requestAnimationFrame.bind(window);
    }
  
    initFpsCounter() {
      this.fpsCounter = document.createElement("div");
      this.fpsCounter.id = "fpsCounter";
      Object.assign(this.fpsCounter.style, {
        color: "white",
        backgroundColor: "rgba(0, 0, 0, 0.2)",
        padding: "5px 10px",
        marginTop: "10px",
        borderRadius: "5px",
        fontFamily: "Arial, sans-serif",
        fontSize: "14px",
        zIndex: "10000",
        pointerEvents: "none",
      });
  
      const uiTopLeft = document.getElementById("ui-top-left");
      if (uiTopLeft) {
        uiTopLeft.appendChild(this.fpsCounter);
      }
  
      this.updateFpsVisibility();
    }
  
    initPingCounter() {
      this.pingCounter = document.createElement("div");
      this.pingCounter.id = "pingCounter";
      Object.assign(this.pingCounter.style, {
        color: "white",
        backgroundColor: "rgba(0, 0, 0, 0.2)",
        padding: "5px 10px",
        marginTop: "10px",
        borderRadius: "5px",
        fontFamily: "Arial, sans-serif",
        fontSize: "14px",
        zIndex: "10000",
        pointerEvents: "none",
      });
  
      const uiTopLeft = document.getElementById("ui-top-left");
      if (uiTopLeft) {
        uiTopLeft.appendChild(this.pingCounter);
      }
      this.updatePingVisibility();
    }
  
    initKillsCounter() {
      this.killsCounter = document.createElement("div");
      this.killsCounter.id = "killsCounter";
      Object.assign(this.killsCounter.style, {
        color: "white",
        backgroundColor: "rgba(0, 0, 0, 0.2)",
        padding: "5px 10px",
        marginTop: "10px",
        borderRadius: "5px",
        fontFamily: "Arial, sans-serif",
        fontSize: "14px",
        zIndex: "10000",
        pointerEvents: "none",
      });
  
      const uiTopLeft = document.getElementById("ui-top-left");
      if (uiTopLeft) {
        uiTopLeft.appendChild(this.killsCounter);
      }
      this.updateKillsVisibility();
    }
  
    updateFpsVisibility() {
      if (this.fpsCounter) {
        this.fpsCounter.style.display = this.isFpsVisible ? "block" : "none";
        this.fpsCounter.style.backgroundColor = this.isFpsVisible
          ? "rgba(0, 0, 0, 0.2)"
          : "transparent";
      }
    }
  
    updatePingVisibility() {
      if (this.pingCounter) {
        this.pingCounter.style.display = this.isPingVisible ? "block" : "none";
      }
    }
  
    updateKillsVisibility() {
      if (this.killsCounter) {
        this.killsCounter.style.display = this.isKillsVisible ? "block" : "none";
        this.killsCounter.style.backgroundColor = this.isKillsVisible
          ? "rgba(0, 0, 0, 0.2)"
          : "transparent";
      }
    }
  
    toggleFpsDisplay() {
      this.isFpsVisible = !this.isFpsVisible;
      this.updateFpsVisibility();
    }
  
    togglePingDisplay() {
      this.isPingVisible = !this.isPingVisible;
      this.updatePingVisibility();
    }
  
    toggleKillsDisplay() {
      this.isKillsVisible = !this.isKillsVisible;
      this.updateKillsVisibility();
    }
  
    getKills() {
      const killElement = document.querySelector(
        ".ui-player-kills.js-ui-player-kills",
      );
      if (killElement) {
        const kills = parseInt(killElement.textContent, 10);
        return isNaN(kills) ? 0 : kills;
      }
      return 0;
    }
  
    getRegionFromLocalStorage() {
      let config = localStorage.getItem("surviv_config");
      if (config) {
        let configObject = JSON.parse(config);
        return configObject.region;
      }
      return null;
    }
  
    startPingTest() {
      const currentUrl = window.location.href;
      const isSpecialUrl = /\/#\w+/.test(currentUrl);
  
      const teamSelectElement = document.getElementById("team-server-select");
      const mainSelectElement = document.getElementById("server-select-main");
  
      const region =
        isSpecialUrl && teamSelectElement
          ? teamSelectElement.value
          : mainSelectElement
            ? mainSelectElement.value
            : null;
  
      if (region && region !== this.currentServer) {
        this.currentServer = region;
        this.resetPing();
  
        const servers = [
          { region: "NA", url: "usr.mathsiscoolfun.com:8001" },
          { region: "EU", url: "eur.mathsiscoolfun.com:8001" },
          { region: "Asia", url: "asr.mathsiscoolfun.com:8001" },
          { region: "SA", url: "sa.mathsiscoolfun.com:8001" },
        ];
  
        const selectedServer = servers.find(
          (server) => region.toUpperCase() === server.region.toUpperCase(),
        );
  
        if (selectedServer) {
          this.pingTest = new PingTest(selectedServer);
          this.pingTest.startPingTest();
        } else {
          this.resetPing();
        }
      }
    }
  
    resetPing() {
      if (this.pingTest && this.pingTest.test.ws) {
        this.pingTest.test.ws.close();
        this.pingTest.test.ws = null;
      }
      this.pingTest = null;
    }
  
    getFpsUncappedFromLocalStorage() {
      const savedConfig = localStorage.getItem("userSettings");
      if (savedConfig) {
        const configObject = JSON.parse(savedConfig);
        return configObject.isFpsUncapped || false;
      }
      return false;
    }
  
    saveFpsUncappedToLocalStorage() {
      let config = JSON.parse(localStorage.getItem("userSettings")) || {};
      config.isFpsUncapped = this.isFpsUncapped;
      localStorage.setItem("userSettings", JSON.stringify(config));
    }
  
    saveBackgroundToLocalStorage(url) {
      localStorage.setItem("lastBackgroundUrl", url);
    }
  
    saveBackgroundToLocalStorage(image) {
      if (typeof image === "string") {
        localStorage.setItem("lastBackgroundType", "url");
        localStorage.setItem("lastBackgroundValue", image);
      } else {
        localStorage.setItem("lastBackgroundType", "local");
        const reader = new FileReader();
        reader.onload = () => {
          localStorage.setItem("lastBackgroundValue", reader.result);
        };
        reader.readAsDataURL(image);
      }
    }
  
    loadBackgroundFromLocalStorage() {
      const backgroundType = localStorage.getItem("lastBackgroundType");
      const backgroundValue = localStorage.getItem("lastBackgroundValue");
  
      const backgroundElement = document.getElementById("background");
      if (backgroundElement && backgroundType && backgroundValue) {
        if (backgroundType === "url") {
          backgroundElement.style.backgroundImage = `url(${backgroundValue})`;
        } else if (backgroundType === "local") {
          backgroundElement.style.backgroundImage = `url(${backgroundValue})`;
        }
      }
    }
  
    toggleFpsUncap() {
      this.isFpsUncapped = !this.isFpsUncapped;
      this.setAnimationFrameCallback();
      this.saveFpsUncappedToLocalStorage();
    }
  
    updateHealthBars() {
      const healthBars = document.querySelectorAll("#ui-health-container");
      healthBars.forEach((container) => {
        const bar = container.querySelector("#ui-health-actual");
        if (bar) {
          const width = Math.round(parseFloat(bar.style.width));
          let percentageText = container.querySelector(".health-text");
  
          if (!percentageText) {
            percentageText = document.createElement("span");
            percentageText.classList.add("health-text");
            Object.assign(percentageText.style, {
              width: "100%",
              textAlign: "center",
              marginTop: "5px",
              color: "#333",
              fontSize: "20px",
              fontWeight: "bold",
              position: "absolute",
              zIndex: "10",
            });
            container.appendChild(percentageText);
          }
  
          percentageText.textContent = `${width}%`;
        }
      });
    }
  
    updateBoostBars() {
      const boostCounter = document.querySelector("#ui-boost-counter");
      if (boostCounter) {
        const boostBars = boostCounter.querySelectorAll(
          ".ui-boost-base .ui-bar-inner",
        );
  
        let totalBoost = 0;
        const weights = [25, 25, 40, 10];
  
        boostBars.forEach((bar, index) => {
          const width = parseFloat(bar.style.width);
          if (!isNaN(width)) {
            totalBoost += width * (weights[index] / 100);
          }
        });
  
        const averageBoost = Math.round(totalBoost);
        let boostDisplay = boostCounter.querySelector(".boost-display");
  
        if (!boostDisplay) {
          boostDisplay = document.createElement("div");
          boostDisplay.classList.add("boost-display");
          Object.assign(boostDisplay.style, {
            position: "absolute",
            bottom: "75px",
            right: "335px",
            color: "#FF901A",
            backgroundColor: "rgba(0, 0, 0, 0.4)",
            padding: "5px 10px",
            borderRadius: "5px",
            fontFamily: "Arial, sans-serif",
            fontSize: "14px",
            zIndex: "10",
            textAlign: "center",
          });
  
          boostCounter.appendChild(boostDisplay);
        }
  
        boostDisplay.textContent = `AD: ${averageBoost}%`;
      }
    }
  
    setupWeaponBorderHandler() {
      const weaponContainers = Array.from(
        document.getElementsByClassName("ui-weapon-switch"),
      );
      weaponContainers.forEach((container) => {
        if (container.id === "ui-weapon-id-4") {
          container.style.border = "3px solid #2f4032";
        } else {
          container.style.border = "3px solid #FFFFFF";
        }
      });
  
      const weaponNames = Array.from(
        document.getElementsByClassName("ui-weapon-name"),
      );
      weaponNames.forEach((weaponNameElement) => {
        const weaponContainer = weaponNameElement.closest(".ui-weapon-switch");
        const observer = new MutationObserver(() => {
          const weaponName = weaponNameElement.textContent.trim();
          let border = "#FFFFFF";
  
          switch (weaponName.toUpperCase()) {
            case "CZ-3A1":
            case "G18C":
            case "M9":
            case "M93R":
            case "MAC-10":
            case "MP5":
            case "P30L":
            case "DUAL P30L":
            case "UMP9":
            case "VECTOR":
            case "VSS":
            case "FLAMETHROWER":
              border = "#FFAE00";
              break;
  
            case "AK-47":
            case "OT-38":
            case "OTS-38":
            case "M39 EMR":
            case "DP-28":
            case "MOSIN-NAGANT":
            case "SCAR-H":
            case "SV-98":
            case "M1 GARAND":
            case "PKP PECHENEG":
            case "AN-94":
            case "BAR M1918":
            case "BLR 81":
            case "SVD-63":
            case "M134":
            case "WATER GUN":
              border = "#007FFF";
              break;
  
            case "FAMAS":
            case "M416":
            case "M249":
            case "QBB-97":
            case "MK 12 SPR":
            case "M4A1-S":
            case "SCOUT ELITE":
            case "L86A2":
              border = "#0f690d";
              break;
  
            case "M870":
            case "MP220":
            case "SAIGA-12":
            case "SPAS-12":
            case "USAS-12":
            case "SUPER 90":
            case "LASR GUN":
            case "M1100":
              border = "#FF0000";
              break;
  
            case "DEAGLE 50":
            case "RAINBOW BLASTER":
              border = "#000000";
              break;
  
            case "AWM-S":
            case "MK 20 SSR":
              border = "#808000";
              break;
  
            case "FLARE GUN":
              border = "#FF4500";
              break;
  
            case "MODEL 94":
            case "PEACEMAKER":
            case "VECTOR (.45 ACP)":
            case "M1911":
            case "M1A1":
              border = "#800080";
              break;
  
            case "M79":
              border = "#008080";
              break;
  
            case "POTATO CANNON":
            case "SPUD GUN":
              border = "#A52A2A";
              break;
  
            case "HEART CANNON":
              border = "#FFC0CB";
              break;
  
            default:
              border = "#FFFFFF";
              break;
          }
  
          if (weaponContainer.id !== "ui-weapon-id-4") {
            weaponContainer.style.border = `3px solid ${border}`;
          }
        });
  
        observer.observe(weaponNameElement, {
          childList: true,
          characterData: true,
          subtree: true,
        });
      });
    }
  
    updateUiElements() {
      const currentUrl = window.location.href;
  
      const isSpecialUrl = /\/#\w+/.test(currentUrl);
  
      const playerOptions = document.getElementById("player-options");
      const teamMenuContents = document.getElementById("team-menu-contents");
      const startMenuContainer = document.querySelector(
        "#start-menu .play-button-container",
      );
  
      if (!playerOptions) return;
  
      if (
        isSpecialUrl &&
        teamMenuContents &&
        playerOptions.parentNode !== teamMenuContents
      ) {
        teamMenuContents.appendChild(playerOptions);
      } else if (
        !isSpecialUrl &&
        startMenuContainer &&
        playerOptions.parentNode !== startMenuContainer
      ) {
        const firstChild = startMenuContainer.firstChild;
        startMenuContainer.insertBefore(playerOptions, firstChild);
      }
      const teamMenu = document.getElementById("team-menu");
      if (teamMenu) {
        teamMenu.style.height = "355px";
      }
      const menuBlocks = document.querySelectorAll(".menu-block");
      menuBlocks.forEach((block) => {
        block.style.maxHeight = "355px";
      });
      //scalable?
    }
  
    updateMenuButtonText() {
      const hideButton = document.getElementById("hideMenuButton");
      hideButton.textContent = this.isMenuVisible
        ? "Hide Menu [P]"
        : "Show Menu [P]";
    }
  
    setupKeyListeners() {
      document.addEventListener("keydown", (event) => {
        if (event.key.toLowerCase() === "p") {
          this.toggleMenuVisibility();
        }
      });
    }
    //menu
    initMenu() {
      const menu = document.createElement("div");
      menu.id = "menu";
      Object.assign(menu.style, {
        backgroundColor: "rgba(0, 0, 0, 0.8)",
        padding: "15px",
        marginLeft: "15px",
        borderRadius: "10px",
        boxShadow: "0 4px 10px rgba(0, 0, 0, 0.6)",
        zIndex: "10001",
        width: "250px",
        fontFamily: "Arial, sans-serif",
        color: "#fff",
        maxHeight: "400px",
        overflowY: "auto",
      });
  
      const title = document.createElement("h2");
      title.textContent = "Glxy Client";
      title.style.margin = "0 0 10px";
      title.style.textAlign = "center";
      title.style.fontSize = "18px";
      title.style.color = "#FFAE00";
      menu.appendChild(title);
  
      const updateLocalStorage = () => {
        localStorage.setItem(
          "userSettings",
          JSON.stringify({
            isFpsVisible: this.isFpsVisible,
            isPingVisible: this.isPingVisible,
            isFpsUncapped: this.isFpsUncapped,
            isKillsVisible: this.isKillsVisible,
          }),
        );
      };
  
      this.loadLocalStorage();
  
      const fpsToggle = document.createElement("button");
      fpsToggle.textContent = `Show FPS ${this.isFpsVisible ? "✅" : "❌"}`;
      Object.assign(fpsToggle.style, {
        backgroundColor: this.isFpsVisible ? "#4CAF50" : "#FF0000",
        border: "none",
        color: "#fff",
        padding: "10px",
        borderRadius: "5px",
        width: "100%",
        marginBottom: "10px",
        fontSize: "14px",
        cursor: "pointer",
      });
      fpsToggle.onclick = () => {
        this.isFpsVisible = !this.isFpsVisible;
        this.updateFpsVisibility();
        fpsToggle.textContent = `Show FPS ${this.isFpsVisible ? "✅" : "❌"}`;
        fpsToggle.style.backgroundColor = this.isFpsVisible
          ? "#4CAF50"
          : "#FF0000";
        updateLocalStorage();
      };
      menu.appendChild(fpsToggle);
  
      const pingToggle = document.createElement("button");
      pingToggle.textContent = `Show Ping ${this.isPingVisible ? "✅" : "❌"}`;
      Object.assign(pingToggle.style, {
        backgroundColor: this.isPingVisible ? "#4CAF50" : "#FF0000",
        border: "none",
        color: "#fff",
        padding: "10px",
        borderRadius: "5px",
        width: "100%",
        marginBottom: "10px",
        fontSize: "14px",
        cursor: "pointer",
      });
      pingToggle.onclick = () => {
        this.isPingVisible = !this.isPingVisible;
        this.updatePingVisibility();
        pingToggle.textContent = `Show Ping ${this.isPingVisible ? "✅" : "❌"}`;
        pingToggle.style.backgroundColor = this.isPingVisible
          ? "#4CAF50"
          : "#FF0000";
        updateLocalStorage();
      };
      menu.appendChild(pingToggle);
  
      const killsToggle = document.createElement("button");
      killsToggle.textContent = `Show Kills ${this.isKillsVisible ? "✅" : "❌"}`;
      Object.assign(killsToggle.style, {
        backgroundColor: this.isKillsVisible ? "#4CAF50" : "#FF0000",
        border: "none",
        color: "#fff",
        padding: "10px",
        borderRadius: "5px",
        width: "100%",
        marginBottom: "10px",
        fontSize: "14px",
        cursor: "pointer",
      });
      killsToggle.onclick = () => {
        this.isKillsVisible = !this.isKillsVisible;
        this.updateKillsVisibility();
        killsToggle.textContent = `Show Kills ${this.isKillsVisible ? "✅" : "❌"}`;
        killsToggle.style.backgroundColor = this.isKillsVisible
          ? "#4CAF50"
          : "#FF0000";
        updateLocalStorage();
      };
      menu.appendChild(killsToggle);
  
      const uncapFpsToggle = document.createElement("button");
      uncapFpsToggle.textContent = `Uncap FPS ${this.isFpsUncapped ? "✅" : "❌"}`;
      Object.assign(uncapFpsToggle.style, {
        backgroundColor: this.isFpsUncapped ? "#4CAF50" : "#FF0000",
        border: "none",
        color: "#fff",
        padding: "10px",
        borderRadius: "5px",
        width: "100%",
        marginBottom: "10px",
        fontSize: "14px",
        cursor: "pointer",
      });
      uncapFpsToggle.onclick = () => {
        this.toggleFpsUncap();
        uncapFpsToggle.textContent = `Uncap FPS ${this.isFpsUncapped ? "✅" : "❌"}`;
        uncapFpsToggle.style.backgroundColor = this.isFpsUncapped
          ? "#4CAF50"
          : "#FF0000";
        updateLocalStorage();
      };
      menu.appendChild(uncapFpsToggle);
  
      const hideShowToggle = document.createElement("button");
      hideShowToggle.textContent = `👀 Hide/Show Menu [P]`;
      Object.assign(hideShowToggle.style, {
        backgroundColor: "#6F42C1",
        border: "none",
        color: "#fff",
        padding: "10px",
        borderRadius: "5px",
        width: "100%",
        marginBottom: "10px",
        fontSize: "14px",
        cursor: "pointer",
      });
      hideShowToggle.onclick = () => this.toggleMenuVisibility();
      menu.appendChild(hideShowToggle);
  
      const backgroundToggle = document.createElement("button");
      backgroundToggle.textContent = `🎨 Change Background`;
      Object.assign(backgroundToggle.style, {
        backgroundColor: "#007BFF",
        border: "none",
        color: "#fff",
        padding: "10px",
        borderRadius: "5px",
        width: "100%",
        marginBottom: "10px",
        fontSize: "14px",
        cursor: "pointer",
      });
      backgroundToggle.onclick = () => {
        const backgroundElement = document.getElementById("background");
        if (!backgroundElement) {
          alert("Element with id 'background' not found.");
          return;
        }
        const choice = prompt(
          "Enter '1' to provide a URL or '2' to upload a local image:",
        );
  
        if (choice === "1") {
          const newBackgroundUrl = prompt(
            "Enter the URL of the new background image:",
          );
          if (newBackgroundUrl) {
            backgroundElement.style.backgroundImage = `url(${newBackgroundUrl})`;
            this.saveBackgroundToLocalStorage(newBackgroundUrl);
            alert("Background updated successfully!");
          }
        } else if (choice === "2") {
          const fileInput = document.createElement("input");
          fileInput.type = "file";
          fileInput.accept = "image/*";
          fileInput.onchange = (event) => {
            const file = event.target.files[0];
            if (file) {
              const reader = new FileReader();
              reader.onload = () => {
                backgroundElement.style.backgroundImage = `url(${reader.result})`;
                this.saveBackgroundToLocalStorage(file);
                alert("Background updated successfully!");
              };
              reader.readAsDataURL(file);
            }
          };
          fileInput.click();
        }
      };
  
      menu.appendChild(backgroundToggle);
  
      window.onload = () => {
        const savedBackground = localStorage.getItem("backgroundImage");
        if (savedBackground) {
          const backgroundElement = document.getElementById("background");
          if (backgroundElement) {
            backgroundElement.style.backgroundImage = `url(${savedBackground})`;
          }
        }
      };
  
      const startRowTop = document.getElementById("start-row-top");
      if (startRowTop) {
        startRowTop.appendChild(menu);
      }
  
      this.menu = menu;
    }
  
    loadLocalStorage() {
      const savedSettings = JSON.parse(localStorage.getItem("userSettings"));
      if (savedSettings) {
        this.isFpsVisible = savedSettings.isFpsVisible ?? this.isFpsVisible;
        this.isPingVisible = savedSettings.isPingVisible ?? this.isPingVisible;
        this.isFpsUncapped = savedSettings.isFpsUncapped ?? this.isFpsUncapped;
        this.isKillsVisible = savedSettings.isKillsVisible ?? this.isKillsVisible;
      }
  
      this.updateKillsVisibility();
      this.updateFpsVisibility();
      this.updatePingVisibility();
    }
  
    toggleMenuVisibility() {
      const isVisible = this.menu.style.display !== "none";
      this.menu.style.display = isVisible ? "none" : "block";
    }
  
    startUpdateLoop() {
      const now = performance.now();
      const delta = now - this.lastFrameTime;
  
      this.frameCount++;
  
      if (delta >= 1000) {
        this.fps = Math.round((this.frameCount * 1000) / delta);
        this.frameCount = 0;
        this.lastFrameTime = now;
  
        this.kills = this.getKills();
  
        if (this.isFpsVisible && this.fpsCounter) {
          this.fpsCounter.textContent = `FPS: ${this.fps}`;
        }
  
        if (this.isKillsVisible && this.killsCounter) {
          this.killsCounter.textContent = `Kills: ${this.kills}`;
        }
  
        if (this.isPingVisible && this.pingCounter && this.pingTest) {
          const result = this.pingTest.getPingResult();
          this.pingCounter.textContent = `PING: ${result.ping} ms`;
        }
      }
  
      this.startPingTest();
      this.animationFrameCallback(() => this.startUpdateLoop());
      this.updateUiElements();
      this.updateBoostBars();
      this.updateHealthBars();
    }
  }
  
  class PingTest {
    constructor(selectedServer) {
      this.ptcDataBuf = new ArrayBuffer(1);
      this.test = {
        region: selectedServer.region,
        url: `wss://${selectedServer.url}/ptc`,
        ping: 9999,
        ws: null,
        sendTime: 0,
        retryCount: 0,
      };
    }
  
    startPingTest() {
      if (!this.test.ws) {
        const ws = new WebSocket(this.test.url);
        ws.binaryType = "arraybuffer";
  
        ws.onopen = () => {
          this.sendPing();
          this.test.retryCount = 0;
        };
  
        ws.onmessage = () => {
          const elapsed = (Date.now() - this.test.sendTime) / 1e3;
          this.test.ping = Math.round(elapsed * 1000);
          this.test.retryCount = 0;
          setTimeout(() => this.sendPing(), 200);
        };
  
        ws.onerror = () => {
          this.test.ping = "Error";
          this.test.retryCount++;
          if (this.test.retryCount < 5) {
            setTimeout(() => this.startPingTest(), 2000);
          } else {
            this.test.ws.close();
            this.test.ws = null;
          }
        };
  
        ws.onclose = () => {
          this.test.ws = null;
        };
  
        this.test.ws = ws;
      }
    }
  
    sendPing() {
      if (this.test.ws.readyState === WebSocket.OPEN) {
        this.test.sendTime = Date.now();
        this.test.ws.send(this.ptcDataBuf);
      }
    }
  
    getPingResult() {
      return {
        region: this.test.region,
        ping: this.test.ping,
      };
    }
  }
  
  const gameMod = new GameMod();