Ironwood RPG - Combat Loot Logger

Logs loot drops and consumable usage after each kill in Ironwood RPG.

// ==UserScript==
// @name         Ironwood RPG - Combat Loot Logger
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Logs loot drops and consumable usage after each kill in Ironwood RPG.
// @author       Rivea
// @match        https://ironwoodrpg.com/skill/14/*
// @match        https://ironwoodrpg.com/skill/8/*
// @match        https://ironwoodrpg.com/skill/6/*
// @match        https://ironwoodrpg.com/skill/7/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=ironwoodrpg.com
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    /*** SCRIPT STATE & CACHE ***/
    let killCount = 0;
    const scriptStartTime = new Date();

    // --- Loot Tracking ---
    let totalLootGained = {}; // Stores the grand total of all loot gained since script start.
    let initialLootState = {}; // Stores the loot card's state at script start to account for existing items.
    let lootInitialized = false; // Flag to ensure loot is initialized only once.

    // --- Consumable Tracking ---
    let initialConsumablesData = {};
    let consumedConsumables = {};
    let consumablesInitialized = false;

    // --- Kill Detection ---
    let monsterIsPresent = false;
    let lastKnownMonsterName = "Unknown";

    // --- UI Element Cache ---
    let cachedElements = {
        nameElement: null
    };
    let cachedCards = [];

    console.log("Loot Logger script started at:", scriptStartTime);

    /*** CACHING FUNCTIONS ***/
    function updateCachedElements() {
        cachedElements.nameElement = document.querySelector('.interface.monster .header .name');
    }

    function updateCardsCache() {
        cachedCards = [...document.querySelectorAll('.card')];
    }

    /*** LOOT TRACKING FUNCTIONS ***/

    function getCurrentLootFromUI() {
        const lootCard = cachedCards.find(card =>
            card.querySelector('.header .name')?.textContent.trim() === 'Loot'
        );
        if (!lootCard) return [];

        return [...lootCard.querySelectorAll('.row')].map(row => ({
            name: row.querySelector('.name')?.textContent.trim() || "Unknown",
            amount: parseInt(row.querySelector('.amount')?.textContent.trim().replace(/,/g, ''), 10) || 0
        }));
    }

    function initializeLoot() {
        const currentLoot = getCurrentLootFromUI();
        currentLoot.forEach(item => {
            initialLootState[item.name] = item.amount;
        });
        lootInitialized = true;
        console.log("Loot tracker initialized. Initial state:", initialLootState);
    }


    function updateTotalLootGained() {
        if (!lootInitialized) return;

        const currentLootOnScreen = getCurrentLootFromUI();
        const currentLootMap = Object.fromEntries(
            currentLootOnScreen.map(item => [item.name, item.amount])
        );

        const allItemNames = new Set([
            ...Object.keys(initialLootState),
            ...Object.keys(currentLootMap)
        ]);

        for (const name of allItemNames) {
            const currentAmount = currentLootMap[name] || 0;
            const initialAmount = initialLootState[name] || 0;

            if (currentAmount > initialAmount) {
                totalLootGained[name] = currentAmount - initialAmount;
            }
        }
    }


    /*** CONSUMABLE TRACKING & INITIALIZATION ***/

    function getConsumables() {
        const consumablesCard = cachedCards.find(card => card.querySelector('.header .name')?.textContent.trim() === 'Consumables');
        if (!consumablesCard) return [];
        return [...consumablesCard.querySelectorAll('.row')].map(row => ({
            name: row.querySelector('.name')?.textContent.trim() || "Unknown",
            amount: parseInt(row.querySelector('.amount')?.textContent.trim().replace(/,/g, ''), 10) || 0
        }));
    }

    function initializeConsumables() {
        const currentConsumables = getConsumables();
        currentConsumables.forEach(item => {
            initialConsumablesData[item.name] = item.amount;
            consumedConsumables[item.name] = 0;
        });
        consumablesInitialized = true;
        console.log("Consumables tracker initialized. Initial state:", initialConsumablesData);
    }

    function updateConsumables() {
        if (!consumablesInitialized) return;
        const currentConsumables = getConsumables();
        if (currentConsumables.length === 0) return;

        currentConsumables.forEach(item => {
            const name = item.name;
            const currentAmount = item.amount;
            if (!(name in initialConsumablesData)) {
                initialConsumablesData[name] = currentAmount;
                consumedConsumables[name] = 0;
                return;
            }

            const initialAmount = initialConsumablesData[name];
            if (currentAmount < initialAmount) {
                consumedConsumables[name] += (initialAmount - currentAmount);
            }
            initialConsumablesData[name] = currentAmount;
        });
    }

    function getConsumedConsumables() {
        return Object.entries(consumedConsumables)
            .filter(([_, amount]) => amount > 0)
            .map(([name, amount]) => ({ name, amount }));
    }


    function waitForUIToInitialize() {
        const check = () => {
            updateCardsCache();

            if (!consumablesInitialized) {
                const consumablesCard = cachedCards.find(card => card.querySelector('.header .name')?.textContent.trim() === 'Consumables');
                if (consumablesCard) {
                    initializeConsumables();
                }
            }

            if (!lootInitialized) {
                const lootCard = cachedCards.find(card => card.querySelector('.header .name')?.textContent.trim() === 'Loot');
                if (lootCard) {
                    initializeLoot();
                }
            }

            if (!consumablesInitialized || !lootInitialized) {
                setTimeout(check, 200);
            }
        };
        check();
    }


    /*** LOGGING & UTILITY FUNCTIONS ***/

    function getTimeElapsed() {
        const diffMs = Date.now() - scriptStartTime;
        const diffSecs = Math.floor(diffMs / 1000);
        return {
            minutes: Math.floor(diffSecs / 60),
            seconds: diffSecs % 60,
            hours: diffMs / (1000 * 60 * 60)
        };
    }

    function logKillStats(killedMonsterName) {
        updateTotalLootGained();
        updateConsumables();

        const time = getTimeElapsed();
        const killsPerHour = time.hours > 0 ? (killCount / time.hours).toFixed(2) : 0;

        const lootLines = Object.entries(totalLootGained).map(([name, total]) => {
            const perHour = time.hours > 0 ? (total / time.hours).toFixed(2) : 0;
            return `  ${name}: ${total.toLocaleString()} | Per Hour: ${perHour}`;
        }).join("\n");

        const usedConsumables = getConsumedConsumables();
        const consumablesLines = usedConsumables.map(({ name, amount }) => {
            const perHour = time.hours > 0 ? (amount / time.hours).toFixed(2) : 0;
            return `  ${name}: ${amount.toLocaleString()} | Per Hour: ${perHour}`;
        }).join("\n");

        let fullLog = `
-----------------------------
Kill #${killCount}: ${killedMonsterName}
Time Elapsed: ${time.minutes}m ${time.seconds}s | Kills/Hour: ${killsPerHour}`;

        if (lootLines) {
            fullLog += `\n\nLoot Gained (This Session):\n${lootLines}`;
        }
        if (consumablesLines) {
            fullLog += `\n\nConsumables Used (This Session):\n${consumablesLines}`;
        }
        console.log(fullLog.trim());
    }


    /*** MAIN OBSERVER & LOGIC ***/

    const observer = new MutationObserver(() => {
        updateCachedElements();
        updateCardsCache();

        const currentMonsterName = cachedElements.nameElement ? cachedElements.nameElement.textContent.trim() : null;

        if (monsterIsPresent && !currentMonsterName) {
            killCount++;
            logKillStats(lastKnownMonsterName);
            monsterIsPresent = false;
        }

        if (!monsterIsPresent && currentMonsterName) {
            monsterIsPresent = true;
            lastKnownMonsterName = currentMonsterName;
        }
    });

    // Start the script
    waitForUIToInitialize();
    observer.observe(document.body, { childList: true, subtree: true });

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址