请阅读这两段代码// ==UserScript==
// @name         Alguien Client - Surve.io Client
// @namespace    https://github.com/SoyAlguien0/AlguienClient
// @version      0.0.2
// @description  A client to enhance the survev.io in-game experience with many features, as well as future features.
// @author       SoyAlguien
// @license      AGPL-3.0
// @require      https://update.greasyfork.org/scripts/391611/743919/WSHook.js
// @run-at       document-end
// @match        *://survev.io/*
// @match        *://66.179.254.36/*
// @match        *://expandedwater.online/*
// @grant        none
// @downloadURL https://update.greasyfork.org/scripts/519982/Alguien%20Client%20-%20Surveio%20Client.user.js
// @updateURL https://update.greasyfork.org/scripts/519982/Alguien%20Client%20-%20Surveio%20Client.meta.js
// ==/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 = "soyAlguienMenu";
  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 = "SoyAlguien 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();
// ==UserScript==
// @name         Surviv.IO Aimbot, ESP & X-Ray
// @namespace    https://greasyfork.org/en/users/662330-zertalious
// @version      0.0.4
// @description  Aimbot and ESP for surviv.io. Locks the aim to the nearest player and shows lines between nearby players. Removes ceilings from buildings and let's you see inside them too.
// @author       Zertalious (Zert)
// @match        *://survev.io/*
// @match        *://surviv2.io/*
// @match        *://2dbattleroyale.com/*
// @match        *://2dbattleroyale.org/*
// @match        *://piearesquared.info/*
// @match        *://thecircleisclosing.com/*
// @match        *://archimedesofsyracuse.info/*
// @match        *://secantsecant.com/*
// @match        *://parmainitiative.com/*
// @match        *://nevelskoygroup.com/*
// @match        *://kugahi.com/*
// @match        *://chandlertallowmd.com/*
// @match        *://ot38.club/*
// @match        *://kugaheavyindustry.com/*
// @match        *://drchandlertallow.com/*
// @match        *://rarepotato.com/*
// @icon         https://www.google.com/s2/favicons?domain=surviv.io
// @grant        none
// @run-at       document-start
// @antifeature  ads
// @downloadURL https://update.greasyfork.org/scripts/518961/SurvivIO%20Aimbot%2C%20ESP%20%20X-Ray.user.js
// @updateURL https://update.greasyfork.org/scripts/518961/SurvivIO%20Aimbot%2C%20ESP%20%20X-Ray.meta.js
// ==/UserScript==
 
let espEnabled = true;
let aimbotEnabled = true;
let xrayEnabled = true;
 
Object.defineProperty( Object.prototype, 'textureCacheIds', {
	set( value ) {
 
		this._textureCacheIds = value;
 
		if ( Array.isArray( value ) ) {
 
			const scope = this;
 
			value.push = new Proxy( value.push, {
				apply( target, thisArgs, args ) {
 
					if ( args[ 0 ].indexOf( 'ceiling' ) > - 1 ) {
 
						Object.defineProperty( scope, 'valid', {
							set( value ) {
 
								this._valid = value;
 
							},
							get() {
 
								return xrayEnabled ? false : this._valid;
 
							}
						} );
 
					}
 
					return Reflect.apply( ...arguments );
 
				}
			} );
 
		}
 
	},
	get() {
 
		return this._textureCacheIds;
 
	}
} );
 
const params = {
	get() {
 
		console.log( 'getting ctx', this );
 
		return null;
 
	}
};
 
Object.defineProperty( window, 'WebGLRenderingContext', params );
Object.defineProperty( window, 'WebGL2RenderingContext', params );
 
let ctx;
 
HTMLCanvasElement.prototype.getContext = new Proxy( HTMLCanvasElement.prototype.getContext, {
	apply( target, thisArgs, args ) {
 
		const result = Reflect.apply( ...arguments );
 
		if ( thisArgs.parentNode ) {
 
			ctx = result;
 
		}
 
		return result;
 
	}
} );
 
const players = [];
 
let radius;
 
let mouseX = 0, mouseY = 0;
 
window.addEventListener( 'mousemove', function ( event ) {
 
	if ( event.dispatchedByMe !== true ) {
 
		mouseX = event.clientX;
		mouseY = event.clientY;
 
	}
 
} );
 
window.addEventListener( 'keyup', function ( event ) {
 
	switch ( String.fromCharCode( event.keyCode ) ) {
 
		case 'N' : espEnabled = ! espEnabled; break;
		case 'B' : aimbotEnabled = ! aimbotEnabled; break;
		case 'H' : xrayEnabled = ! xrayEnabled; break;
 
	}
 
} );
 
const Context2D = CanvasRenderingContext2D.prototype;
 
Context2D.drawImage = new Proxy( Context2D.drawImage, {
	apply( target, thisArgs, args ) {
 
		if ( aimbotEnabled && args[ 0 ].src && args[ 0 ].src.indexOf( 'loadout' ) > - 1 && args[ 8 ] === 142 ) {
 
			const { a, b, e, f } = thisArgs.getTransform();
 
			radius = Math.hypot( a, b ) * args[ 8 ] + 10;
 
			const centerX = thisArgs.canvas.width / 2;
			const centerY = thisArgs.canvas.height / 2;
 
			if ( e !== centerX && f !== centerY ) {
 
				players.push( { x: e, y: f } );
 
			}
 
		}
 
		return Reflect.apply( ...arguments );
 
	}
} );
 
window.requestAnimationFrame = new Proxy( window.requestAnimationFrame, {
	apply( target, thisArgs, args ) {
 
		args[ 0 ] = new Proxy( args[ 0 ], {
			apply( target, thisArgs, args ) {
 
				players.length = 0;
 
				Reflect.apply( ...arguments );
 
				ctx.fillStyle = '#fff';
 
				const array = [
					[ '[B] Aimbot', aimbotEnabled ],
					[ '[N] ESP', espEnabled ],
					[ '[H] X-Ray', xrayEnabled ]
				];
 
				const fontSize = 20;
 
				ctx.textAlign = 'center';
				ctx.textBaseline = 'top';
 
				ctx.font = 'bolder ' + fontSize + 'px monospace';
 
				for ( let i = 0; i < array.length; i ++ ) {
 
					const [ text, status ] = array[ i ];
 
					ctx.globalAlpha = status ? 1 : 0.5;
 
					ctx.fillText( text + ': ' + ( status ? 'ON' : 'OFF' ), ctx.canvas.width / 2, 10 + i * fontSize );
 
				}
 
				ctx.globalAlpha = 1;
 
				if ( players.length === 0 ) {
 
					return;
 
				}
 
				ctx.lineWidth = 5;
				ctx.strokeStyle = 'red';
 
				if ( espEnabled ) {
 
					const centerX = ctx.canvas.width / 2;
					const centerY = ctx.canvas.height / 2;
 
					ctx.beginPath();
 
					for ( let i = 0; i < players.length; i ++ ) {
 
						const player = players[ i ];
 
						ctx.moveTo( centerX, centerY );
 
						ctx.lineTo( player.x, player.y );
 
					}
 
					ctx.stroke();
 
				}
 
				if ( aimbotEnabled ) {
 
					let minDistance = Infinity;
					let targetPlayer;
 
					for ( let i = 0; i < players.length; i ++ ) {
 
						const player = players[ i ];
 
						const distance = Math.hypot( player.x - mouseX, player.y - mouseY );
 
						if ( distance < minDistance ) {
 
							minDistance = distance;
							targetPlayer = player;
 
						}
 
					}
 
					ctx.beginPath();
 
					ctx.arc( targetPlayer.x, targetPlayer.y, radius, 0, Math.PI * 2 );
 
					ctx.stroke();
 
					window.dispatchEvent( new MouseEvent( 'mousemove', {
						clientX: targetPlayer.x,
						clientY: targetPlayer.y,
						dispatchedByMe: true
					} ) );
 
				}
 
			}
		} );
 
		return Reflect.apply( ...arguments );
 
	}
} );
 
window.addEventListener( 'DOMContentLoaded', function () {
 
	const value = parseInt( new URLSearchParams( window.location.search ).get( 'showAd' ), 16 );
 
	const shouldShowAd = isNaN( value ) || Date.now() - value < 0 || Date.now() - value > 10 * 60 * 1000;
 
	const el = document.createElement( 'div' );
 
	el.innerHTML = `<style>
 
	.my-dialog {
		position: absolute;
		left: 50%;
		top: 50%;
		padding: 20px;
		background: rgba(0, 0, 0, 0.9);
		box-shadow: 0 0 0 1000vw rgba(0, 0, 0, 0.5);
		border-radius: 5px;
		color: #fff;
		transform: translate(-50%, -50%);
		text-align: center;
		z-index: 999999;
	}
 
	.my-dialog * {
		color: #fff;
	}
 
	.my-close {
		position: absolute;
		right: 5px;
		top: 5px;
		width: 20px;
		height: 20px;
		opacity: 0.5;
		cursor: pointer;
	}
 
	.my-close:before, .my-close:after {
		content: ' ';
		position: absolute;
		left: 50%;
		top: 50%;
		width: 100%;
		height: 20%;
		transform: translate(-50%, -50%) rotate(-45deg);
		background: #fff;
	}
 
	.my-close:after {
		transform: translate(-50%, -50%) rotate(45deg);
	}
 
	.my-close:hover {
		opacity: 1;
	}
 
	</style>
	<div class="my-dialog">${shouldShowAd ? `<big>Loading ad...</big>` : `<div class="my-close" onclick="this.parentNode.style.display='none';"></div>
		<big style="font-size: 2em;">Aimbot, ESP & X-Ray</big>
		<br>
		<br>
		[B] to toggle aimbot
		<br>
		[H] to toggle x-ray
		<br>
		[N] to toggle esp
		<br>
		<br>
		By Zertalious
		<br>
		<br>
		<div class="btn-purple btn-darken menu-option" style="position: unset !important;" onclick="window.open('https://discord.gg/K24Zxy88VM')">Discord</div>
		<div class="btn-orange btn-darken menu-option" onclick="window.open('https://www.instagram.com/zertalious/', '_blank')">Instagram</div>
		<div class="btn-blue btn-darken menu-option" onclick="window.open('https://twitter.com/Zertalious', '_blank')">Twitter</div>
		<div class="btn-green btn-darken menu-option" onclick="window.open('https://greasyfork.org/en/users/662330-zertalious', '_blank')">More scripts</div>
		` }
	</div>`;
 
	while ( el.children.length > 0 ) {
 
		document.body.appendChild( el.children[ 0 ] );
 
	}
 
	if ( shouldShowAd ) {
 
		const url = new URL( window.location.href );
 
		url.searchParams.set( 'showAd', Date.now().toString( 16 ) );
 
		window.location.href = 'https://zertalious.xyz?ref=' + new TextEncoder().encode( url.href ).toString();
 
	}
 
} );