FlatmmoPlus - Framework

framework for FlatMMO

Version vom 02.06.2025. Aktuellste Version

Dieses Skript sollte nicht direkt installiert werden. Es handelt sich hier um eine Bibliothek für andere Skripte, welche über folgenden Befehl in den Metadaten eines Skriptes eingebunden wird // @require https://update.greasyfork.ip-ddns.com/scripts/538103/1600334/FlatmmoPlus%20-%20Framework.js

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

(function () {
    'use strict';

    const VERSION = "0.0.2";

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

    function logFancy(s, color="#00f7ff") {
        console.log("%cFlatmmoPlus: %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();


})();