Displaycase Item Sets (enhanced + sorted + safe) By Papanad

Show plushie/flower set counts + missing items + how many more you need for next set (sorted) with null checks

// ==UserScript==
// @name         Displaycase Item Sets (enhanced + sorted + safe) By Papanad
// @namespace    http://tampermonkey.net/
// @version      0.8
// @description  Show plushie/flower set counts + missing items + how many more you need for next set (sorted) with null checks
// @author       Papanad
// @match        https://www.torn.com/displaycase*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=torn.com
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // wait for page
    window.addEventListener(
        "load",
        function () {
            // sometimes Torn is slow to render the displaycase items, so give it a bit
            setTimeout(doIt, 400);
        },
        false
    );

    const plushieIds = [258, 261, 266, 268, 269, 273, 274, 281, 384, 618];
    const flowerIds = [260, 263, 264, 267, 271, 272, 276, 277, 282, 385, 617];

    const plushieNames = {
        258: "Teddy Bear",
        261: "Kitten Plushie",
        266: "Jaguar Plushie",
        268: "Lion Plushie",
        269: "Monkey Plushie",
        273: "Penguin Plushie",
        274: "Panda Plushie",
        281: "Dragon Plushie",
        384: "Cheshire Cat Plushie",
        618: "Kappa Plushie"
    };

    const flowerNames = {
        260: "Dahlia",
        263: "Orchid",
        264: "Peony",
        267: "Crocus",
        271: "Canna Lily",
        272: "Edelweiss",
        276: "Tribulus Omanense",
        277: "Ceibo Flower",
        282: "Banana Orchid",
        385: "African Violet",
        617: "Maple Leaf"
    };

    function doIt() {
        const infoBoxEl = document.querySelector(".display-main-page");
        if (!infoBoxEl) {
            // page structure different / still loading
            return;
        }

        const container = document.createElement("div");
        container.style.padding = "10px";
        container.style.background = "rgba(0,0,0,0.2)";
        container.style.marginBottom = "10px";
        container.style.border = "1px solid rgba(255,255,255,0.05)";
        container.style.color = "#fff";
        container.style.fontSize = "12px";

        // sometimes the items list is empty at first
        const items = Array.from(document.querySelectorAll("li.torn-divider"));

        const plushieResult = getSetInfo(items, plushieIds, plushieNames);
        const flowerResult = getSetInfo(items, flowerIds, flowerNames);

        const plushieHtml = renderSection("Plushie sets", plushieResult);
        const flowerHtml = renderSection("Flower sets", flowerResult);

        container.innerHTML = plushieHtml + "<hr>" + flowerHtml;

        infoBoxEl.insertBefore(container, infoBoxEl.firstChild);
    }

    /**
     * Build counts, figure out current sets, missing items, and what we need for next set.
     */
    function getSetInfo(allItemsEls, requiredIds, nameMap) {
        // start with all required items at 0
        const counts = requiredIds.map((id) => ({ id, count: 0 }));

        allItemsEls.forEach((itemEl) => {
            // image might not exist on some weird nodes
            const img = itemEl.querySelector("img.torn-item");
            if (!img || !img.src) return;

            // extract numeric ID from src
            const src = img.src;
            const numeric = src.replace(/\D/g,'');
            if (!numeric) return;

            const id = Number(numeric);
            const idx = counts.findIndex((x) => x.id === id);
            if (idx === -1) return; // not one of the set items

            // some items might not have the amount span (then assume 1)
            const countEl = itemEl.querySelector(".b-item-amount");
            let count = 1;
            if (countEl && countEl.innerText) {
                const num = Number(countEl.innerText.replace(/\D/g,''));
                if (!isNaN(num) && num > 0) {
                    count = num;
                }
            }

            counts[idx] = { id, count };
        });

        // calculate current full sets
        let fullSets = 0;
        if (counts.length > 0) {
            // find lowest count among required items
            const lowest = counts.reduce((min, cur) => cur.count < min ? cur.count : min, counts[0].count);
            fullSets = typeof lowest === "number" && lowest >= 0 ? lowest : 0;
        }

        // which items are totally missing for 1 set
        const missingForFirst = counts
            .filter((x) => x.count === 0)
            .map((x) => nameMap[x.id] || String(x.id));

        // how many we need to reach the NEXT set
        const target = fullSets + 1;
        const neededForNext = counts
            .map((x) => {
                const need = target - x.count;
                return {
                    id: x.id,
                    name: nameMap[x.id] || String(x.id),
                    need: need > 0 ? need : 0
                };
            })
            .filter((x) => x.need > 0)
            // sort biggest need first
            .sort((a, b) => {
                if (b.need !== a.need) return b.need - a.need;
                return a.name.localeCompare(b.name);
            });

        return {
            fullSets,
            missingForFirst,
            neededForNext
        };
    }

    function renderSection(title, result) {
        let html = `<h3 style="margin:0 0 6px 0;font-size:14px;color:#fff;">${title}: <span style="color:#7fff7f;">${result.fullSets}</span></h3>`;

        // only show this line if actually missing something
        if (result.missingForFirst.length > 0) {
            html += `<div><strong>Missing for 1 full set:</strong> ${result.missingForFirst.join(", ")}</div>`;
        } else {
            html += `<div><strong>Missing for 1 full set:</strong> none ✅</div>`;
        }

        if (result.neededForNext.length > 0) {
            html += `<div style="margin-top:4px;"><strong>To reach ${result.fullSets + 1} sets:</strong><ul style="margin:4px 0 0 16px;">`;
            result.neededForNext.forEach((item) => {
                html += `<li>${item.name}: need ${item.need} more</li>`;
            });
            html += `</ul></div>`;
        }

        return html;
    }
})();

QingJ © 2025

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