Greasy Fork

FlatmmoPlus - Framework

framework for FlatMMO

当前为 2025-06-02 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/538103/1600331/FlatmmoPlus%20-%20Framework.js

// ==UserScript==
// @name         FlatmmoPlus - Framework
// @namespace    com.blank.flatmmo
// @version      0.0.1
// @description  framework for FlatMMO
// @author       blank
// @match        *://flatmmo.com/play.php*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    const VERSION = "0.0.1";

    if(window.FlatmmoPlus) {
        // already loaded
        return;
    }

    function logFancy(s, color="#00f7ff") {
        console.log("%cIdlePixelPlus: %c"+s, `color: ${color}; font-weight: bold; font-size: 12pt;`, "color: black; font-weight: normal; font-size: 10pt;");
    }


    class FlatmmoPlusPlugin {
        constructor(id, opts = {}) {
            if (typeof id !== "string") {
                throw new TypeError("FlatmmoPlusPlugin constructor requires (id: string, opts?: object)");
            }

            this.id = id;
            this.opts = opts;

            this.config = {}; // placeholder for future config handling
        }

        onLogin() {}
        onMessageReceived(data) {}
        onMessageSent(data) {}
        onVariableSet(key, valueBefore, valueAfter) {}
    }


    class FlatmmoPlus {
        constructor() {
            console.log('[Framework] Initialising');
            this.messageQueue = [];
            this.hookedSockets = new WeakSet();
            this.plugins = {};  // NEW: store registered plugins
            this.init();
        }

        registerPlugin(plugin) {
            if (!(plugin instanceof FlatmmoPlusPlugin)) {
                console.warn(`[Framework] Invalid plugin:`, plugin);
                return;
            }

            const id = plugin.id;
            if (this.plugins[id]) {
                console.warn(`[Framework] Plugin "${id}" already registered.`);
                return;
            }

            this.plugins[id] = plugin;

            const version = plugin.opts?.about?.version || "?";
            logFancy(`registered plugin "${id}" (v${version})`);
        }

        broadcast(methodName, ...args) {
            for (const plugin of Object.values(this.plugins)) {
                const fn = plugin[methodName];
                if (typeof fn === "function") {
                    try {
                        fn.apply(plugin, args);
                    } catch (err) {
                        console.error(`[Framework] Error in plugin "${plugin.id}" method "${methodName}":`, err);
                    }
                }
            }
        }

        init() {
            this.overrideGlobalWebSocket();
            this.waitForGameVisibility();
        }

        overrideGlobalWebSocket() {
            const NativeWebSocket = window.WebSocket;
            const self = this;

            console.log('[Framework] Overriding global WebSocket constructor');

            window.WebSocket = function (...args) {
                const ws = new NativeWebSocket(...args);
                setTimeout(() => self.hookSocket(ws), 1000);
                return ws;
            };

            window.WebSocket.prototype = NativeWebSocket.prototype;
            Object.assign(window.WebSocket, NativeWebSocket);
        }

        hookSocket(ws) {
            if (!ws || this.hookedSockets.has(ws)) return;

            this.hookedSockets.add(ws);

            const origSend = ws.send;
            ws.send = (...args) => {
                const data = args[0];
                this.onMessageSent(data);
                return origSend.apply(ws, args);
            };

            const origOnMessage = ws.onmessage;
            ws.onmessage = (event) => {
                this.onMessageReceived(event.data);
                if (typeof origOnMessage === 'function') {
                    origOnMessage.call(ws, event);
                }
            };
        }

        waitForGameVisibility() {
            const gameDiv = document.getElementById('game');
            if (!gameDiv) return console.warn('[Framework] #game not found');

            const obs = new MutationObserver(() => {
                const visible = window.getComputedStyle(gameDiv).display !== 'none';
                if (visible) {
                    obs.disconnect();
                    this.onLogin();
                }
            });

            obs.observe(gameDiv, { attributes: true, attributeFilter: ['style'] });
        }

        // Event relays to plugins
        onMessageReceived(data) {
            this.broadcast("onMessageReceived", data);
        }

        onMessageSent(data) {
            this.broadcast("onMessageSent", data);
        }

        onLogin() {
            this.broadcast("onLogin");
        }
    }

    // Add to window and init
    window.FlatmmoPlusPlugin = FlatmmoPlusPlugin;
    window.FlatmmoPlus = new FlatmmoPlus();


})();