ATLYSS TechPendium

Module:Trade Items

Viewing old revision of Module:Trade_Items

You are viewing an old revision of this page from 2/27/2026, 9:54:58 AM.

View latest version
This Module contains Wiki logic and must be loaded using require keyword inside other logic modules.
Note that require supports only static imports.
This module can be {{#invoke}}-ed to render dynamic parts of the page.
const Utils = await require("Utils");
const Wiki = await require("Wiki");
const Game = await require("Game");
const { latest: latestVersion } = await requireData("Versions");
const { TooltipBuilder } = await require("Tooltip_Builder");
const { InfoboxBuilder } = await require("Infobox_Builder");

function renderTable(items, version, notes = {}) {
    let wikitext = "";
    let tooltips = "";

    const notesAvailable = !!Object.keys(notes).length;

    wikitext += '{| class="wiki-table" style="width: auto;"\n';
    wikitext += `! colspan=${notesAvailable ? "4" : "3"} | Version: ${Wiki.versionSelector(version, "Trade_Items", "wikiTable")}:\n`;
    wikitext += '|-\n';
    wikitext += `! Name !! Rarity !! Max Stack${notesAvailable ? " !! Notes" : ""}\n`;


    for (const id in items) {
        const item = items[id];

        wikitext += '|-\n';

        const tooltipId = `TradeItem:Table:${id}`;

        // Name
        wikitext += `| data-tooltip-id="${tooltipId}" | [[Trade Items/${item.name || id}|${item.name || id}]]\n`;

        // Rarity
        if (item.rarity) {
            wikitext += `| ${Wiki.wikiRarity(item.rarity)}\n`;
        } else {
            wikitext += `| N/A}\n`;
        }

        // Max Stack
        if (item.stack?.max) {
            wikitext += `| ${item.stack.max}\n`;
        } else {
            wikitext += `| N/A\n`;
        }

        // Optional Notes
        if (notesAvailable) {
            if (notes[item.name]) {
                wikitext += `| ${notes[item.name]}\n`;
            } else {
                wikitext += "|\n";
            }
        }

        // Add tooltip
        const tooltip = new TooltipBuilder(tooltipId);
        tooltip.addLine(`'''${item.name || id}'''`);
        if (item.description) {
            const lines = item.description.replace(/\n{2,}/g, "\n").split('\n');
            for (const line of lines) {
                tooltip.addLine(line.replace(/<color=(.*?)>(.*?)<\/color>/g, "<span style='color:$1;'>$2</span>"));
            }
        }
        tooltips += tooltip.build();
    }

    wikitext += '|}\n';
    wikitext += tooltips;

    return wikitext;
}

function renderNavbox(items, version) {
    let wikitext = '';

    const versionTooltip = new TooltipBuilder(`TradeItem:Navbox:Version:${version.replace(/\./g, '_')}`);
    versionTooltip.addLine(`Data version: '''${version}''' <span class="${version === latestVersion ? 'latest' : 'outdated'}">(${version === latestVersion ? 'latest' : 'outdated'})</span>`);

    wikitext += `{| class="wiki-table navbox"\n`;
    wikitext += `! Version: ${Wiki.versionCode(version, versionTooltip)}:\n`;
    wikitext += `|-\n`;
    wikitext += `! Trade Items Navigation\n`;

    const itemsList = [];
    let tooltips = "";

    for (const id in items) {
        const item = items[id];

        const tooltipId = `TradeItem:Navbox:${id}`;

        itemsList.push(`<span data-tooltip-id="${tooltipId}">[[Trade Items/${item.name || id}|${item.name || id}]]</span>`);

        // Add tooltip
        const tooltip = new TooltipBuilder(tooltipId);
        tooltip.addLine(`'''${item.name || id}'''`);
        if (item.description) {
            const lines = item.description.replace(/\n{2,}/g, "\n").split('\n');
            for (const line of lines) {
                tooltip.addLine(line.replace(/<color=(.*?)>(.*?)<\/color>/g, "<span style='color:$1;'>$2</span>"));
            }
        }
        tooltips += tooltip.build();
    }

    wikitext += `|-\n`;
    wikitext += `| ${itemsList.join(' • ')}\n`;
    wikitext += `|}\n`;
    wikitext += tooltips;

    return wikitext;
}

async function wikiTable(props) {
    const args = Utils.resolveArgs(props);
    const selectedVersion = args["version"] || args[0];
    const notes = args["notes"];

    const { data, version } = await Utils.resolveData("Trade_Items", false, selectedVersion);

    if (Utils.isModuleEmpty(data)) {
        return `<div id="Trade_Items-wikiTable">⚠️ Trade item data is unavailable for version ${version}.${Wiki.versionSelector(version, "Trade_Items", "wikiTable")}</div>`;
    }

    const parsedNotes = notes?.length
        ? notes.split(",").reduce((acc, pair) => {
            const [key, ...rest] = pair.split(":");

            if (!key) return acc;

            acc[key.trim()] = rest.join(":").trim();
            return acc;
        }, {})
        : notes;

    const sortedData = Utils.sortObjectByKey(data);

    return `<div id="Trade_Items-wikiTable">${renderTable(sortedData, version, parsedNotes || {})}</div>`
};

async function wikiNavbox() {
    const { data, version } = await Utils.resolveData("Trade_Items", false);

    if (Utils.isModuleEmpty(data)) {
        return '⚠️ Trade items data is unavailable for all known versions.';
    }

    const sortedData = Utils.sortObjectByKey(data);

    let output = '';
    output += renderNavbox(sortedData, version);

    return output;
};

async function wikiTooltip(props) {
    const args = Utils.resolveArgs(props);
    const itemArg = args["item"] || args[0];

    if (!itemArg) return "";

    frame.tradeItemsCache ??= {};
    frame.tradeItemsCache.tooltip ??= {};

    const itemId = itemArg.toLowerCase().replace(/\s+/g, '_');
    const cacheKey = `TradeItem:${itemId}`;

    // Return cached tooltip if exists
    if (frame.tradeItemsCache.tooltip[cacheKey]) {
        return frame.tradeItemsCache.tooltip[cacheKey];
    }

    const { data } = await Utils.resolveData("Trade_Items", false);

    if (Utils.isModuleEmpty(data)) {
        return "⚠️ Trade items data are unavailable.";
    }

    const itemData = data[itemId];

    if (!itemData) {
        return `⚠️ Trade item '${itemArg}' not found in data.`;
    }

    const tooltip = new TooltipBuilder(`TradeItem:Item:${itemId}`);

    const name = itemData.name || itemArg;
    const description = itemData.description || "";

    const sanitizedDescription = description
        .replace(/\n{2,}/g, "\n")
        .replace(/<color=(.*?)>(.*?)<\/color>/g,
            "<span style='color:$1;'>$2</span>");

    tooltip.addLine(
        `<div style="display:flex;justify-content:space-between;">` +
        `<div>'''${name}'''</div>` +
        `<div>[[File:${itemData.id}_icon.png|26px|inline|alt=${name}]]</div>` +
        `</div>`
    );

    if (sanitizedDescription) {
        tooltip.addLine(sanitizedDescription.split('\n').join("<br>"));
    }

    const result =
        `<span data-tooltip-id="${tooltip.id}">` +
        `[[Trade Items/${name}|${name}]]` +
        `</span>`;

    // Save cache
    frame.tradeItemsCache.tooltip[cacheKey] = result;

    return result + tooltip.build();;
}

async function wikiInfobox(props) {
    const args = Utils.resolveArgs(props);
    const itemArg = args["item"] || args[0];
    const selectedVersion = args["version"] || args[1];

    const { data, version } = await Utils.resolveData("Trade_Items", false, selectedVersion);

    if (Utils.isModuleEmpty(data)) {
        return `⚠️ Trade items data is unavailable for version ${selectedVersion}.${Wiki.versionSelector(version, "Trade_Items", "wikiInfobox", [itemArg])}`;
    }

    const itemId = itemArg?.toLowerCase()?.replace(/\s+/g, '_');
    const itemData = data[itemId];

    if (!itemData) {
        return `⚠️ Trade item '${itemArg}' not found in data version ${version}.`;
    }

    const infobox = new InfoboxBuilder();

    infobox.setTitle((itemData.name || itemArg) + Wiki.versionSelector(version, "Trade_Items", "wikiInfobox", [itemArg]));
    infobox.setSubtitle(["Trade Item", ...itemData.tags].join(" "));
    infobox.setRarity(itemData.rarity);
    infobox.setImage(`[[File:${itemData.id}_icon.png|32px|${itemData.name || itemArg}]]`);

    if (itemData.description) {
        infobox.setDescription(itemData.description.replace(/\n{2,}/g, "<br>").replace(/<color=(.*?)>(.*?)<\/color>/g, "<span style='color:$1;'>$2</span>"));
    }

    if (itemData.stack?.max) {
        infobox.startSection("General").addRow("Max Stack Size", itemData.stack.max);
    }

    if (itemData.value?.vendor) {
        infobox.startSection("Price").addRow("Buy Price", itemData.value.vendor).addRow("Sell Price", Game.getSellPrice(itemData.value.vendor));
    }

    return `<div id="Trade_Items-wikiInfobox" style="float: ${infobox.float || "right"}">${infobox.build()}</div>`;
}

async function wikiDropSources(props) {
    const args = Utils.resolveArgs(props);
    const itemArg = args["item"] || args[0];
    const selectedVersion = args["version"] || args[1];

    const { data: items, version } = await Utils.resolveData("Trade_Items", false, selectedVersion);

    if (Utils.isModuleEmpty(items)) {
        return `<div id="Trade_Items-wikiDropSources">⚠️ Trade item data is unavailable for version ${version}. ${Wiki.versionSelector(version, "Trade_Items", "wikiDropSources", [itemArg])}</div>`;
    }

    const itemId = itemArg?.toLowerCase()?.replace(/\s+/g, '_');
    const itemData = items[itemId];

    if (!itemData) {
        return `<div id="Trade_Items-wikiDropSources">⚠️ Trade item '${itemArg}' not found in data version ${version}. ${Wiki.versionSelector(version, "Trade_Items", "wikiDropSources", [itemArg])}</div>`;
    }

    const { data: creeps } = await Utils.resolveData("Creeps", false, version);

    if (Utils.isModuleEmpty(creeps)) {
        return `<div id="Trade_Items-wikiDropSources">⚠️ Creep data is unavailable for version ${version}. ${Wiki.versionSelector(version, "Trade_Items", "wikiDropSources", [itemArg])}</div>`;
    }

    const dropSources = [];

    for (const creepId in creeps) {
        const creep = creeps[creepId];

        if (!creep?.loot?.items?.length) continue;

        // 🔥 Use the proper async drop calculator
        const drop = await Game.calculateCreepDrops(
            creep,
            itemData.guid,
            { version }
        );

        if (!drop) continue;

        dropSources.push({
            name: creep.name,
            level: creep.level,
            isElite: creep.isElite,
            rawChance: +(drop.dropChance * 100).toFixed(2),
            effectiveChance: +(drop.effectiveDrop * 100).toFixed(4),
            quantity: drop.quantity ?? 1
        });
    }

    if (!dropSources.length) {
        return `<div id="Trade_Items-wikiDropSources">No known enemy drops in version ${version}. ${Wiki.versionSelector(version, "Trade_Items", "wikiDropSources", [itemArg])}</div>`;
    }

    dropSources.sort((a, b) => b.effectiveChance - a.effectiveChance);

    let wikitext = `{| class="wiki-table" style="width:auto;"\n`;
    wikitext += `! colspan="6" | Version: ${Wiki.versionSelector(version, "Trade_Items", "wikiDropSources", [itemArg])}:\n`;
    wikitext += "|-\n";
    wikitext += "! Enemy !! Level !! Elite !! Raw Chance !! Effective Chance !! Quantity\n";

    for (const source of dropSources) {
        wikitext += "|-\n";
        wikitext += `| [[Creeps/${source.name}|${source.name}]]`;
        wikitext += ` || ${source.level ?? "?"}`;
        wikitext += ` || ${source.isElite ? "Yes" : "No"}`;
        wikitext += ` || ${source.rawChance.toFixed(2)}%`;
        wikitext += ` || ${source.effectiveChance.toFixed(4)}%`;
        wikitext += ` || ${source.quantity}\n`;
    }

    wikitext += "|}\n";

    return `<div id="Trade_Items-wikiDropSources">${wikitext}</div>`;
}

async function wikiShopSources(props) {
    const args = Utils.resolveArgs(props);
    const itemArg = args["item"] || args[0];
    const selectedVersion = args["version"] || args[1];

    const { data: items, version } = await Utils.resolveData("Trade_Items", false, selectedVersion);

    if (Utils.isModuleEmpty(items)) {
        return `<div id="Trade_Items-wikiShopSources">⚠️ Trade item data is unavailable for version ${version}. ${Wiki.versionSelector(version, "Trade_Items", "wikiShopSources", [itemArg])}</div>`;
    }

    const itemId = itemArg?.toLowerCase()?.replace(/\s+/g, '_');
    const itemData = items[itemId];

    if (!itemData) {
        return `<div id="Trade_Items-wikiShopSources">⚠️ Trade item '${itemArg}' not found in data version ${version}. ${Wiki.versionSelector(version, "Trade_Items", "wikiShopSources", [itemArg])}</div>`;
    }

    const { data: shops } = await Utils.resolveData("Shops", false, version);

    if (Utils.isModuleEmpty(shops)) {
        return `<div id="Trade_Items-wikiShopSources">⚠️ Shop data is unavailable for version ${version}. ${Wiki.versionSelector(version, "Trade_Items", "wikiShopSources", [itemArg])}</div>`;
    }

    const shopSources = [];

    for (const shopId in shops) {
        const shop = shops[shopId];
        if (!shop?.tables?.length) continue;

        for (const table of shop.tables) {
            if (!table?.items?.length) continue;

            for (const entry of table.items) {
                if (entry.itemGuid !== itemData.guid) continue;

                let price = null;
                let currency = null;

                // Special currency cost
                if (entry.specialStoreCost && entry.specialStoreCostQuantity) {
                    price = entry.specialStoreCostQuantity;

                    currency = Object.values(items).find(
                        i => i.guid === entry.specialStoreCost
                    );
                }

                // Default fallback currency = Gold (Crowns)
                if (!price && itemData.value?.vendor) {
                    price = itemData.value.vendor;

                    currency = {
                        name: "Crowns",
                        id: "gold"
                    };
                }

                shopSources.push({
                    shopName: shop.name,
                    npcName: shop.shopOwner?.name,
                    levelRequirement: table.levelRequirement ?? null,
                    price,
                    currency,
                    isGamble: entry.isGambleSlot
                });
            }
        }
    }

    if (!shopSources.length) {
        return `<div id="Trade_Items-wikiShopSources">No known shop sources in version ${version}. ${Wiki.versionSelector(version, "Trade_Items", "wikiShopSources", [itemArg])}</div>`;
    }

    shopSources.sort((a, b) => {
        const lvlA = a.levelRequirement ?? 0;
        const lvlB = b.levelRequirement ?? 0;

        if (lvlA !== lvlB) return lvlA - lvlB;

        return (a.price ?? 0) - (b.price ?? 0);
    });

    let wikitext = `{| class="wiki-table" style="width:auto;"\n`;
    wikitext += `! colspan="5" | Version: ${Wiki.versionSelector(version, "Trade_Items", "wikiShopSources", [itemArg])}:\n`;
    wikitext += "|-\n";
    wikitext += "! Shop !! Vendor !! Level Req. !! Price !! Gamble\n";

    for (const source of shopSources) {
        let priceText = "-";

        if (source.price && source.currency) {
            if (source.currency.id === "gold") {
                priceText = `${source.price} ${source.currency.name}`;
            } else {
                priceText = `${source.price} × [[Trade Items/${source.currency.name}|${source.currency.name}]]`;
            }
        }

        const shopLink = `[[Shops/${source.shopName}|${source.shopName}]]`;
        const npcLink = source.npcName
            ? `[[NPCs/${source.npcName}|${source.npcName}]]`
            : "Unknown";

        wikitext += "|-\n";
        wikitext += `| ${shopLink}`;
        wikitext += ` || ${npcLink}`;
        wikitext += ` || ${source.levelRequirement ?? "-"}`;
        wikitext += ` || ${priceText}`;
        wikitext += ` || ${source.isGamble ? "Yes" : "No"}\n`;
    }

    wikitext += "|}\n";

    return `<div id="Trade_Items-wikiShopSources">${wikitext}</div>`;
}

async function wikiQuestDrops(props) {
    const args = Utils.resolveArgs(props);
    const itemArg = args["item"] || args[0];
    const selectedVersion = args["version"] || args[1];

    const { data: items, version } = await Utils.resolveData("Trade_Items", false, selectedVersion);

    if (Utils.isModuleEmpty(items)) {
        return `<div id="Trade_Items-wikiQuestDrops">⚠️ Trade item data is unavailable for version ${version}. ${Wiki.versionSelector(version, "Trade_Items", "wikiQuestDrops", [itemArg])}</div>`;
    }

    const itemId = itemArg?.toLowerCase()?.replace(/\s+/g, '_');
    const itemData = items[itemId];

    if (!itemData) {
        return `<div id="Trade_Items-wikiQuestDrops">⚠️ Trade item '${itemArg}' not found in data version ${version}. ${Wiki.versionSelector(version, "Trade_Items", "wikiQuestDrops", [itemArg])}</div>`;
    }

    const { data: quests } = await Utils.resolveData("Quests", false, version);

    if (Utils.isModuleEmpty(quests)) {
        return `<div id="Trade_Items-wikiQuestDrops">⚠️ Quest data is unavailable for version ${version}. ${Wiki.versionSelector(version, "Trade_Items", "wikiQuestDrops", [itemArg])}</div>`;
    }

    const questSources = [];

    for (const questId in quests) {
        const quest = quests[questId];
        if (!quest?.rewards?.items?.length) continue;

        const rewardEntry = quest.rewards.items.find(r => r?.guid === itemData.guid);
        if (!rewardEntry) continue;

        questSources.push({
            questName: quest.name,
            questLevel: quest.level ?? null,
            quantity: rewardEntry.quantity ?? 0,
            questId
        });
    }

    if (!questSources.length) {
        return `<div id="Trade_Items-wikiQuestDrops">No known quest rewards in version ${version}. ${Wiki.versionSelector(version, "Trade_Items", "wikiQuestDrops", [itemArg])}</div>`;
    }

    // Sort by level ascending, then quest name
    questSources.sort((a, b) => {
        const lvlA = a.questLevel ?? 0;
        const lvlB = b.questLevel ?? 0;

        if (lvlA !== lvlB) return lvlA - lvlB;
        return a.questName.localeCompare(b.questName);
    });

    let wikitext = `{| class="wiki-table" style="width:auto;"\n`;
    wikitext += `! colspan="3" | Version: ${Wiki.versionSelector(version, "Trade_Items", "wikiQuestDrops", [itemArg])}:\n`;
    wikitext += "|-\n";
    wikitext += "! Quest !! Level !! Reward Quantity\n";

    for (const source of questSources) {
        const questLink = `[[Quests/${source.questName}|${source.questName}]]`;
        wikitext += "|-\n";
        wikitext += `| ${questLink} || ${source.questLevel ?? "-"} || ${source.quantity}\n`;
    }

    wikitext += "|}\n";

    return `<div id="Trade_Items-wikiQuestDrops">${wikitext}</div>`;
}

exports = {
    wikiTable,
    wikiNavbox,
    wikiTooltip,
    wikiInfobox,
    wikiDropSources,
    wikiShopSources,
    wikiQuestDrops
}
Last Edited by LiveGobe on 2/27/2026, 9:54:58 AM

This page categories: