// ==UserScript==
// @name Reorder Certificates
// @namespace FarmRPGCertificates
// @version 1.0.25
// @description Reorders certificates based on completion percentage and simplifies the interface
// @author ClientCoin
// @match *farmrpg.com/index.php
// @match *farmrpg.com/
// @match *alpha.farmrpg.com/
// @match *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");
summaryElement.innerHTML = `
<div style="font-weight: bold; color: gold; text-align: center; padding: 5px;">
${completedCount}/${maxCount} Certificates COMPLETE
</div>
`;
certificatesContainer.appendChild(summaryElement);
// 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";
}
/*
element.style.padding = "1px 0"; // Reduce vertical padding
element.style.margin = "2px"; // Remove margins
element.style.lineHeight = "0"; // Reduce space between lines
let itemInner = element.querySelector(".item-inner");
if (itemInner) {
itemInner.style.padding = "1px 0";
}
let itemTitle = element.querySelector(".item-title");
if (itemTitle) {
itemTitle.style.margin = "0";
}
*/
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 outputPad = itemsToGo === 0 ? `COMPLETE [${onHand.toLocaleString().padStart(7, '\u00A0')} on hand]` : `${itemsToGo.toLocaleString().padStart(7, '\u00A0')} to go [${onHand.toLocaleString().padStart(7, '\u00A0')} on hand]`
let textContent =
`<span style="color: ${color};">
<div style="display:flex;flex-flow:row nowrap;align-items:center;vertical-align:middle;margin-top:auto;margin-bottom:auto;">
<div style="width:20%;text-align:left;">${itemName}: </div>
<div style="width:80%;text-align:left;">${outputPad}</div>
</div>
</span>`;
titleElement.outerHTML = `<span style="font-family: monospace; width: 100%">${textContent}</span>`;
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";
}
//console.log(`%c[Script] Updated text for ${itemName}: ${itemsToGo} to go, ${onHand} on hand.`, "color: lightblue;");
} else {
console.warn("%c[Script] Failed to extract data for certificate text update.", "color: orange;");
}
}
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();
});
})