Reorder Certificates

Reorders certificates based on completion percentage and simplifies the interface

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Reorder Certificates
// @namespace    FarmRPGCertificates
// @version      1.1.1
// @description  Reorders certificates based on completion percentage and simplifies the interface
// @author       ClientCoin
// @match        http*://*farmrpg.com/index.php
// @match        http*://*farmrpg.com/
// @match        http*://*alpha.farmrpg.com/
// @match        http*://*alpha.farmrpg.com/index.php
// @icon         https://www.google.com/s2/favicons?sz=64&domain=farmrpg.com
// @grant        none
// ==/UserScript==



function reorderCertificates() {

    const locName = location.hash.slice(location.hash.search(/[^#!\/]/), location.hash.search(/.php/))
    if (locName != "temple") {
        return;
    }

    //console.log("%c[Script] Reordering certificates...", "color: cyan; font-weight: bold;");

    let allContentBlocks = document.querySelectorAll(".content-block");
    let certificatesContainer = null;

    allContentBlocks.forEach(block => {
        let titleElement = block.querySelector(".content-block-title");
        if (titleElement && titleElement.textContent.trim() === "Secret Chains") {
            certificatesContainer = block.querySelector(".list-block ul");
        }
    });

    if (!certificatesContainer) {
        console.warn("%c[Script] Certificates container not found. Exiting.", "color: red;");
        return;
    }

    let certificateElements = Array.from(certificatesContainer.querySelectorAll("li"));
    if (certificateElements.length === 0) {
        console.warn("%c[Script] No certificate elements found. Exiting.", "color: red;");
        return;
    }

    //console.log("%c[Script] Found certificate elements:", "color: cyan;", certificateElements);

    let certificateBlocks = certificateElements.map(certificate => {
        let progress = extractProgress(certificate);
        //console.log(`%c[Script] Extracted progress: ${progress}%`, "color: lightgreen;");
        return { element: certificate, progress };
    });

    let completedCount = certificateBlocks.filter(c => c.progress === 100).length;
    let maxCount = certificateBlocks.length;

    // Sorting: Completed (100%) certificates at the bottom
    certificateBlocks.sort((a, b) => {
        // Move 100% completed items to the bottom
        if (a.progress === 100 && b.progress !== 100) return 1;
        if (b.progress === 100 && a.progress !== 100) return -1;

        // Sort by highest progress first
        let progressSort = b.progress - a.progress;
        if (progressSort !== 0) return progressSort;

        // If progress is the same, sort by itemsToGo (ascending)
        return a.itemsToGo - b.itemsToGo;
    });



    //console.log("%c[Script] Certificates sorted by progress.", "color: cyan;");

    certificatesContainer.innerHTML = ""; // Clear the list

    // Insert summary at the top
    let summaryElement = document.createElement("li");
    const certificateImages = [
        '7896-yellow.png', '7896-yellow.png', '7896-yellow.png',
        '7896-yellow.png', '7896-yellow.png', '7896-yellow.png',
        '7896-green.png', '7896-green.png', '7896-green.png',
        '7896-green.png', '7896-green.png', '7896-green.png',
        '7896.png', '7896.png', '7896.png', '7896.png',
        '7896-orange.png', '7896-orange.png',
        '7896-purple.png', '7896-purple.png',
        '7896-gray.png',
        '7896-blue.png'
    ];

    // Shuffle function
    function shuffleArray(arr) {
        return arr.map(value => ({ value, sort: Math.random() }))
            .sort((a, b) => a.sort - b.sort)
            .map(({ value }) => value);
    }

    const shuffledCertificates = shuffleArray(certificateImages);
    const beforeImages = shuffledCertificates.slice(0, 11);
    const afterImages = shuffledCertificates.slice(11);

    // Create HTML blocks
    const makeImageBlock = (images) =>
    images.map(src => `<img src="/img/items/${src}" style="height:1rem;width:1rem;margin:0 1px;">`).join('');

    summaryElement.innerHTML = `
    <div style="display: flex; align-items: center; justify-content: center; gap: 4px; white-space: nowrap; overflow: hidden;">
        <div style="display: flex; gap: 2px; flex-shrink: 1; overflow: hidden;">
            ${makeImageBlock(beforeImages)}
        </div>
        <div style="font-weight: bold; color: gold; text-align: center; padding: 5px; flex-shrink: 0; min-width: 180px;">
            ${completedCount}/${maxCount} Certificates COMPLETE
        </div>
        <div style="display: flex; gap: 2px; flex-shrink: 1; overflow: hidden;">
            ${makeImageBlock(afterImages)}
        </div>
    </div>
`;

    certificatesContainer.appendChild(summaryElement);

// === Progress Summary (Total Crops Remaining) ===
// === Progress Summary (Total Crops Remaining) ===
let totalToGo = 0;
let cropCount = 0;

certificateBlocks.forEach(({ element }) => {
    const span = element.querySelector(".item-inner span");
    if (!span) return;

    const rawText = span.textContent.trim();
    //console.log("[DEBUG] Raw span content:", rawText);

    const match = rawText.match(/([\d,]+)\s*\/\s*([\d,]+)\s*Items Sacrificed/i);
    if (match) {
        const sacrificed = parseInt(match[1].replace(/,/g, ''));
        const required = parseInt(match[2].replace(/,/g, ''));
        const toGo = required - sacrificed;
        totalToGo += toGo;
        cropCount++;
        //console.log(`[DEBUG] Sacrificed=${sacrificed}, Required=${required}, To Go=${toGo}`);
    } else if (/COMPLETE/i.test(rawText)) {
        cropCount++;
        //console.log("[DEBUG] COMPLETE certificate detected");
    } else {
        console.warn("[DEBUG] No match for sacrificed/required in:", rawText);
    }
});

const totalRequired = cropCount * 100000;
const percentComplete = ((totalRequired - totalToGo) / totalRequired * 100).toFixed(2);

const cropSummary = document.createElement("li");
cropSummary.innerHTML = `
    <div style="font-weight: bold; color: lightgreen; text-align: center; padding: 5px;">
        ${totalToGo.toLocaleString()} to go out of ${totalRequired.toLocaleString()} crops (${percentComplete}%)
    </div>
`;
certificatesContainer.appendChild(cropSummary);





    // Append reordered certificates
    certificateBlocks.forEach(({ element }) => {
        simplifyText(element);
        certificatesContainer.appendChild(element);
    });
    console.log("Certificates reordered and updated.");
}

function extractProgress(element) {
    let titleElement = element.querySelector(".item-title");
    if (!titleElement) {
        console.warn("%c[Script] Title element not found in certificate block.", "color: orange;");
        return 0;
    }

    // Extract sacrificed and required item counts
    let progressMatch = titleElement.innerText.match(/(\d{1,3}(?:,\d{3})*) \/ (\d{1,3}(?:,\d{3})*) Items Sacrificed/);

    if (!progressMatch) {
        console.warn("%c[Script] No progress values found in:", "color: orange;", titleElement.innerText);
        return 0;
    }

    let sacrificed = parseInt(progressMatch[1].replace(/,/g, '')); // Remove commas
    let required = parseInt(progressMatch[2].replace(/,/g, ''));

    // Calculate precise percentage
    let progress = (sacrificed / required) * 100;

    //console.log(`%c[Script] Corrected progress: ${progress.toFixed(2)}% (${sacrificed}/${required})`, "color: lightgreen;");

    return progress;
}


function simplifyText(element) {
    let titleElement = element.querySelector(".item-title");
    let imgElement = element.querySelector(".item-media img");

    if (!titleElement) {
        console.warn("%c[Script] Title element not found in certificate block.", "color: orange;");
        return;
    }

    if (element.classList.contains("row")) {
        element.style.marginBottom = "0px";
    }

    let nameMatch = titleElement.innerText.match(/Certificate of (.+?) Giving/);
    let progressMatch = titleElement.innerText.match(/(\d+(?:,\d+)?) \/ (\d+(?:,\d+)?) Items Sacrificed/);
    let onHandMatch = titleElement.innerText.match(/Sacrifice: (.+?) \(You have (\d+(?:,\d+)?)\)/);

    if (nameMatch && progressMatch && onHandMatch) {
        let itemName = nameMatch[1];
        let totalRequired = parseInt(progressMatch[2].replace(/,/g, ''));
        let sacrificed = parseInt(progressMatch[1].replace(/,/g, ''));
        let onHand = parseInt(onHandMatch[2].replace(/,/g, ''));
        let itemsToGo = totalRequired - sacrificed;
        let progressPercent = (sacrificed / totalRequired) * 100;

        let color = "white";
        if (itemsToGo === 0) color = "gold";
        else if (progressPercent >= 75) color = "lightgreen";
        else if (sacrificed < 10000) color = "grey";

        let lower10kThreshold = Math.floor(itemsToGo / 10000) * 10000;
        let remainingToLower10k = itemsToGo - lower10kThreshold;
        if (remainingToLower10k === 0) remainingToLower10k = 10000;

        let nn = "COMPLETE".padStart(15, '\u00A0');
        let outputPad = itemsToGo === 0
        ? `${nn} [${onHand.toLocaleString().padStart(7, '\u00A0')} on hand]`
    : `${itemsToGo.toLocaleString().padStart(9, '\u00A0')} to go [${onHand.toLocaleString().padStart(7, '\u00A0')} on hand]`;


        // Build the left block (60%) — content inside <a>
        let container = document.createElement("div");
        container.style.display = "flex";
        container.style.flexFlow = "row nowrap";
        container.style.alignItems = "center";
        container.style.verticalAlign = "middle";
        container.style.marginTop = "auto";
        container.style.marginBottom = "auto";

        let left = document.createElement("div");
        left.style.width = "20%";
        left.style.textAlign = "left";
        left.textContent = `${itemName}: `;

        let right = document.createElement("div");
        right.style.width = "80%";
        right.style.textAlign = "left";
        right.style.whiteSpace = "nowrap";
        right.style.overflow = "hidden";
        right.style.textOverflow = "ellipsis"; // optional, if you want to clip long lines with …
        right.innerHTML = `${outputPad}`;


        container.appendChild(left);
        container.appendChild(right);

        let span = document.createElement("span");
        span.style.color = color;
        span.appendChild(container);

        let outer = document.createElement("span");
        outer.style.fontFamily = "monospace";
        outer.style.width = "100%";
        outer.style.fontSize = "0.833rem";
        outer.appendChild(span);

        titleElement.outerHTML = outer.outerHTML;

        if (imgElement) {
            imgElement.style.width = "1rem";
            imgElement.style.height = "1rem";
        }

        let smallImgElement = element.querySelector(".item-after img");
        if (smallImgElement) {
            smallImgElement.style.width = "1rem";
            smallImgElement.style.height = "1rem";
        }

        // === Restructure LI ===
        let liElement = element.closest("li");
        let aTag = liElement.querySelector("a");
        if (!liElement || !aTag) return;

        // Create flex container
        let wrapper = document.createElement("div");
        wrapper.style.display = "flex";
        wrapper.style.width = "100%";

        // Left side: wrap <a> in div
        let leftDiv = document.createElement("div");
        leftDiv.style.width = "60%";
        leftDiv.appendChild(aTag); // Moves <a> inside left div

        // Right side: build button panel
        let rightDiv = document.createElement("div");
        rightDiv.style.width = "40%";
        rightDiv.style.display = "flex";
        rightDiv.style.flexDirection = "row";
        rightDiv.style.alignItems = "center";
        rightDiv.style.justifyContent = "space-between";

        const wrap = (link) => {
            const wrapper = document.createElement("div");
            wrapper.style.width = "33.33%";
            wrapper.style.display = "flex";
            wrapper.style.justifyContent = "center";
            wrapper.appendChild(link);
            return wrapper;
        };

        const isComplete = itemsToGo === 0;
        const notEnoughForNext10k = onHand < remainingToLower10k;

        const link1 = createLink(`Give ${onHand}`, onHand, itemName, 'all', itemsToGo, remainingToLower10k, isComplete);
        const give1kDisabled = isComplete || onHand < 1000;
        const link2 = createLink(`Give 1k`, onHand, itemName, '1k', itemsToGo, remainingToLower10k, give1kDisabled);
        const link3 = createLink(`Give ${remainingToLower10k}`, onHand, itemName, 'tothenext10k', itemsToGo, remainingToLower10k, isComplete || notEnoughForNext10k);


        rightDiv.appendChild(wrap(link1));
        rightDiv.appendChild(wrap(link2));
        rightDiv.appendChild(wrap(link3));


        // Clear the original LI content and re-append
        liElement.innerHTML = "";
        wrapper.appendChild(leftDiv);
        wrapper.appendChild(rightDiv);
        liElement.appendChild(wrapper);
    } else {
        console.warn("%c[Script] Failed to extract data for certificate text update.", "color: orange;");
    }
}

function createLink(linkText, onHand, itemName, type, itemsToGo, remainingToLower10k, disabled = false) {
    let link = document.createElement("a");
    link.innerText = linkText;
    link.href = "#";

    link.style.textDecoration = "none";
    link.style.background = "transparent";
    link.style.border = "1px solid";
    link.style.borderRadius = "6px";
    link.style.fontSize = "0.7rem";
    link.style.cursor = disabled ? "not-allowed" : "pointer";
    link.style.padding = "0.1rem 0.6rem";
    link.style.margin = "0";
    link.style.display = "inline-block";
    link.style.width = "90px";               // fixed width
    link.style.textAlign = "center";
    link.style.zIndex = 2;
    link.style.whiteSpace = "nowrap";

    if (disabled) {
        link.style.color = "gray";
        link.style.borderColor = "gray";
        link.onclick = function (e) {
            e.preventDefault();
        };
    } else {
        link.style.color = "gold";
        link.style.borderColor = "gold";

        link.onclick = function (e) {
            e.preventDefault();
            const params = new URLSearchParams();
            params.append("go", "sacrificeitem");
            params.append("item", itemName);
            params.append("amt", getAmount(type, onHand, itemsToGo, remainingToLower10k));
            params.append("special", "1");

            const url = `worker.php?${params.toString()}`;
            fetch(url, { method: "GET" })
                .then((response) => response.text())
                .then((data) => {
                if (data === "success") {
                    window.location.reload();
                } else {
                    console.warn("%c[Error] Something went wrong: ", "color: red;", data);
                }
            })
                .catch((error) => {
                console.error("%c[Error] AJAX request failed: ", "color: red;", error);
            });
        };
    }

    return link;
}


function getAmount(type, onHand, itemsToGo, remainingToLower10k) {
    switch (type) {
        case "all":
            return onHand;
        case "1k":
            return 1000;
        case "tothenext10k":
            return remainingToLower10k;
        default:
            return 0;
    }
}








function init() {
    //console.log("%c[Script] Initializing...", "color: green;");
    reorderCertificates();
    injectCSS();
}

window.addEventListener("load", init); // Ensures the script runs only once


function injectCSS() {
    const style = document.createElement('style');
    style.innerHTML = `
.list-block .item-content {
    min-height: 0 !important;
}

.list-block .item-inner {
    min-height: 0 !important;
}



    `;
    document.head.appendChild(style);
}

$(document).ready( () => {
    const target = document.querySelector("#fireworks")
    const observer = new MutationObserver( mutation => {if (mutation[0]?.attributeName == "data-page") reorderCertificates()} )


    const config = {
        attributes: true,
        childlist: true,
        subtree: true
    }


    observer.observe(target, config);

    const observera = new MutationObserver(() => {
        reorderCertificates();
    });

})