Farm RPG Harvest Log Sum

Extracts and permanently stores all harvested crops, calculates average drops per harvest, and outputs to the console. Includes automatic data purging.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Farm RPG Harvest Log Sum
// @namespace    ClientCoinCropSum
// @version      4.5.1
// @description  Extracts and permanently stores all harvested crops, calculates average drops per harvest, and outputs to the console. Includes automatic data purging.
// @author       ClientCoin
// @match        https://farmrpg.com/*
// @match        https://alpha.farmrpg.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=farmrpg.com
// @grant        GM_getValue
// @grant        GM_setValue
// ==/UserScript==

(function() {
    'use strict';

    // --- SCRIPT SETTINGS ---
    // Set this to `true` for detailed console output, `false` to run silently.
    const DEBUG_MODE = false;

    // The maximum storage size in KB before old data is purged.
    const MAX_STORAGE_KB = 4096; // Corresponds to 4 MB

    // A map defining special sub-drops and their corresponding main crops.
    const SUB_DROPS_MAP = {
        'Gold Peppers': 'Peppers',
        'Yellow Watermelon': 'Watermelon',
        'Hot Potato': 'Potato',
        'Inferno Pepper': 'Peppers',
        'Gold Eggplant': 'Eggplant',
        'Gold Peas': 'Peas',
        'Gold Carrot': 'Carrot',
        'Gold Cucumber': 'Cucumber',
        'Mega Beet': 'Beet',
        'Mega Cotton': 'Cotton',
        'Mega Sunflower': 'Sunflower'
        // Add more special drops here if needed in the future, e.g.: 'Special Drop': 'Main Crop'
    };

    let isProcessing = false;

    /**
     * Parses the harvest log from the current page and adds any new data to persistent storage.
     * Then, it processes all the stored data to produce the final summary.
     */
    async function processHarvestLog() {
        if (isProcessing) {
            // A flag to prevent the function from running multiple times in quick succession.
            return;
        }

        isProcessing = true;

        const harvestLogTitle = document.getElementById('harvestlog');
        if (!harvestLogTitle) {
            isProcessing = false;
            return;
        }

        // 1. Correctly find the main container for the harvest log items.
        // The list of harvests is in the second `div.card` element that is a sibling
        // of the `content-block-title` element.
        const harvestLogCard = harvestLogTitle.nextElementSibling.nextElementSibling;

        if (!harvestLogCard) {
            console.error('Could not find the card container for the harvest log. Please check your page HTML.');
            isProcessing = false;
            return;
        }

        // Retrieve existing harvest data from storage.
        // Initialize an empty object if no data is found.
        const storedData = await GM_getValue('harvestData', {});
        const processedHarvests = new Set(Object.keys(storedData));

        let newHarvestsAdded = 0;
        let purgedEntries = 0;

        // 2. Iterate over the harvest log on the current page.
        harvestLogCard.querySelectorAll('.list-block > div.list-group').forEach(group => {
            const titleElement = group.querySelector('.list-group-title');
            if (!titleElement) return;

            // Use the timestamp as a unique ID for this harvest session.
            const harvestTimestamp = titleElement.textContent.trim().replace('Harvest on ', '');

            // Only process harvests we haven't seen before.
            if (!processedHarvests.has(harvestTimestamp)) {
                newHarvestsAdded++;
                processedHarvests.add(harvestTimestamp);
                storedData[harvestTimestamp] = [];

                const items = group.querySelectorAll('li.close-panel');
                const mainCropElement = items[0].querySelector('.item-title strong');
                const currentMainCropName = mainCropElement ? mainCropElement.textContent.trim() : null;

                items.forEach(item => {
                    const cropNameElement = item.querySelector('.item-title strong');
                    const harvestAmountElement = item.querySelector('.item-after');

                    const cropName = cropNameElement ? cropNameElement.textContent.trim() : null;
                    const harvestAmount = parseInt(harvestAmountElement ? harvestAmountElement.textContent.trim().replace(/,/g, '') : '0', 10);

                    if (cropName && !isNaN(harvestAmount)) {
                        storedData[harvestTimestamp].push({
                            name: cropName,
                            amount: harvestAmount,
                            isMain: cropName === currentMainCropName
                        });
                    }
                });

                // Check and purge the oldest entry if storage limit is exceeded after adding a new one.
                let storageSize = JSON.stringify(storedData).length;
                if (storageSize / 1024 > MAX_STORAGE_KB) {
                    const sortedKeys = Object.keys(storedData).sort();
                    if (sortedKeys.length > 0) {
                        delete storedData[sortedKeys[0]];
                        purgedEntries++;
                    }
                }
            }
        });

        // Save the updated data back to storage if any changes were made.
        if (newHarvestsAdded > 0 || purgedEntries > 0) {
            await GM_setValue('harvestData', storedData);
        }

        // 3. Process all stored data to generate the final summary.
        const mainCropTotals = new Map();
        const mainCropOccurrences = new Map();
        const subDropTotals = new Map();

        Object.values(storedData).forEach(sessionItems => {
            let currentMainCropName = null;
            if (sessionItems.length > 0) {
                currentMainCropName = sessionItems[0].isMain ? sessionItems[0].name : null;
            }

            if (currentMainCropName) {
                const currentMainOccurrences = mainCropOccurrences.get(currentMainCropName) || 0;
                mainCropOccurrences.set(currentMainCropName, currentMainOccurrences + 1);
            }

            sessionItems.forEach(item => {
                const isSubDrop = Object.keys(SUB_DROPS_MAP).includes(item.name);

                if (isSubDrop) {
                    const currentSubTotal = subDropTotals.get(item.name) || 0;
                    subDropTotals.set(item.name, currentSubTotal + item.amount);
                } else if (item.isMain) {
                    const currentMainTotal = mainCropTotals.get(item.name) || 0;
                    mainCropTotals.set(item.name, currentMainTotal + item.amount);
                }
            });
        });

        // Calculate storage size again after any potential purging.
        const finalStorageSize = JSON.stringify(storedData).length;
        const finalStorageSizeKB = (finalStorageSize / 1024).toFixed(2);

        // 4. Log the final summary of all crops to the console.
        console.log('--- Final Harvest Log Summary ---');
        if(DEBUG_MODE) {
            console.log(`Total Harvest Sessions Recorded: ${Object.keys(storedData).length}`);
            console.log(`Current data size: ${finalStorageSizeKB} KB`);
            if (purgedEntries > 0) {
                console.log(`Note: Automatically purged ${purgedEntries} oldest harvest entries to manage storage.`);
            }
            console.log(`To clear all stored harvest data, run 'clearHarvestData()' in the console.`);
            console.log('\nHarvest Totals and Rates:');
        }
        if (purgedEntries > 0) {
            console.log(`Note: Automatically purged ${purgedEntries} oldest harvest entries to manage storage.`);
        }

        mainCropTotals.forEach((total, crop) => {
            const occurrences = mainCropOccurrences.get(crop) || 0;
            if (occurrences > 0) {
                const aph = (total / occurrences);
                console.log(`${crop}: ${aph.toFixed(2)} aph (${total} in ${occurrences} harvests)`);
            }
        });

        subDropTotals.forEach((total, crop) => {
            const mainCrop = SUB_DROPS_MAP[crop];
            const mainCropTotal = mainCropTotals.get(mainCrop) || 0;

            if (mainCropTotal > 0 && total > 0) {
                const dropRate = (mainCropTotal / total);
                console.log(`${crop}: 1 in ${Math.round(dropRate)} (${total} from ${mainCropTotal})`);
            }
        });

        //console.log('---------------------------------');

        isProcessing = false;
    }

    // Set up the MutationObserver to listen for page changes and then run the script.
    const target = document.getElementById("fireworks");
    if (!target) {
        console.error('Target element #fireworks not found. MutationObserver cannot be set up.');
        return;
    }

    const observer = new MutationObserver(mutations => {
        for (const mutation of mutations) {
            if (mutation.attributeName === "data-page") {
                const newPage = target.getAttribute("data-page");
                if (newPage === "farminfo") {
                    processHarvestLog();
                }
            }
        }
    });

    const config = {
        attributes: true,
        childList: false,
        subtree: false
    };

    observer.observe(target, config);

    // Run immediately if the user is already on the correct page when the script is loaded.
    const currentPage = target.getAttribute("data-page");
    if (currentPage === "farminfo") {
        processHarvestLog();
    }
})();