// ==UserScript==
// @name Farm RPG Locksmith Gold Indicator
// @version 1.0.26
// @description Adds a gold indicator to items in the Locksmith and shows the total gold available.
// @author ClientCoin
// @icon https://www.google.com/s2/favicons?sz=64&domain=farmrpg.com
// @match *://*farmrpg.com/index.php*
// @match *://*farmrpg.com/
// @match *://*alpha.farmrpg.com/index.php*
// @match *://*alpha.farmrpg.com/
// @grant none
// @license MIT
// @namespace whatisthisok
// ==/UserScript==
'use strict';
// Set to a bitwise sum of desired flags: 1=Errors, 2=Info, 4=Container search details, 8=Detailed item processing.
const DEBUG_LEVEL = 0;
const DEBUG_ERRORS = 1;
const DEBUG_INFO = 2;
const DEBUG_CONTAINER_SEARCH = 4;
const DEBUG_ITEM_PROCESSING = 8;
/**
* Custom logging function that checks against the DEBUG_LEVEL bitfield.
* @param {string} message - The message to log.
* @param {number} level - The log level flag (e.g., DEBUG_ERRORS, DEBUG_INFO).
* @param {*} [data=null] - Optional data to log with the message.
*/
function logDebug(message, level, data = null) {
if ((DEBUG_LEVEL & level) === level) {
if (data !== null) {
console.log(`[Locksmith Gold] ${message}`, data);
} else {
console.log(`[Locksmith Gold] ${message}`);
}
}
}
// A map of item names to the amount of gold they contain.
const GOLD_ITEMS = {
"Thomas's Tavern Tote": 25,
"Stack of Cards": 25,
"Pot of Gold (Large)": 50,
"Pot of Gold (Small)": 10,
"Cornucopia": 25,
"Box of Chocolate 01": 25,
"Spring Basket 01": 25,
"frank's Basket": 25,
"Summer Basket": 25,
"Christmas Present 01": 50,
"Present 02": 50,
"Backpack": 25,
"Fall Basket": 25,
"Treat Bag 04": 25,
"Cornucopia 01": 25,
"Lovely Present": 25,
"Winter Basket": 25,
"Box Of Chocolate 02": 25,
"Holger's Lunch Box": 25,
"Beatrix's Big Box of Boom": 25,
"Buddy's School Bag": 25,
"Apple Basket": 25,
"Treat Bag 05": 25,
"Cornucopia 02": 25,
"Fancy Present": 25,
"Frozen Chest 01": 25,
"Heart-shaped Box 01": 25,
"Green Backpack": 85,
"Pot of Gold (Medium)": 25,
"Magical Chest 01": 25,
"Rosalie's Beach Tote": 25,
"Beatrix's Booming Brawl Box 01": 25,
"Beatrix's Booming Brawl Box 02": 25,
"Baba's Snack Pack": 25,
"Apple Crate 01": 25,
"Apple Crate 02": 25,
"Treat Bag 06": 25,
"Shiny Present": 25,
"Frozen Chest 02": 25,
"Heart-Shaped Box 02": 25,
"Spring Basket 02": 25,
"Magical Chest 02": 25,
"Buddy's Beach Bag": 25,
"Beatrix's Booming Brawl Box 02": 25,
// New items added below
"5 Gold": 5,
"50 Gold": 50,
"10 Gold": 10,
"25 Gold": 25,
"100 Gold": 100,
};
const GOLD_EMOJI = '💰';
const PROCESSED_ATTRIBUTE = 'data-fpgi-processed';
const processedItems = new Set();
/**
* Main function that orchestrates the entire process.
* This is called by the MutationObserver when a relevant page change occurs.
*/
function addGoldIndicatorsToLocksmith() {
logDebug("Executing main script function for Locksmith page.", DEBUG_INFO);
// Step 1: Clean up any previous indicators to avoid duplicates on refresh.
const oldTotalIndicator = document.getElementById('total-gold-indicator');
if (oldTotalIndicator) {
oldTotalIndicator.remove();
logDebug("🗑️ Removed old total gold indicator from previous run.", DEBUG_INFO);
}
const oldTotalIndicatorFooter = document.getElementById('total-gold-indicator-footer');
if (oldTotalIndicatorFooter) {
oldTotalIndicatorFooter.remove();
logDebug("🗑️ Removed old footer total gold indicator from previous run.", DEBUG_INFO);
}
document.querySelectorAll(`[${PROCESSED_ATTRIBUTE}]`).forEach(el => {
el.removeAttribute(PROCESSED_ATTRIBUTE);
const oldIndivIndicator = el.querySelector('.individual-gold-indicator');
if(oldIndivIndicator) {
oldIndivIndicator.remove();
}
// Remove the highlight class from any old items
el.style.border = '';
el.style.boxShadow = '';
const titleElement = el.querySelector('.item-title strong');
if (titleElement) {
titleElement.style.color = '';
}
});
processedItems.clear();
logDebug("✅ Cleaned up old indicators and reset processed flags.", DEBUG_INFO);
// Step 2: Find all items that can be opened on the locksmith page.
const locksmithItems = document.querySelectorAll('.page[data-page="locksmith"] .list-block:not(.searchbar-not-found) .item-content');
if (locksmithItems.length === 0) {
logDebug("No locksmith items found in the current view.", DEBUG_INFO);
return;
}
logDebug(`🔍 Found ${locksmithItems.length} potential items to process.`, DEBUG_INFO);
let totalGold = 0;
// Step 3: Loop through each item, check for gold, and add an indicator.
locksmithItems.forEach((item, index) => {
logDebug(`Processing item at index ${index}.`, DEBUG_ITEM_PROCESSING);
const itemNameElement = item.querySelector('.item-title strong');
if (itemNameElement) {
const itemName = itemNameElement.textContent.trim();
const cleanName = itemName.replace(/\s*\((\d+,?\d*)\)|\s*(\(\d+,\d+\)|\s*\(\d+\))$/, '').trim();
logDebug(`🔎 Extracted item name: "${cleanName}"`, DEBUG_ITEM_PROCESSING);
let quantity = 1;
const quantityMatch = itemName.match(/\((\d+,?\d*)\)/);
if (quantityMatch) {
quantity = parseInt(quantityMatch[1].replace(/,/g, ''), 10);
}
logDebug(`🔢 Item quantity detected as: ${quantity}`, DEBUG_ITEM_PROCESSING);
if (GOLD_ITEMS[cleanName]) {
const goldAmount = GOLD_ITEMS[cleanName];
const totalItemGold = goldAmount * quantity;
logDebug(`✨ Found a match! "${cleanName}" contains ${goldAmount} gold each. Total for this item: ${totalItemGold}.`, DEBUG_ITEM_PROCESSING);
// Only count for global total if we haven't seen this item before
if (!processedItems.has(cleanName)) {
totalGold += totalItemGold;
processedItems.add(cleanName);
logDebug(`➕ Adding to total gold. Current total: ${totalGold}.`, DEBUG_INFO);
} else {
logDebug(`⚠️ Item "${cleanName}" already counted for global total. Skipping.`, DEBUG_INFO);
}
// Visual enhancements
item.style.border = '2px solid goldenrod';
item.style.boxShadow = '0 0 5px goldenrod, inset 0 0 5px goldenrod';
itemNameElement.style.color = 'goldenrod';
const goldIndicator = document.createElement('span');
goldIndicator.classList.add('individual-gold-indicator');
goldIndicator.innerHTML = ` <span style="font-size: 11px; color: goldenrod;">(${totalItemGold.toLocaleString()}${GOLD_EMOJI})</span>`;
itemNameElement.appendChild(goldIndicator);
} else {
logDebug(`🤷 No gold amount defined for item: "${cleanName}".`, DEBUG_ITEM_PROCESSING);
}
} else {
logDebug(`❌ No 'strong' tag found within item-title for item at index ${index}, skipping.`, DEBUG_ERRORS);
}
item.setAttribute(PROCESSED_ATTRIBUTE, 'true');
});
// Step 4: Find the specific warning card and add the total gold indicator.
const warningCardInner = document.querySelector('.page[data-page="locksmith"] .card.searchbar-found .card-content-inner');
if (totalGold > 0 && warningCardInner) {
logDebug(`💰 Final calculated total gold is: ${totalGold.toLocaleString()}.`, DEBUG_INFO);
const totalIndicatorHTML = `<p id="total-gold-indicator" style="text-align: center; font-size: 1.2em; font-weight: bold; margin-bottom: 10px; color: goldenrod;">${GOLD_EMOJI} **Total Gold Available: ${totalGold.toLocaleString()}** ${GOLD_EMOJI}</p>`;
const totalIndicatorElement = document.createElement('div');
totalIndicatorElement.innerHTML = totalIndicatorHTML;
const totalIndicatorElementFooter = document.createElement('div');
totalIndicatorElementFooter.innerHTML = totalIndicatorHTML;
totalIndicatorElementFooter.querySelector('#total-gold-indicator').id = 'total-gold-indicator-footer';
// Insert at the beginning and the end of the warning card content.
warningCardInner.prepend(totalIndicatorElement);
warningCardInner.append(totalIndicatorElementFooter);
logDebug("➕ Added new total gold indicator to the warning card.", DEBUG_INFO);
} else {
logDebug("📉 No gold-containing items found, or the warning card container could not be located. Skipping total indicator.", DEBUG_INFO);
}
}
// Global scope logic for initialization
const target = document.querySelector("#fireworks");
if (target) {
logDebug("✅ Target element '#fireworks' found.", DEBUG_INFO);
const observer = new MutationObserver(mutations => {
logDebug("MutationObserver triggered by a DOM change.", DEBUG_INFO);
for (const mutation of mutations) {
if (mutation.attributeName === "data-page") {
const newPage = target.getAttribute("data-page");
logDebug(`Page change detected. New page is: '${newPage}'`, DEBUG_INFO);
if (newPage === "locksmith") {
setTimeout(addGoldIndicatorsToLocksmith, 100);
}
}
}
});
const config = {
attributes: true,
attributeFilter: ['data-page'],
childList: true,
subtree: true
};
observer.observe(target, config);
logDebug("Observer attached to '#fireworks' element, listening for 'data-page' changes.", DEBUG_INFO);
const currentPage = target.getAttribute("data-page");
logDebug(`Initial page check. Current page is: '${currentPage}'`, DEBUG_INFO);
if (currentPage === "locksmith") {
setTimeout(addGoldIndicatorsToLocksmith, 100);
}
} else {
logDebug("❌ Target element '#fireworks' not found. Script will not run automatically on page changes.", DEBUG_ERRORS);
}