您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Displays the progress value from the mining bar, while collecting data in the background.
当前为
// ==UserScript== // @name Farm RPG Mining Progress Display // @namespace http://tampermonkey.net/ // @version 1.3b // @description Displays the progress value from the mining bar, while collecting data in the background. // @author ClientCoin // @match http://farmrpg.com/* // @match https://farmrpg.com/* // @grant GM_getValue // @grant GM_setValue // @icon https://www.google.com/s2/favicons?sz=64&domain=farmrpg.com // @license MIT // ==/UserScript== (function() { 'use strict'; console.log('Mining Progress Display Initiated'); // --- CONFIGURATION --- const isDebugMode = false; const debugLog = (...args) => { if (isDebugMode) { console.log('Tampermonkey script:', ...args); } }; // --- END CONFIGURATION --- // A unique key for storing data in GM_storage // Function to get the current mining location name from the DOM // Function to get the current mining location name from the DOM // =========================================== // SECTION 1: DATA COLLECTION LOGIC // - This section ONLY handles data storage. // - It does NOT modify the UI. // =========================================== let dataRetryCount = 0; const MAX_DATA_RETRIES = 5; // Function to get the current mining location name from the DOM function getMiningLocation() { debugLog('2. DATA: Executing getMiningLocation function...'); const allCenterSlidingDivs = document.querySelectorAll('.center.sliding'); debugLog(`2. DATA: - Found ${allCenterSlidingDivs.length} elements with class ".center.sliding"`); for (const div of allCenterSlidingDivs) { // Find the 'info' icon element inside the current div. const infoIcon = div.querySelector('a i.f7-icons'); // This is the most reliable way to identify the mining location header. if (infoIcon) { const fullText = div.textContent; // The location name is the part of the string before the 'info' text. const locationText = fullText.substring(0, fullText.indexOf('info')).trim(); debugLog(`2. DATA: - Found a match! Location element has 'info' icon. Location: ${locationText}`); return locationText; } } debugLog('2. DATA: - No matching element found. Returning null.'); return null; } function getCurrentFloor() { const floorLabel = document.querySelector('.col-30 strong'); const floorMatch = floorLabel.textContent.match(/(\d{1,3}(?:,\d{3})*)/); const currentFloor = floorMatch ? parseInt(floorMatch[1].replace(/,/g, ''), 10) : null; debugLog("Current Floor is: " + currentFloor); return currentFloor; } // Function to reset all stored data for the current mining location async function resetMiningData(locationName) { if (!locationName) { console.warn('Tampermonkey script: Cannot reset data, location name is not defined.'); return; } // Confirmation prompt to prevent accidental data loss if (confirm(`Are you sure you want to delete all mining data for ${locationName}?`)) { try { let miningData = await GM_getValue(STORAGE_KEY, {}); delete miningData[locationName]; await GM_setValue(STORAGE_KEY, miningData); console.log(`Tampermonkey script: Successfully deleted all mining data for ${locationName}.`); // Reload the page to refresh the display window.location.reload(); } catch (error) { console.error('Tampermonkey script: Error resetting data:', error); } } } // A unique key for storing data in GM_storage const STORAGE_KEY = 'farmrpg_mining_data'; async function collectAndStoreMiningData() { const progressBar = document.getElementById('mpb'); const floorElement = document.querySelector('.col-30 strong span'); const locationName = getMiningLocation(); if (!progressBar || !floorElement || !locationName) { if (dataRetryCount < MAX_DATA_RETRIES) { dataRetryCount++; setTimeout(collectAndStoreMiningData, 50); } else { dataRetryCount = 0; } return; } dataRetryCount = 0; const currentFloor = getCurrentFloor(); const currentProgress = parseFloat(progressBar.getAttribute('data-progress')); try { let miningData = await GM_getValue(STORAGE_KEY, {}); // Store the data in the new nested format: location > floor > progress miningData[locationName] = miningData[locationName] || {}; miningData[locationName][currentFloor] = { progress: currentProgress }; await GM_setValue(STORAGE_KEY, miningData); } catch (error) { console.error('Tampermonkey script: Error during data collection:', error); } } // =========================================== // SECTION 2: DISPLAY LOGIC (v1.1) // =========================================== // Function to get the average progress per floor from recent floors using an exponential weighted average // Function to get the average progress per floor from recent floors using a standard deviation filter // Function to get the average progress per floor from recent floors using a standard deviation filter function getRecentProgressPerFloor(locationData) { debugLog('1. DISPLAY: Calculating progress rate...'); const ALPHA = 0.2; const MAD_THRESHOLD = 2.5; // A common multiplier for the MAD to define the outlier threshold. const MAD_MINIMUM_PERCENTAGE_OF_MEDIAN = 0.1; // Failsafe to prevent filtering when MAD is insignificant. const allFloors = Object.keys(locationData).map(Number).sort((a, b) => a - b); const recentFloors = allFloors.slice(Math.max(0, allFloors.length - 100)); if (recentFloors.length < 2) { debugLog('1. DISPLAY: Not enough data points to calculate a rate. (Need at least 2 floors)'); return null; } let progressRates = []; for (let i = 1; i < recentFloors.length; i++) { const previousFloor = recentFloors[i - 1]; const floorsGained = recentFloors[i] - previousFloor; if (floorsGained > 0) { const progressChange = locationData[recentFloors[i]].progress - locationData[previousFloor].progress; if (progressChange > 0) { progressRates.push(progressChange / floorsGained); } } } debugLog('1. DISPLAY: Original progress rates:', progressRates.map(rate => rate.toFixed(4))); if (progressRates.length < 3) { debugLog('1. DISPLAY: Not enough data points for statistical filtering. (Need at least 3)'); return null; } // --- Outlier detection using MAD --- const sortedRates = [...progressRates].sort((a, b) => a - b); const median = sortedRates.length % 2 === 0 ? (sortedRates[sortedRates.length / 2 - 1] + sortedRates[sortedRates.length / 2]) / 2 : sortedRates[Math.floor(sortedRates.length / 2)]; const deviations = sortedRates.map(rate => Math.abs(rate - median)); deviations.sort((a, b) => a - b); let mad = deviations.length % 2 === 0 ? (deviations[deviations.length / 2 - 1] + deviations[deviations.length / 2]) / 2 : deviations[Math.floor(deviations.length / 2)]; if (mad < 0.01) {mad = median * MAD_MINIMUM_PERCENTAGE_OF_MEDIAN}; const filteredRates = progressRates.filter(rate => Math.abs(rate - median) <= MAD_THRESHOLD * mad); const rejectedRates = progressRates.filter(rate => !filteredRates.includes(rate)); debugLog(`1. DISPLAY: Median: ${median.toFixed(4)}, MAD: ${mad.toFixed(4)}`); debugLog(`1. DISPLAY: Filtering data points within ${MAD_THRESHOLD}x MAD from median.`); debugLog('1. DISPLAY: Rejected rates (outliers):', rejectedRates.map(rate => rate.toFixed(4))); if (filteredRates.length === 0) { debugLog('1. DISPLAY: No valid data points remain after filtering.'); return null; } let weightedAverage = filteredRates[0]; for (let i = 1; i < filteredRates.length; i++) { weightedAverage = (ALPHA * filteredRates[i]) + ((1 - ALPHA) * weightedAverage); } debugLog(`1. DISPLAY: Final calculated weighted average: ${weightedAverage.toFixed(4)}`); return weightedAverage; } async function updateMiningDisplay() { const progressBar = document.getElementById('mpb'); const floorElement = document.querySelector('.col-30 strong span'); const locationName = getMiningLocation(); if (!progressBar || !floorElement || !locationName) { return; } const floorLabel = document.querySelector('.col-30 strong'); const currentFloor = getCurrentFloor(); if (isNaN(currentFloor)) { debugLog('1. DISPLAY: Floor number is not a valid number. Exiting.'); return; } if (floorLabel) { const textNode = Array.from(floorLabel.childNodes).find(node => node.nodeType === Node.TEXT_NODE && node.textContent.trim() === 'Floor'); if (textNode) { textNode.remove(); } const brElement = floorLabel.querySelector('br'); if (brElement) { brElement.remove(); } if (!floorLabel.querySelector('#floor-label')) { const labelSpan = document.createElement('span'); labelSpan.id = 'floor-label'; labelSpan.textContent = 'Floor: '; labelSpan.style.fontSize = '17px'; labelSpan.style.fontWeight = 'bold'; floorLabel.prepend(labelSpan); const floorNumberSpan = floorLabel.querySelector('span:not(#floor-label)'); if(floorNumberSpan) floorNumberSpan.style.fontSize = '17px'; } } const currentProgress = parseFloat(progressBar.getAttribute('data-progress')); let miningData = await GM_getValue(STORAGE_KEY, {}); const locationData = miningData[locationName] || {}; let floorsToGoText = 'Data gathering...'; let targetFloorText = 'Complete 4 Floors to Start'; let lastKnownProgress = 0; const allFloors = Object.keys(locationData).map(Number).sort((a, b) => b - a); if (allFloors.length > 0) { const lastFloorNumber = allFloors[0]; lastKnownProgress = locationData[lastFloorNumber]?.progress || 0; } debugLog(miningData); if (currentProgress < lastKnownProgress) { miningData[locationName] = {}; await GM_setValue(STORAGE_KEY, miningData); } const progressRate = getRecentProgressPerFloor(locationData, currentFloor); if (progressRate && progressRate > 0) { const progressRemaining = 100 - currentProgress; const estimatedFloorsToGo = progressRemaining / progressRate; floorsToGoText = `Est. Floors: ${Math.round(estimatedFloorsToGo)}`; targetFloorText = `Target Floor: ${Math.round(currentFloor + estimatedFloorsToGo)}`; } const createOrUpdateDisplay = (progressValue) => { const floorContainer = document.querySelector('.col-30'); if (floorContainer) { let progressDisplay = document.getElementById('farmrpg-progress-display'); let estimateDisplay = document.getElementById('farmrpg-estimate-display'); let targetFloorDisplay = document.getElementById('farmrpg-target-floor-display'); let resetButton = document.getElementById('farmrpg-reset-button'); if (!progressDisplay) { progressDisplay = document.createElement('div'); progressDisplay.id = 'farmrpg-progress-display'; floorContainer.insertBefore(progressDisplay, floorContainer.querySelector('strong')); progressDisplay.style.cssText = `font-weight: bold !important; color: lightgreen !important; font-size: 14px !important; margin-bottom: 5px !important; text-shadow: 0 0 5px rgba(144, 238, 144, 0.5) !important;`; } const formattedProgress = parseFloat(progressValue).toFixed(2); progressDisplay.textContent = `Progress: ${formattedProgress}%`; if (!estimateDisplay) { estimateDisplay = document.createElement('div'); estimateDisplay.id = 'farmrpg-estimate-display'; floorContainer.insertBefore(estimateDisplay, floorContainer.querySelector('strong').nextSibling); estimateDisplay.style.cssText = `font-weight: bold !important; color: #add8e6 !important; font-size: 12px !important; margin-top: 5px !important;`; } estimateDisplay.textContent = floorsToGoText; if (!targetFloorDisplay) { targetFloorDisplay = document.createElement('div'); targetFloorDisplay.id = 'farmrpg-target-floor-display'; floorContainer.insertBefore(targetFloorDisplay, estimateDisplay.nextSibling); targetFloorDisplay.style.cssText = `font-weight: bold !important; color: #ffcc00 !important; font-size: 12px !important; margin-top: 5px !important;`; } targetFloorDisplay.textContent = targetFloorText; if (!resetButton) { resetButton = document.createElement('a'); resetButton.id = 'farmrpg-reset-button'; resetButton.className = 'button btnpurple'; resetButton.style.cssText = `font-size: 11px; height: 20px; line-height: 18px; width: 100px; margin: 10px auto 0 auto; display: block;`; resetButton.textContent = 'Reset Data'; resetButton.onclick = () => resetMiningData(locationName); floorContainer.insertBefore(resetButton, targetFloorDisplay.nextSibling); } } }; createOrUpdateDisplay(currentProgress); } // Function to remove <br> tags from the target div function removeBreakLines() { const floorContainer = document.querySelector('.col-30'); if (floorContainer) { const breakLines = floorContainer.querySelectorAll('br'); //breakLines.forEach(br => br.remove()); } } // =========================================== // SECTION 3: ACTIVATION // - This section activates both functions. // =========================================== updateMiningDisplay(); collectAndStoreMiningData(); const targetNode = document.querySelector("#fireworks"); if (targetNode) { const navObserver = new MutationObserver((mutationsList) => { if (mutationsList.some(m => m.attributeName === 'data-page')) { debugLog('3. ACTIVATION: Navigation detected via data-page attribute change.'); updateMiningDisplay(); collectAndStoreMiningData(); } }); navObserver.observe(targetNode, { attributes: true }); debugLog('3. ACTIVATION: Navigation observer set up.'); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址