您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
View bank contents remotely
// ==UserScript== // @name FlatMMO Remote Bank Viewer // @namespace com.pizza1337.flatmmo.remotebank // @version 1.0.1 // @description View bank contents remotely // @author Pizza1337 // @match *://flatmmo.com/play.php* // @grant none // @require https://update.greasyfork.org/scripts/544062/FlatMMOPlus.js // @license MIT // ==/UserScript== (function () { 'use strict'; // Fixed bank panel height when in bank view (change if you want) const BANK_HEIGHT_PX = 500; class RemoteBankViewerPlugin extends FlatMMOPlusPlugin { constructor() { super('remote-bank-viewer', { about: { name: GM_info.script.name, version: GM_info.script.version, author: GM_info.script.author, description: GM_info.script.description } // Always on: no FlatMMOPlus config entries }); // State this.isChangingView = false; this.stickyBank = false; // when true, hold bank view this.userForcedInventory = false; // set when user clicks inventory button this.inventoryObserver = null; this.stickyObs = null; this.restoreTimer = null; this._cachedBank = null; // Previous container styles for restoring (we no longer replace children) this.prevContainerStyles = { height: '', maxHeight: '', overflow: '', overflowX: '', overflowY: '', position: '' }; this.injectStyles(); this._installCoreHooks(); this._attachDOMWatchers(); this._hookInventoryButton(); } /* =========================== Styles (overlay approach) =========================== */ injectStyles() { if (document.getElementById('rb-overlay-styles')) return; const style = document.createElement('style'); style.id = 'rb-overlay-styles'; style.textContent = ` /* Ensure container can host overlay */ #ui-panel-inventory-content.rb-bank-mode { position: relative !important; } /* In bank mode, hide everything except our overlay */ #ui-panel-inventory-content.rb-bank-mode > :not(#remote-bank-overlay) { display: none !important; } /* Overlay layer to show bank items */ #remote-bank-overlay { position: absolute !important; inset: 0 !important; overflow-y: auto !important; overflow-x: hidden !important; z-index: 5 !important; background: transparent; } `; document.head.appendChild(style); } /* =========================== FM+ lifecycle & events =========================== */ onLogin() { // If we were in sticky bank before login, re-assert it if (this.stickyBank) requestAnimationFrame(() => this.showBankView()); } // No onConfigsChanged (always on) onPanelChanged(before, after) { // If user explicitly asked for inventory, do NOT re-assert bank if (this.userForcedInventory) return; if (this.stickyBank && after === 'inventory') { requestAnimationFrame(() => this.showBankView()); } } onMapChanged() { if (this.stickyBank && !this.userForcedInventory) { requestAnimationFrame(() => this.showBankView()); } } onInventoryChanged() { if (this.stickyBank && !this.userForcedInventory) { requestAnimationFrame(() => this.showBankView()); } } /* =========================== Core hooks & observers =========================== */ _installCoreHooks() { // Save bank data when the real bank UI closes const originalCloseBank = window.close_bank; if (typeof originalCloseBank === 'function') { window.close_bank = (...args) => { this._saveBankContents(); return originalCloseBank.apply(window, args); }; } // ESC also saves if bank is open document.addEventListener('keydown', (e) => { if (e.key === 'Escape') this._saveBankContents(); }); // Wrap game's refresh_inventory so bank doesn't flicker out while sticky this._wrapRefreshInventory(); // Wrap switch_panels to honor explicit inventory intent (kills sticky) this._wrapSwitchPanels(); } _wrapRefreshInventory() { if (window.__rb_wrappedRefresh) return; const original = window.refresh_inventory; if (typeof original !== 'function') { setTimeout(() => this._wrapRefreshInventory(), 250); return; } window.refresh_inventory = (...args) => { try { const content = this._content(); const inBank = content && content.dataset.viewMode === 'bank'; if (this.stickyBank && inBank && !this.userForcedInventory) { // Swallow redraw attempts while sticky bank is active return; } } catch {} return original.apply(window, args); }; window.__rb_wrappedRefresh = true; } _wrapSwitchPanels() { if (window.__rb_wrappedSwitchPanels) return; const original = window.switch_panels; if (typeof original !== 'function') { setTimeout(() => this._wrapSwitchPanels(), 250); return; } window.switch_panels = (...args) => { try { const target = args?.[0]; if (target === 'inventory') { // User explicitly chose inventory -> disable sticky, mark intent this.userForcedInventory = true; this.stickyBank = false; // Immediately restore inventory view to avoid any lag/flicker requestAnimationFrame(() => this.showInventoryView()); } else { // Any other panel change clears the inventory-intent flag this.userForcedInventory = false; } } catch {} return original.apply(window, args); }; window.__rb_wrappedSwitchPanels = true; } _attachDOMWatchers() { // Sticky observer — if anything replaces our bank, re-assert it quickly this._startStickyObserver(); } _startStickyObserver() { const content = this._content(); if (!content) { setTimeout(() => this._startStickyObserver(), 250); return; } if (this.stickyObs) this.stickyObs.disconnect(); this.stickyObs = new MutationObserver(() => { if (this.isChangingView) return; if (this.userForcedInventory) return; // user wants inventory → don’t fight it if (this.stickyBank) { const overlay = this._getOverlay(); const lostBank = content.dataset.viewMode !== 'bank' || !content.classList.contains('rb-bank-mode') || !overlay || overlay.style.display === 'none' || !content.querySelector('#remote-bank-marker'); if (lostBank) { clearTimeout(this.restoreTimer); this.restoreTimer = setTimeout(() => { if (this.stickyBank && !this.userForcedInventory) { requestAnimationFrame(() => this.showBankView()); } }, 0); } } }); this.stickyObs.observe(content, { childList: true, subtree: true, attributes: true, attributeFilter: ['data-view-mode', 'style', 'class'] }); } _hookInventoryButton() { // Ensure that clicking the UI inventory button ALSO forces inventory mode const attach = () => { const btn = document.getElementById('ui-button-inventory'); if (!btn) return; if (btn.__rb_hooked) return; btn.__rb_hooked = true; btn.addEventListener('click', () => { this.userForcedInventory = true; this.stickyBank = false; requestAnimationFrame(() => this.showInventoryView()); }, true); // capture = true to run before game handler }; // Try now, and keep trying as DOM changes attach(); const obs = new MutationObserver(attach); obs.observe(document.body, { childList: true, subtree: true }); } /* =========================== Overlay helpers =========================== */ _getOverlay() { const content = this._content(); return content ? content.querySelector('#remote-bank-overlay') : null; } _ensureOverlay() { const content = this._content(); if (!content) return null; let overlay = this._getOverlay(); if (!overlay) { overlay = document.createElement('div'); overlay.id = 'remote-bank-overlay'; overlay.style.display = 'none'; content.appendChild(overlay); } return overlay; } /* =========================== Public actions =========================== */ showBankView() { const content = this._content(); if (!content || this.isChangingView) return; this.isChangingView = true; this.stickyBank = true; // lock to bank this.userForcedInventory = false; // clear explicit inventory intent // Save container styles the first time we enter bank mode if (content.dataset.__rb_saved !== '1') { this.prevContainerStyles.height = content.style.height || ''; this.prevContainerStyles.maxHeight= content.style.maxHeight || ''; this.prevContainerStyles.overflow = content.style.overflow || ''; this.prevContainerStyles.overflowX= content.style.overflowX || ''; this.prevContainerStyles.overflowY= content.style.overflowY || ''; this.prevContainerStyles.position = content.style.position || ''; content.dataset.__rb_saved = '1'; } // Lock container size; overlay scrolls content.style.height = `${BANK_HEIGHT_PX}px`; content.style.maxHeight = `${BANK_HEIGHT_PX}px`; content.style.position = 'relative'; content.style.overflowY = 'hidden'; content.style.overflowX = 'hidden'; // Create / update overlay (do NOT touch original children — listeners survive) const overlay = this._ensureOverlay(); if (overlay) { overlay.innerHTML = this._createBankViewHTML() + '<div id="remote-bank-marker" style="display:none"></div>'; overlay.style.display = 'block'; } // Mark mode and hide everything else via CSS content.dataset.viewMode = 'bank'; content.classList.add('rb-bank-mode'); this._updateButtons('bank'); setTimeout(() => { this.isChangingView = false; this._startStickyObserver(); }, 20); } showInventoryView() { const content = this._content(); if (!content || this.isChangingView) return; this.isChangingView = true; this.stickyBank = false; // Hide overlay only; keep original inventory DOM (listeners intact) const overlay = this._getOverlay(); if (overlay) overlay.style.display = 'none'; content.classList.remove('rb-bank-mode'); content.dataset.viewMode = 'inventory'; // Restore container scrolling/height to game defaults content.style.height = this.prevContainerStyles.height; content.style.maxHeight = this.prevContainerStyles.maxHeight; content.style.overflow = this.prevContainerStyles.overflow; content.style.overflowX = this.prevContainerStyles.overflowX; content.style.overflowY = this.prevContainerStyles.overflowY; content.style.position = this.prevContainerStyles.position; this._updateButtons('inventory'); setTimeout(() => { this.isChangingView = false; // keep observer running; stickyBank stays false this._startStickyObserver(); }, 20); } /* =========================== UI helpers =========================== */ _updateButtons(mode) { const invBtn = document.querySelector('.inventory-btn'); const bankBtn = document.querySelector('.view-bank-btn'); if (mode === 'bank') { if (invBtn) invBtn.style.opacity = '0.6'; if (bankBtn) bankBtn.style.opacity = '1'; } else { if (invBtn) invBtn.style.opacity = '1'; if (bankBtn) bankBtn.style.opacity = '0.6'; } } ensureHeaderButtons() { const panel = document.getElementById('ui-panel-inventory'); if (!panel) return; const title = panel.querySelector('.ui-panel-title'); if (!title || title.querySelector('.view-bank-btn')) return; title.innerHTML = ` <span class="inventory-btn" style="cursor:pointer;transition:opacity .2s;">INVENTORY</span> <span style="margin:0 5px;">•</span> <span class="view-bank-btn" style="cursor:pointer;opacity:0.6;transition:opacity .2s;">VIEW BANK</span> `; const invBtn = title.querySelector('.inventory-btn'); const bankBtn = title.querySelector('.view-bank-btn'); invBtn.addEventListener('click', () => { const content = this._content(); if (content && content.dataset.viewMode === 'bank') { this.userForcedInventory = true; this.stickyBank = false; this.showInventoryView(); } }); bankBtn.addEventListener('click', () => { const content = this._content(); if (content && content.dataset.viewMode !== 'bank') { this.showBankView(); } }); } /* =========================== Bank persistence =========================== */ _saveBankContents() { const storageDiv = document.getElementById('storage'); if (!storageDiv || storageDiv.style.display === 'none') return; const items = []; storageDiv.querySelectorAll('[data-bank-item-name]').forEach(div => { const name = div.getAttribute('data-bank-item-name'); const img = div.querySelector('img'); const tooltip = div.querySelector('.tooltiptext'); const amountEl = div.querySelector('.item-amount'); if (img && name) { const onclickAttr = img.getAttribute('onclick'); const match = onclickAttr ? onclickAttr.match(/,\s*"(\d+)"\)/) : null; const amount = match ? match[1] : '1'; items.push({ name, amount, imgSrc: img.src, tooltipText: tooltip ? tooltip.innerHTML : name.toUpperCase().replace(/_/g, ' '), amountDisplay: amountEl ? amountEl.innerHTML : amount, backgroundColor: div.style.backgroundColor || 'rgb(225, 225, 225)' }); } }); this._cachedBank = items; // keep in memory // Persist in localStorage (simple, no GM storage) localStorage.setItem('rb_bankData', JSON.stringify(items)); localStorage.setItem('rb_bankUpdated', new Date().toISOString()); } _loadBankContents() { try { const raw = localStorage.getItem('rb_bankData'); return raw ? JSON.parse(raw) : []; } catch { return []; } } /* =========================== Rendering helpers =========================== */ _createBankViewHTML() { const items = this._cachedBank || this._loadBankContents(); const updated = localStorage.getItem('rb_bankUpdated'); if (!items.length) { return `<div class="bank-last-updated" style="width:100%;text-align:center;margin-top:6px;padding-bottom:6px;color:#888;font-size:11px;"> No bank data saved. Open your bank to save its contents. </div>`; } let html = ''; for (const it of items) { html += ` <div data-bank-item-name="${it.name}" class="tooltip item" style="${it.backgroundColor ? 'background-color:' + it.backgroundColor + ';' : 'background-color: rgb(225,225,225);'} border:1px solid rgb(205,205,205);"> <span class="item-amount">${it.amountDisplay}</span> <img src="${it.imgSrc}" draggable="false"> <span class="tooltiptext">${it.tooltipText}</span> </div> `; } if (updated) { html += `<div class="bank-last-updated" style="width:100%;text-align:center;margin-top:6px;padding-bottom:6px;color:#888;font-size:11px;"> Last updated: ${this._timeAgo(new Date(updated))} </div>`; } return html; } _timeAgo(date) { const s = Math.floor((Date.now() - date.getTime()) / 1000); const unit = [['year',31536000],['month',2592000],['week',604800],['day',86400],['hour',3600],['minute',60]]; for (const [n,sec] of unit) { const v = Math.floor(s / sec); if (v >= 1) return v + ' ' + n + (v !== 1 ? 's' : '') + ' ago'; } return 'just now'; } /* =========================== DOM helpers =========================== */ _content() { return document.getElementById('ui-panel-inventory-content'); } } /* =========================== Boot =========================== */ const plugin = new RemoteBankViewerPlugin(); // Ensure header buttons appear as soon as the panel exists const titleObs = new MutationObserver(() => plugin.ensureHeaderButtons()); titleObs.observe(document.body, { childList: true, subtree: true }); // Register with FlatMMOPlus FlatMMOPlus.registerPlugin(plugin); })();