Waypoints [Sploop.io]

Easily traverse the world of Sploop.io with Waypoints!

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Waypoints [Sploop.io]
// @name:zh      航点 [Sploop.io]
// @name:tr      Yolu Göstergeleri [Sploop.io]
// @name:ru      Точки маршрута [Sploop.io]
// @name:vi      Điểm chỉ đường [Sploop.io]
// @name:ar      علامات الطريق [Sploop.io]
// @description  Easily traverse the world of Sploop.io with Waypoints!
// @description:zh 轻松通过航点在Sploop.io世界中穿行!
// @description:tr  Kolayca Sploop.io dünyasında yol göstergeleri ile gezin!
// @description:ru  Легко путешествуйте по миру Sploop.io с помощью точек маршрута!
// @description:vi  Dễ dàng di chuyển trong thế giới Sploop.io với các điểm chỉ đường!
// @description:ar  اجتاز عالم Sploop.io بسهولة باستخدام علامات الطريق!
// @version      2.0.0
// @author       Hori + viper
// @match        https://sploop.io/
// @icon         https://i.ibb.co/ym4LXJF7/Waypoints-3-removebg-preview.png
// @grant        none
// @run-at       document-start
// @license      MIT
// @namespace https://greasyfork.org/users/1441649
// ==/UserScript==

/**
 *  Authors: horizion, viper
 *
 *  Menu Keybind : ESCAPE
 *
 *  (!) - You are able to modify the script in any way, as long as it abides by the in-game rules.
 *  (!) - Contact Horizion if the script does not work.
 *
 *  Discord: horizon2025.#0000 | <@880415818142347297>
 *
 *  Official Sploop.io Discord Server: https://discord.com/invite/CYadgpyv78
 */

(function () {
	"use strict";
	const win = window;
	const log = console.log;
	const version = `2.0.0`;

	const ARROW_DISTANCE_FROM_PLAYER = 210;
	const COLORS = [
		"red",
		"orange",
		"yellow",
		"green",
		"dark_blue",
		"light_blue",
		"purple",
		"violet",
		"pink",
		"rose",
	];
	const MARKER_IMAGES = [
		"https://i.ibb.co/20h3vpRG/map-cross-red.png",
		"https://i.ibb.co/VWZCzbgK/map-cross-orange.png",
		"https://i.ibb.co/x8jT4J4x/map-cross-yellow.png",
		"https://i.ibb.co/dstqTPQP/map-cross-darkgreen.png",
		"https://i.ibb.co/dsSZtg0Q/map-cross-darkblue.png",
		"https://i.ibb.co/4RPBRMH7/map-cross-lightblue.png",
		"https://i.ibb.co/84KYxdNd/map-cross-purple.png",
		"https://i.ibb.co/8LJ4FbbW/map-cross-indigo.png",
		"https://i.ibb.co/bgNv5NB7/map-cross-pink.png",
		"https://i.ibb.co/5WMsX1f1/map-cross-main.png",
	];
	const ARROW_IMAGES = [
		"https://i.ibb.co/chgGndnF/arrow-red.png",
		"https://i.ibb.co/LdkChynd/arrow-orange.png",
		"https://i.ibb.co/v6x76XN6/arrow-yellow.png",
		"https://i.ibb.co/2YYvqNHj/arrow-darkgreen.png",
		"https://i.ibb.co/6RrD7MvR/arrow-darkblue.png",
		"https://i.ibb.co/9kqtG2k4/arrow-lightblue.png",
		"https://i.ibb.co/ZzjZhK0g/arrow-purple.png",
		"https://i.ibb.co/sdgDvP0Y/arrow-indigo.png",
		"https://i.ibb.co/yFFq6jBN/arrow-pink.png",
		"https://i.ibb.co/zT2rYdFT/arrow-main.png",
	];
	const MARKER_COLOR_IMAGES = {
		red: "https://i.ibb.co/ksRyM6FY/circle-red.png",
		orange: "https://i.ibb.co/4gNKXnp6/circle-orange.png",
		yellow: "https://i.ibb.co/60rHrBWP/circle-yellow.png",
		green: "https://i.ibb.co/kVGgcVKT/circle-green.png",
		dark_blue: "https://i.ibb.co/9mbLDCLq/circle-darkblue.png",
		light_blue: "https://i.ibb.co/LhNv7JXx/circle-lightblue.png",
		purple: "https://i.ibb.co/WvddsXyk/circle-purple.png",
		violet: "https://i.ibb.co/PZKjyN2g/circle-indigo.png",
		pink: "https://i.ibb.co/bf2LzgM/circle-pink.png",
		rose: "https://i.ibb.co/0pMvXrMS/circle-main.png",
	};

	let waypointID = 0;
	let waypoints = [];
	let cards = [];

	win.showWaypointArrows = 1;

	//** Styling DOM */

	const style = document.createElement("style");
	style.type = "text/css";
	style.textContent = `
        :root {
            --main: #de5978;
            --secondary: rgb(43, 37, 37, 1);
        }
        button:focus {
            border: none;
            outline: none;
        }
        input:focus {
            outline: none;
            border: none;
        }

        /* Making scroll bar more pro */

        ::-webkit-scrollbar {
            width: 7px;
        }

        ::-webkit-scrollbar-thumb {
            background-color: var(--main);
            border-top-right-radius: 10px;
            border-bottom-right-radius: 10px;
            border-top-left-radius: 0;
            border-bottom-left-radius: 0;
        }

        ::-webkit-scrollbar-track {
            background-color: var(--secondary);
            border-top-right-radius: 10px;
            border-bottom-right-radius: 10px;
            border-top-left-radius: 0;
            border-bottom-left-radius: 0;
        }

        /* Main menu styling */

        #wayMenu {
            display: none;
            position: fixed;
            top: 160px;
            left: 293px;
            z-index: 99999;
            padding: 20px;
            background: var(--secondary);
            border-radius: 10px;
            max-height: 60%;
            overflow-y: auto;
            width: 400px;
            border: 1.5px solid var(--main);
        }
        .wayHeader {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 10px;
        }
        .wayTitle {
            font-size: 20px;
            color: var(--main);
        }
        .version {
            font-size: 15px;
            color: rgba(100, 100, 100,1);
        }
        .wayArrowToggle {
            display: flex;
            align-items: center;
            gap: 5px;
            opacity: 0;
            transition: all 0.3s ease-in-out;
            pointer-events: none;
        }
        .wayArrowToggle i {
            color: var(--main);
            font-size: 18px;
        }

        .switch {
            position: relative;
            display: inline-block;
            width: 40px;
            height: 20px;
        }

        .switch input {
            opacity: 0;
            width: 0;
            height: 0;
        }

        .slider {
            position: absolute;
            cursor: pointer;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: rgb(41, 41, 41);
            transition: 0.3s;
            border-radius: 20px;
            border: 1px solid rgb(112, 53, 74);
        }

        .slider:before {
            position: absolute;
            content: "";
            height: 14px;
            width: 14px;
            left: 3px;
            background-color: var(--main);
            bottom: 3px;
            transition: 0.3s;
            border-radius: 50%;
        }

        input:checked + .slider {
            background-color: rgba(187, 75, 101,0.6);
        }

        input:checked + .slider:before {
            transform: translateX(20px);
        }
        .wayCreateNewButton {
            margin-top: 25px;
            border: none;
            color: white;
            width: 250px;
            text-align: center;
            border-radius: 10px;
            padding: 7px;
            background: var(--main);
            font-size: 18px;
            cursor: pointer;
            font-weight: 990;
            height:35px;
            transition: transform 0.4s ease;
            border: 1px solid var(--secondary);
        }
        .wayCreateNewButton:hover {
            transform: translateY(-1px);
        }
        .wayCreateNewButton:disabled {
            background:rgb(187, 75, 101);
            color:rgba(156, 156, 156, 0.99);
            cursor: not-allowed;
            opacity: 0.85;
            transform: none;
        }
        .wayNotFound {
            text-align: center;
            display: block;
            font-size: 15px;
            color: white;
            margin-top: 20px;
        }
        .wayCreate {
        text-align: center;
        }

        /* popup styling */

        #wayPopMenu {
            display: block;
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            padding: 25px;
            background: var(--secondary);
            border-radius: 10px;
            max-height: 50%;
            overflow-y: auto;
            text-align: center;
        }
        .popupHeader {
            position: absolute;
            top: 0;
            right: 8px;
            margin-bottom: 8px;
        }
        .closePopup {
            font-size: 24px;
            font-weight: bold;
            cursor: pointer;
            color: #fff;
        }
        .popupHeader:hover {
            transform: translateY(-1px);
        }
        #markerName {
            border-radius: 10px;
            height:35px;
            text-align: center;
            font-size: 16px;
            border: none;
            width: 200px;
        }
        #wayColors {
            padding: 7px;
            height:35px;
            width: 200px;
            border: none;
            border-radius: 10px;
            font-size: 16px;
            margin-bottom: 16px;
            background: #fff;
            color: #333;
            -webkit-appearance: none;
            -moz-appearance: none;
            appearance: none;
            text-align: center;
            font-weight: 800;
            cursor: pointer;
        }
        .wayCreateButton {
            border: none;
            color: white;
            width: 200px;
            text-align: center;
            border-radius: 10px;
            padding: 7px;
            background: var(--main);
            cursor: pointer;
            font-weight: 560;
            font-size: 16px;
            height:35px;
            transition: transform 0.4s ease;
        }
        .wayCreateButton:focus{
            border:none;
        }
        .wayCreateButton:hover {
            transform: translateY(-1px);
        }

        /* Card Styling */

        .card {
            display: flex;
            align-items: center;
            background:rgb(41, 41, 41);
            border-radius: 10px;
            padding: 10px;
            margin-top: 10px;
            color: white;
            border: 1px solid rgb(112, 53, 74);
        }

        .card img {
            width: 22px;
            height: 22px;
            margin-right: 10px;
            border-radius: 50%;
        }

        .card .card-name {
            flex: 4;
            font-size: 16px;
            margin-right: 10px;
            max-width: 145px;
        }

        .card .card-coords {
            flex: 3;
            font-size: 16px;
            margin-right: 10px;
            color: rgba(100, 100, 100,1);
        }

        .card .card-remove {
            background: #e43939;
            border: none;
            color: white;
            padding: 5px 10px;
            border-radius: 5px;
            cursor: pointer;
        }
        .card .card-remove:hover {
            transform: translateY(-1px);
        }
        #trash:hover {
            cursor: pointer;
        }
    `;
	document.head.appendChild(style);

	//** Main */

	document.addEventListener("DOMContentLoaded", () => {
		log(
			`%cWaypoints %cVersion ${version}\n%cCreators: %cviper + Hori`,
			"font-size: 90px; font-weight: 900; color: #de5978;",
			"font-size: 20px; color: gray;",
			"font-size: 20px; font-weight: 900; color:rgb(255, 255, 255); margin-top: 8px; margin-bottom: 10px;",
			"font-size: 20px; color:rgb(235, 187, 97); margin-top: 8px; margin-bottom: 10px;"
		);

		const wayMenu = document.createElement("div");
		wayMenu.id = "wayMenu";
		wayMenu.innerHTML = `
            <div class="wayHeader">
                <span class="wayTitle">Waypoints <span class="version">- v${version}</span></span>
                <div class="wayArrowToggle">
                    <i class="fa-solid fa-location-arrow"></i>
                    <label class="switch">
                        <input type="checkbox" id="waypointToggle" checked>
                        <span class="slider"></span>
                    </label>
                </div>
            </div>
            <div class="wayContent">
                <div id="wayNotFound" class="wayNotFound">
                    No Waypoints Found
                    <i class="fa-solid fa-magnifying-glass"></i>
                </div>
            </div>
            <div class="wayCreate">
                <button id="wayCreateNewButton" class="wayCreateNewButton" disabled>+</button>
            </div>
            <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.3.0/css/all.min.css" integrity="sha512-SzlrxWUlpfuzQ+pcUCosxcglQRNAq/DZjVsC0lE40xsADsfeQoEypE+enwcOiGjk/bSuGGKHEyjSoQ1zVisanQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
        `;
		document.body.append(wayMenu);

		let isDragging = false,
			displayToggled = false,
			offsetX,
			offsetY;

		wayMenu.addEventListener("mousedown", function (event) {
			isDragging = true;
			offsetX = event.clientX - wayMenu.getBoundingClientRect().left;
			offsetY = event.clientY - wayMenu.getBoundingClientRect().top;
		});
		document.addEventListener("mousemove", function (event) {
			if (isDragging) {
				const newX = event.clientX - offsetX;
				const newY = event.clientY - offsetY;
				wayMenu.style.left = newX + "px";
				wayMenu.style.top = newY + "px";
			}
		});
		document.addEventListener("mouseup", () => {
			isDragging = false;
		});

		document
			.getElementById("wayCreateNewButton")
			.addEventListener("click", () => {
				if (!document.getElementById("wayPopMenu")) {
					createPopup();
				}
			});

		document.addEventListener("keydown", (event) => {
			if (event.key === "Escape" && !isChatWrapperVisible()) {
				wayMenu.style.display = displayToggled ? "none" : "block";
				displayToggled = !displayToggled;
			}
		});

		document
			.getElementById("waypointToggle")
			.addEventListener("change", function () {
				if (this.checked) {
					win.showWaypointArrows = 1;
				} else {
					win.showWaypointArrows = 0;
				}
			});

		const observer = new MutationObserver(() => {
			updateCreateButton();
		});
		const homepage = document.getElementById("homepage");
		if (homepage) {
			observer.observe(homepage, {
				attributes: true,
				attributeFilter: ["style"],
			});
		}
	});

	const addWaypoint = new Proxy(waypoints, {
		set(target, property, value, receiver) {
			const result = Reflect.set(target, property, value, receiver);
			if (!isNaN(property)) {
				createCard(value);
			}
			return result;
		},
	});

	function createPopup() {
		let wayPopMenu = document.createElement("div");
		wayPopMenu.id = "wayPopMenu";
		wayPopMenu.innerHTML = `
            <div class="popupHeader">
                <span id="closePopup" class="closePopup">x</span>
            </div>
            <div class="inputContainer">
                <input type="text" id="markerName"placeholder="New Waypoint" maxLength="13"><br><br>
            </div>
            <div class="wayColor">
                <select name="colors" id="wayColors">
                    <option value="0">Red</option>
                    <option value="1">Orange</option>
                    <option value="2">Yellow</option>
                    <option value="3">Green</option>
                    <option value="4">Dark Blue</option>
                    <option value="5">Light Blue</option>
                    <option value="6">Purple</option>
                    <option value="7">Violet</option>
                    <option value="8">Pink</option>
                    <option value="9">Rose</option>
                </select>
            </div>
            <div class="wayCreate">
                <button id="createButton" class="wayCreateButton">Create</button>
            </div>
        `;
		document.body.appendChild(wayPopMenu);

		document
			.getElementById("markerName")
			.addEventListener("keydown", function (event) {
				event.stopPropagation();
			});
		document.getElementById("closePopup").addEventListener("click", () => {
			wayPopMenu.remove();
		});
		document
			.getElementById("createButton")
			.addEventListener("click", getOption);
	}

	function createCard(waypoint) {
		cards.push({
			name: waypoint.name,
			id: waypoint.id,
		});

		const card = document.createElement("div");
		card.className = "card";
		card.innerHTML = `
            <img src="${MARKER_COLOR_IMAGES[waypoint.color]}" alt="color">
            <div class="card-name">${waypoint.name}</div>
            <div class="card-coords">[${Math.round(waypoint.x)},${Math.round(waypoint.y)}]</div>
            <button class="card-remove"><i class="fa-solid fa-trash" id="trash"></i></button>
        `;

		document.querySelector(".wayContent").appendChild(card);

		updateArrowToggleAlpha();

		const removeButton = card.querySelector(".card-remove");

		removeButton.addEventListener("click", function () {
			const waypointIndex = waypoints.findIndex(
				(way) => way.id === waypoint.id
			);
			if (waypointIndex > -1) {
				waypoints.splice(waypointIndex, 1);
			}
			const cardIndex = cards.findIndex(
				(card) => card.id === waypoint.id
			);
			if (cardIndex > -1) {
				cards.splice(cardIndex, 1);
			}
			card.remove();
			if (cards.length === 0) {
				document.getElementById("wayNotFound").style.display = "block";
			}
			updateArrowToggleAlpha();
		});
	}

	function createWaypoint(name, color) {
		const chosenMarker = MARKER_IMAGES[color];
		const chosenArrow = ARROW_IMAGES[color];

		const markerImg = createImage(chosenMarker);
		const arrowImg = createImage(chosenArrow);

		const newWaypoint = {
			name,
			id: waypointID++,
			arrow: arrowImg,
			marker: markerImg,
			x: win.coords.x,
			y: win.coords.y,
			color: COLORS[color],
		};

		addWaypoint.push(newWaypoint);
	}
	function getOption() {
		const markerName = document.getElementById("markerName").value;
		const wayColor = document.getElementById("wayColors");
		const color = wayColor.value;
		if (markerName !== "") {
			createWaypoint(markerName, color);
			document.getElementById("wayNotFound").style.display = "none";
			document.getElementById("wayPopMenu").remove();
		}
	}
	function updateCreateButton() {
		const button = document.getElementById("wayCreateNewButton");
		const homepage = document.getElementById("homepage");

		if (homepage && homepage.style.display === "flex") {
			button.disabled = true;
		} else {
			button.disabled = false;
		}
	}
	function updateArrowToggleAlpha() {
		const toggle = document.querySelector(".wayArrowToggle");
		if (toggle) {
			toggle.style.opacity = cards.length === 0 ? "0" : "1";
			toggle.style.pointerEvents = cards.length === 0 ? "none" : "auto";
		}
	}
	function createImage(URL) {
		const image = new Image();
		image.src = URL;
		return image;
	}
	function isChatWrapperVisible() {
		return (
			document.getElementById("chat-wrapper").style.display === "block"
		);
	}

	//** Draw Waypoints, Arrows, Names */

	class Drawing {
		arrows(context, arrow, angle, x, y) {
			context.save();
			context.globalAlpha = 0.52;
			context.translate(x, y);
			context.rotate(angle);
			context.drawImage(arrow, -25 / 2, -25 / 2, 25, 25);
			context.restore();
		}
		name(context, waypoint) {
			const x = waypoint.x;
			const y = waypoint.y - 42;

			context.font = "24px Baloo Paaji";
			context.textAlign = "center";
			context.lineWidth = 8;
			context.strokeStyle = "#414141";
			context.lineJoin = "round";
			context.strokeText(waypoint.name, x, y);
			context.fillStyle = "white";
			context.fillText(waypoint.name, x, y);
		}
		waypoint(context, waypoint) {
			context.drawImage(
				waypoint.marker,
				waypoint.x - 70 / 2,
				waypoint.y - 70 / 2,
				70,
				70
			);
		}
	}
	const Draw = new Drawing();

	win.draw = (context) => {
		waypoints.forEach((waypoint) => {
			const distance = Math.hypot(
				waypoint.x - win.coords.x,
				waypoint.y - win.coords.y
			);

			if (distance < 1200) {
				Draw.waypoint(context, waypoint);
				Draw.name(context, waypoint);
			}
			if (win.showWaypointArrows && distance > 450) {
				const angle = Math.atan2(
					waypoint.y - win.coords.y,
					waypoint.x - win.coords.x
				);
				let radius = ARROW_DISTANCE_FROM_PLAYER;

				const x = win.coords.x + Math.cos(angle) * radius;
				const y = win.coords.y + Math.sin(angle) * radius;

				Draw.arrows(context, waypoint.arrow, angle, x, y);
			}
		});
	};

	//** Hooking onto the game */

	const TYPEOF = (value) =>
		Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
	const NumberSystem = [
		{
			radix: 2,
			prefix: "0b0*",
		},
		{
			radix: 8,
			prefix: "0+",
		},
		{
			radix: 10,
			prefix: "",
		},
		{
			radix: 16,
			prefix: "0x0*",
		},
	];
	class Regex {
		constructor(code, unicode, namespace) {
			if (!namespace)
				namespace =
					"new_script_" + Math.random().toString(36).substr(2, 8);
			this.code = code;
			this.COPY_CODE = code;
			this.unicode = unicode || false;
			this.hooks = {};
			this.namespace = namespace;
			this.totalHooks = 0;
		}

		static parseValue(value) {
			try {
				return Function(`return (${value})`)();
			} catch (err) {
				return null;
			}
		}

		isRegexp(value) {
			return TYPEOF(value) === "regexp";
		}

		generateNumberSystem(int) {
			const template = NumberSystem.map(
				({ prefix, radix }) => prefix + int.toString(radix)
			);
			return `(?:${template.join("|")})`;
		}

		parseVariables(regex) {
			regex = regex.replace(/\{VAR\}/g, "(?:let|var|const)");
			regex = regex.replace(/\{QUOTE\}/g, "['\"`]");
			regex = regex.replace(/ARGS\{(\d+)\}/g, (...args) => {
				let count = Number(args[1]),
					arr = [];
				while (count--) arr.push("\\w+");
				return arr.join("\\s*,\\s*");
			});
			regex = regex.replace(/NUMBER\{(\d+)\}/g, (...args) => {
				const int = Number(args[1]);
				return this.generateNumberSystem(int);
			});
			return regex;
		}

		_hookName(name) {
			return `${this.namespace}:${name}`;
		}

		format(name, inputRegex, flags) {
			this.totalHooks++;
			let regex = "";
			if (Array.isArray(inputRegex)) {
				regex = inputRegex
					.map((exp) => (this.isRegexp(exp) ? exp.source : exp))
					.join("\\s*");
			} else if (this.isRegexp(inputRegex)) {
				regex = inputRegex.source;
			}
			regex = this.parseVariables(regex);
			if (this.unicode) {
				regex = regex.replace(/\\w/g, "(?:[^\\x00-\\x7F-]|\\$|\\w)");
			}
			const expression = new RegExp(
				regex.replace(/\{INSERT\}/, ""),
				flags
			);
			return regex.includes("{INSERT}")
				? new RegExp(regex, flags)
				: expression;
		}

		template(type, name, regex, substr) {
			const hookName = this._hookName(name);
			const expression = new RegExp(
				`(${this.format(hookName, regex).source})`
			);
			const match = this.code.match(expression) || [];
			this.code = this.code.replace(
				expression,
				type === 0 ? "$1" + substr : substr + "$1"
			);
			this.hooks[hookName] = { expression, match };
			return match;
		}

		logHooks() {
			log(
				"%cApplied Hooks:",
				"font-weight: bold; font-size: 24px; color:rgb(255, 255, 255); margin-top: 20px;"
			);
			let index = 1;
			for (const hookName in this.hooks) {
				const hook = this.hooks[hookName];
				const matches = Array.isArray(hook.match)
					? hook.match
					: [hook.match];
				const status =
					matches && matches.length > 0
						? "successful"
						: "unsuccessful";
				const style =
					matches && matches.length > 0
						? "color:rgb(134, 204, 125); font-size: 14px;"
						: "color:rgb(221, 94, 113); font-size: 14px;";
				log(`%c${index}. ${hookName} | ${status}`, style);
				index++;
			}
			log(
				"%c-----------------",
				"font-weight: bold; color: gray; margin-bottom: 5px; margin-top: 20px;"
			);
		}

		match(name, regex, flags) {
			const hookName = this._hookName(name);
			const expression = this.format(hookName, regex, flags);
			const match = this.code.match(expression) || [];
			this.hooks[hookName] = { expression, match };
			return match;
		}

		replace(name, regex, substr, flags) {
			const hookName = this._hookName(name);
			const expression = this.format(hookName, regex, flags);
			const preMatch = this.code.match(expression) || [];
			this.code = this.code.replace(expression, substr);
			this.hooks[hookName] = {
				expression,
				match: preMatch,
				replaced: preMatch.length > 0,
			};
			return this.code.match(expression) || [];
		}

		append(name, regex, substr) {
			return this.template(0, name, regex, substr);
		}

		prepend(name, regex, substr) {
			return this.template(1, name, regex, substr);
		}
	}

	const applyHooks = (code) => {
		const namespace = `waypoints_v${version}`;
		const Hook = new Regex(window.HOOKED_CODE || code, true, namespace);

		window.COPY_CODE = (Hook.COPY_CODE.match(
			/^(\(function \w+\(\w+\)\{.+)\(.+?\);$/
		) || [])[1];
		// log(Hook.COPY_CODE);

		if (!window.HOOKED_CODE)
			Hook.append(
				"EXTERNAL fix",
				/\(function (\w+)\(\w+\)\{/,
				"let $2 = eval(`(() => ${COPY_CODE})()`); delete window.COPY_CODE;"
			);
		Hook.replace("LOADER", /Loading Sploop.io/, `Loading...`);
		Hook.append(
			"COORDS",
			/,this\.\w{2}=0\},this\.\w{2}=\w+\((\w),(\w),\w\)\{/,
			`window.coords = { x: $2, y: $3 };`
		);
		Hook.prepend("DRAW",/\w=\w\[\w\(\)\.\w+\],\w=\w.{7,8};for\(let \w=\d;\w\W\w;\w\+\+\)!\(\w\[\w\]\.\w+&\w\(\)\.\w+\)&&\w+\(\w\[\w\],(\w),\w\);/,"/*55306335646D457A5457645A4D3070735756685362467044516D6C6C553046335A555657616D4649546C5A50626B70365756453950513D3D*/window.draw($2);");

		// log(Hook.code);
		window.HOOKED_CODE = Hook.code;

		Hook.logHooks();
		return Hook.code;
	};
	window.eval = new Proxy(window.eval, {
		apply(target, _this, args) {
			const code = args[0];
			if (code.length > 1e5) {
				args[0] = applyHooks(code);
				window.eval = target;
				target.apply(_this, args);
				return;
			}
			return target.apply(_this, args);
		},
	});
})();