您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
FlatMMO plugin framework (Heavily inspired by & copied from Anwinity's IdlePixel+)
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/538103/1603307/Flatmmo%2B.js
// ==UserScript== // @name Flatmmo+ TESTING // @namespace com.Zlef.flatmmo // @version 0.0.6 // @description FlatMMO plugin framework (Heavily inspired & copied from Anwinity's IdlePixel+) // @author Zlef // @match *://flatmmo.com/play.php* // @grant none // ==/UserScript== // LIVE VERSION = 0.0.6 (function () { 'use strict'; const VERSION = "0.0.6"; 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: white; 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; console.log(`[FlatmmoPlusPlugin] Initialising plugin: ${id}`); this.config = {}; // placeholder for future config use try { if (opts.settings) { console.log(`[FlatmmoPlusPlugin] Found settings for ${id}`); if (typeof window.zlef?.SettingsManager !== "function") { throw new Error("zlef.SettingsManager is not defined yet."); } this.settingsManager = new zlef.SettingsManager(id, opts.settings); this.settings = this.settingsManager.getSettings(); console.log(`[FlatmmoPlusPlugin] Created SettingsManager for ${id}`); console.log(`[FlatmmoPlusPlugin] Loaded settings for ${id}:`, this.settings); } } catch (err) { console.error(`[FlatmmoPlusPlugin] Failed to initialise settings for "${id}":`, err); } /* if (opts.settings) { console.log(`[FlatmmoPlusPlugin] Found settings for ${id}`); const sectionTitle = opts.about?.name || id.replace(/([A-Z])/g, ' $1').trim(); this.settingsManager = new zlef.SettingsManager(id, opts.settings); this.settings = this.settingsManager.getSettings(); console.log(`[FlatmmoPlusPlugin] Created SettingsManager for ${id}`); console.log(`[FlatmmoPlusPlugin] Loaded settings for ${id}:`, this.settings); } */ } getSetting(key, type = null) { return window.FlatmmoPlus?.getSetting(`${this.id}.${key}`, type); } onLogin() {} onMessageReceived(data) {} onMessageSent(data) {} onVariableSet(key, valueBefore, valueAfter) {} onSettingsChanged() {} } class FlatmmoPlus { constructor() { console.log('[Framework] Initialising'); logFancy(`VERSION ${VERSION}`); this.messageQueue = []; this.hookedSockets = new WeakSet(); this.plugins = {}; this.login_on_ws = true; this._hasLoggedIn = false; // Custom panel support this.customPanels = []; // { id, pluginId } this.customPanelPage = 0; this.activePanel = "inventory"; this.bottomButtons = []; // last 5 interface buttons this.customPanelButtons = []; this.init(); } getSetting(key, type = null) { if (!this.settingsManagers) return undefined; const [pluginId, ...path] = key.split("."); const manager = this.settingsManagers[pluginId]; if (!manager) { console.warn(`[FlatmmoPlus] No settings manager for plugin "${pluginId}"`); return undefined; } let current = manager.getSettings(); for (let i = 0; i < path.length; i++) { const part = path[i]; if (!current[part]) return undefined; const setting = current[part]; // If it's a section, go deeper if (setting.type === "section") { current = setting.settings; } else { // If we're at the last part, return the value with optional coercion if (i === path.length - 1) { let value = setting.value; if (type) { switch (type.toLowerCase()) { case "int": case "integer": return parseInt(value); case "bool": case "boolean": return value === true || value === "true"; case "string": return String(value); default: console.warn(`[FlatmmoPlus] Unknown type coercion '${type}' for setting '${key}'`); return value; } } return value; } else { // Trying to go deeper into a non-section setting return undefined; } } } return undefined; } 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; if (plugin.settingsManager) { if (!this.settingsManagers) this.settingsManagers = {}; this.settingsManagers[plugin.id] = plugin.settingsManager; } 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() { window.FlatmmoPlusSettingsManagers = {}; this.initCustomCSS(); this.overrideGlobalWebSocket(); if (!this.login_on_ws) { this.waitForGameVisibility(); } } initCustomCSS() { const css = ` .fmp-button-container { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 0px; justify-content: flex-start; align-items: center; } .fmp-back-btn { display: none; width: 15px; height: 50px; line-height: 50px; font-size: 18px; padding: 0; text-align: center; background: white; color: black; } .fmp-next-btn { display: none; width: 15px; height: 50px; line-height: 50px; font-size: 18px; padding: 0; text-align: center; background: white; color: black; } .fmp-panel-btn { width: 50px; height: 50px; background-size: cover; background-position: center; } .fmp-custom-panel { display: none; max-height: 500px; overflow-y: auto; } `; const style = document.createElement('style'); style.type = 'text/css'; style.appendChild(document.createTextNode(css)); document.head.appendChild(style); } 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) => { if (this.login_on_ws && event.data.startsWith("LOGGED_IN=") && !this._hasLoggedIn) { this._hasLoggedIn = true; this._onLogin(); this.onLogin(); } 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 && !this._hasLoggedIn) { obs.disconnect(); this._hasLoggedIn = true; this._onLogin(); this.onLogin(); } }); obs.observe(gameDiv, { attributes: true, attributeFilter: ['style'] }); } injectPluginSettingsUI() { const settingsPanel = document.getElementById('ui-panel-settings'); if (!settingsPanel) return; const settingsTable = settingsPanel.querySelector('.settings-ui'); if (!settingsTable) return; const container = document.createElement('div'); container.style.marginTop = "10px"; const hr = document.createElement('hr'); container.appendChild(hr); const title = document.createElement('div'); title.className = "ui-panel-title"; title.innerText = "PLUGIN SETTINGS"; container.appendChild(title); const btn = document.createElement('button'); btn.innerText = "Open Plugin Settings"; btn.className = "btn"; btn.style.marginTop = "6px"; btn.onclick = () => this.createMultiSettingsModal(); container.appendChild(btn); settingsPanel.appendChild(container); } createMultiSettingsModal() { const modal = new window.zlef.Modal("Plugin Settings"); const content = document.createElement('div'); modal.addTitle(content, "Plugin Settings", 2, 'center'); for (const [pluginId, manager] of Object.entries(this.settingsManagers)) { const sectionTitle = pluginId; const section = modal.addSection(content, sectionTitle); const build = (settings, parent, parentKey = '') => { for (const key in settings) { const setting = settings[key]; const fullKey = parentKey ? `${parentKey}.${key}` : key; const label = setting.label || key; if (setting.type === 'section') { const nested = modal.addSection(parent, label); build(setting.settings, nested, fullKey); } else if (setting.type === 'multicheckbox') { const nested = modal.addSection(parent, label); for (const subKey in setting.values) { modal.addCheckbox(nested, setting.values[subKey], value => { manager.settingsChanged(fullKey, value, subKey); }, subKey); } } else if (setting.type === 'checkbox') { modal.addCheckbox(parent, setting.value, value => { manager.settingsChanged(fullKey, value); }, label); } else if (setting.type === 'numinput') { modal.addInput(parent, 'number', setting.value, '', setting.minValue, setting.maxValue, value => { manager.settingsChanged(fullKey, value); }, label); } else if (setting.type === 'text') { modal.addInput(parent, 'text', setting.value, setting.placeholder, undefined, undefined, value => { manager.settingsChanged(fullKey, value); }, label); } else if (setting.type === 'radio') { modal.addRadioButtons(parent, label, setting.options, setting.value, value => { manager.settingsChanged(fullKey, value); }, label); } else if (setting.type === 'combobox') { modal.addCombobox(parent, setting.options, setting.value, value => { manager.settingsChanged(fullKey, value); }, label); } else if (setting.type === 'button') { modal.addButton(parent, label, setting.function, 'btn'); } } }; build(manager.getSettings(), section); } modal.addModal(content, "Plugin Settings", 600, "auto", () => { for (const [pluginId, manager] of Object.entries(this.settingsManagers)) { manager.saveSettings(); if (manager.isDirty()) { const plugin = this.plugins[pluginId]; if (plugin && typeof plugin.onSettingsChanged === "function") { plugin.onSettingsChanged(); } manager.clearDirty(); // reset after broadcast } } }); } showCombinedPluginSettings() { const modal = new zlef.Modal("AllPluginSettings"); const content = document.createElement("div"); modal.addTitle(content, "Plugin Settings", 2, "center"); for (const [pluginId, manager] of Object.entries(this.settingsManagers)) { const sectionName = manager.defaultSettings[pluginId]?.name || pluginId; const section = modal.addSection(content, sectionName); // Build settings UI directly into section manager._buildSettings(manager.settings, section, modal); // Or whatever internal builder logic you extract } modal.addModal(content, "Plugin Settings", 600, "auto", () => { for (const manager of Object.values(this.settingsManagers)) { manager.saveSettings(); } }); } _onLogin(){ this.captureBottomButtons(); this.injectPaginationButtons(); this.hijackSwitchPanels(); this.injectPluginSettingsUI(); } // Event relays to plugins onMessageReceived(data) { this.broadcast("onMessageReceived", data); } onMessageSent(data) { this.broadcast("onMessageSent", data); } onLogin() { this.broadcast("onLogin"); } onSettingsChanged() { this.broadcast('onSettingsChanged'); } captureBottomButtons() { const tdUI = document.querySelector('.td-ui'); if (!tdUI) return; const allButtons = Array.from(tdUI.querySelectorAll('.interface-btn')).filter(btn => btn.id.startsWith('ui-button-')); this.bottomButtons = allButtons.slice(-5); } hijackSwitchPanels() { const original = window.switch_panels; const self = this; window.switch_panels = function(id) { const custom = self.customPanels.find(p => p.id === `ui-panel-${id}`); if (custom) { document.querySelectorAll('.ui-panel').forEach(p => p.style.display = 'none'); const el = document.getElementById(`ui-panel-${id}`); if (el) el.style.display = 'block'; self.activePanel = id; return; } original(id); self.activePanel = id; self.customPanels.forEach(p => { const el = document.getElementById(p.id); if (el) el.style.display = 'none'; }); }; } addPanel(plugin, title = "Untitled", icon = 'https://i.imgur.com/kAp2VY9.png') { console.log(plugin); const pluginCount = this.customPanels.filter(p => p.pluginId === plugin.id).length; const id = `${plugin.id.replace(/\s/g, '')}${pluginCount + 1}`; const fullId = `ui-panel-${id}`; const div = document.createElement('div'); div.id = fullId; div.className = 'ui-panel fmp-custom-panel'; div.innerHTML = ` <div class="ui-panel-title">${title}</div> `; const after = document.getElementById('ui-panel-worship'); if (after?.parentNode) after.parentNode.insertBefore(div, after.nextSibling); this.customPanels.push({ id: fullId, pluginId: plugin.id, icon }); this.updatePanelButtons(); return div; } injectPaginationButtons() { const style = document.createElement('style'); style.innerHTML = ` #ui-button-monster_log, #ui-button-worship, #ui-button-donor-shop, #ui-button-settings, #ui-button-database { margin-right: 4px; } `; document.head.appendChild(style); const tdUI = document.querySelector('.td-ui'); if (!tdUI || this.bottomButtons.length < 5) return; const btnContainer = document.createElement('div'); btnContainer.id = 'fmp-button-container'; btnContainer.className = 'fmp-button-container'; // Move existing buttons this.bottomButtons.forEach(btn => btnContainer.appendChild(btn)); // Create back button const backBtn = document.createElement('div'); backBtn.id = 'fmp-back-btn'; backBtn.className = 'interface-btn hover fmp-back-btn'; backBtn.innerText = '<'; backBtn.title = 'Back'; backBtn.onclick = () => { this.customPanelPage--; this.updatePanelButtons(); }; this.backBtn = backBtn; btnContainer.appendChild(backBtn); // Create next button const nextBtn = document.createElement('div'); nextBtn.id = 'fmp-next-btn'; nextBtn.className = 'interface-btn hover fmp-next-btn'; nextBtn.innerText = '>'; nextBtn.title = 'Next'; nextBtn.onclick = () => { this.customPanelPage++; this.updatePanelButtons(); }; this.nextBtn = nextBtn; btnContainer.appendChild(nextBtn); tdUI.appendChild(btnContainer); } updatePanelButtons() { const total = this.customPanels.length; const pages = Math.ceil(total / 5) + 1; const showingBaseUI = this.customPanelPage === 0; const container = document.getElementById('fmp-button-container'); if (!container) return; this.customPanelButtons.forEach(btn => btn.remove()); this.customPanelButtons = []; this.bottomButtons.forEach(btn => btn.style.display = showingBaseUI ? 'inline-block' : 'none'); if (this.customPanelPage > 0) { const startIndex = (this.customPanelPage - 1) * 5; const panelsToShow = this.customPanels.slice(startIndex, startIndex + 5); panelsToShow.forEach((panel, i) => { const btn = document.createElement('div'); btn.className = 'interface-btn hover fmp-panel-btn'; btn.title = `${panel.pluginId} (${i + 1})`; btn.id = `fmp-panel-btn-${i}`; btn.onclick = () => window.switch_panels(panel.id.replace("ui-panel-", "")); btn.style.backgroundImage = `url('${panel.icon}')`; btn.style.backgroundSize = "cover"; btn.style.backgroundPosition = "center"; if (this.nextBtn && container.contains(this.nextBtn)) { container.insertBefore(btn, this.nextBtn); } else { container.appendChild(btn); } this.customPanelButtons.push(btn); }); } if (this.backBtn && this.nextBtn) { this.backBtn.style.display = this.customPanelPage > 0 ? 'inline-block' : 'none'; this.nextBtn.style.display = this.customPanelPage < pages - 1 ? 'inline-block' : 'none'; } } } // Add to window and init window.FlatmmoPlusPlugin = FlatmmoPlusPlugin; window.FlatmmoPlus = new FlatmmoPlus(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址